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

实现多任务的内核Linux0.00分析

时间: 2024-12-13 23:15:54

实现多任务的内核Linux0.00分析的相关文章

linux0.11内核fork实现分析(不看不知道,一看很简单)

pcDuino3下支持mmc启动,官方的Uboot是采用SPL框架实现的,因为内部的SRAM空间达到32K,我们完全可以在这32K空间内编写一个完整可用小巧的bootloader来完成引导Linux kernel的目的. 我们首先介绍下SPL框架,可以先看下<GNU ARM汇编--(十八)u-boot-采用nand_spl方式的启动方法>和<GNU ARM汇编--(十九)u-boot-nand-spl启动过程分析>,NAND_SPL也算是SPL框架下的一种模式. 当使用Nand f

Linux-0.12内核sleep_on函数分析

sleep_on用于进程休眠,原型如下: void sleep_on(struct task_struct **p) 当进程访问某个互斥资源时,如果资源被另外进程占用,当前进程就需要休眠. 假设资源的结构如下: struct res { .... struct task_struct *wait; } 其实我们参考下文件系统的i节点就会发现,i节点也是一种资源,它的结构体中就有一个变量i_wait.那么我们就用i节点举例.如果进程访问某个i节点,发现i节点被锁住,当前进程就需要睡眠:sleep_

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Linux内核源代码情景分析-系统初始化

我们跳过boot,setup,直接来到head代码,内核映像的起点是stext,也是_stext,引导和解压缩以后的整个映像放在内存从0x100000即1MB开始的区间.CPU执行内核映像的入口startup_32就在内核映像开头的地方,因此其物理地址也是0x100000. 然而,在正常运行时整个内核映像都应该在系统空间中,系统空间的虚拟地址与物理地址间有个固定的位移,这就是0xC0000000,即3GB.所以,在连接内核映像时已经在所有的符号地址加了一个偏移量0xC0000000,这样star

《Linux内核原理与分析》教学进程

目录 2019-2020-1 <Linux内核原理与分析>教学进程 考核方案 第一周: 第二周: 第三周: 第四周: 第五周 第六周 第七周: 第八周 第九周 第十周 第十一周: 第十二周 第十三周 2019-2020-1 <Linux内核原理与分析>教学进程 考核方案 采取过程化考核,平时成绩占100分,成绩计算:30+30+15+25=100: 翻转课堂基础考核10次: 3*10 = 30 每次考试20-30道题目,考试成绩规格化成3分(比如总分30分就除以10) 翻转课堂测试

Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938396.html 在基本分析完内核启动流程的之后,还有一个比较重要的初始化函数没有分析,那就是do_basic_setup.在内核init线程中调用了do_basic_setup,这个函数也做了很多内核和驱动的初始化工作,详解

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入

Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938393.html 在分析start_kernel函数的时候,其中有构架相关的初始化函数setup_arch. 此函数根据构架而异,对于ARM构架的详细分析如下: void __init setup_arch(char **cmdlin

Linux tcp被动打开内核源码分析

[我是从2个角度来看,其实所谓2个角度,是发现我分析源码时,分析重复了,写了2个分析报告,所以现在都贴出来.] [如果你是想看看,了解一下内核tcp被动打开时如何实现的话,我觉得还是看看概念就可以了,因为即使看了源码,过一个个礼拜你就忘了,如果是你正在修改协议栈,为不知道流程而发愁,那么希望你能看看源码以及注释,希望你给你帮助.] 概念: tcp被动打开,前提是你listen,这个被动打开的前提.你listen过后,其实创建了一个监听套接字,专门负责监听,不会负责传输数据. 当第一个syn包到达