操作系统实现---多进程(有特权级转移)

    上一篇的博文中,多进程全部是以特权级0运行,一个最大的特征就是没有堆栈的切换;原以为无特权级的多进程实现了之后,有特权级转移的实现就非常容易了,可我竟在这里卡了足足一周的时间。

   参考于《一个操作系统的实现》于渊著。

有问题的程序:

;进程在执行:
;
;|--------|                     |##code##|
;|--------|                     |########|<-eip
;|--------|                     |--------|
;|--------|                     |--------|
;|--------|                     |--------|
;|--------|                     |--------|
;|--------|  /|\ 向上为低地址        
;|--------|                     |--------|
;|########|<-esp                |--------|
;|##stack#|                     |--------|
;|##data##|                     |--------|
;|########|                     |--------|
;|########|                     |--------|
;
;没有特权级的转移的中断发生:
;
;|--------|                     |##code##|
;|--------|                     |########|
;|--------|                     |--------|
;|--------|                     |--------|
;|--------|                     |--------|
;|--eip---|<-esp                |--------|
;|---|-cs-|  /|\ 向上为低地址        
;|--eflag-|                     |--------|
;|########|        interrupt:   |--------|<-eip
;|##stack#|                     |--------|
;|##data##|                     |--------|
;|########|                     |--------|
;|########|                     |--------|
;
;有特权级的转移的中断发生:;
;
;|--------|                     |--------|                     |##code##|
;|--------|                     |--------|                     |########|
;|--------|                     |--------|                     |--------|
;|--------|                     |--------|                     |--------|
;|--------|                     |--------|                     |--------|
;|--------|                     |--------|                     |--------|
;|--------|                     |--------|  /|\ 向上为低地址        
;|--------|                     |--------|                     |--------|
;|---eip--|<-esp         |----->|########|        interrupt:   |--------|<-eip
;|---|-cs-|              |      |##stack#|                     |--------|
;|--eflag-|              |      |##data##|                     |--------|
;|--esp---|----原栈栈顶----      |########|                     |--------|
;|---|-ss-|-->原栈的ss值         |########|                     |--------|
;
;栈底值由当前tss.esp0指定
%macro SAVE 0
		pushad 
		push ds
		push es
		push fs
		push gs
%endmacro
%macro RESTART 0
		pop gs
		pop fs
		pop	es
		pop ds
		popad
%endmacro
%define PROC_N 2	;进程数目
jmp start
	

gdt:		db 0,0,0,0,0,0,0,0
gdt_cs:		db 0xff,0x7,0,0,0,0x9a,0xc0,0		;特权级0代码段
gdt_ds:		db 0xff,0x7,0,0,0,0x92,0xc0,0		;特权级0数据段
gdt_gs:		db 0x02,0,0,0x80,0x0b,0x92,0xc0,0	;
gdt_usr_cs:	db 0xff,0x7,0,0,0,0xfa,0xc0,0	;特权级3代码段
gdt_usr_ds:	db 0xff,0x7,0,0,0,0xf2,0xc0,0	;特权级3数据段

gdt_tss:	dw tssLen - 1,tss,0xe900,0

gdtLen equ $ - gdt

gdtPtr:
	dw gdtLen - 1
	dd gdt

selector_cs 	equ gdt_cs - gdt
selector_ds 	equ gdt_ds - gdt
selector_gs 	equ gdt_gs - gdt
selector_usr_cs	equ	gdt_usr_cs - gdt + 3	;RPL = 3
selector_usr_ds	equ	gdt_usr_ds - gdt + 3	;RPL = 3
selector_tss	equ	gdt_tss	- gdt
idt:
%rep 8
	dw 	intHandler	;offset
	dw	selector_cs	;selector
	dw	0x8e00		;property
	dw	0			;offset
%endrep
;	the NO.8 interrupt
	dw 	timerHandler;offset
	dw	selector_cs	;selector
	dw	0x8e00		;property
	dw	0			;offset
%rep 0x80 - 9
	dw 	intHandler	;offset
	dw	selector_cs	;selector
	dw	0x8e00		;property
	dw	0			;offset
%endrep
;	the NO. 0x80 interrupt
	dw 	sysCall		;offset
	dw	selector_cs	;selector
	dw	0xef00		;property	特权级3,供用户程序使用系统调用
	dw	0			;offset
%rep 256 - 0x80
	dw 	intHandler	;offset
	dw	selector_cs	;selector
	dw	0x8e00		;property
	dw	0			;offset
