实模式中断处理例程未按预期工作
Real mode Interrupt handling routine not working as expected
我设法通过执行远跳转到 0x0090:0x0000
的引导加载程序将一个小内核加载到内存中。当我从那里打印一个字符来测试它并且它工作正常时,内核加载成功。
我想将中断 0x08->0x0F
和 0x70->0x77
重新映射到中断 0x20->0x2F
,因此 exception/reserved 中断不会重叠。到目前为止,我只是在处理键盘按下并尝试将其打印到屏幕上。
我检查了很多次,出于某种原因,我只是不知道为什么,但是当我按下时没有任何反应一把钥匙。
键盘映射只是一个扫描码数组,它们对应的 ASCII 值。
如果这有任何帮助:我测试了 运行 一个循环并打印了一个字符,然后 HLT
ing,一旦打印了 2 个字符,它就挂了。我确保启用中断。
(我正在为引导加载程序加载 4 个扇区(从扇区 2 到扇区 5),这就是为什么我要填充它以使其大小为 2048 字节)。
顺便说一句,我不必 CLI
在我的中断程序中,因为它已经为我完成了,对吧?我记得读过这篇文章,但我不太确定。
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 将是我们写入中断向量table 的段。在您的情况下,它是 0x0090,因为您这样做(使用 DS=0x0000)来更新中断向量 table:
mov [ds:0x86], cs
因为我们不能依赖 DS 当我们的中断处理程序被调用时,我们可以将 DS 推到堆栈,复制 CS 到 DS 并通过 DS 访问我们的内存变量,然后恢复 DS。或者,我们可以修改我们的内存操作数,以便它们明确使用 CS 寄存器。我们可以将中断处理程序修改为如下所示:
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
我记录了我添加的行。但重要的是:
- 我们不能保证ES就是我们想要的,所以我们需要将它显式设置到显存段。
- 寄存器必须恢复到中断前的状态。我们将修改 ES 寄存器和 DI 寄存器,因此我们应该将它们保存在堆栈中(并在最后恢复它们)。
- 我们不能依赖 DI 实际上是我们期望的值,所以我们必须在中断调用之间保存它的值,以便我们可以正确地前进到下一个单元格写作的屏幕。
- 内存操作数已被重写为使用 CS 寄存器而不是 DS 寄存器。进行段覆盖避免了必须将 CS 复制到 DS 和 save/restore DS中断处理程序。
- 我们不能指望方向标志就是我们想要的。由于您在中断处理程序中使用 STOSW,我们要确保它被 CLD 清除,以便它前进 DI前进。
- 我们可以简单地将 BX 寄存器的上部清零,而不是使用
movzx
。 movzx
仅适用于 386 处理器。如果您使用的是 386+,则可以保留该指令,但如果您打算针对 8086/8088/80188/80286,则不能使用它。
将您的启动例程修改为:
start:
mov word [videopos], 0x0000 ; Initialize starting video position
sti
.progloop:
hlt
jmp .progloop
如果您使用 jmp $
进行紧密循环,某些模拟器并不总是会刷新屏幕。更好的方法是使用 HLT 指令。当中断发生时,CPU 将暂停处理器直到下一个中断发生。当一个确实发生时,它将由中断处理程序提供服务,并最终落入下一条指令。在这种情况下,我们跳回并再次执行 HLT 等待下一个中断。
由于我们添加了一个新变量来跟踪我们正在写入的屏幕单元格,因此我们需要向您添加 videopos
.data
段:
; --- DATA --- ;
drive db 0
videopos dw 0
这些更改似乎适用于 Bochs、QEMU 和 VirtualBox。如果这对您不起作用,那么您可能没有将第二阶段(价值 4 个扇区)正确加载到 0x0090:0x0000 中?因为我看不到你的第一阶段代码,所以我不能肯定地说。
我设法通过执行远跳转到 0x0090:0x0000
的引导加载程序将一个小内核加载到内存中。当我从那里打印一个字符来测试它并且它工作正常时,内核加载成功。
我想将中断 0x08->0x0F
和 0x70->0x77
重新映射到中断 0x20->0x2F
,因此 exception/reserved 中断不会重叠。到目前为止,我只是在处理键盘按下并尝试将其打印到屏幕上。
我检查了很多次,出于某种原因,我只是不知道为什么,但是当我按下时没有任何反应一把钥匙。
键盘映射只是一个扫描码数组,它们对应的 ASCII 值。
如果这有任何帮助:我测试了 运行 一个循环并打印了一个字符,然后 HLT
ing,一旦打印了 2 个字符,它就挂了。我确保启用中断。
(我正在为引导加载程序加载 4 个扇区(从扇区 2 到扇区 5),这就是为什么我要填充它以使其大小为 2048 字节)。
顺便说一句,我不必 CLI
在我的中断程序中,因为它已经为我完成了,对吧?我记得读过这篇文章,但我不太确定。
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 将是我们写入中断向量table 的段。在您的情况下,它是 0x0090,因为您这样做(使用 DS=0x0000)来更新中断向量 table:
mov [ds:0x86], cs
因为我们不能依赖 DS 当我们的中断处理程序被调用时,我们可以将 DS 推到堆栈,复制 CS 到 DS 并通过 DS 访问我们的内存变量,然后恢复 DS。或者,我们可以修改我们的内存操作数,以便它们明确使用 CS 寄存器。我们可以将中断处理程序修改为如下所示:
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
我记录了我添加的行。但重要的是:
- 我们不能保证ES就是我们想要的,所以我们需要将它显式设置到显存段。
- 寄存器必须恢复到中断前的状态。我们将修改 ES 寄存器和 DI 寄存器,因此我们应该将它们保存在堆栈中(并在最后恢复它们)。
- 我们不能依赖 DI 实际上是我们期望的值,所以我们必须在中断调用之间保存它的值,以便我们可以正确地前进到下一个单元格写作的屏幕。
- 内存操作数已被重写为使用 CS 寄存器而不是 DS 寄存器。进行段覆盖避免了必须将 CS 复制到 DS 和 save/restore DS中断处理程序。
- 我们不能指望方向标志就是我们想要的。由于您在中断处理程序中使用 STOSW,我们要确保它被 CLD 清除,以便它前进 DI前进。
- 我们可以简单地将 BX 寄存器的上部清零,而不是使用
movzx
。movzx
仅适用于 386 处理器。如果您使用的是 386+,则可以保留该指令,但如果您打算针对 8086/8088/80188/80286,则不能使用它。
将您的启动例程修改为:
start:
mov word [videopos], 0x0000 ; Initialize starting video position
sti
.progloop:
hlt
jmp .progloop
如果您使用 jmp $
进行紧密循环,某些模拟器并不总是会刷新屏幕。更好的方法是使用 HLT 指令。当中断发生时,CPU 将暂停处理器直到下一个中断发生。当一个确实发生时,它将由中断处理程序提供服务,并最终落入下一条指令。在这种情况下,我们跳回并再次执行 HLT 等待下一个中断。
由于我们添加了一个新变量来跟踪我们正在写入的屏幕单元格,因此我们需要向您添加 videopos
.data
段:
; --- DATA --- ;
drive db 0
videopos dw 0
这些更改似乎适用于 Bochs、QEMU 和 VirtualBox。如果这对您不起作用,那么您可能没有将第二阶段(价值 4 个扇区)正确加载到 0x0090:0x0000 中?因为我看不到你的第一阶段代码,所以我不能肯定地说。