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