%endrep

idtLen equ $ - idt

idtPtr:	dw idtLen -1
		dd idt

start:
	mov ax,cs
	mov ds,ax
	mov es,ax

	lgdt [gdtPtr]
	
	cli

	lidt [idtPtr]

	in al,0x92
	or al,00000010b
	out 0x92,al

	mov eax,cr0
	or eax,1
	mov cr0,eax
;
;此时的cpl为0 无法访问dpl为3的段
;00013904842e[CPU0 ] check_cs(0x0023): non-conforming code seg descriptor dpl != cpl, dpl=3, cpl=0
;
;	jmp selector_usr_cs:start_32
	jmp selector_cs:start_32


[bits 32]
start_32:
	mov ax,selector_gs
	mov gs,ax
	mov ax,selector_ds
	mov ds,ax
	mov ss,ax
	mov esp,stack_kernel_top

	;load tss
	mov ax,selector_tss
	ltr	ax

	;setup timer
	mov al,0x36		;控制字:通道0工作方式3、计数初值采用二进制
	out 0x43,al
	mov ax,11930	;频率为100hz
	out 0x40,al		;低位
	mov al,ah
	out 0x40,al		;高位

	
	sti

	;初始化进程1	

	mov esp,task_stack_frames
	sub esp,stack_frame_len

	push dword selector_usr_ds
	push dword stack_usr_1		;in usr process,stack is not used now
	pushf
	push dword selector_usr_cs
	push dword usr_task_1

	SAVE

	;启动进程0
	mov esp,task_stack_frames

	push dword selector_usr_ds
	push dword stack_usr_0		;in usr process,stack is not used now
	pushf
	push dword selector_usr_cs
	push dword usr_task_0
	
	mov eax,task_stack_frames
	mov [tss_esp0],eax

	iret

	jmp $

intHandler:
	SAVE
	mov ax,selector_ds
	mov ds,ax
	mov ax,selector_gs
	mov gs,ax
	mov ah,0x0c
	mov al,'I'
	mov [gs:((80*17 + 4)*2)],ax

	mov ecx,0x1fffff		;delay
	loop $
	
	RESTART
	iret
timerHandler:
	SAVE
	;切换到内核栈,注意,认为内核栈是空的
	mov	ax,selector_ds
	mov ds,ax
	mov ss,ax
	mov esp,stack_kernel_top
	
	
	mov ah,0x04
	mov al,'E'
	int 0x80

	mov ah,0x04
	mov al,'F'
	int 0x80

	mov ah,0x04
	mov al,'G'
	int 0x80


	;计算下一个进程号	
	mov eax,[proc_now]
	inc eax
	cmp eax,PROC_N
	jb l3
	xor eax,eax

l3: 
	mov [proc_now],eax
	mov ecx,stack_frame_len
	mul ecx

	
	mov ecx,task_stack_frames
	sub ecx,eax
	
	;切换堆栈,注意还要设置tss的esp0
	mov [tss_esp0],ecx
	mov esp,ecx
	sub esp,stack_frame_real_len

endt:
	mov al,0x20			;发送EOI 	
	out 0x20,al			;中断处理结束,要是没有这一句的话,只能响应中断一次

	RESTART
	iret

;param is ax
sysCall:
;	cli
	SAVE
	mov dx,selector_ds
	mov ds,dx
	mov dx,selector_gs
	mov gs,dx
	mov ecx,[cursor_i]
	shl ecx,1
	mov [gs:ecx],ax
	shr ecx,1
	inc ecx
	cmp ecx,1000
	jb j4
	xor ecx,ecx
j4:	mov [cursor_i],ecx
	
	mov ecx,0xffffff		;模拟耗时的系统调用
;	loop $					
	RESTART
;	sti						;不用软件开中断?CPU 执行中断完毕之后自动开?
	iret
usr_task_0:
l1:
	mov ax,selector_usr_ds
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov gs,ax

	mov ecx,0xfffff
	loop $		;delay
	mov ah,0x0c
	mov al,'A'
	int 0x80
	jmp l1

usr_task_1:
l2:
	mov edx,0x04
	mov ecx,0xfffff
	loop $		;delay
	mov ah,0x0b
	mov al,'B'
	int 0x80
	jmp l2

label_ds:

