大家都知道操作系统利用中断来与外设进行交互。80xx86兼容机使用两片级联的8259A可编程中断控制芯片组成一个中断控制器,用于实现与I/O设备的交互。可编程意味着我们可以为不同的外设(时钟,键盘,鼠标等)分配不同的中断号,从而执行不同的中断处理过程。
BIOS初始化这个中断控制器时将中断号8分配给了时钟控制器,因而下面我们通过将中断向量表中的8号描述符设为我们的中断程序地址,进而实现时钟中断程序。我们在中断程序中打印字符'E',每打2000次后再从头开始打,为了能够看出重复打印的效果,满2000次后更换颜色。
LATCH equ 11930
VIDEO_DS equ 0x18
[SECTION text]
[BITS 32]
startup_32:
mov eax,0x10
mov ds,ax
lss esp,[init_stack] ;mem low->reg,mem high->ss
call setup_idt ;初始化IDT中断向量表
call setup_gdt ;初始化GDT
mov eax,0x10 ;初始化完GDT后重新加载ds,es,fs,gs,ss,sp
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
lss esp,[init_stack]
;设置8253定时芯片,把计数器通道0设置成每隔10ms向中断控制器发送一个中断请求
mov al,0x36
mov edx,0x43
out dx,al
mov eax,LATCH
mov edx,0x40
out dx,al
mov al,ah
out dx,al
sti ;开中断
loop:
jmp loop
setup_gdt:
lgdt [lgdt_opcode]
ret
;begin setup_idt fun ;初始化idt,256个描述符全初始化为打印字符函数
setup_idt:
lea edx,[ignore_int] ;中断入口偏移放入edx
mov eax,0x00080000 ;eax高位为内核代码段选择子
mov ax,dx ;eax低位为中断入口偏移低位
mov dx,0x8E00 ;edx低位为中断门定义.DPL=0
lea edi,[idt] ;得到idt表位置,准备初始化
mov ecx,256 ;初始化256 IDT描述符
rp_idt:
mov [edi],eax
mov [edi+4],edx
add edi,8
dec ecx
jne rp_idt
lidt [lidt_opcode]
ret
;end setup_idt fun
ignore_int: ;默认中断处理函数
push ds
push eax
mov eax,0x10
mov ds,ax
mov eax,69
call write_char
mov al,0x20
out 0x20,al
pop eax
pop ds
iret
;BEGIN lidt
;Begin write_char
write_char: ;打印字符函数
push gs
push ebx
mov ebx,VIDEO_DS ;取显存数据段
mov gs,bx
mov bx,[color] ;取显示颜色
cmp bx,0
jg change_color ;当前显示1
mov ah, 0Ch ;黑底红字
jmp dis
change_color:
mov ah, 0Fh ;
dis:
mov bx,[DWORD scr_loc] ;取显存位置
mov [byte gs:ebx],ax ;写入显存
add ebx,2 ;下一个位置
mov [DWORD scr_loc],ebx ;放入内存保存
cmp ebx,2000 ;是否大于2000
jb exit ;小于2000直接退出
mov bx,[color]
cmp bx,0
jz change_color1 ;原来为0,改为1
mov bx,0 ;原来为1,改为0
jmp exit1
change_color1:
mov bx,1
exit1:
mov [color],bx
xor ebx,ebx ;位置置0
mov [DWORD scr_loc],ebx
exit:
pop ebx
pop gs
ret
;End write_char
color: db 0 ;显示颜色
current: dd 0 ;当前执行的任务号
scr_loc: dw 0 ;屏幕位置
align 2
lidt_opcode: ;LIDT寄存器加载的内容
dw 256*8-1 ;长度
dd idt ;线性基址
;END lidt
lgdt_opcode: ;GDT寄存器加载的内容
dw (end_gdt - gdt)-1
dd gdt
align 3 ;GDT内容
gdt:
dd 0x00000000 ;NULL
dd 0x00000000
dd 0x000007ff
dd 0x00c09a00 ;内核代码段0x08
dd 0x000007ff
dd 0x00c09200 ;内核数据段0x10
dd 0x80000002
dd 0x00c0920b ;显存数据段描述符0x18
dw 0x68
dw tss0
dw 0x8900
dw 0x0000 ;内核TSS 0x20
end_gdt:
tss0:
dd 0
dd init_stack,0x10 ;内核esp0,ss0
dd 0,0,0,0,0 ;esp1,ss1,esp2,ss2,cr3
dd 0,0,0,0,0 ;eip,eflags,eax,ecx,edx
dd 0,0,0,0,0 ;ebx,esp,ebp,esi,edi
dd 0,0,0,0,0,0 ;es,cs,ss,ds,fs,gs
dd 0,0x80000000 ;ldt,trace bitmap
idt:
times 512 dd 0 ;初始化中断描述表
kernel_stack:
times 128 dw 0 ;512B内核堆栈
init_stack:
dd init_stack ;堆栈段偏移位置
dw 0x10 ;内核数据段选择符
同样,将编译生成的bin文件复制到前面所说的软盘映像IMG的第2扇区开始处,引导程序就会将其加载到内存0处并执行,然后可以看到屏幕上不停地打印字符'E'.如果对程序中有所疑问,可以查看大家一起写操作系统预备知识.我会将中断的相关知识写到预备知识里。下一章将会介绍如何创建2个任务,并在任务间来回调度。