试图从软盘驱动器读取扇区时,INT 13、2挂在x86实模式下

时间:2018-07-27 11:56:46

标签: assembly x86 x86-16 bios osdev

我正在为学校项目编写DOS克隆,并尝试使用BIOS INT 13、2从软盘驱动器(主要是FAT12文件系统的根目录,扇区19)读取某些扇区。正确的参数-或至少我认为是这样-然后用AH = 2调用INT 0x13。然后,系统挂起。我不知道为什么。

我正在使用以下代码来调用中断:

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.

上面的磁盘读取代码在处理键击的键盘中断处理程序中运行。当中断处理程序找到可识别的命令(即DIR)时,它将运行调用磁盘读取代码的例程。我的键盘处理程序如下:

; 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

我一直在使用QEMU和Virtual Box进行测试。它们不会跳转到.error子例程,也不会在调用中断后继续执行。我也对Bochs进行了测试,但Bochs确实抬高了进位标志并跳至.error。真的不知道为什么。

重要的是要注意,我不是在写引导程序。我的系统已经使用实际有效的类似程序加载到内存中(不是使用硬编码的值,这些值仅用于测试,但是使用从其他BIOS函数获得的其他值似乎也不起作用)。同样的步骤在这里也不起作用。另外,我的系统在0x0500:0x0000处加载,并且堆栈段设置为0x0780:0x0000,堆栈基数和堆栈指针都从0x0780:0x0400(1KiB)开始。

我做错什么了吗?我的参数不正确吗,我缺少什么吗?我没有在这里发布任何有用的信息吗?

1 个答案:

答案 0 :(得分:4)

您的代码不起作用,因为int 0x13 BIOS调用返回了0x80状态代码(超时)。这是因为您正在中断处理程序(ISR)中进行磁盘读取BIOS调用。

当CPU以实模式将控制权转移到您的ISR时,它将清除IF标志。这导致CPU忽略可屏蔽的外部中断。即使要使用STI启用中断,也不会再从PIC发送比当前中断优先级低或相等的中断。本质上,IRQ0(比IRQ1高优先级的中断)是在发送EOI之前可以获得的唯一中断。您不会得到BIOS调用正确完成请求所需的软盘控制器中断。这可能是造成超时的原因。

进行ISR的最佳方法是将其限制在最低限度,并在尽可能短的时间内完成。除非您知道自己在做什么,否则应避免从ISR进行其他BIOS调用。

在键盘ISR中,您可以将击键读取到缓冲区中,并将处理推迟到以后。内核经常使用ring buffer来处理键盘数据。将字符读入缓冲区后,您可以发送EOI并退出ISR。用处理键盘ISR存储的键的循环替换JMP $(它是内核的主循环)。然后,您可以采取任何适当的措施。您可以将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

由于此操作是在ISR外部完成的,因此您不受ISR内部运行的问题的束缚。


下面显示了可以与一个使用者和生产者一起使用的中断安全无锁环形缓冲区的示例实现。该示例具有一个键盘ISR,该ISR会获取每个扫描码并将其放入缓冲区(如果缓冲区未满)。主循环检查每个迭代是否有可用的扫描代码(缓冲区不为空)。如果可用,它将转换为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

注意:此实现是一个演示。实际的OS可能会有主循环要检查的事件队列。 ISR将事件推送到队列中,并且主循环会弹出每个事件并对其进行处理。该演示的效率很低,因为它总是检查缓冲区中是否有键盘事件发生的扫描代码。

enter image description here

代码基于环形缓冲区的实现,在伪代码中如下所示:

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; }

在使用saveelement之前,您必须调用isfull以确保缓冲区未满。在使用getelement之前,您必须调用isempty以确保有一个要读取的值。