;esp0 for each usr task
;process 1:
stack_frame:
;leave more spaces than needed
;times 68 db 0
stack_frame_real:
;push ....
task_gs:	dd	selector_usr_ds
task_fs:	dd	selector_usr_ds
task_es:	dd	selector_usr_ds
task_ds:	dd	selector_usr_ds

;pushad
task_edi:	dd	0
task_esi:	dd	0
task_ebp:	dd	0
task_esp_:	dd	0
task_ebx:	dd	0
task_edx:	dd	0
task_ecx:	dd	0
task_eax:	dd	0

;push by CPU

task_eip:	dd	0
task_cs:	dd	0
task_flag:	dd 	0
task_esp:	dd	0
task_ss:	dd	0

stack_frame_len	equ	$ - stack_frame
stack_frame_real_len equ $ - stack_frame_real
task_stack_frame1:
;process 0

times stack_frame_len db 0
;往上依次是task0、task1的esp0栈
task_stack_frames:

proc_now 	dd  0	;the process running now
cursor_i	dd	40	;the display cursor 

tss:
tss_back:	dd	0
tss_esp0:	dd	0
tss_ss0:	dd	selector_ds			;all task use kernel ds
tss_esp1:	dd	0
tss_ss1:	dd	0
tss_esp2:	dd	0
tss_ss2:	dd	0
tss_cr3:	dd	0
tss_eip:	dd	0
tss_flag:	dd	0
tss_eax:	dd	0
tss_ecx:	dd	0
tss_edx:	dd	0
tss_ebx:	dd	0
tss_esp:	dd	0
tss_ebp:	dd	0
tss_esi:	dd	0
tss_edi:	dd	0
tss_es:		dd	0
tss_cs:		dd	0
tss_ss:		dd	0
tss_ds:		dd	0
tss_fs:		dd	0
tss_gs:		dd	0
tss_ldt:	dd	0
tss_debug:	dw	0
tss_IO_off:	dw	$ - tss + 2
tss_IO_end:	db	0xff

tssLen	equ $-tss

;kernel stack
stack_kernel:
	times 512 db 0
stack_kernel_top:

	times 512 db 0
stack_usr_0:
	times 512 db 0
stack_usr_1:

times 512*20 - ($ - $$) db 0


这个程序感觉没有什么问题,但是实际上运行一会儿就会有一个毛病:

系统随机过一段时间就要重启,这个问题困扰了 我很久一段时间,后来,我在系统调用中加入延时程序(mov ecx,0xffffff loop $这两句)之后,系统几乎不能运行。

回过头再来审视中断切换的过程:中断发生时,由于发生了特权级变化,导致堆栈切换(切换到哪里去由当前的tss中的esp0指定,但是注意,切换回去的时候是根据esp0栈中保存的ss,esp切回去的,即不会会写到tss中的esp0,总是认为这个esp0是空栈),在我写的这个程序中,由于进程的内核栈大小刚好保存现场那么大,而在这个程序中,如果在执行系统调用(内核代码)的时候发生了时钟中断,由于没有特权级变化,并不会切换堆栈(依然在进程的内核栈中),而程序并没有考虑到这一点(1、进程的内核栈太小了2、认为进程内核栈栈顶指针为一个固定值)

;|--各种 --|
;|--寄存器-|
;|---eip--|
;|---|-cs-|
;|--eflag-|
;|--esp---|
;|---|-ss-|
;|--各种 --|<----1号进程内核栈栈底<------------------------此时的esp
;|--寄存器-|
;|---eip--|
;|---|-cs-|
;|--eflag-|
;|--esp---|
;|---|-ss-|
;|--------|<----0号进程内核栈栈底

如果此时又发生了时钟中断:
;|--各种 --|
;|--寄存器-|
;|--各种 --|1号进程上下文被破坏    <------------------------此时的esp
;|--寄存器-|1号进程上下文被破坏
;|--eip---|1号进程上下文被破坏
;|---|-cs-|1号进程上下文被破坏
;|--eflag-|1号进程上下文被破坏
;|--各种 --|<----1号进程内核栈栈底
;|--寄存器-|
;|---eip--|
;|---|-cs-|
;|--eflag-|
;|--esp---|
;|---|-ss-|
;|--------|<----0号进程内核栈栈底

所以1、分配足够的内核空间给进程2、进程的内核栈栈指针是变化的,进程切换时需要保存(有时内核栈中有可能保存了多个现场(系统调用时又被时钟中断了))

改进之后的代码

%macro SAVE 0
		pushad 
		push ds
		push es
		push fs
		push gs
