在上一篇的基礎之上,寫一個多進程程序:程序切換原理:在定時中斷的時候將當前進程的現場保存在當前進程的堆棧中(中斷時並自動壓入eflag,cs,eip,因爲沒有特權級的轉變,所以也沒有堆棧的切換),然後將棧設爲目標進程的堆棧,並彈出該進程現場,中斷最後的iret指令會將程序接着目標進程的eip運行。
kernel.s因爲將所有的段偏移設爲0,所以編程時地址的處理非常容易。
boot.s
%define KERNEL_SEG 0x1000 ;內核開始運行的段位置
%define KERNEL_LEN 20 ;內核扇區所佔數目
org 0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ax,msg
mov bp,ax
mov ax,0x1301
mov bx,0x000c
mov cx,msgLen
mov dx,0x0000
int 10h;
load:
mov ax,KERNEL_SEG
mov es,ax
mov ah,02
mov al,KERNEL_LEN
xor bx,bx
mov ch,0
mov cl,2
mov dh,0
mov dl,0
int 13h
movKernel:
mov ax,KERNEL_SEG
mov ds,ax
xor ax,ax
mov es,ax
xor si,si
xor di,di
mov cx,KERNEL_LEN * 512
cli
copy:
mov al,[ds:si]
mov [es:di],al
inc si
inc di
loop copy
jmp 0:0
msg: db "Loading kernel"
msgLen equ $-msg
times 510-($-$$) db 0
dw 0xaa55
kernel.s:
%define STACK_LEN 1024 ;內核棧空間、進程棧空間大小
jmp start
;gdt
gdt: db 0,0,0,0,0,0,0,0
gdt_cs: db 0xff,0x7,0,0,0,0x9a,0xc0,0
gdt_ds: db 0xff,0x7,0,0,0,0x92,0xc0,0
gdt_gs: db 0x02,0,0,0x80,0x0b,0x92,0xc0,0
gdtLen equ $-gdt
selector_cs equ gdt_cs - gdt
selector_ds equ gdt_ds - gdt
selector_gs equ gdt_gs - gdt
gdtPtr:
dw gdtLen - 1
dd gdt
idt:
%rep 8
dw intHandler ;offset
dw selector_cs ;selector
dw 0x8e00 ;property
dw 0 ;offset
%endrep
dw timeInt ;offset
dw selector_cs ;selector
dw 0x8e00 ;property
dw 0 ;offset
%rep 256-9
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
jmp selector_cs:start_32
[bits 32]
start_32:
mov ax,selector_ds
mov ds,ax ;數據段初始化
mov ss,ax ;堆棧段初始化
mov esp,stack_top ;堆棧棧底
mov ah,0x0c
mov al,'P'
call dis_str
int 0x80
int 0x80
mov al,0x36 ;控制字:通道0工作方式3、計數初值採用二進制
out 0x43,al
mov ax,11930 ;頻率爲100hz
out 0x40,al ;低位
mov al,ah
out 0x40,al ;高位
sti
;初始化兩個進程,主要是進程對應的堆棧區,事先要壓入現場信息
;初始化進程1的堆棧
mov ax,selector_ds
mov ss,ax ;堆棧段初始化
mov esp,stack_top_1 ;堆棧棧底
pushf
push dword selector_cs ;push cs
push dword task_1 ;push eip
push ds
push es
push fs
push gs
pushad
mov [stack_1],esp ;保存進程1現在的棧指針
;初始化進程0的堆棧
mov ax,selector_ds
mov ss,ax ;堆棧段初始化
mov esp,stack_top_0 ;堆棧棧底
mov [stack_0],esp ;進程0的堆棧指針
pushf
push dword selector_cs ;push cs
push dword task_0 ;push eip
iret ;啓動進程0
jmp $ ;等待時鐘中斷去切換執行兩個任務
;
dis_str:
push ebx
mov bx,selector_gs
mov gs,bx
mov bx,selector_ds
mov ds,bx
mov ebx,[cursor_i]
shl ebx,1
mov [gs:ebx],ax
shr ebx,1
inc ebx
cmp ebx,80*25
jne .1
mov ebx,0
.1: mov [cursor_i],ebx
pop ebx
ret
intHandler:
iret
mov ah,0x0c
mov al,'I'
call dis_str
mov al,'n'
call dis_str
mov al,'t'
call dis_str
; mov al,0x20 ;發送EOI ;爲什麼這些中斷不需要寫這句話?
; out 0x20,al ;中斷處理結束,要是沒有這一句的話,只能響應中斷一次
iret
timeInt:
push ds
push es
push fs
push gs
pushad
;由於已經保存了現場,這些寄存器可以使用了
;切換到內核空間
mov ax,selector_ds
mov ds,ax
mov al,[process_now]
cmp al,0
jne j1
;如果是進程0
mov [stack_0],esp ;保存進程0的堆棧指針
mov al,1
mov [process_now],al
mov ax,selector_ds
mov ss,ax ;切換到進程1的堆棧
mov esp,[stack_1] ;
jmp j2
j1:
;如果是進程1
mov [stack_1],esp
mov al,0
mov [process_now],al
mov ax,selector_ds
mov ss,ax ;
mov esp,[stack_0] ;切換到進程0的堆棧,
j2: mov al,0x20 ;發送EOI
out 0x20,al ;中斷處理結束,要是沒有這一句的話,只能響應中斷一次
;將目標進程的現場回覆
popad ;popad 將通用寄存器彈出(除了esp)
pop gs
pop fs
pop es
pop ds
iret
task_0:
loo:
mov ah,0x0c
mov al,'A'
call dis_str
mov ecx,0xfffff ;delay
loop $
jmp loo
task_1:
loo2:
mov ah,0x0b
mov al,'B'
call dis_str
mov ecx,0xfffff ;delay
loop $
jmp loo2
data_label:
cursor_i dd 80 ;顯示屏幕位置
process_now db 0 ;當前運行進程
stack_0 dd 0 ;0進程的棧指針
stack_1 dd 0 ;1進程的棧指針
times STACK_LEN db 0
stack_top: ;內核棧底
times STACK_LEN db 0
stack_top_0: ;進程0使用的堆棧
times STACK_LEN db 0
stack_top_1: ;進程1使用的堆棧
times (512*20 - ($-$$)) db 0