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