%endmacro
%macro RESTART 0
		pop gs
		pop fs
		pop	es
		pop ds
		popad
%endmacro
%define PROC_N 2	;进程数目
jmp start
	

gdt:		db 0,0,0,0,0,0,0,0
gdt_cs:		db 0xff,0x7,0,0,0,0x9a,0xc0,0		;特权级0代码段
gdt_ds:		db 0xff,0x7,0,0,0,0x92,0xc0,0		;特权级0数据段
gdt_gs:		db 0x02,0,0,0x80,0x0b,0x92,0xc0,0	;
gdt_usr_cs:	db 0xff,0x7,0,0,0,0xfa,0xc0,0	;特权级3代码段
gdt_usr_ds:	db 0xff,0x7,0,0,0,0xf2,0xc0,0	;特权级3数据段

gdt_tss:	dw tssLen - 1,tss,0xe900,0

gdtLen equ $ - gdt

gdtPtr:
	dw gdtLen - 1
	dd gdt

selector_cs 	equ gdt_cs - gdt
selector_ds 	equ gdt_ds - gdt
selector_gs 	equ gdt_gs - gdt
selector_usr_cs	equ	gdt_usr_cs - gdt + 3	;RPL = 3
selector_usr_ds	equ	gdt_usr_ds - gdt + 3	;RPL = 3
selector_tss	equ	gdt_tss	- gdt
idt:
%rep 8
	dw 	intHandler	;offset
	dw	selector_cs	;selector
	dw	0x8e00		;property
	dw	0			;offset
%endrep
;	the NO.8 interrupt
	dw 	timerHandler;offset
	dw	selector_cs	;selector
	dw	0x8e00		;property
	dw	0			;offset
%rep 0x80 - 9
	dw 	intHandler	;offset
	dw	selector_cs	;selector
	dw	0x8e00		;property
	dw	0			;offset
%endrep
;	the NO. 0x80 interrupt
	dw 	sysCall		;offset
	dw	selector_cs	;selector
	dw	0xef00		;property	特权级3,供用户程序使用系统调用
	dw	0			;offset
%rep 256 - 0x80
	dw 	intHandler	;offset
	dw	selector_cs	;selector
	dw	0x8e00		;property
	dw	0			;offset
%endrep

idtLen equ $ - idt

idtPtr:	dw idtLen -1
		dd idt

start:
	mov ax,cs
	mov ds,ax
	mov es,ax

	lgdt [gdtPtr]
	
	cli

	lidt [idtPtr]

	in al,0x92
	or al,00000010b
	out 0x92,al

	mov eax,cr0
	or eax,1
	mov cr0,eax
;
;此时的cpl为0 无法访问dpl为3的段
;00013904842e[CPU0 ] check_cs(0x0023): non-conforming code seg descriptor dpl != cpl, dpl=3, cpl=0
;
;	jmp selector_usr_cs:start_32
	jmp selector_cs:start_32


[bits 32]
start_32:
	mov ax,selector_gs
	mov gs,ax
	mov ax,selector_ds
	mov ds,ax
	mov ss,ax
	mov esp,stack_kernel_top

	;load tss
	mov ax,selector_tss
	ltr	ax

	;setup timer
	mov al,0x36		;控制字:通道0工作方式3、计数初值采用二进制
	out 0x43,al
	mov ax,11930	;频率为100hz
	out 0x40,al		;低位
	mov al,ah
	out 0x40,al		;高位



;	mov ah,0x0d
;	mov al,'C'
;	int 0x80
	
	sti

	;初始化进程1	

	mov esp,proc_tabel + PROC_TABEL_LEN * 1 + PROC_TABEL_LEN

	push dword selector_usr_ds
	push dword stack_usr_1		
	pushf
	push dword selector_usr_cs
	push dword usr_task_1

	SAVE
	mov dword [proc_tabel+PROC_TABEL_LEN*1],esp	;proc_kernel_esp

	;启动进程0
	mov esp,proc_tabel + PROC_TABEL_LEN * 0 + PROC_TABEL_LEN

	push dword selector_usr_ds
	push dword stack_usr_0		
	pushf
	push dword selector_usr_cs
	push dword usr_task_0
	
	mov eax,proc_tabel + PROC_TABEL_LEN * 0 + PROC_TABEL_LEN
	mov [tss_esp0],eax

	iret

	jmp $

