实践内容
预备工作
- 8259A初始化,读写IMR寄存器,发送EOI控制字,等
实践一
- 自定义软中断的实现 (内部中断处理)
实践二
- 时钟中断的响应及处理 (外部中断处理)
预备工作
编写延迟函数 (Delay)
编写8259A初始化函数 (Init8259A)
编写8259A中断屏蔽寄存器读写函数 (ReadIMR;WriteIMR)
编写8259A中断结束符写入函数 (WriteEOI)
Init8259A
读写IMR寄存器
使用OCW1设置IMR的目标值
写入对应端口 (0x21或0xA1)
汇编小贴士
汇编语言支持预处理语句 (如:%include)
与c语言中的情况类似,汇编预处理语句常用于文本替换
示例:语句重复 (%rep)
inc.asm新增的一些常量定义
; PIC-8259A Ports
MASTER_ICW1_PORT equ 0x20
MASTER_ICW2_PORT equ 0x21
MASTER_ICW3_PORT equ 0x21
MASTER_ICW4_PORT equ 0x21
MASTER_OCW1_PORT equ 0x21
MASTER_OCW2_PORT equ 0x20
MASTER_OCW3_PORT equ 0x20
SLAVE_ICW1_PORT equ 0xA0
SLAVE_ICW2_PORT equ 0xA1
SLAVE_ICW3_PORT equ 0xA1
SLAVE_ICW4_PORT equ 0xA1
SLAVE_OCW1_PORT equ 0xA1
SLAVE_OCW2_PORT equ 0xA0
SLAVE_OCW3_PORT equ 0xA0
MASTER_EOI_PORT equ 0x20
MASTER_IMR_PORT equ 0x21
MASTER_IRR_PORT equ 0x20
MASTER_ISR_PORT equ 0x20
SLAVE_EOI_PORT equ 0xA0
SLAVE_IMR_PORT equ 0xA1
SLAVE_IRR_PORT equ 0xA0
SLAVE_ISR_PORT equ 0xA0
预备工作
%include "inc.asm"
org 0x9000
jmp ENTRY_SEGMENT
[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_32 + DA_C
VIDEO_DESC : Descriptor 0xb8000, 0x7fff, DA_32 + DA_DRWA
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_32 + DA_DRW
STACK32_DESC : Descriptor 0, TopOfStack32, DA_32 + DA_DRW
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0
Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
TopOfStack16 equ 0x7c00
[section .dat]
[bits 32]
DATA32_SEGMENT:
DTOS db "D.T.OS!", 0
DTOS_Offset equ DTOS - $$
HELLOWORLD db "Hello, World!", 0
HELLOWORLD_Offset equ HELLOWORLD - $$
Data32SegLen equ $ - DATA32_SEGMENT
[section .s16]
[bits 16]
ENTRY_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStack16
; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DESC
call InitDescItem
mov esi, DATA32_SEGMENT
mov edi, DATA32_DESC
call InitDescItem
mov esi, STACK32_SEGMENT
mov edi, STACK32_DESC
call InitDescItem
; initalize GDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, GDT_ENTRY
mov dword [GdtPtr + 2], eax
; 1. load GDT
lgdt [GdtPtr]
; 2. close interrupt
cli
; 3. open A20
in al, 0x92
or al, 00000010b
out 0x92, al
; 4. enter protect mode
mov eax, cr0
or eax, 0x01
mov cr0, eax
; 5. jump to 32 bits code
jmp dword Code32Selector : 0
; esi --> code segment labelBACK_ENTRY_SEGMENT
; edi --> descriptor label
InitDescItem:
push eax
mov eax, 0
mov ax, cs
shl eax, 4
add eax, esi
mov word [edi + 2], ax
shr eax, 16
mov byte [edi + 4], al
mov byte [edi + 7], ah
pop eax
ret
[section .s32]
[bits 32]
CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax
mov ax, Stack32Selector
mov ss, ax
mov eax, TopOfStack32
mov esp, eax
mov ax, Data32Selector
mov ds, ax
mov ebp, DTOS_Offset
mov bx, 0x0c
mov dh, 13
mov dl, 33
call PrintString
call Init8259A
mov ax, 0xFF
mov dx, MASTER_IMR_PORT
call WriteIMR
mov ax, 0xFF
mov dx, SLAVE_IMR_PORT
call WriteIMR
jmp $
;
;
Delay:
%rep 5
nop
%endrep
ret
;
;
Init8259A:
push ax
; Master
; ICW1
mov al, 00010001B
out MASTER_ICW1_PORT, al
call Delay
; ICW2
mov al, 0x20
out MASTER_ICW2_PORT, al
call Delay
; ICW3
mov al, 00000100B
out MASTER_ICW3_PORT, al
call Delay
; ICW4
mov al, 00010001B
out MASTER_ICW4_PORT, al
call Delay
; Slave
; ICW1
mov al, 00010001B
out SLAVE_ICW1_PORT, al
call Delay
; ICW2
mov al, 0x28
out SLAVE_ICW2_PORT, al
call Delay
; ICW3
mov al, 00000010B
out SLAVE_ICW3_PORT, al
call Delay
; ICW4
mov al, 00000001B
out SLAVE_ICW4_PORT, al
call Delay
pop ax
ret
;al --> IMR register value
;dx --> 8259A port
WriteIMR:
out dx, al
call Delay
ret
; dx --> 8259A port
; return:
; al --> IMR register value
ReadIMR:
in al, dx
call Delay
ret
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintString:
push ebp
push cx
push eax
push dx
push edi
print:
mov cl, [ds:ebp]
cmp cl, 0
je end
mov eax, 80
mul dh
add al, dl
shl eax, 1
mov edi, eax
mov ah, bl
mov al, cl
mov [gs:edi], ax
inc ebp
inc dl
jmp print
end:
pop edi
pop dx
pop eax
pop cx
pop ebp
ret
Code32SegLen equ $ - CODE32_SEGMENT
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1024 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
153行-200行,我们定义了初始化8259A的函数;202-217行,我们定义了读写8259A中断屏蔽寄存器的函数。
实践一
自定义保护模式下的软中断 (0x80)
0x80中断使用后,在屏幕上打印字符串
示例
实现思路
注意事项
x86处理器一共支持256个中断类型,因此,中断描述表中需要有256个描述符与之对应。
中断描述符表
内部中断实现
%include "inc.asm"
org 0x9000
jmp ENTRY_SEGMENT
[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_32 + DA_C
VIDEO_DESC : Descriptor 0xb8000, 0x7fff, DA_32 + DA_DRWA
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_32 + DA_DRW
STACK32_DESC : Descriptor 0, TopOfStack32, DA_32 + DA_DRW
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0
Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
[section .idt]
align 32
[bits 32]
IDT_ENTRY:
; IDT definition
; Selector, Offset, DCount, Attribut
%rep 128
Gate Code32Selector, DefaultHandler, 0, DA_386IGate
%endrep
Int0x80 : Gate Code32Selector, Int0x80Handler, 0, DA_386IGate
%rep 127
Gate Code32Selector, DefaultHandler, 0, DA_386IGate
%endrep
; IDT end
IdtLen equ $ - IDT_ENTRY
IdtPtr:
dw IdtLen - 1
dd 0
TopOfStack16 equ 0x7c00
[section .dat]
[bits 32]
DATA32_SEGMENT:
DTOS db "D.T.OS!", 0
DTOS_Offset equ DTOS - $$
HELLOWORLD db "Hello, World!", 0
HELLOWORLD_Offset equ HELLOWORLD - $$
INT_0X80 db "int 0x80", 0
INT_0X80_Offset equ INT_0X80 - $$
Data32SegLen equ $ - DATA32_SEGMENT
[section .s16]
[bits 16]
ENTRY_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStack16
; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DESC
call InitDescItem
mov esi, DATA32_SEGMENT
mov edi, DATA32_DESC
call InitDescItem
mov esi, STACK32_SEGMENT
mov edi, STACK32_DESC
call InitDescItem
; initialize GDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, GDT_ENTRY
mov dword [GdtPtr + 2], eax
; initialize IDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, IDT_ENTRY
mov dword [IdtPtr + 2], eax
; 1. load GDT
lgdt [GdtPtr]
; 2. close interrupt and load IDT
cli
lidt [IdtPtr]
; 3. open A20
in al, 0x92
or al, 00000010b
out 0x92, al
; 4. enter protect mode
mov eax, cr0
or eax, 0x01
mov cr0, eax
; 5. jump to 32 bits code
jmp dword Code32Selector : 0
; esi --> code segment labelBACK_ENTRY_SEGMENT
; edi --> descriptor label
InitDescItem:
push eax
mov eax, 0
mov ax, cs
shl eax, 4
add eax, esi
mov word [edi + 2], ax
shr eax, 16
mov byte [edi + 4], al
mov byte [edi + 7], ah
pop eax
ret
[section .s32]
[bits 32]
CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax
mov ax, Stack32Selector
mov ss, ax
mov eax, TopOfStack32
mov esp, eax
mov ax, Data32Selector
mov ds, ax
mov ebp, DTOS_Offset
mov bx, 0x0c
mov dh, 13
mov dl, 33
call PrintString
call Init8259A
mov ax, 0xFF
mov dx, MASTER_IMR_PORT
call WriteIMR
mov ax, 0xFF
mov dx, SLAVE_IMR_PORT
call WriteIMR
mov ebp, INT_0X80_Offset
mov bx, 0x0c
mov dh, 14
mov dl, 32
int 0x80
jmp $
;
;
Delay:
%rep 5
nop
%endrep
ret
;
;
Init8259A:
push ax
; Master
; ICW1
mov al, 00010001B
out MASTER_ICW1_PORT, al
call Delay
; ICW2
mov al, 0x20
out MASTER_ICW2_PORT, al
call Delay
; ICW3
mov al, 00000100B
out MASTER_ICW3_PORT, al
call Delay
; ICW4
mov al, 00010001B
out MASTER_ICW4_PORT, al
call Delay
; Slave
; ICW1
mov al, 00010001B
out SLAVE_ICW1_PORT, al
call Delay
; ICW2
mov al, 0x28
out SLAVE_ICW2_PORT, al
call Delay
; ICW3
mov al, 00000010B
out SLAVE_ICW3_PORT, al
call Delay
; ICW4
mov al, 00000001B
out SLAVE_ICW4_PORT, al
call Delay
pop ax
ret
;al --> IMR register value
;dx --> 8259A port
WriteIMR:
out dx, al
call Delay
ret
; dx --> 8259A port
; return:
; al --> IMR register value
ReadIMR:
in al, dx
call Delay
ret
DefaultHandlerFunc:
iret
DefaultHandler equ DefaultHandlerFunc - $$
Int0x80HandlerFunc:
call PrintString
iret
Int0x80Handler equ Int0x80HandlerFunc - $$
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintString:
push ebp
push cx
push eax
push dx
push edi
print:
mov cl, [ds:ebp]
cmp cl, 0
je end
mov eax, 80
mul dh
add al, dl
shl eax, 1
mov edi, eax
mov ah, bl
mov al, cl
mov [gs:edi], ax
inc ebp
inc dl
jmp print
end:
pop edi
pop dx
pop eax
pop cx
pop ebp
ret
Code32SegLen equ $ - CODE32_SEGMENT
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1024 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
257行-261行,我们定义了默认的中断函数,注意:在中断函数里需要iret指令返回。
263行-266行,我们定义了字符串打印的中断函数。
29行-42行,我们定义了中断描述符表,由于中断描述符表中需要给出256个中断描述符,而我们只需要用到128(0x80)号中断,所以我们需要填补其他的中断描述符,用%rep来填补。
94行-99行,我们初始化了IDT结构体的中断描述符表的起始地址,107行,我们将中断描述符表加载进内存中。
172行-176行,我们调用了0x80号我们自己定义的中断来调用字符串打印函数,这和BIOS提供的0x10号中断的功能是一样的。
我们成功的通过了0x80号中断打印了"int 0x80"这个字符串。
实践二
处理外部时钟中断 (主8259A-IR0引脚上的中断请求)
接受到时钟中断后,在屏幕上循环打印0-9
实现思路
注意事项
由于8259A初始化为手动结束中断的方式,因此,外部中断服务程序中需要手动发送结束控制字。
发送中断结束命令字
时钟中断处理
%include "inc.asm"
org 0x9000
jmp ENTRY_SEGMENT
[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_32 + DA_C
VIDEO_DESC : Descriptor 0xb8000, 0x7fff, DA_32 + DA_DRWA
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_32 + DA_DRW
STACK32_DESC : Descriptor 0, TopOfStack32, DA_32 + DA_DRW
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0
Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
[section .idt]
align 32
[bits 32]
IDT_ENTRY:
; IDT definition
; Selector, Offset, DCount, Attribut
%rep 32
Gate Code32Selector, DefaultHandler, 0, DA_386IGate
%endrep
Int0x20 : Gate Code32Selector, TimerHandler, 0, DA_386IGate
%rep 95
Gate Code32Selector, DefaultHandler, 0, DA_386IGate
%endrep
Int0x80 : Gate Code32Selector, Int0x80Handler, 0, DA_386IGate
%rep 127
Gate Code32Selector, DefaultHandler, 0, DA_386IGate
%endrep
; IDT end
IdtLen equ $ - IDT_ENTRY
IdtPtr:
dw IdtLen - 1
dd 0
TopOfStack16 equ 0x7c00
[section .dat]
[bits 32]
DATA32_SEGMENT:
DTOS db "D.T.OS!", 0
DTOS_Offset equ DTOS - $$
HELLOWORLD db "Hello, World!", 0
HELLOWORLD_Offset equ HELLOWORLD - $$
INT_0X80 db "int 0x80", 0
INT_0X80_Offset equ INT_0X80 - $$
Data32SegLen equ $ - DATA32_SEGMENT
[section .s16]
[bits 16]
ENTRY_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStack16
; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DESC
call InitDescItem
mov esi, DATA32_SEGMENT
mov edi, DATA32_DESC
call InitDescItem
mov esi, STACK32_SEGMENT
mov edi, STACK32_DESC
call InitDescItem
; initialize GDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, GDT_ENTRY
mov dword [GdtPtr + 2], eax
; initialize IDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, IDT_ENTRY
mov dword [IdtPtr + 2], eax
; 1. load GDT
lgdt [GdtPtr]
; 2. close interrupt and load IDT
cli
lidt [IdtPtr]
; 3. open A20
in al, 0x92
or al, 00000010b
out 0x92, al
; 4. enter protect mode
mov eax, cr0
or eax, 0x01
mov cr0, eax
; 5. jump to 32 bits code
jmp dword Code32Selector : 0
; esi --> code segment labelBACK_ENTRY_SEGMENT
; edi --> descriptor label
InitDescItem:
push eax
mov eax, 0
mov ax, cs
shl eax, 4
add eax, esi
mov word [edi + 2], ax
shr eax, 16
mov byte [edi + 4], al
mov byte [edi + 7], ah
pop eax
ret
[section .s32]
[bits 32]
CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax
mov ax, Stack32Selector
mov ss, ax
mov eax, TopOfStack32
mov esp, eax
mov ax, Data32Selector
mov ds, ax
mov ebp, DTOS_Offset
mov bx, 0x0c
mov dh, 13
mov dl, 33
call PrintString
call Init8259A
mov ax, 0xFF
mov dx, MASTER_IMR_PORT
call WriteIMR
mov ax, 0xFF
mov dx, SLAVE_IMR_PORT
call WriteIMR
mov ebp, INT_0X80_Offset
mov bx, 0x0C
mov dh, 14
mov dl, 32
int 0x80
sti
call EnableTimer
jmp $
;
;
EnableTimer:
push ax
push dx
mov ah, 0x0C
mov al, '0'
mov [gs : ((15 * 80 + 32) * 2)], ax
mov dx, MASTER_IMR_PORT
call ReadIMR
and ax, 0xFE
call WriteIMR
pop ax
pop dx
ret
;
;
Delay:
%rep 5
nop
%endrep
ret
;
;
Init8259A:
push ax
; Master
; ICW1
mov al, 00010001B
out MASTER_ICW1_PORT, al
call Delay
; ICW2
mov al, 0x20
out MASTER_ICW2_PORT, al
call Delay
; ICW3
mov al, 00000100B
out MASTER_ICW3_PORT, al
call Delay
; ICW4
mov al, 00010001B
out MASTER_ICW4_PORT, al
call Delay
; Slave
; ICW1
mov al, 00010001B
out SLAVE_ICW1_PORT, al
call Delay
; ICW2
mov al, 0x28
out SLAVE_ICW2_PORT, al
call Delay
; ICW3
mov al, 00000010B
out SLAVE_ICW3_PORT, al
call Delay
; ICW4
mov al, 00000001B
out SLAVE_ICW4_PORT, al
call Delay
pop ax
ret
;al --> IMR register value
;dx --> 8259A port
WriteIMR:
out dx, al
call Delay
ret
; dx --> 8259A port
; return:
; al --> IMR register value
ReadIMR:
in al, dx
call Delay
ret
;
; dx --> 8259A port
WriteEOI:
push ax
mov al, 0x20
out dx, al
call Delay
pop ax
ret
DefaultHandlerFunc:
iret
DefaultHandler equ DefaultHandlerFunc - $$
Int0x80HandlerFunc:
call PrintString
iret
Int0x80Handler equ Int0x80HandlerFunc - $$
TimerHandlerFunc:
push ax
push dx
mov ax, [gs : ((15 * 80 + 32) * 2)]
cmp al, '9'
je throtate
inc al
jmp thshow
throtate:
mov al, '0'
thshow:
mov [gs : ((15 * 80 + 32) * 2)], ax
mov dx, MASTER_OCW2_PORT
call WriteEOI
pop ax
pop dx
iret
TimerHandler equ TimerHandlerFunc - $$
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintString:
push ebp
push cx
push eax
push dx
push edi
print:
mov cl, [ds:ebp]
cmp cl, 0
je end
mov eax, 80
mul dh
add al, dl
shl eax, 1
mov edi, eax
mov ah, bl
mov al, cl
mov [gs:edi], ax
inc ebp
inc dl
jmp print
end:
pop edi
pop dx
pop eax
pop cx
pop ebp
ret
Code32SegLen equ $ - CODE32_SEGMENT
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1024 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
310行-332行,定义了外部时钟中断的中断处理函数,这个函数的作用是在屏幕的15行32列,循环打印数字0-9;327行,手动发送结束控制字,因为此函数是外部中断函数,并且我们将8259A初始化为手动结束中断的方式,所以在外部中断函数调用结束时,需要手动清除对应的ISR标志位。
189行-205行,我们定义了使能外部时钟中断的函数,我们将IMR的第0位置0,表示8259A将IR0对应的中断放行,IR0所连接的就是时钟,这样8259A就可以把时钟对应的中断请求发送给处理器了!
第182行,我们打开了处理中断的总开关,由于进入保护模式前我们把中断开关关闭了,所以需要重新打开,打开这个开关后,处理器就可以接收到8259A发送过来的中断请求了。
第183我们调用了使能时钟的函数,处理器在接受到时钟中断后,在屏幕上循环打印0-9。