1.说明
a. 书上《Linux内核完全注释V3.0.pdf》
4.9 一个简单的多任务内核实例,看完之后基本上进程切换就没有大问题了
但是书上为了与linux 0.11相似多写了一些无用的代码,这儿我作了一些修改,使代码更加简洁,看起来更加直观。
b.代码流程
mbr.S: -->这个除了引导之外没有别的作用,看一下就够了,把重点放在loader.S中
a. 从0x7c00启动,打印1mbr四个字符
b. 从硬盘的第2个扇区LBA_2读20个sector的loader.bin到0x00
c. 初始化8259A,将主从8259A的断号配为0x20-0x27,0x28-0x2F
d. 跳到jmp 0x00:0, 也就是loader.bin处执行
loader.S:
a. 从0x0处运行,开启保护模式-->打开A20,加载GDT,cr0第0位置1
b. 跳到保护模式,设置各个段寄存器ds=es=ss=选择子
c. 初始化IDT,单独设置时钟中断与系统调用int 0x80中断描述符
d. 初始化时钟
e. 开中断
c. 设置iret前的栈,执行iret后进入task0
2. 代码
2.1 mbr.S
-
mov ax, cs
-
mov ds, ax
-
mov es, ax
-
mov ss, ax
-
mov ax, 0xb800
-
mov gs, ax
-
-
;print 1MBR
-
mov byte [gs:0x00], '1'
-
mov byte [gs:0x01], 0xA4
-
-
mov byte [gs:0x02], 'M'
-
mov byte [gs:0x03], 0xA4
-
-
mov byte [gs:0x04], 'B'
-
mov byte [gs:0x05], 0xA4
-
-
mov byte [gs:0x06], 'R'
-
mov byte [gs:0x07], 0xA4
-
;因为loader.bin没有超过0x7c00(31K)如果超过了31K的话需要mbr.S自己把自己移动到0x90000处,
-
;然后再跳到0x9000处执行,这儿直接把loader.bin加载到0x0处就够了,没有必要多次一举
-
;loader loader.bin to 0x00000
-
mov ax, 0x0000
-
mov es, ax
-
mov bx, 0x00 ;[es:bx]-->dst_add in memory
-
mov cl, 20 ;sector cnt of disk
-
mov eax, 2 ;LBA addr
-
call load_disk
-
-
call init_8259 -->初始化8259A,把前0x20个中断预留出来,主8259A中断从IR[0-7]-->[0x20-0x27]
-
-->从8259A中断 IR[8-15]-->[0x28-0x2F]
-
-
jmp dword 0x0:0 -->跳到0x0处执行loader.bin的代码了,这个mbr.S就没看的必要的,是不是很简单
-
-
load_disk:
-
;eax --> src_addr of disk, in the format of LBA
-
;es:bx --> memory dst_addr
-
;cx --> sector_count
-
mov esi, eax
-
mov di, cx
-
-
mov dx, 0x1f2 ;sector count
-
mov al, cl
-
out dx, al
-
-
mov eax, esi ;LBA-->0-8
-
mov dx, 0x1f3
-
out dx, al
-
-
mov cl, 8
-
shr eax, cl ;LBA--> 8-16
-
mov dx, 0x1f4
-
out dx, al
-
-
shr eax, cl ;LBA-->16-24
-
mov dx, 0x1f5
-
out dx, al
-
-
shr eax, cl ;LBA-->16-24
-
and al, 0x0f
-
or al, 0xe0
-
mov dx, 0x1f6
-
out dx, al
-
-
mov dx,0x1f7 ;send read cmd
-
mov al, 0x20
-
out dx, al
-
-
not_ready:
-
nop
-
in al, dx
-
and al,0x88
-
cmp al,0x08
-
jnz not_ready
-
-
mov ax, di
-
mov dx, 256
-
mul dx
-
mov cx, ax
-
mov dx, 0x1f0
-
goon_read:
-
in ax, dx
-
mov [es:bx], ax
-
add bx, 2
-
loop goon_read
-
ret
-
-
; ==================================================================================
-
; 初始化8259
-
; 《自己动手写操作系统》,P113
-
init_8259:
-
-
; 主从的ICW1
-
; 边沿触发,级联,需要ICW4
-
mov al,0x11
-
out 0x20,al
-
out 0xa0,al
-
-
; 主从的ICW2
-
; 中断向量:IRQ0:0x20,IRQ8:0x28
-
mov al,0x20
-
out 0x21,al ;IR[0-7]-->[0x20-0x27]
-
mov al,0x28
-
out 0xa1,al ;IR[8-15]-->[0x28-0x2F]
-
-
; 主从的ICW3
-
mov al,0x04
-
out 0x21,al
-
mov al,0x02
-
out 0xa1,al
-
-
; 主从的ICW4
-
mov al,0x01
-
out 0x21,al
-
out 0xa1,al
-
-
; 屏蔽所有中断
-
mov al,0xff
-
out 0x21,al
-
out 0xa1,al
-
-
ret
-
-
times 510-($-$$) db 0 ; 填充余下空间
-
dw 0xaa55
-
; ==============================end of this file====================================
2.2 loader.S
-
jmp begin
-
align 8
-
gdt_base:
-
dw 0,0,0,0 ; 第一个须为空,不使用;select=0x0
-
dw 0x07ff, 0x0000, 0x9a00, 0x00c0 ; 0x08第二个GDT:代码段描述符,[0-8M]段限长2KB*4KB=8MB
-
dw 0x07ff, 0x0000, 0x9200, 0x00c0 ; 0x10第三个GDT:数据段描述符,[0-8M]
-
dw 0x0002, 0x8000, 0x920b, 0x00c0 ; 0x18第四个GDT:显存数据段描述符
-
dw 0x0068, tss0, 0xe900, 0x0000 ; 0x20第五个GDT:TSS0描述符 G=0,DPL=3,type=9<LDT>
-
dw 0x0040, ldt0, 0xe200, 0x0000 ; 0x28第六个GDT: LDT0描述符 G=0,DPL=3,type=2<TSS>
-
dw 0x0068, tss1, 0xe900, 0x0000 ; 0x30第七个GDT:TSS1描述符 G=0,DPL=3,type=9<TSS>
-
dw 0x0040, ldt1, 0xe200, 0x0000 ; 0x38第八个GDT:LDT1描述符 G=0,DPL=3,type=2<LDT>
-
end_gdt:
-
times 8 db 0
-
gdt_ptr:
-
dw (end_gdt-gdt_base) - 1
-
dd gdt_base
-
idt_base:
-
times (256*8) db 0 ; 共256个
-
idt_ptr:
-
dw 256*8-1
-
dd idt_base
-
end_idt:
-
times (256*8) db 0 ;stack --> 2kstack --> 2k
-
stack_ptr: ; 初始化时的堆栈指针
-
dd stack_ptr
-
dw 10h
-
-
ldt0:
-
dw 0,0,0,0 ; 第一个须为空,不使用
-
dw 0x03ff, 0x0000, 0xfa00, 0x00c0 ;slt=0x0F-->[0-4M]代码段,P=1,DPL=3,S=1,TYPE=0x0a(ra),G=1
-
dw 0x03ff, 0x0000, 0xf200, 0x00c0 ;slt=0x17-->[0-4M]数据段,P=1,DPL=3,S=1,TYPE=0x02(rw),G=1
-
-
tss0:
-
dd 0 ; 前一任务链接
-
dd kernel_stack0, 0x10 ; esp0,ss0
-
dd 0,0,0,0,0 ; esp1,ss1,esp2,ss2,cr3
-
dd task0, 0x200 ; EIP为task0,EFLAGS为0x200(IF) -->task0的EIP是iret L172行设定的,这儿的task0其实是个假的。
-
dd 0,0,0,0 ; eax,ecx,edx,ebx
-
dd stack0_ptr,0,0,0 ; esp,ebp,esi,edi
-
dd 0x17, 0x0f, 0x17, 0x17, 0x17, 0x17 ; es,cs,ss,ds,fs,gs
-
dd LDT0_SEL, 0x8000000 ; ldt,trace bitmap
-
times 128 dd 0 ; 再留一些内核栈空间
-
-
kernel_stack0: ; esp0,任务0的核心态使用的栈
-
dd 0
-
ldt1:
-
dw 0,0,0,0 ; 第一个须为空
-
dw 0x03ff, 0x0000, 0xfa00, 0x00c0 ;slt=0x0F-->[0-4M]代码段,P=1,DPL=3,S=1,TYPE=0x0a(ra),G=1
-
dw 0x03ff, 0x0000, 0xf200, 0x00c0 ;slt=0x17-->[0-4M]数据段,P=1,DPL=3,S=1,TYPE=0x02(rw),G=1
-
-
tss1:
-
dd 0 ; 前一任务链接
-
dd kernel_stack1, 0x10 ; esp0,ss0
-
dd 0,0,0,0,0 ; esp1,ss1,esp2,ss2,cr3
-
dd task1, 0x200 ; EIP为task1,EFLAGS为0x200(IF)
-
dd 0,0,0,0 ; eax,ecx,edx,ebx
-
dd stack1_ptr,0,0,0 ; esp,ebp,esi,edi
-
dd 0x17, 0x0f, 0x17, 0x17, 0x17, 0x17 ; es,cs,ss,ds,fs,gs
-
dd LDT1_SEL, 0x8000000 ; ldt,trace bitmap
-
times 128 dd 0 ; 再留一些内核栈空间
-
-
kernel_stack1: ; esp0,任务1的核心态使用的栈
-
dd 0
-
-
; 一些选择子,依据GDT计算出来的,以8h递增
-
CODE_SEL equ 0x08 ; 数据
-
DATA_SEL equ 0x10 ; 数据
-
SCRN_SEL equ 0x18 ; 显存
-
TSS0_SEL equ 0x20 ; TSS0
-
LDT0_SEL equ 0x28 ; LDT0
-
TSS1_SEL equ 0x30 ; TSS1
-
LDT1_SEL equ 0x38 ; LDT1
-
-
LATCH equ 11930
-
-
align 4
-
begin:
-
mov ax,0x0
-
mov ds,ax
-
-
mov cx, ax
-
;----------------- 打开A20 ----------------
-
in al,0x92
-
or al,0000_0010B
-
out 0x92,al
-
-
;----------------- 加载GDT ----------------
-
lgdt [gdt_ptr]
-
-
;----------------- cr0第0位置1 ----------------
-
mov eax, cr0
-
or eax, 0x00000001
-
mov cr0, eax
-
-
;----------------- 跳到保护模式 ----------------
-
jmp dword CODE_SEL:protect_mode
-
[bits 32]
-
; ==================================================================================
-
protect_mode:
-
mov ax,DATA_SEL ; 首先设置数据段,注意,是选择子
-
mov ds,ax
-
mov es,ax
-
mov fs,ax
-
mov gs,ax
-
lss esp,[stack_ptr] ; 设置堆栈,stack_ptr在下面定义:GDT总长加上余留空间
-
-
call setup_idt ; 初始化IDT
-
-
; //////////////////////////////////////////////////////////////////
-
;单独设置时钟中断中断描述符
-
mov eax,0x00080000 ; 0008 是选择子
-
mov ax,timer_int ; 时钟中断地址
-
mov dx,0x8e00 ; 属性
-
mov ecx,0x20 ; 放至IDT中第20h个描述符中
-
lea esi,[idt_base+ecx*8]
-
mov [esi],eax
-
mov [esi+4],edx
-
-
; //////////////////////////////////////////////////////////////////
-
; 单独设置系统调用中断描述符
-
; 在用户级任务中可用 int 80h系统调用
-
mov ax, sys_int
-
mov dx, 0xef00
-
mov ecx, 0x80
-
lea esi,[idt_base+ecx*8]
-
mov [esi],eax
-
mov [esi+4],edx
-
; //////////////////////////////////////////////////////////////////
-
; 初始化时钟
-
; 36二进制: 00 11 011 0
-
; 含义: 通道0 先写低字节 方式3 二进制
-
; 43h为对应的端口
-
; 《Linux内核完全剖析》P292
-
mov al,0x36
-
mov dx,0x43
-
out dx,al
-
-
mov ax,LATCH*4 ; 这里可以修改初始值
-
mov dx,0x40 ; 此时端口为40h
-
out dx,al ; 注意:out指令源操作数只有AL和AX的,这里分两次输出
-
mov al,ah
-
out dx,al
-
-
-
; //////////////////////////////////////////////////////////////////
-
; 将21H端口的值清第0位,作用未调查过
-
mov dx, 0x21
-
in al,dx
-
and al,0xfe
-
out dx,al
-
-
; //////////////////////////////////////////////////////////////////
-
; 清零EFLAGS中的NT标志
-
pushf
-
and dword [esp], 0xffffbfff
-
popf
-
-
; //////////////////////////////////////////////////////////////////
-
; 开工,首先是任务0
-
mov ax,TSS0_SEL
-
ltr ax
-
mov ax,LDT0_SEL
-
lldt ax
-
mov dword [current],0
-
sti ; 开中断,响应之
-
-
push 0x17 ; 任务0数据段选择子
-
push stack_ptr ; 初始的堆栈
-
pushf ; 标志寄存器入栈
-
push 0x0f ; 任务0代码段选择子
-
push task0 ; 将代码指针入栈
-
iret ; 从中断返回,切换至用户级任务0,执行 L172
-
-
; ==================================================================================
-
; 设置IDT--全部设置为默认的中断处理程序
-
setup_idt:
-
lea edx,[default_int]
-
mov eax,0x00080000 ;selector=0x8=code
-
mov ax,dx
-
mov dx, 0x8e00 ;set prop=0x8e
-
lea edi,[idt_base] ; 目标地址
-
mov ecx,256 ; 256个
-
_rp_idt:
-
mov [edi],eax
-
mov [edi+4],edx
-
-
; 执行到此处时,eax和ebx如下:
-
; eax中:高16位为选择子(即8),低16位为中断入口地址低16位
-
; edx中:高16位为中断入口地址高16位,低16位为属性(此处是8e,即386中断门),
-
; 更详细请参见门描述符
-
add edi,8 ; 下一个
-
dec ecx ; 看是否够256个
-
jne _rp_idt
-
-
lidt [idt_ptr] ; 最后,加载IDT的基地址到lidt
-
ret
-
-
; ==================================================================================
-
; 显示一个字符
-
write_char:
-
push gs
-
push ebx
-
push eax
-
mov ebx,SCRN_SEL ; 显存选择子
-
mov gs,bx
-
mov ebx,[scr_loc] ; 位置
-
shl ebx,1 ; 注意,屏幕上显示一字符,需在前加一字节的属性。
-
mov [gs:ebx],al
-
shr ebx,1
-
inc ebx ; 下一位置
-
cmp ebx,1824 ; 一个经过我计算的值
-
jb .next
-
mov ebx,0 ; 重新计数
-
.next:
-
mov [scr_loc],ebx
-
pop eax
-
pop ebx
-
pop gs
-
ret
-
-
; ==================================================================================
-
; 中断处理一:默认中断,只是打印一个“!”
-
align 4
-
default_int:
-
push ds
-
push eax
-
; 要继续接收中断,则要发送一个EOI给8259,如下所示
-
; 《自己动手写操作系统》P115
-
mov al,20h
-
out 20h,al
-
mov eax,DATA_SEL
-
mov dx,ax
-
mov al,'!' ; !
-
call write_char
-
pop eax
-
pop ds
-
iret
-
-
; ==================================================================================
-
; 中断处理二:时钟中断
-
align 4
-
timer_int:
-
push ds
-
push edx
-
push ecx
-
push ebx
-
push eax
-
-
mov eax,DATA_SEL
-
mov ds,ax
-
-
; 要继续接收中断,则要发送一个EOI给8259,如下所示
-
; 《自己动手写操作系统》P115
-
mov al,20h
-
out 20h,al
-
-
; 两任务在这里切换
-
mov eax,1
-
cmp [current],eax
-
je .switch_task0 ; 为1,则在下面跳至任务0
-
mov [current],eax ; 为0,则赋值为1,并跳至任务1
-
jmp TSS1_SEL:0
-
jmp .quit
-
.switch_task0:
-
mov dword [current],0 ; 重新赋值为0
-
jmp TSS0_SEL:0
-
-
.quit:
-
pop eax
-
pop ebx
-
pop ecx
-
pop edx
-
pop ds
-
iret
-
-
; ==================================================================================
-
; 中断处理三:系统调用,调用打印字符函数
-
align 4
-
sys_int:
-
push ds
-
push edx
-
push ecx
-
push ebx
-
push eax
-
-
mov edx,DATA_SEL
-
mov ds,dx
-
call write_char
-
-
pop eax
-
pop ebx
-
pop ecx
-
pop edx
-
pop ds
-
iret
-
-
; **********************************************************************************
-
current: dd 0 ; 任务
-
scr_loc: dd 0 ; 屏幕位置
-
; =============================== end of this file =================================
-
-
task0:
-
mov ax,0x17 ; LDT中数据描述符选择子
-
mov ds,ax
-
mov al,'F'
-
int 0x80
-
mov ecx,0fffh
-
.t0:
-
loop .t0
-
jmp task0
-
-
times 128 dd 0 ; 用户级任务0堆栈空间大小
-
stack0_ptr:
-
dd 0
-
-
task1:
-
mov ax,0x17
-
mov ds,ax
-
mov al,'N'
-
int 0x80
-
mov ecx, 0x0fff
-
.t1:
-
loop .t1
-
jmp task1
-
-
times 128 dd 0 ; 用户级任务1堆栈空间大小
-
stack1_ptr:
-
dd 0
3. 调试
3.1 从loader.lst中确定timer_int的地址
-
timer_int:
-
000016A8 1E push ds
-
000016A9 52 push edx
-
-
tss0:
-
0000107A 00000000 dd 0 ; 前一任务链接
-
0000107E [E2120000]10000000 dd kernel_stack0, 0x10 ; esp0,ss0
-
kernel_stack0: ; esp0,任务0的核心态使用的栈
-
000012E2 00000000 dd 0
-
-
-
-
tss1:
-
000012FE 00000000 dd 0 ; 前一任务链接
-
00001302 [66150000]10000000 dd kernel_stack1, 0x10 ; esp0,ss0
-
kernel_stack1: ; esp0,任务1的核心态使用的栈
-
00001566 00000000 dd 0
3.2 用bochs调试
-
(0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0
-
<bochs:1> b 0x16A8 -->在timer_int的起始打断点,不知道为毛这个不起作用
-
<bochs:2> b 0x16a9 -->在timer_int的起始打断点,再打一个
-
<bochs:3> c
-
(0) Breakpoint 2, 0x000016a9 in ?? () -->task0运行一段时间后,进入timer中断
-
Next at t=158838059
-
(0) [0x0000000016a9] 0008:000016a9 (unk. ctxt): push edx ; 52
-
<bochs:4> n
-
Next at t=158838060
-
(0) [0x0000000016aa] 0008:000016aa (unk. ctxt): push ecx ; 51
-
<bochs:5>
-
Next at t=158838061
-
(0) [0x0000000016ab] 0008:000016ab (unk. ctxt): push ebx ; 53
-
<bochs:6>
-
Next at t=158838062
-
(0) [0x0000000016ac] 0008:000016ac (unk. ctxt): push eax ; 50
-
<bochs:7>
-
Next at t=158838063
-
(0) [0x0000000016ad] 0008:000016ad (unk. ctxt): mov eax, 0x00000010 ; b810000000
-
<bochs:8> r
-
eax: 0x00080046 524358
-
ecx: 0x0000009d 157
-
edx: 0x00000021 33
-
ebx: 0x00002800 10240
-
esp: 0x000012ba 4794
-
ebp: 0x00000000 0
-
esi: 0x00000456 1110
-
edi: 0x00000856 2134
-
eip: 0x000016ad
-
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
-
<bochs:9> print-stack
-
Stack address size 4
-
| STACK 0x000012ba [0x00080046] -->刚刚入栈eax
-
| STACK 0x000012be [0x00002800] -->刚刚入栈ebx
-
| STACK 0x000012c2 [0x0000009d] -->刚刚入栈ecx
-
| STACK 0x000012c6 [0x00000021] -->刚刚入栈edx
-
| STACK 0x000012ca [0x00000017] -->刚刚入栈ds
-
| STACK 0x000012ce [0x0000171a] -->中断时入栈eip,task0从中断返回后运行的eip
-
| STACK 0x000012d2 [0x0000000f] -->中断时入格CS
-
| STACK 0x000012d6 [0x00000282] -->中断时入格PSW
-
| STACK 0x000012da [0x0000105c] -->中断时入格ESP
-
| STACK 0x000012de [0x00000017] -->中断时入格SS
-
| STACK 0x000012e2 [0x00000000]
-
| STACK 0x000012e6 [0x00000000]
-
| STACK 0x000012ea [0x00000000]
-
| STACK 0x000012ee [0x000003ff]
-
| STACK 0x000012f2 [0x00c0fa00]
-
| STACK 0x000012f6 [0x000003ff]
-
<bochs:10> n
-
Next at t=158838064
-
(0) [0x0000000016b2] 0008:000016b2 (unk. ctxt): mov ds, ax ; 8ed8
-
<bochs:11>
-
Next at t=158838065
-
(0) [0x0000000016b4] 0008:000016b4 (unk. ctxt): mov al, 0x20 ; b020
-
<bochs:12>
-
Next at t=158838066
-
(0) [0x0000000016b6] 0008:000016b6 (unk. ctxt): out 0x20, al ; e620
-
<bochs:13>
-
Next at t=158838067
-
(0) [0x0000000016b8] 0008:000016b8 (unk. ctxt): mov eax, 0x00000001 ; b801000000
-
<bochs:14>
-
Next at t=158838068
-
(0) [0x0000000016bd] 0008:000016bd (unk. ctxt): cmp dword ptr ds:0x00001703, eax ; 390503170000
-
<bochs:15> xp /4 0x1703
-
[bochs]:
-
0x00001703 <bogus+ 0>: 0x00000000 0x000001e4 0x0017b866 0x46b0d88e
-
<bochs:16> n
-
Next at t=158838069
-
(0) [0x0000000016c3] 0008:000016c3 (unk. ctxt): jz .+14 (0x000016d3) ; 740e
-
<bochs:17>
-
Next at t=158838070
-
(0) [0x0000000016c5] 0008:000016c5 (unk. ctxt): mov dword ptr ds:0x00001703, eax ; a303170000
-
<bochs:18>
-
Next at t=158838071
-
(0) [0x0000000016ca] 0008:000016ca (unk. ctxt): jmpf 0x0030:00000000 ; ea000000003000 -->jmp TSS1_SEL:0
-
<bochs:19>
-
Next at t=158838072
-
(0) [0x000000001922] 000f:00001922 (unk. ctxt): mov ax, 0x0017 ; 66b81700
-
xp /32 0x107A
[bochs]:
0x0000107a : 0x00000000 0x000012e2 0x00000010 0x00000000
0x0000108a : 0x00000000 0x00000000 0x00000000 0x00000000
0x0000109a : 0x000016d1 0x00000097 0x00000001 0x0000009d -->这个0x16d1就是再次切换到任务0时要执行的下一条指令,
0x000010aa : 0x00000021 0x00002800 0x000012ba 0x00000000 -->即jmp .quit,这个地方不好理解,多看看
0x000010ba : 0x00000456 0x00000856 0x00000000 0x00000008
0x000010ca : 0x00000010 0x00000010 0x00000000 0x00000000
0x000010da : 0x00000028 0x08000000 0x00000000 0x00000000
0x000010ea : 0x00000000 0x00000000 0x00000000 0x00000000
-
<bochs:20> c
-
(0) Breakpoint 2, 0x000016a9 in ?? ()
-
Next at t=160837759
-
(0) [0x0000000016a9] 0008:000016a9 (unk. ctxt): push edx ; 52
-
<bochs:21> n
-
Next at t=160837760
-
(0) [0x0000000016aa] 0008:000016aa (unk. ctxt): push ecx ; 51
-
<bochs:22>
-
Next at t=160837761
-
(0) [0x0000000016ab] 0008:000016ab (unk. ctxt): push ebx ; 53
-
<bochs:23>
-
Next at t=160837762
-
(0) [0x0000000016ac] 0008:000016ac (unk. ctxt): push eax ; 50
-
<bochs:24>
-
Next at t=160837763
-
(0) [0x0000000016ad] 0008:000016ad (unk. ctxt): mov eax, 0x00000010 ; b810000000
-
<bochs:25> print-stack
-
Stack address size 4
-
| STACK 0x0000153e [0x0000004e]
-
| STACK 0x00001542 [0x00000000]
-
| STACK 0x00001546 [0x000000c9]
-
| STACK 0x0000154a [0x00000000]
-
| STACK 0x0000154e [0x00000017]
-
| STACK 0x00001552 [0x00001931]
-
| STACK 0x00001556 [0x0000000f]
-
| STACK 0x0000155a [0x00000202]
-
| STACK 0x0000155e [0x00001b35]
-
| STACK 0x00001562 [0x00000017]
-
| STACK 0x00001566 [0x00000000]
-
| STACK 0x0000156a [0x00b89090]
-
| STACK 0x0000156e [0x89d88e00]
-
| STACK 0x00001572 [0x0c92e4c1]
-
| STACK 0x00001576 [0x0f92e602]
-
| STACK 0x0000157a [0x00501601]
-
<bochs:26> r
-
eax: 0x0000004e 78
-
ecx: 0x000000c9 201
-
edx: 0x00000000 0
-
ebx: 0x00000000 0
-
esp: 0x0000153e 5438
-
ebp: 0x00000000 0
-
esi: 0x00000000 0
-
edi: 0x00000000 0
-
eip: 0x000016ad
-
eflags 0x00000002: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af pf cf
-
<bochs:27> n
-
Next at t=160837764
-
(0) [0x0000000016b2] 0008:000016b2 (unk. ctxt): mov ds, ax ; 8ed8
-
<bochs:28>
-
Next at t=160837765
-
(0) [0x0000000016b4] 0008:000016b4 (unk. ctxt): mov al, 0x20 ; b020
-
<bochs:29>
-
Next at t=160837766
-
(0) [0x0000000016b6] 0008:000016b6 (unk. ctxt): out 0x20, al ; e620
-
<bochs:30>
-
Next at t=160837767
-
(0) [0x0000000016b8] 0008:000016b8 (unk. ctxt): mov eax, 0x00000001 ; b801000000
-
<bochs:31>
-
Next at t=160837768
-
(0) [0x0000000016bd] 0008:000016bd (unk. ctxt): cmp dword ptr ds:0x00001703, eax ; 390503170000
-
<bochs:32>
-
Next at t=160837769
-
(0) [0x0000000016c3] 0008:000016c3 (unk. ctxt): jz .+14 (0x000016d3) ; 740e
-
<bochs:33> xp /4 0x1703
-
[bochs]:
-
0x00001703 <bogus+ 0>: 0x00000001 0x000003c8 0x0017b866 0x46b0d88e
-
<bochs:34> n
-
Next at t=160837770
-
(0) [0x0000000016d3] 0008:000016d3 (unk. ctxt): mov dword ptr ds:0x00001703, 0x00000000 ; c7050317000000000000
-
<bochs:35>
-
Next at t=160837771
-
(0) [0x0000000016dd] 0008:000016dd (unk. ctxt): jmpf 0x0020:00000000 ; ea000000002000 -->jmp TSS0_SEL:0
-
<bochs:36>
-
Next at t=160837772
-
(0) [0x0000000016d1] 0008:000016d1 (unk. ctxt): jmp .+17 (0x000016e4) ; eb11 -->jmp .quit 在切到task1时,把task0的eip 0x16d1入栈,这个jmp时又跳到这儿执行
-
<bochs:37>
-
Next at t=160837773
-
(0) [0x0000000016e4] 0008:000016e4 (unk. ctxt): pop eax ; 58
-
<bochs:38>
-
Next at t=160837774
-
(0) [0x0000000016e5] 0008:000016e5 (unk. ctxt): pop ebx ; 5b
-
<bochs:39>
-
Next at t=160837775
-
(0) [0x0000000016e6] 0008:000016e6 (unk. ctxt): pop ecx ; 59
-
<bochs:40>
-
Next at t=160837776
-
(0) [0x0000000016e7] 0008:000016e7 (unk. ctxt): pop edx ; 5a
-
<bochs:41>
-
Next at t=160837777
-
(0) [0x0000000016e8] 0008:000016e8 (unk. ctxt): pop ds ; 1f
-
<bochs:42>
-
Next at t=160837778
-
(0) [0x0000000016e9] 0008:000016e9 (unk. ctxt): iret ; cf -->iret后运行的地址0x171a,这个是在task0切换之前就保存在栈中的
-
<bochs:43> print-stack
-
Stack address size 4
-
| STACK 0x000012ce [0x0000171a]
-
| STACK 0x000012d2 [0x0000000f]
-
| STACK 0x000012d6 [0x00000282]
-
| STACK 0x000012da [0x0000105c]
-
| STACK 0x000012de [0x00000017]
-
| STACK 0x000012e2 [0x00000000]
-
| STACK 0x000012e6 [0x00000000]
-
| STACK 0x000012ea [0x00000000]
-
| STACK 0x000012ee [0x000003ff]
-
| STACK 0x000012f2 [0x00c0fb00]
-
| STACK 0x000012f6 [0x000003ff]
-
| STACK 0x000012fa [0x00c0f300]
-
| STACK 0x000012fe [0x00000000]
-
| STACK 0x00001302 [0x00001566]
-
| STACK 0x00001306 [0x00000010]
-
| STACK 0x0000130a [0x00000000]
-
<bochs:44> n
-
Next at t=160837779
-
(0) [0x00000000171a] 000f:0000171a (unk. ctxt): loop .-2 (0x0000171a) ; e2fe
4. 这儿最不好理解的就是这个timer_int
4.1 由task0切换到task1(只关注task0部分,task1是一样的)
task0运行运行 -->发生时钟中断-->进入timer_int-->需要切换到task1
jmp TSS1_SEL:0时还是在task0中,这个jmp做了两件事
a. 把task0的下一条指令jmp .quit保存在tss0中
b. 跳到task1中执行,执行的EIP是tss1中写好的task1这个入口地址
4.2 由task1切换到task0
(只关注task0部分,task1是一样的)
jmp TSS0_SEL:0 ,这时要执行的下一条地址是TSS0中保存的EIP,也就是jmp .quit处
4.3 再捋一遍
jmp TSS1:SEL:0 -->下一条要执执的地址是TSS1中保存的EIP也就是标签task1处,并把jmp .quit保存在tss0的EIP处
jmp TSS0:SEL:0 -->
下一条要执执的地址是TSS0中保存的EIP也就是jmp .quit处
-
241 align 4
-
242 timer_int:
-
243 push ds
-
244 push edx
-
245 push ecx
-
246 push ebx
-
247 push eax
-
248
-
249 mov eax,DATA_SEL
-
250 mov ds,ax
-
251
-
252 ; 要继续接收中断,则要发送一个EOI给8259,如下所示
-
253 ; 《自己动手写操作系统》P115
-
254 mov al,20h
-
255 out 20h,al
-
256
-
257 ; 两任务在这里切换
-
258 mov eax,1
-
259 cmp [current],eax
-
260 je .switch_task0 ; 为1,则在下面跳至任务0
-
261 mov [current],eax ; 为0,则赋值为1,并跳至任务1
-
262 jmp TSS1_SEL:0
-
263 jmp .quit
-
264 .switch_task0:
-
265 mov dword [current],0 ; 重新赋值为0
-
266 jmp TSS0_SEL:0
-
267
-
268 .quit:
-
269 pop eax
-
270 pop ebx
-
271 pop ecx
-
272 pop edx
-
273 pop ds
-
274 iret
5. 关于task0与task1的tss中的EIP
-
167 push 0x17 ; 任务0数据段选择子
-
168 push stack_ptr ; 初始的堆栈
-
169 pushf ; 标志寄存器入栈
-
170 push 0x0f ; 任务0代码段选择子
-
171 push task0 ; 将代码指针入栈
-
172 iret ; 从中断返回,切换至用户级任务0,执行
5.1 关于task0的运行
task0是通过这个iret从ring0-->切换到了ring3并跳到task0中运行的,显式运行的task0
此时task0的tsss中的EIP部分并未改变,
所以在初始化时task0的tss中的EIP时,没有必要写task0,写任意数都可以运行
5.2 关于task1的运行
task1是在时钟中断后发生任务切换时,才由task0切换到了task1,
切换过程是这样的: 在时钟中断中jmp TSS1_SEL:0, 这时会把tss1中EIP(即task1)作为下一条指令的地址
所以tss1的EIP部分初始化时必须得写上task1,以后就靠cpu自动的覆盖这个值了。
上图出自《Linux内核完全注释V3.0书签版.pdf》P122
6. 代码打包
mod000.rar(下载后改名为mod000.tar.gz)
附录:
1. IA-32指令手册关于IRET的描述:
As with a real-address mode interrupt return, the IRET instruction pops the return instruction pointer, return code segment selector, and EFLAGS image from the stack to the EIP, CS, and EFLAGS registers, respectively, and then resumes execution of the interrupted program or procedure. If the return is to another privilege level, the IRET instruction also pops the stack pointer and SS from the stack, before resuming program execution.
push 0x17 ; 任务0数据段选择子
push stack_ptr ; 初始的堆栈
pushf ; 标志寄存器入栈
push 0x0f ; 任务0代码段选择子
push task0 ; 将代码指针入栈
iret ; 从中断返回,切换至用户级任务0,执行
iret之后的出栈顺序是:
EIP --> CS --> FLAGS --> ESP --> DS
附2. 参考文章
附2.1 原先的代码出处:
分享0.11注解v3.0中的“一个简单的多任务内核实例“可运行程序
http://www.oldlinux.org/oldlinux/viewthread.php?tid=14160&highlight=%B6%E0%C8%CE%CE%F1
附2.2
一个简单的多任务内核实例 <原代码在csdn的下载地址>
http://download.csdn.net/download/subfate/1389518
原作者将at的代码改成nasm的了,我是在这个基础上改的
阅读(1378) | 评论(0) | 转发(0) |