2

我不明白为什么我为我的程序编写的键盘中断服务例程(每次按下一个键时都应该打印“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)推送和弹出寄存器,但它们都不起作用。我知道 ISR 至少运行一次,因为屏幕上出现“hello world”消息,但是每次我按下一个键时它都会打印出来,我不知道为什么它不打印。

我该如何解决这个问题?

4

2 回答 2

1

你必须做一些工作来“释放”键盘和中断。看看这里。最简单的方法是在你自己的处理程序结束时跳转到旧的 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
于 2015-10-13T20:48:18.243 回答
1

基本上,除非您在完成工作后调用默认处理程序,否则您必须告诉 PIC(可编程中断控制器)您已完成 - 默认处理程序将为您执行此操作 - 发送 EOI(结束中断信号)到PIC(S)。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(键盘)处理程序。注册函数只是将函数的偏移量复制到一个interrupt_handlers32 位地址的表 ( ) 中。我处于平面内存模式(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
于 2015-10-14T14:07:58.760 回答