首页 > 代码库 > 实现多任务的内核Linux0.00分析
实现多任务的内核Linux0.00分析
最近终于把实现多任务的微内核调试了一遍,我们阐述了如何在保护模式下切换任务。同时知识包括: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,并且也会实现切换到当前任务的内核栈。