实模式中断处理例程无法按预期工作 [英] Real mode Interrupt handling routine not working as expected

查看:85
本文介绍了实模式中断处理例程无法按预期工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我设法通过一个引导程序将一个小的内核加载到内存中,该引导程序执行到0x0090:0x0000的远距离跳转.当我从那里打印一个字符进行测试时,内核已成功加载,并且工作正常.

I managed to load a small kernel into memory via a bootloader that performs a far jump to 0x0090:0x0000. The kernel is loaded successfully as I print a character from there to test it and it works properly.

我想将中断0x08->0x0F0x70->0x77重新映射为中断0x20->0x2F,因此异常/保留中断不会重叠.到目前为止,我只处理键盘按键并尝试将其打印到屏幕上.
我经过了很多次,由于某种原因,我只是不知道为什么,但是当我按下一个键时什么也没发生.
键盘映射只是扫描代码的数组,这些扫描代码是其受尊重的ASCII值.

I wanted to remap interrupts 0x08->0x0F and 0x70->0x77 to interrupts 0x20->0x2F, so the exception/reserved interrupts are not overlapped. So far, I am only handling a keyboard press and attempting to print it to the screen.
I went over it a bunch of times and for some reason, I just don't know why but nothing happens when I press a key.
The keymap is just an array of the scancodes to their respected ASCII value.

