8086 汇编键盘 ISR 实现
8086 Assembly keyboard ISR implementation
我不明白为什么我为我的程序编写的键盘中断服务例程(每次按下一个键都应该打印"hello world")在dosbox上执行.exe时只发生一次。
这是代码:
NAME keyb
PILE SEGMENT STACK
db 20 dup ('LA PILE ')
PILE ENDS
DONNEE SEGMENT
message db "Hello wolrd !$"
DONNEE ENDS
PROGRAMME SEGMENT
ASSUME CS:PROGRAMME , DS:DONNEE, ES:NOTHING, SS:PILE
debut:
mov ax,DONNEE
mov ds,ax
cli
xor ax,ax
mov es, ax ; load ES with segment address of interrupt pointer table
mov bx, 24h ; load BX with interrupt type
mov word ptr es:[bx], OFFSET SUBR ; isr address
mov word ptr es:[bx]+2, SEG SUBR ; isr segment
mov ax, 01h
sti
BOUCLE: jmp BOUCLE
SUBR PROC NEAR
cli
mov ah,9
mov dx,OFFSET message
int 21h
sti
IRET
SUBR ENDP
PROGRAMME ENDS
END debut
我尝试了一些操作,例如压入和弹出寄存器,使用另一个中断(系统时钟 08h),但是 none 成功了。
我知道 ISR 至少运行一次,因为消息 "hello world" 出现在屏幕上,但每次我按下一个键时它应该打印,但我不知道为什么不打印。
我该如何解决?
你必须对 "free" 键盘和中断做一些工作。看看here。最简单的方法是跳转到您自己的处理程序末尾的旧 IRQ 处理程序:
NAME keyb
PILE SEGMENT STACK
db 20 dup ('LA PILE ')
PILE ENDS
DONNEE SEGMENT
message db "Hello wolrd !$"
oldvec dw 0, 0
DONNEE ENDS
PROGRAMME SEGMENT
ASSUME CS:PROGRAMME , DS:DONNEE, ES:NOTHING, SS:PILE
debut:
mov ax,DONNEE
mov ds,ax
cli
xor ax,ax
mov es, ax ; load ES with segment address of interrupt pointer table
mov bx, 24h ; load BX with interrupt type
; Store the old IRQ vector
mov ax, es:[bx]
mov oldvec, ax
mov ax, es:[bx+2]
mov oldvec + 2, ax
mov word ptr es:[bx], OFFSET SUBR ; isr address
mov word ptr es:[bx]+2, SEG SUBR ; isr segment
mov ax, 01h
sti
BOUCLE: jmp BOUCLE
SUBR PROC NEAR
push ax
push dx
mov ah,9
mov dx,OFFSET message
int 21h
pop dx
pop ax
jmp dword ptr [oldvec]
SUBR ENDP
PROGRAMME ENDS
END debut
基本上,除非您在完成工作后调用默认处理程序,否则您必须告诉 PIC(可编程中断控制器)您已完成 - 默认处理程序会为您完成此操作 -向 PIC 发送 EOI(中断信号结束)。 PIC 不会再次触发中断,直到您告诉它您已完成当前中断。
在 32 位保护模式下执行此操作的代码如下所示。请注意,我对所有 IRQ 使用通用处理程序,只是将其传递给适当注册的用户回调函数。我把两者都包含了。
首先,通用 IRQ 处理程序
irq_handler:
push ebp
mov ebp, esp
add ebp, 8
mov eax, [ebp +registers_t.int_no]
cmp eax, IRQ7 ; this just dumps spurious IRQ7 interrupts
je .irqHandlerDone
cmp eax, IRQ8 ; if it's IRQ0 - IRQ7, the first controller fired the int, otherwise the slave controller did
jb .slaveResetDone
.resetSlave:
mov al,20H ; send End-Of-Interrupt signal
out 0xA0,al ; to the 8259 _slave_ Programmable Interrupt Controller
.slaveResetDone:
.resetMaster:
mov al, 0x20 ; send End-Of-Interrupt signal
out 0x20, al ; to the 8259 master Programmable Interrupt Controller
mov eax, [ebp + registers_t.int_no]
shl eax, 2 ; x4
mov esi, interrupt_handlers
add esi, eax ; esi --> interrupt_handlers[int_no]
cmp dword [esi], 0
je .irqHandlerDone
call [esi]
.irqHandlerDone:
pop ebp
ret
接下来是 IRQ1(键盘)处理程序。注册函数只是将函数的偏移量复制到 32 位地址的 table (interrupt_handlers
) 中。
我处于平面内存模式(4GB 可寻址,ES 已经拥有可写数据段的段选择器。0xB8000 + 79*2 只是指向 80x25 模式 3 文本屏幕右上角的字符)
; keyboard IRQ handler
irq1Handler:
push ebp
mov ebp, esp
add ebp, 8+4
in al, 0x60
mov bl, al
mov byte [port60], al
in al, 0x61
mov ah, al
or al, 0x80
out 0x61, al
xchg ah, al
out 0x61, al
and bl, 0x80
jnz .done
pusha
;mov al, [port60]
;call outputChar
mov edi, 0xB8000 + 79*2
mov al, [port60]
mov [es:edi], al
popa
.done:
pop ebp
ret
port60 db 0
代码取自 James M 的教程,此处:http://www.jamesmolloy.co.uk/tutorial_html/5.-IRQs%20and%20the%20PIT.html
在 OSDev.org - http://wiki.osdev.org/Main_Page
上可以阅读很多关于硬件接口的内容
更新:
这是在 DosBox 中运行的 16 位 ISR。
;-----------------------------------------------------
; handles int 0x09
;-----------------------------------------------------
keyhandler:
cli
pusha
in al, 0x60 ; get key data
mov bl, al ; save it
mov byte [port60], al
in al, 0x61 ; keybrd control
mov ah, al
or al, 0x80 ; disable bit 7
out 0x61, al ; send it back
xchg ah, al ; get original
out 0x61, al ; send that back
mov al, 0x20 ; End of Interrupt
out 0x20, al ;
and bl, 0x80 ; key released
jnz done ; don't repeat
mov al, [port60]
;
; do something with the scan-code here
;
done:
popa
iret
port60 db 0 ; where we'll store the scan-code
我不明白为什么我为我的程序编写的键盘中断服务例程(每次按下一个键都应该打印"hello world")在dosbox上执行.exe时只发生一次。 这是代码:
NAME keyb
PILE SEGMENT STACK
db 20 dup ('LA PILE ')
PILE ENDS
DONNEE SEGMENT
message db "Hello wolrd !$"
DONNEE ENDS
PROGRAMME SEGMENT
ASSUME CS:PROGRAMME , DS:DONNEE, ES:NOTHING, SS:PILE
debut:
mov ax,DONNEE
mov ds,ax
cli
xor ax,ax
mov es, ax ; load ES with segment address of interrupt pointer table
mov bx, 24h ; load BX with interrupt type
mov word ptr es:[bx], OFFSET SUBR ; isr address
mov word ptr es:[bx]+2, SEG SUBR ; isr segment
mov ax, 01h
sti
BOUCLE: jmp BOUCLE
SUBR PROC NEAR
cli
mov ah,9
mov dx,OFFSET message
int 21h
sti
IRET
SUBR ENDP
PROGRAMME ENDS
END debut
我尝试了一些操作,例如压入和弹出寄存器,使用另一个中断(系统时钟 08h),但是 none 成功了。 我知道 ISR 至少运行一次,因为消息 "hello world" 出现在屏幕上,但每次我按下一个键时它应该打印,但我不知道为什么不打印。
我该如何解决?
你必须对 "free" 键盘和中断做一些工作。看看here。最简单的方法是跳转到您自己的处理程序末尾的旧 IRQ 处理程序:
NAME keyb
PILE SEGMENT STACK
db 20 dup ('LA PILE ')
PILE ENDS
DONNEE SEGMENT
message db "Hello wolrd !$"
oldvec dw 0, 0
DONNEE ENDS
PROGRAMME SEGMENT
ASSUME CS:PROGRAMME , DS:DONNEE, ES:NOTHING, SS:PILE
debut:
mov ax,DONNEE
mov ds,ax
cli
xor ax,ax
mov es, ax ; load ES with segment address of interrupt pointer table
mov bx, 24h ; load BX with interrupt type
; Store the old IRQ vector
mov ax, es:[bx]
mov oldvec, ax
mov ax, es:[bx+2]
mov oldvec + 2, ax
mov word ptr es:[bx], OFFSET SUBR ; isr address
mov word ptr es:[bx]+2, SEG SUBR ; isr segment
mov ax, 01h
sti
BOUCLE: jmp BOUCLE
SUBR PROC NEAR
push ax
push dx
mov ah,9
mov dx,OFFSET message
int 21h
pop dx
pop ax
jmp dword ptr [oldvec]
SUBR ENDP
PROGRAMME ENDS
END debut
基本上,除非您在完成工作后调用默认处理程序,否则您必须告诉 PIC(可编程中断控制器)您已完成 - 默认处理程序会为您完成此操作 -向 PIC 发送 EOI(中断信号结束)。 PIC 不会再次触发中断,直到您告诉它您已完成当前中断。
在 32 位保护模式下执行此操作的代码如下所示。请注意,我对所有 IRQ 使用通用处理程序,只是将其传递给适当注册的用户回调函数。我把两者都包含了。
首先,通用 IRQ 处理程序
irq_handler:
push ebp
mov ebp, esp
add ebp, 8
mov eax, [ebp +registers_t.int_no]
cmp eax, IRQ7 ; this just dumps spurious IRQ7 interrupts
je .irqHandlerDone
cmp eax, IRQ8 ; if it's IRQ0 - IRQ7, the first controller fired the int, otherwise the slave controller did
jb .slaveResetDone
.resetSlave:
mov al,20H ; send End-Of-Interrupt signal
out 0xA0,al ; to the 8259 _slave_ Programmable Interrupt Controller
.slaveResetDone:
.resetMaster:
mov al, 0x20 ; send End-Of-Interrupt signal
out 0x20, al ; to the 8259 master Programmable Interrupt Controller
mov eax, [ebp + registers_t.int_no]
shl eax, 2 ; x4
mov esi, interrupt_handlers
add esi, eax ; esi --> interrupt_handlers[int_no]
cmp dword [esi], 0
je .irqHandlerDone
call [esi]
.irqHandlerDone:
pop ebp
ret
接下来是 IRQ1(键盘)处理程序。注册函数只是将函数的偏移量复制到 32 位地址的 table (interrupt_handlers
) 中。
我处于平面内存模式(4GB 可寻址,ES 已经拥有可写数据段的段选择器。0xB8000 + 79*2 只是指向 80x25 模式 3 文本屏幕右上角的字符)
; keyboard IRQ handler
irq1Handler:
push ebp
mov ebp, esp
add ebp, 8+4
in al, 0x60
mov bl, al
mov byte [port60], al
in al, 0x61
mov ah, al
or al, 0x80
out 0x61, al
xchg ah, al
out 0x61, al
and bl, 0x80
jnz .done
pusha
;mov al, [port60]
;call outputChar
mov edi, 0xB8000 + 79*2
mov al, [port60]
mov [es:edi], al
popa
.done:
pop ebp
ret
port60 db 0
代码取自 James M 的教程,此处:http://www.jamesmolloy.co.uk/tutorial_html/5.-IRQs%20and%20the%20PIT.html
在 OSDev.org - http://wiki.osdev.org/Main_Page
上可以阅读很多关于硬件接口的内容更新: 这是在 DosBox 中运行的 16 位 ISR。
;-----------------------------------------------------
; handles int 0x09
;-----------------------------------------------------
keyhandler:
cli
pusha
in al, 0x60 ; get key data
mov bl, al ; save it
mov byte [port60], al
in al, 0x61 ; keybrd control
mov ah, al
or al, 0x80 ; disable bit 7
out 0x61, al ; send it back
xchg ah, al ; get original
out 0x61, al ; send that back
mov al, 0x20 ; End of Interrupt
out 0x20, al ;
and bl, 0x80 ; key released
jnz done ; don't repeat
mov al, [port60]
;
; do something with the scan-code here
;
done:
popa
iret
port60 db 0 ; where we'll store the scan-code