最近终于把实现多任务的微内核调试了一遍,我们阐述了如何在保护模式下切换任务。同时知识包括:gdt,idt,ldt,tss,时钟中断服务,特权级切换,显存编程,boot和loader功能,bios调用等等。详细知识还要在实践中摸索学习,希望大家一起进步。这篇文章仅仅做个记录,如没有亲身调试过代码,可能不大好理解。接下几天重点看看0.12启动程序,多分页需要更加深入了解。
;#Mode=Dos ;放在.code前面
.386p
.model small
LATCH = 11930
SCRN_SEL = 18H
TSS0_SEL = 20H
LDT0_SEL = 28H
TSS1_SEL = 30H
LDT1_SEL = 38H
.code
start:
mov eax,10h
mov ds,ax
lss esp,FWORD ptr [init_stack]
call setup_idt
call setup_gdt
mov eax,10h
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
lss esp,FWORD ptr [init_stack]
mov al,36h
mov edx,43h
out dx,al
mov eax,LATCH
mov edx,40h
out dx,al
mov al,ah
out dx,al
mov eax,80000h
mov ecx,timer_interrupt
mov ax,cx
mov dx,8e00h
mov ecx,8
lea esi,[ecx * 8 + idt]
mov [esi],eax
mov [esi + 4],edx
mov ecx,system_interrupt
mov ax,cx
mov dx,0ef00h
mov ecx,80h
lea esi,[ecx * 8 + idt]
mov [esi],eax
mov [esi + 4],edx
;//增加的代码
mov ecx,div_int
mov ax,cx
mov dx,0ef00h
mov ecx,0
lea esi,[ecx * 8 + idt]
mov [esi],eax
mov [esi + 4],edx
pushfd
and DWORD ptr [esp],0ffffbfffh
popfd
mov eax,TSS0_SEL
ltr ax
mov eax,LDT0_SEL
lldt ax
;mov DWORD ptr [current],0 ;问题1:前缀2e
db 0c7h, 05h
dd current
dd 0
sti
;///增加代码
;///
push 17h
push init_stack
pushfd
push 0fh
push task0
iretd
setup_gdt:
lgdt FWORD ptr [lgdt_opcode]
ret
setup_idt:
mov edx, ignore_init
mov eax,80000h
mov ax,dx
mov dx,8e00h
mov edi, idt
mov ecx,256
rp_idt:
mov [edi],eax
mov [edi + 4],edx
add edi,8
dec ecx
jne rp_idt
lidt FWORD ptr [lidt_opcode]
ret
write_char:
push gs
push ebx
mov ebx,SCRN_SEL
mov gs,bx
mov ebx,DWORD ptr [scr_loc]
shl ebx,1
mov BYTE ptr gs:[ebx],al
shr ebx,1
inc ebx
cmp ebx,2000
jb @f
mov ebx,0
@@:
; mov DWORD ptr [scr_loc],ebx
db 89h, 1dh
dd scr_loc
pop ebx
pop gs
ret
;align 4
ignore_init:
push ds
push eax
mov eax,10h
mov ds,ax
mov eax,67
call write_char
pop eax
pop ds
iretd
;//增加代码
div_int:
iret
;align 4
timer_interrupt:
push ds
push eax
mov eax,10h
mov ds,ax
mov al,20h
out 20h,al
mov eax,1
cmp DWORD ptr [current],eax
je n1
; mov DWORD ptr [current],eax
db 0a3h
dd current
BYTE 0eah
WORD 0, 0, TSS1_SEL
jmp n2
n1:
; mov DWORD ptr [current],0
db 0c7h, 05h
dd current
dd 0
db 0eah
WORD 0,0, TSS0_SEL
n2:
pop eax
pop ds
iretd
;align 4
system_interrupt:
push ds
push edx
push ecx
push ebx
push eax
mov edx,10h
mov ds,dx
call write_char
pop eax
pop ebx
pop ecx
pop edx
pop ds
iretd
current: DWORD 0
scr_loc: DWORD 0
;align 4
lidt_opcode:
WORD 256*8-1
DWORD idt
lgdt_opcode:
WORD (end_gdt-gdt)-1
DWORD gdt
;align 4
idt:
QWORD 256 dup (0)
gdt:
QWORD 0000000000000000h
QWORD 00c09a00000007ffh
QWORD 00c09200000007ffh
QWORD 00c0920b80000002h
WORD 68h, tss0, 0e900h, 0
WORD 40h, ldt0, 0e200h, 0
WORD 68h, tss1, 0e900h, 0
WORD 40h, ldt1, 0e200h, 0
end_gdt:
DWORD 128 dup (0)
init_stack:
DWORD init_stack
WORD 10h
;align 4
ldt0:
QWORD 0000000000000000h
QWORD 00c0fa00000003ffh
QWORD 00c0f200000003ffh
tss0:
DWORD 0
DWORD krn_stk0, 10h
DWORD 0, 0, 0, 0, 0
DWORD 0, 0, 0, 0, 0
DWORD 0, 0, 0, 0, 0
DWORD 0, 0, 0, 0, 0, 0
DWORD LDT0_SEL, 8000000h
DWORD 128 dup (0)
krn_stk0:
ldt1:
QWORD 0000000000000000h
QWORD 00c0fa00000003ffh
QWORD 00c0f200000003ffh
tss1:
DWORD 0
DWORD krn_stk1, 10h
DWORD 0, 0, 0, 0, 0
DWORD task1, 200h
DWORD 0, 0, 0, 0
DWORD usr_stk1, 0, 0, 0
DWORD 17h, 0fh, 17h, 17h, 17h, 17h
DWORD LDT1_SEL, 8000000h
DWORD 128 dup (0)
krn_stk1:
task0:
mov eax,17h
mov ds,ax
mov al,65
int 80h
mov ecx,0fffh
t1:
loop t1
jmp task0
task1:
mov eax,17h
mov ds,ax
mov al,66
int 80h
mov ecx,0fffh
t2:
loop t2
jmp task1
DWORD 128 dup (0)
usr_stk1:
end start
要感谢一个热心网友提供的内核镜像,学到不少东西。下面是我调试的记录:
代码分析:
57:pushl $0x17
pushl $init_stack
pushfl
pushl $0x0f
pushl $task0
iret
57:入栈任务0的ss,(更改了特权级为3)
入栈任务0的堆栈指针esp
入栈标志寄存器
入栈任务0的代码段选择符(cs)
入栈如任务的指令指针(eip)
iret退栈,分别赋值了ss,esp,eflags,cs,eip。
效果:
1.CPU特权级由0->3,因为cs寄存器的内容由0x10(RPL位为0)转为0x0f(RPL为3)
2.跳转到任务0开始执行。cs得到LDT中代码段描述符基址:0x00,偏移量eip=task0,这样就跳转到任务0处开始执行
总结:任务0开始在特权级3上面开始执行。
iret指令之前各个寄存器的值:
r:
ebx: 0x00000000 0
esp: 0x00000bf1 3057
ebp: 0x00000000 0
esi: 0x000001c5 453
edi: 0x000009c5 2501
eip: 0x000000d8
sreg:
es:0x0010, dh=0x00c09300, dl=0x000007ff, valid=1
Data segment, base=0x00000000, limit=0x007fffff, Read/Write, Accessed
cs:0x0008, dh=0x00c09b00, dl=0x000007ff, valid=3
Code segment, base=0x00000000, limit=0x007fffff, Execute/Read, Accessed, 32-bit
ss:0x0010, dh=0x00c09300, dl=0x000007ff, valid=7
Data segment, base=0x00000000, limit=0x007fffff, Read/Write, Accessed
ds:0x0010, dh=0x00c09300, dl=0x000007ff, valid=7
Data segment, base=0x00000000, limit=0x007fffff, Read/Write, Accessed
fs:0x0010, dh=0x00c09300, dl=0x000007ff, valid=1
Data segment, base=0x00000000, limit=0x007fffff, Read/Write, Accessed
gs:0x0010, dh=0x00c09300, dl=0x000007ff, valid=1
Data segment, base=0x00000000, limit=0x007fffff, Read/Write, Accessed
ldtr:0x0028, dh=0x0000e200, dl=0x0c0b0040, valid=1
tr:0x0020, dh=0x0000eb00, dl=0x0c230068, valid=1
gdtr:base=0x000009c5, limit=0x3f
idtr:base=0x000001c5, limit=0x7ff
执行完iret指令后:
r:
ebx: 0x00000000 0
esp: 0x00000c05 3077
ebp: 0x00000000 0
esi: 0x000001c5 453
edi: 0x000009c5 2501
eip: 0x0000110b
ss:0x17
cs:0x0f
-------------------------------------------------------------------------
224:int $0x80
在bochs中输入:s就会进入到中断程序:system_interrupt
在IDT表中有三种门描述符:中断门、陷阱门、任务门。
他们靠什么区别呢?描述符中有个TYPE字段。中断门:13、陷阱门:14、任务门:5。
int 0x80。执行完指令之后,系统检测到中断,然后根据中断号80,找到相应处理程序。这是一个陷阱门描述符。
根据完全剖析:121分析
(1)处理过程将在高特权级上执行时就会发生堆栈的切换。堆栈切换的过程如下:
处理器从当前任务的TSS段中取得中断处理过程使用的堆栈的段选择符和栈指针(例如tss.ss0、tss.esp0)。然后处理器会把被中断程序的栈选择符和栈指针压入新栈中。
(2)一旦执行int 80,就会执行堆栈切换,切换到任务0的内核堆栈--krn_stk0(esp)、0x10(ss),在哪里找要切换到的内核栈呢?在当前任务的TSS中有内核栈地址。
还有个问题需要考虑,执行流程怎么跳转到系统调用程序?当然是通过陷阱门的们描述符,里面指定了处理程序的地址,然后更改eip就ok了。
(3)进入内核态,后通过iret退栈(原来cs又弹出来),进入用户态
执行完int 0x80,此时堆栈确实切换了,并且把缘任务的东西入栈
| STACK 0x00000e77 [0x00001117]---栈顶---[EIP]
| STACK 0x00000e7b [0x0000000f]----------[cs]
| STACK 0x00000e7f [0x00000246]----------[eflags]
| STACK 0x00000e83 [0x00000c05]----------[原esp]
| STACK 0x00000e87 [0x00000017]----------[原ss]
此时寄存器信息:
cs:0x0008, dh=0x00c09b00, dl=0x000007ff, valid=1
Code segment, base=0x00000000, limit=0x007fffff, Execute/Read, Accessed, 32-bit
ss:0x0010, dh=0x00c09300, dl=0x000007ff, valid=1
Data segment, base=0x00000000, limit=0x007fffff, Read/Write, Accessed
ds:0x0017, dh=0x00c0f300, dl=0x000003ff, valid=1
Data segment, base=0x00000000, limit=0x003fffff, Read/Write, Accessed
fs:0x0000, dh=0x00c09300, dl=0x000007ff, valid=0
gs:0x0000, dh=0x00c09300, dl=0x000007ff, valid=0
ldtr:0x0028, dh=0x0000e200, dl=0x0c0b0040, valid=1
tr:0x0020, dh=0x0000eb00, dl=0x0c230068, valid=1
gdtr:base=0x000009c5, limit=0x3f
idtr:base=0x000001c5, limit=0x7ff
esp: 0x00000e77 3703
eip: 0x00000199
这个时候cs=0x10,说CPU的特权级由3升到0。
异常和中断过程中的保护:CPL必须小于等于门的DPL。这里是满足的,因为任务0的CPL是3,系统调用陷阱门的DPL也是3。
在system_interrupt执行最后一句iret指令之前,寄存器状态和上面一样。
执行完iret之后:
esp: 0x00000c05 3077
ebp: 0x00000000 0
esi: 0x000001c5 453
edi: 0x000009c5 2501
eip: 0x00001117(对应int 0x10后面那条指令)
cs:0x000f, dh=0x00c0fb00, dl=0x000003ff, valid=1
Code segment, base=0x00000000, limit=0x003fffff, Execute/Read, Accessed, 32-bit
ss:0x0017, dh=0x00c0f300, dl=0x000003ff, valid=1
Data segment, base=0x00000000, limit=0x003fffff, Read/Write, Accessed
ds:0x0017, dh=0x00c0f300, dl=0x000003ff, valid=1
Data segment, base=0x00000000, limit=0x003fffff, Read/Write, Accessed
fs:0x0000, dh=0x00c09300, dl=0x000007ff, valid=0
gs:0x0000, dh=0x00001000, dl=0x00000000, valid=0
ldtr:0x0028, dh=0x0000e200, dl=0x0c0b0040, valid=1
tr:0x0020, dh=0x0000eb00, dl=0x0c230068, valid=1
gdtr:base=0x000009c5, limit=0x3f
idtr:base=0x000001c5, limit=0x7ff
iret指令切换到原来的栈中(陷阱门那节书中有说到这点)
调用ljmp $TSS1_SEL,$0之前寄存器信息:
eax: 0x00000001 1
ecx: 0x0000097c 2428
edx: 0x0000ef00 61184
ebx: 0x00000000 0
esp: 0x00000e6f 3695
ebp: 0x00000000 0
esi: 0x000001c5 453
edi: 0x000009c5 2501
eip: 0x0000017c
sreg:
es:0x0000, dh=0x00c09300, dl=0x000007ff, valid=0
cs:0x0008, dh=0x00c09b00, dl=0x000007ff, valid=3
Code segment, base=0x00000000, limit=0x007fffff, Execute/Read, Accessed, 32-bit
ss:0x0010, dh=0x00c09300, dl=0x000007ff, valid=7
Data segment, base=0x00000000, limit=0x007fffff, Read/Write, Accessed
ds:0x0010, dh=0x00c09300, dl=0x000007ff, valid=7
Data segment, base=0x00000000, limit=0x007fffff, Read/Write, Accessed
fs:0x0000, dh=0x00c09300, dl=0x000007ff, valid=0
gs:0x0000, dh=0x00001000, dl=0x00000000, valid=0
ldtr:0x0028, dh=0x0000e200, dl=0x0c0b0040, valid=1
tr:0x0020, dh=0x0000eb00, dl=0x0c230068, valid=1
gdtr:base=0x000009c5, limit=0x3f
idtr:base=0x000001c5, limit=0x7ff
执行之后:
切换到任务0或者1,从TSS取得寄存器信息。特权级由0->3。
---------------------------------------
任务0一直在运行,然后收到时钟中断,这个时候会从特权级3跃升到特权级0,并且也会实现切换到当前任务的内核栈。