上一篇的博文中,多进程全部是以特权级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