intHandler:
	SAVE
	mov ax,selector_ds
	mov ds,ax
	mov ax,selector_gs
	mov gs,ax
	mov ah,0x0c
	mov al,'I'
	mov [gs:((80*17 + 4)*2)],ax

	mov ecx,0x1fffff		;delay
	loop $
	
	RESTART
	iret
timerHandler:
	SAVE
	;切换到内核栈,注意,认为内核栈是空的
	mov	ax,selector_ds
	mov ds,ax

	;保存当前进程内核栈指针
	mov eax,[proc_now]
	mov ecx,PROC_TABEL_LEN
	mul ecx	
	mov ecx,proc_tabel
	add ecx,eax
	mov [ecx],esp

	mov	ax,selector_ds	
	mov ss,ax
	mov esp,stack_kernel_top

;	mov ecx,0xfffffff		;模拟耗时的系统调用
;	loop $	
	
	
	mov ah,0x04
	mov al,'E'
	int 0x80

	mov ah,0x04
	mov al,'F'
	int 0x80

	mov ah,0x04
	mov al,'G'
	int 0x80

	;下一个进程号
	mov eax,[proc_now]
	inc eax
	cmp eax,PROC_N
	jb l3
	xor eax,eax

l3: 
	mov [proc_now],eax
	mov ecx,PROC_TABEL_LEN
	mul ecx
	
	mov ecx,proc_tabel
	add ecx,eax

	;切换堆栈,注意还要设置tss的esp0
	mov esp,[ecx]		;the proc_kernel_esp
	add ecx,PROC_TABEL_LEN
	mov [tss_esp0],ecx

endt:
	mov al,0x20			;发送EOI 	
	out 0x20,al			;中断处理结束,要是没有这一句的话,只能响应中断一次

	RESTART
	iret

;param is ax
sysCall:
	SAVE
	mov dx,selector_ds
	mov ds,dx
	mov dx,selector_gs
	mov gs,dx
	mov ecx,[cursor_i]
	shl ecx,1
	mov [gs:ecx],ax
	shr ecx,1
	inc ecx
	cmp ecx,1000
	jb j4
	xor ecx,ecx
j4:	mov [cursor_i],ecx
	
	mov ecx,0xffffff		;模拟耗时的系统调用
;	loop $					
	RESTART
	iret
usr_task_0:
l1:
	mov ax,selector_usr_ds
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov gs,ax

	mov ecx,0xfffff
	loop $		;delay
	mov ah,0x0c
	mov al,'A'
	int 0x80
	jmp l1

usr_task_1:
l2:
	mov edx,0x04
	mov ecx,0xfffff
	loop $		;delay
	mov ah,0x0b
	mov al,'B'
	int 0x80
	jmp l2

label_ds:

;进程上下文
;|进程控制块,进程内核栈|
;这里进程控制块只使用到了:内核栈指针,
;-------------------
;proc_kernel_esp:
;
;
;
;
;
;          ^
;         /|\
;          |
;		   |
;-------------------

proc_tabel:
;proc_0:
			dd	0 	;proc_kernel_esp
times 1024 	db 0	;stack

PROC_TABEL_LEN equ $ - proc_tabel

;proc_1:

times PROC_TABEL_LEN db 0



proc_now 	dd  0	;the process running now
cursor_i	dd	40	;the display cursor 

tss:
tss_back:	dd	0
tss_esp0:	dd	0
tss_ss0:	dd	selector_ds			;all task use kernel ds
tss_esp1:	dd	0
tss_ss1:	dd	0
tss_esp2:	dd	0
tss_ss2:	dd	0
tss_cr3:	dd	0
tss_eip:	dd	0
tss_flag:	dd	0
tss_eax:	dd	0
tss_ecx:	dd	0
tss_edx:	dd	0
tss_ebx:	dd	0
tss_esp:	dd	0
tss_ebp:	dd	0
tss_esi:	dd	0
tss_edi:	dd	0
tss_es:		dd	0
tss_cs:		dd	0
tss_ss:		dd	0
tss_ds:		dd	0
tss_fs:		dd	0
tss_gs:		dd	0
tss_ldt:	dd	0
tss_debug:	dw	0
tss_IO_off:	dw	$ - tss + 2
tss_IO_end:	db	0xff

tssLen	equ $-tss

;kernel stack
stack_kernel:
	times 512 db 0
stack_kernel_top:

	times 512 db 0
stack_usr_0:
	times 512 db 0
stack_usr_1:

times 512*20 - ($ - $$) db 0



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章