在上一篇的基础之上,写一个多进程程序:程序切换原理:在定时中断的时候将当前进程的现场保存在当前进程的堆栈中(中断时并自动压入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