如果有帮助:我测试了运行循环并打印一个字符,然后进行HLT ing,一旦打印了2个字符,它将挂起.我确保启用了中断.
(我正在为引导加载程序加载4个扇区(从扇区2到扇区5),这就是为什么我将其填充为2048字节的原因.

If this is of any help: I tested running a loop and printing a character then HLTing, and once 2 characters were printed, it hanged. I made sure to enable interrupts.
(I'm loading 4 sectors (from sector 2 to sector 5) for the bootloader which is why I'm padding this to make it 2048 bytes in size).

顺便说一句,我不必在中断程序中执行CLI,因为它已经为我完成了,对吗?我记得读过这篇文章,但我不太确定.

By the way, I don't have to CLI in my interrupt procedures since it's done for me, right? I remember reading this but I am not too sure.

BITS 16
ORG 0x0000

; Setup Segments ;
cli
cld
mov ax, cs
mov ds, ax              ; this program was far-jumped to (0x0090:0x0000) so ds = cs = 0x0090
mov ax, VIDEO_ORIGIN
mov es, ax

; Remap PIC Interrupt Vector Offsets to 0x20 -> 0x35 ;
remapInterrupts:
    ; Send Initialization Command (expecting ICW4)
    mov al, 0x11
    out 0x20, al
    out 0xA0, al

    ; Remap Vector Offsets (ICW2)
    mov al, 0x20        ; Master IRQ lines mapped to 0x20 -> 0x27
    out 0x21, al
    mov al, 0x28        ; Slave IRQ lines mapped to 0x28 -> 0x2F
    out 0xA1, al

    ; Set Cascade Lines between Master and Slave PICs (ICW3)
    mov al, 0x04        ; 00000100 (line 2)
    out 0x21, al
    mov al, 0x02        ; 00000010 (line 2 in binary, cascade identity)
    out 0xA1, al

    ; Set 80x86 Mode (ICW4)
    mov al, 0x01
    out 0x21, al
    out 0xA1, al

    ; Set Masks
    mov al, 0xFD        ; 11111101 (keyboard)
    out 0x21, al
    mov al, 0xFF        ; 11111111
    out 0xA1, al

setInterrupts:
    push ds
    mov ax, 0x0000
    mov ds, ax
    mov [ds:0x84], word interrupt21     ; 0x84 = 0x21 * 4
    mov [ds:0x86], cs
    pop ds
    jmp start

    interrupt20:                ; Programmable Interval Timer
        ; NOT SUPPORTED, place holder
        push ax
        mov al, 0x20
        out 0x20, al
        pop ax
        iret
    interrupt21:                ; Keyboard
        push ax
        push bx
        in al, 0x60
        test al, 0x80           ; high-bit set = keyup = don't print
        jnz .finish
        movzx bx, al
        mov al, [keymap + bx]
        mov ah, 0x07
        stosw

        .finish:
        mov al, 0x20
        out 0x20, al

        pop bx
        pop ax
        iret
    interrupt22:                ; Slave Cascade
    interrupt23:                ; COM2 / COM4
    interrupt24:                ; COM1 / COM3
    interrupt25:                ; LPT2
    interrupt26:                ; Floppy controller
    interrupt27:                ; LPT1
        ; NOT SUPPORTED, place holder
        push ax
        mov al, 0x20
        out 0x20, al
        pop ax
        iret
    interrupt28:                ; RTC
    interrupt29:                ; Unassigned
    interrupt2A:                ; Unassigned
    interrupt2B:                ; Unassigned
    interrupt2C:                ; Mouse Controller
    interrupt2D:                ; Math Coprocessor
    interrupt2E:                ; Hard Disk Controller 1
    interrupt2F:                ; Hard Disk Controller 2
        ; NOT SUPPORTED, place holder
        push ax
        mov al, 0x20
        out 0xA0, al
        out 0x20, al
        pop ax
        iret

start:
    sti
    xor di, di
    jmp $

; --- CONSTANTS --- ;
VIDEO_ORIGIN EQU 0xB800

; --- DATA --- ;
drive db 0
keymap:
    db 00h, 1Bh, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 08h, 09h
    db 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']', 00h, 00h
    db 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', "'", '`', 00h, '\'
    db 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 00h, 00h, 00h, ' ', 00h,
    db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h,
    db '-', 00h, 00h, 00h, '+', 00h, 00h, 00h, 00h, 00h

times 2048 - ($ - $$) db 0

推荐答案

必须开发实模式中断例程,就好像只知道了 CS 寄存器(并且清除了中断标志)一样.当我们收到硬件中断时,可以通过中断向量设置 CS:IP . CS 将是我们写入中断向量表的段.在您的情况下,它是0x0090,因为您执行了此操作(使用 DS = 0x0000)来更新中断向量表:

Real mode interrupt routines have to be developed as if nothing but the CS register is known (and the interrupt flag is cleared). CS:IP is set via the interrupt vector when we get a hardware interrupt. CS will be the segment we wrote to the interrupt vector table. In your case it was 0x0090 since you did this (with DS=0x0000) to update the interrupt vector table:

mov [ds:0x86], cs

由于我们不能依赖 DS 来调用中断处理程序,因此我们可以将 DS 推入堆栈,因此复制 CS DS 并通过 DS 访问我们的内存变量,然后恢复 DS .另外,我们可以修改内存操作数,以便它们明确使用 CS 寄存器.我们可以将中断处理程序修改如下:

Since we can't rely on DS being what we want when our interrupt handler is called we can either push DS onto the stack, copy CS to DS and access our memory variables via DS, and then restore DS. Alternatively we can modify our memory operands so they explicitly use the CS register. We could modify the interrupt handler to look like this:

interrupt21:                ; Keyboard
    push ax
    push bx
    push di                 ; Save DI
    push es                 ; Save ES

    mov  ax, VIDEO_ORIGIN
    mov  es, ax             ; Set ES to video memory segment
    mov  di, [cs:videopos]  ; Get last videopos into DI
    in al, 0x60
    test al, 0x80           ; high-bit set = keyup = don't print
    jnz .finish
    xor bh, bh              ; set high byte of BX to zero
    mov bl, al              ; low byte of BX is scancode
    mov al, [cs:keymap + bx]; Reference keymap via CS segment (not DS)
    mov ah, 0x07
    cld                     ; Set the direction flag forward
    stosw
    mov [cs:videopos], di   ; Save current video position

.finish:
    mov al, 0x20
    out 0x20, al

    pop es                 ; Restore ES
    pop di                 ; Restore DI
    pop bx
    pop ax
    iret

我已经记录了我添加的行.但是重要的是:

I've documented the lines I added. But important things are:

  • 我们不能保证 ES 就是我们想要的,因此我们需要将其显式设置为视频内存段.
  • 必须将寄存器恢复到中断之前的状态.我们将修改 ES 寄存器和 DI 寄存器,因此我们应将它们保存在堆栈中(并在末尾还原它们).
  • 我们实际上不能依靠 DI 作为我们期望的值,因此我们必须在中断调用之间保存它的值,以便我们可以正确地前进到屏幕上的下一个单元进行写入.
  • 已将内存操作数重写为使用 CS 寄存器而不是 DS 寄存器.进行段覆盖可以避免将 CS 复制到 DS 并将 DS 保存/恢复到我们的中断处理程序中.
  • 我们不能依赖方向标记就是我们想要的.由于您在中断处理程序中使用了 STOSW ,因此我们希望确保使用 CLD 将其清除,从而使 DI 向前移动.
  • 除了使用movzx之外,我们还可以简单地将 BX 寄存器的高位清零. movzx仅在386处理器上可用.如果您使用的是386+,则可以保留该说明,但是如果您打算定位8086/8088/80188/80286,则不能使用它.
  • We can't guarantee that ES will be what we want, so we'll need to set it explicitly to the video memory segment.
  • Registers have to be restored to the same state they were before the interrupt. We will be modifying the ES register and the DI register so we should save them on the stack (and restore them at the end).
  • We can't rely on DI actually being the value we expect, so we have to save its value between interrupt calls so that we can properly advance to the next cell on the screen for writing.
  • The memory operands have been rewritten to use the CS register rather than the DS register. Doing a segment override avoids having to copy CS to DS and save/restore DS in our interrupt handler.
  • We can't rely on the direction flag being what we want. Since you use STOSW in the interrupt handler we want to make sure it is cleared with CLD so that it advances DI forward.
  • Rather than use movzx we can simply clear the upper part of the BX register to zero. movzx is only available on 386 processors. You can keep that instruction if you are on 386+, but if you intend to target 8086/8088/80188/80286 then you can't use it.

将您的启动例程修改为:

Modify your start routine to be:

start:
    mov word [videopos], 0x0000 ; Initialize starting video position
    sti
.progloop:
    hlt
    jmp .progloop

如果您使用jmp $进行紧密循环,则某些模拟器并不总是刷新屏幕.更好的方法是使用 HLT 指令.当发生中断时,CPU将暂停处理器,直到发生下一个中断为止.当发生错误时,它将由中断处理程序提供服务,并最终落入下一条指令.在这种情况下,我们跳回并再次执行 HLT ,等待下一个中断.

Some emulators don't always do screen refreshes if you do a tight loop with jmp $. A better way to do it is with the HLT instruction. When interrupts are on the CPU will halt the processor until the next interrupt occurs. When one does occur it will be serviced by the interrupt handlers and eventually will fall to the next instruction. In this case we jump back and do the HLT again waiting for the next interrupt.

由于我们添加了一个新变量来跟踪正在写入的屏幕单元,因此我们需要在您的.data段中添加videopos:

Since we added a new variable to keep track of the screen cell we are writing to, we will need to add videopos to you .data segment:

; --- DATA --- ;
drive db 0
videopos dw 0

这些更改似乎适用于 Bochs QEMU VirtualBox .如果这对您不起作用,则可能是您没有将第二阶段(价值4个扇区)正确地加载到0x0090:0x0000中?由于看不到您的第一阶段代码,因此我无法确定.

These changes do seem to work on Bochs, QEMU, and VirtualBox. If this doesn't work for you then possibly you aren't loading the second stage (4 sectors worth) properly into 0x0090:0x0000? Since I can't see your first stage code I can't really say for certain.

这篇关于实模式中断处理例程无法按预期工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