首页 > 代码库 > 实现多任务的内核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,并且也会实现切换到当前任务的内核栈。