[英]INT 13, 2 hanging on x86 real mode when trying to read sectors from floppy drive
I'm writing a DOS clone for a school project and I'm trying to read some sectors from a floppy drive (mainly the root directory of a FAT12 file system, sector 19) using BIOS INT 13, 2. I set the parameters right - or at least I think I do so - and then call INT 0x13 with AH = 2. Then, though, the system hangs. 我正在为学校项目编写一个DOS克隆,我正在尝试使用BIOS INT 13,2从软盘驱动器(主要是FAT12文件系统的根目录,扇区19)读取一些扇区。我将参数设置为正确 - 或者至少我认为我这样做 - 然后用AH = 2调用INT 0x13。然而,系统挂起。 I can't figure why.
我无法理解为什么。
I'm using the following code to call the interrupt: 我正在使用以下代码来调用中断:
mov ah, 0x2 ;Read sectors function
mov al, 1 ;I want to read one sector
mov ch, 0 ;From track 0
mov cl, 2 ;Sector 2
mov dh, 1 ;Head 1
mov dl, [device_number] ;Obtained from BIOS during boot
mov bx, 0x7C0 ;Buffer located at 0x7C00:0x0000*
mov es, bx
mov bx, 0
stc ;Set carry for older BIOSes that just unset it.
int 0x13 ;Call BIOS INT 0x13
jc .error ;If there's an error, jump to the .error subroutine.
The disk read code above is run inside a keyboard interrupt handler that processes keystrokes. 上面的磁盘读取代码在键盘中断处理程序内运行,该处理程序处理击键。 When the interrupt handler finds a command it recognizes (ie
DIR
) it runs the routine that calls the disk reading code. 当中断处理程序找到它识别的命令(即
DIR
)时,它运行调用磁盘读取代码的例程。 My keyboard handler looks like: 我的键盘处理程序如下:
; Keyboard ISR
; Installed to Real mode IVT @ 0x0000:0x0024 (IRQ1) replacing
; original BIOS interrupt vector
isr_teclado:
pusha
in al, 0x60 ; Get keystroke
call check_pressed_key ; Process Character
call fin_int_pic1 ; Send EOI to Master PIC
popa
iret
; Routine to send EOI to master PIC.
fin_int_pic1:
push ax
mov al, 0x20 ;PIC EOI signal
out 0x20, al ;Send signal to PIC 1
pop ax
ret
I've been testing with QEMU and with Virtual Box. 我一直在测试QEMU和Virtual Box。 They don't jump to the
.error
subroutine nor continue execution after the interrupt has been called. 它们不会跳转到
.error
子程序,也不会在调用中断后继续执行。 I've also tested with Bochs, but Bochs DOES lift the carry flag and jump to .error
. 我也和Bochs一起测试过,但是Bochs提升了进位旗并跳转到
.error
。 Don't really know why. 不知道为什么。
It's important to note that I'm not writing a bootloader. 重要的是要注意我不是在编写引导加载程序。 My system has already been loaded into memory using a similar procedure that actually works (not with hardcoded values, these are just for testing, but using other values obtained from other BIOS functions doesn't seem to work either).
我的系统已经使用实际工作的类似程序加载到内存中(不是硬编码值,这些仅用于测试,但使用从其他BIOS功能获得的其他值似乎也不起作用)。 That same procedure doesn't work here neither.
同样的程序在这里也不起作用。 Also, my system is loaded at 0x0500:0x0000 and the stack segment is set to 0x0780:0x0000, with the stack base and the stack pointer both starting at 0x0780:0x0400 (1KiB).
此外,我的系统加载在0x0500:0x0000,堆栈段设置为0x0780:0x0000,堆栈基址和堆栈指针都从0x0780:0x0400(1KiB)开始。
Am I doing anything wrong? 我做错了吗? Are my parameters incorrect, am I missing something?
我的参数不正确,我错过了什么吗? Any information that would be useful that I've not posted here?
我没有在这里发布任何有用的信息?
Your code isn't working because the int 0x13
BIOS call is returning a 0x80 status code (timeout). 您的代码无法正常工作,因为
int 0x13
BIOS调用返回0x80状态代码(超时)。 This is because you are doing the disk read BIOS call inside an interrupt handler (ISR). 这是因为您正在中断处理程序(ISR)中执行磁盘读取BIOS调用。
When the CPU transfers control to your ISR in real mode it clears the IF flag. 当CPU在实模式下将控制权转移到ISR时,它会清除IF标志。 This causes the CPU to ignore maskable external interrupts.
这会导致CPU忽略可屏蔽的外部中断。 Even if you were to enable interrupts with
STI
you won't get any more interrupts sent from the PICs that are of lower or equal priority to the current interrupt. 即使您使用
STI
启用中断,也不会再获得从当前中断优先级更低或相同的PIC发送的更多中断。 In essence IRQ0 (higher priority interrupt than IRQ1) is the only interrupt you could get until you send an EOI. 本质上,IRQ0(比IRQ1更高优先级的中断)是您发送EOI之前唯一可以获得的中断。 You won't get the floppy disk controller interrupts that the BIOS call needs to properly complete a request.
您将无法获得BIOS调用正确完成请求所需的软盘控制器中断。 This is likely the cause of the timeout.
这可能是超时的原因。
The best idea for doing ISRs is to limit them to doing the bare minimum and do it in the least amount of time possible. 做ISR的最佳想法是将它们限制在最低限度,并在尽可能少的时间内完成。 You should avoid making other BIOS calls from your ISR unless you know what you are doing.
除非您知道自己在做什么,否则应该避免从ISR拨打其他BIOS电话。
In a keyboard ISR you can read keystrokes into a buffer and defer processing them until later. 在键盘ISR中,您可以将键击读入缓冲区并将其处理延迟到以后。 A ring buffer is often used by kernels for handling keyboard data.
内核经常使用环形缓冲区来处理键盘数据。 Once the character is read into the buffer you can send the EOI and exit your ISR.
将字符读入缓冲区后,您可以发送EOI并退出ISR。 Replace the
JMP $
that is your kernel's main loop with a loop that processes the keys stored by the keyboard ISR. 用一个处理键盘ISR存储的密钥的循环替换作为内核主循环的
JMP $
。 You can then take whatever actions are appropriate. 然后,您可以采取任何适当的行动。 You could replace your
JMP $
with something like: 您可以用以下内容替换您的
JMP $
:
main_loop:
hlt ; Halt processor until next interrupt occurs
[check for characters in the keyboard buffer and process them as needed]
...
jmp main_loop
Since this is done outside an ISR you are not constrained by the issues you had running inside an ISR. 由于这是在ISR之外完成的,因此您不会受到在ISR中运行的问题的限制。
An example implementation of an interrupt safe lockless ring buffer that can work with one consumer and producer is shown below. 可以与一个消费者和生产者一起工作的中断安全无锁环缓冲器的示例实现如下所示。 The example has a keyboard ISR that takes each scancode and places it into a buffer if the buffer isn't full.
该示例有一个键盘ISR,它接收每个扫描码并在缓冲区未满时将其放入缓冲区。 The main loop checks each iteration if there is a scancode available (buffer isn't empty).
如果有可用的扫描码(缓冲区不为空),主循环将检查每次迭代。 If one is available it is translated to ASCII and printed to the console.
如果有可用,则将其转换为ASCII并打印到控制台。
KBD_BUFSIZE equ 32 ; Keyboard Buffer length. **Must** be a power of 2
; Maximum buffer size is 2^15 (32768)
KBD_IVT_OFFSET equ 0x0024 ; Base address of keyboard interrupt (IRQ) in IVT
bits 16
org 0x7c00
start:
xor ax, ax
mov ds, ax ; DS=0 since we use an ORG of 0x7c00.
; 0x0000<<4+0x7C00=0x07C00
mov ss, ax
mov sp, 0x7c00 ; SS:SP stack pointer set below bootloader
cli ; Don't want to be interrupted when updating IVT
mov word [KBD_IVT_OFFSET], kbd_isr
; 0x0000:0x0024 = IRQ1 offset in IVT
mov [KBD_IVT_OFFSET+2], ax ; 0x0000:0x0026 = IRQ1 segment in IVT
sti ; Enable interrupts
mov ax, 0xb800
mov es, ax ; Set ES to text mode segment (page 0)
xor di, di ; DI screen offset = 0 (upper left)
mov ah, 0x1F ; AH = White on Blue screen attribute
mov bx, keyboard_map ; BX = address of translate table used by XLAT
cld ; String instructions set to forward direction
.main_loop:
hlt ; Halt processor until next interrupt
mov si, [kbd_read_pos]
cmp si, [kbd_write_pos]
je .main_loop ; If (read_pos == write_pos) then buffer empty and
; we're finished
lea cx, [si+1] ; Index of next read (tmp = read_pos + 1)
and si, KBD_BUFSIZE-1 ; Normalize read_pos to be within 0 to KBD_BUFSIZE
mov al, [kbd_buffer+si] ; Get next scancode
mov [kbd_read_pos], cx ; read_pos++ (read_pos = tmp)
test al, 0x80 ; Is scancode a key up event?
jne .main_loop ; If so we are finished
xlat ; Translate scancode to ASCII character
test al, al
je .main_loop ; If character to print is NUL we are finished
stosw ; Display character on console in white on blue
jmp .main_loop
; Keyboard ISR (IRQ1)
kbd_isr:
push ax ; Save all registers we modify
push si
push cx
in al, 0x60 ; Get keystroke
mov cx, [cs:kbd_write_pos]
mov si, cx
sub cx, [cs:kbd_read_pos]
cmp cx, KBD_BUFSIZE ; If (write_pos-read_pos)==KBD_BUFSIZE then buffer full
je .end ; If buffer full throw char away, we're finished
lea cx, [si+1] ; Index of next write (tmp = write_pos + 1)
and si, KBD_BUFSIZE-1 ; Normalize write_pos to be within 0 to KBD_BUFSIZE
mov [cs:kbd_buffer+si], al ; Save character to buffer
mov [cs:kbd_write_pos], cx ; write_pos++ (write_pos = tmp)
.end:
mov al, 0x20
out 0x20, al ; Send EOI to Master PIC
pop cx ; Restore all registers modified
pop si
pop ax
iret
align 2
kbd_read_pos: dw 0
kbd_write_pos: dw 0
kbd_buffer: times KBD_BUFSIZE db 0
; Scancode to ASCII character translation table
keyboard_map:
db 0, 27, '1', '2', '3', '4', '5', '6', '7', '8' ; 9
db '9', '0', '-', '=', 0x08 ; Backspace
db 0x09 ; Tab
db 'q', 'w', 'e', 'r' ; 19
db 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 0x0a ; Enter key
db 0 ; 29 - Control
db 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';' ; 39
db "'", '`', 0 ; Left shift
db "\", 'z', 'x', 'c', 'v', 'b', 'n' ; 49
db 'm', ',', '.', '/', 0 ; Right shift
db '*'
db 0 ; Alt
db ' ' ; Space bar
db 0 ; Caps lock
db 0 ; 59 - F1 key ... >
db 0, 0, 0, 0, 0, 0, 0, 0
db 0 ; < ... F10
db 0 ; 69 - Num lock
db 0 ; Scroll Lock
db 0 ; Home key
db 0 ; Up Arrow
db 0 ; Page Up
db '-'
db 0 ; Left Arrow
db 0
db 0 ; Right Arrow
db '+'
db 0 ; 79 - End key
db 0 ; Down Arrow
db 0 ; Page Down
db 0 ; Insert Key
db 0 ; Delete Key
db 0, 0, 0
db 0 ; F11 Key
db 0 ; F12 Key
times 128 - ($-keyboard_map) db 0 ; All other keys are undefined
times 510 - ($-$$) db 0 ; Boot signature
dw 0xAA55
Note: This implementation is a demonstration. 注意:此实现是一个演示。 A real OS would likely have a queue of events that the main loop would check for.
真正的操作系统可能会有主循环检查的事件队列。 The ISRs would push an event into a queue, and the main loop would pop each off and process them.
ISR会将事件推送到队列中,主循环会弹出每个事件并处理它们。 The demonstration is inefficient since it is always checking for scancodes in the buffer whether a keyboard event occurred or not.
演示是低效的,因为它始终检查缓冲区中的扫描码是否发生键盘事件。
The code is based on a ring buffer implementation that would look like this in pseudo-code: 代码基于一个环形缓冲区实现,在伪代码中看起来像这样:
buffer[BUFSIZE]; /* BUFSIZE has to be power of 2 and be <= 32768 */
uint16_t read_pos = 0;
uint16_t write_pos = 0;
normalize(val) { return val & (BUFSIZE - 1); }
saveelement(val) { buffer[normalize(write_pos++)] = val; }
getelement() { return buffer[normalize(read_pos++)]; }
numelements() { return write_pos - read_pos; }
isfull() { return numelements() == BUFSIZE; }
isempty() { return write_pos == read_pos; }
Before using saveelement
you must call isfull
to make sure the buffer isn't full. 在使用
saveelement
之前,必须调用isfull
以确保缓冲区未满。 Before using getelement
you must call isempty
to make sure there is a value to read. 在使用
getelement
之前,必须调用isempty
以确保有值要读取。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.