实模式中断处理例程无法按预期工作 [英] Real mode Interrupt handling routine not working as expected
问题描述
我设法通过一个引导程序将一个小的内核加载到内存中,该引导程序执行到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->0x0F
和0x70->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 HLT
ing, 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屋!