操作系統實現---多進程(有特權級轉移)

    上一篇的博文中,多進程全部是以特權級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



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