切换到“虚幻”模式时,处理器崩溃。

6

我正在编写一个简单的引导程序,其主要任务是加载内核,并将处理器切换到虚拟模式。我的问题是当我打开虚拟模式时,处理器会崩溃。以下是我的代码(部分代码使用了MikeOS)。我使用NASM。

    BITS 16

    jmp short bootloader_start  ; Jump past disk description section
    nop             ; Pad out before disk description


; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette

OEMLabel        db "16DOSRUN"   ; Disk label
BytesPerSector      dw 512      ; Bytes per sector
SectorsPerCluster   db 1        ; Sectors per cluster
ReservedForBoot     dw 1        ; Reserved sectors for boot record
NumberOfFats        db 2        ; Number of copies of the FAT
RootDirEntries      dw 224      ; Number of entries in root dir
                    ; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors      dw 2880     ; Number of logical sectors
MediumByte      db 0F0h     ; Medium descriptor byte
SectorsPerFat       dw 9        ; Sectors per FAT
SectorsPerTrack     dw 18       ; Sectors per track (36/cylinder)
Sides           dw 2        ; Number of sides/heads
HiddenSectors       dd 0        ; Number of hidden sectors
LargeSectors        dd 0        ; Number of LBA sectors
DriveNo         dw 0        ; Drive No: 0
Signature       db 41       ; Drive signature: 41 for floppy
VolumeID        dd 00000000h    ; Volume ID: any number
VolumeLabel     db "16DOS      "; Volume Label: any 11 chars
FileSystem      db "FAT12   "   ; File system type: don't change!


; ------------------------------------------------------------------
; Main bootloader code

bootloader_start:
    xor ax, ax       ; make it zero
    mov ds, ax             ; DS=0
    mov ss, ax             ; stack starts at seg 0
    mov sp, 0x9c00         ; 2000h past code start, 
                          ; making the stack 7.5k in size
 ;***********HERE I TRY TO SWITCH INTO "UNREAL" MODE***********;
    cli                    ; no interrupts
    push ds                ; save real mode

    lgdt [gdtinfo]         ; load gdt register

    mov  eax, cr0          ; switch to pmode by
    or al,1                ; set pmode bit
    mov  cr0, eax

    jmp $+2                ; tell 386/486 to not crash

    mov  bx, 0x08          ; select descriptor 1
    mov  ds, bx            ; 8h = 1000b

    and al,0xFE            ; back to realmode
    mov  cr0, eax          ; by toggling bit again

    pop ds                 ; get back old segment
    sti

    ;***********END***********;

    mov ax, 07C0h           ; Set up 4K of stack space above buffer
    add ax, 544         ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
    cli             ; Disable interrupts while changing stack
    mov ss, ax
    mov sp, 4096
    sti             ; Restore interrupts

    mov ax, 07C0h           ; Set data segment to where we're loaded
    mov ds, ax

    ; NOTE: A few early BIOSes are reported to improperly set DL

    cmp dl, 0
    je no_change
    mov [bootdev], dl       ; Save boot device number
    mov ah, 8           ; Get drive parameters
    int 13h
    jc fatal_disk_error
    and cx, 3Fh         ; Maximum sector number
    mov [SectorsPerTrack], cx   ; Sector numbers start at 1
    movzx dx, dh            ; Maximum head number
    add dx, 1           ; Head numbers start at 0 - add 1 for total
    mov [Sides], dx

no_change:
    mov eax, 0          ; Needed for some older BIOSes



; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33

floppy_ok:              ; Ready to read first block of data
    mov ax, 19          ; Root dir starts at logical sector 19
    call l2hts

    mov si, buffer          ; Set ES:BX to point to our buffer (see end of code)
    mov bx, ds
    mov es, bx
    mov bx, si

    mov ah, 2           ; Params for int 13h: read floppy sectors
    mov al, 14          ; And read 14 of them

    pusha               ; Prepare to enter loop


read_root_dir:
    popa                ; In case registers are altered by int 13h
    pusha

    stc             ; A few BIOSes do not set properly on error
    int 13h             ; Read sectors using BIOS

    jnc search_dir          ; If read went OK, skip ahead
    call reset_floppy       ; Otherwise, reset floppy controller and try again
    jnc read_root_dir       ; Floppy reset OK?


search_dir:
    popa

    mov ax, ds          ; Root dir is now in [buffer]
    mov es, ax          ; Set DI to this info
    mov di, buffer

    mov cx, word [RootDirEntries]   ; Search all (224) entries
    mov ax, 0           ; Searching at offset 0


next_root_entry:
    xchg cx, dx         ; We use CX in the inner loop...

    mov si, kern_filename       ; Start searching for kernel filename
    mov cx, 11
    rep cmpsb
    je found_file_to_load       ; Pointer DI will be at offset 11

    add ax, 32          ; Bump searched entries by 1 (32 bytes per entry)

    mov di, buffer          ; Point to next entry
    add di, ax

    xchg dx, cx         ; Get the original CX back
    loop next_root_entry

    mov si, file_not_found      ; If kernel is not found, bail out
    call print_string


found_file_to_load:         ; Fetch cluster and load FAT into RAM
    mov ax, word [es:di+0Fh]    ; Offset 11 + 15 = 26, contains 1st cluster
    mov word [cluster], ax

    mov ax, 1           ; Sector 1 = first sector of first FAT
    call l2hts

    mov di, buffer          ; ES:BX points to our buffer
    mov bx, di

    mov ah, 2           ; int 13h params: read (FAT) sectors
    mov al, 9           ; All 9 sectors of 1st FAT

    pusha               ; Prepare to enter loop


read_fat:
    popa                ; In case registers are altered by int 13h
    pusha

    stc
    int 13h             ; Read sectors using the BIOS

    jnc read_fat_ok         ; If read went OK, skip ahead
    call reset_floppy       ; Otherwise, reset floppy controller and try again
    jnc read_fat            ; Floppy reset OK?

; ******************************************************************
fatal_disk_error:
; ******************************************************************
    mov si, disk_error


read_fat_ok:
    popa

    mov ax, 2000h           ; Segment where we'll load the kernel
    mov es, ax
    mov bx, 0

    mov ah, 2           ; int 13h floppy read params
    mov al, 1

    push ax             ; Save in case we (or int calls) lose it


; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
;               = (cluster number) + 31

load_file_sector:
    mov ax, word [cluster]      ; Convert sector to logical
    add ax, 31

    call l2hts          ; Make appropriate params for int 13h

    mov ax, 2000h           ; Set buffer past what we've already read
    mov es, ax
    mov bx, word [pointer]

    pop ax              ; Save in case we (or int calls) lose it
    push ax

    stc
    int 13h

    jnc calculate_next_cluster  ; If there's no error...

    call reset_floppy       ; Otherwise, reset floppy and retry
    jmp load_file_sector


    ; In the FAT, cluster values are stored in 12 bits, so we have to
    ; do a bit of maths to work out whether we're dealing with a byte
    ; and 4 bits of the next byte -- or the last 4 bits of one byte
    ; and then the subsequent byte!

calculate_next_cluster:
    mov ax, [cluster]
    mov dx, 0
    mov bx, 3
    mul bx
    mov bx, 2
    div bx              ; DX = [cluster] mod 2
    mov si, buffer
    add si, ax          ; AX = word in FAT for the 12 bit entry
    mov ax, word [ds:si]

    or dx, dx           ; If DX = 0 [cluster] is even; if DX = 1 then it's odd

    jz even             ; If [cluster] is even, drop last 4 bits of word
                    ; with next cluster; if odd, drop first 4 bits

odd:
    shr ax, 4           ; Shift out first 4 bits (they belong to another entry)
    jmp short next_cluster_cont


even:
    and ax, 0FFFh           ; Mask out final 4 bits


next_cluster_cont:
    mov word [cluster], ax      ; Store cluster

    cmp ax, 0FF8h           ; FF8h = end of file marker in FAT12
    jae end

    add word [pointer], 512     ; Increase buffer pointer 1 sector length
    jmp load_file_sector


end:                    ; We've got the file to load!
    pop ax              ; Clean up the stack (AX was pushed earlier)
    mov dl, byte [bootdev]      ; Provide kernel with boot device info

    jmp 2000h:0000h         ; Jump to entry point of loaded kernel!


; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES


print_string:               ; Output string in SI to screen
    pusha

    mov ah, 0Eh         ; int 10h teletype function

.repeat:
    lodsb               ; Get char from string
    cmp al, 0
    je .done            ; If char is zero, end of string
    int 10h             ; Otherwise, print it
    jmp short .repeat

.done:
    popa
    ret


reset_floppy:       ; IN: [bootdev] = boot device; OUT: carry set on error
    push ax
    push dx
    mov ax, 0
    mov dl, byte [bootdev]
    stc
    int 13h
    pop dx
    pop ax
    ret


l2hts:          ; Calculate head, track and sector settings for int 13h
            ; IN: logical sector in AX, OUT: correct registers for int 13h
    push bx
    push ax

    mov bx, ax          ; Save logical sector

    mov dx, 0           ; First the sector
    div word [SectorsPerTrack]
    add dl, 01h         ; Physical sectors start at 1
    mov cl, dl          ; Sectors belong in CL for int 13h
    mov ax, bx

    mov dx, 0           ; Now calculate the head
    div word [SectorsPerTrack]
    mov dx, 0
    div word [Sides]
    mov dh, dl          ; Head/side
    mov ch, al          ; Track

    pop ax
    pop bx

    mov dl, byte [bootdev]      ; Set correct device

    ret


; ------------------------------------------------------------------
; STRINGS AND VARIABLES

    kern_filename   db "KERNEL  SYS"    ; MikeOS kernel filename

    disk_error  db "Error.", 0
    file_not_found  db "Error.", 0

    bootdev     db 0    ; Boot device number
    cluster     dw 0    ; Cluster of the file we want to load
    pointer     dw 0    ; Pointer into Buffer, for loading kernel

    gdtinfo:
        dw gdt_end - gdt - 1   ;last byte in table
        dd gdt                 ;start of table
    gdt         dd 0,0        ; entry 0 is always unused
    flatdesc    db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
    gdt_end:
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START

    times 510-($-$$) db 0   ; Pad remainder of boot sector with zeros
    dw 0AA55h       ; Boot signature (DO NOT CHANGE!)


buffer:             ; Disk buffer begins (8k after this, stack starts)


; ==================================================================

那么如何修复这段代码呢?如果在我的情况下切换到“虚幻”模式是不可能的,那么我该如何在实模式下访问整个内存(4GiB会持续),我已经在内核代码中打开了A20。 几年后:发现SmallerC支持进入虚幻模式,因此实际上并不需要所有汇编代码,我可以直接用C语言编写。


3
"虚幻模式"并不是一个实际存在的模式,它只是普通的实模式,只是其中一个或多个选择器缓存具有在实模式下通常无法具备的值。例如,在保护模式下,如果你用一个拥有基址和限制使你可以访问所有内存的选择器来加载DS寄存器,然后切换回实模式,你仍然可以使用DS寄存器来访问所有内存。但是,任何改变DS寄存器的实模式代码都会将基址和限制设置回正常的实模式值。这意味着,在虚幻模式下,你不能启用中断或使用BIOS调用,因为它们可能会更改DS的值。 - Ross Ridge
1
你没有展示给我们如何组装这段代码,但如果你使用 NASM 并带有 -f bin 选项(或者根本不指定格式),它将默认原点为 0x0000。在 PC 引导加载程序中,如果你打算将段设置为 0(你的代码确实这样做了),则需要一个原点为 0x7c00。在代码顶部添加 ORG 0x7c00 指令。 - Michael Petch
1
@MichaelPetch很不幸,他从其他地方复制的代码假定ORG为0。 - Ross Ridge
2
正如Ross所指出的那样,您似乎将两种类型的代码拼接到了一个引导加载程序中。代码的第一部分似乎是复制和粘贴或在假定ORG为0x7c00的环境中创建的代码,其余部分需要ORG为0x0000的代码。您遇到段错误的最终原因是因为_NASM_计算出的gdtinfo偏移量错误。 - Michael Petch
2
我不确定您需要什么解决方案(如果以下代码是您想要的,我可以创建一个SO答案)。我修改了您的代码,以便所有内容都假定ORG为0x0000。我更改的地方有我的缩写MDP。我不得不修改段初始化,将其全部基于0x07c0的段,同时gdtinfo需要进行修改,以将gdt的地址转换为线性地址(而不是段:偏移)。修订后的代码可以在此处找到:http://www.capp-sysware.com/misc/stackoverflow/40223669/boot.asm - Michael Petch
显示剩余5条评论
1个回答

4

MikeOS带有一个引导程序,它假定一个段落为0x07c0和偏移量为0x0000(0x07c0:0x0000)。偏移部分也是原点(NASM中的ORG值)。在20位段地址:偏移地址寻址中:段地址为0x07c0,偏移地址为0x0000,物理地址为0x07c00(0x07c0<<4+0x0000=0x07c00),这就是引导程序在内存中的预期位置。

看起来当你使用MikeOS时,你插入了一些来自OSDev Wiki的非真实模式代码,它假设原点基于地址0x0000:0x7c00的段:偏移量。这也代表物理地址0x07c00(0x0000<<4+0x7c00=0x7c00)。在这种情况下,你需要在NASM代码中使用ORG 0x7c00。
在使用NASM进行汇编时,如果使用-f bin选项(如果未指定输出格式,则为默认选项):如果不指定ORG指令,则默认为ORG 0x0000
你需要使用其中之一,而不是两者都用。由于大多数MikeOS引导加载程序代码依赖于0x07c0和0x0000的段,更改代码以与MikeOS引导加载程序最初使用的代码相似会更容易。以下是代码:
bootloader_start:
    xor ax, ax       ; make it zero
    mov ds, ax             ; DS=0
    mov ss, ax             ; stack starts at seg 0
    mov sp, 0x9c00         ; 2000h past code start, 
                          ; making the stack 7.5k in size
 ;***********HERE I TRY TO SWITCH INTO "UNREAL" MODE***********;
    cli                    ; no interrupts

可以更改为:

bootloader_start:

    ; Modify all segment setup code to assume an ORG of 0x0000
    mov ax, 07C0h   ; Set data segment to where we're loaded
    mov ds, ax

    add ax, 544     ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
    cli             ; Disable interrupts while changing stack and entering
                    ; protected mode, turn them on after when in unreal mode
    mov ss, ax
    mov sp, 4096

完成 Unreal 模式设置后,您可以删除所有重复的代码。需要消除以下行:
mov ax, 07C0h           ; Set up 4K of stack space above buffer
add ax, 544         ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli             ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
sti             ; Restore interrupts

mov ax, 07C0h           ; Set data segment to where we're loaded
mov ds, ax

通常情况下,虚幻模式将所有数据寄存器设置为平面内存模型。您不仅可以将DS更新为指向平面4GB选择器,还可以设置DS/ES/FS/GS寄存器。修改代码以执行以下操作:
mov  bx, 0x08          ; select descriptor 1
mov  ds, bx            ; 8h = 1000b
mov  es, bx            ; 8h = 1000b
mov  fs, bx            ; 8h = 1000b
mov  gs, bx            ; 8h = 1000b

完成这一步骤后,需要对gdtinfo结构进行更改。您将其放置在引导加载程序中:
gdtinfo:
    dw gdt_end - gdt - 1   ;last byte in table
    dd gdt                 ;start of table
gdt         dd 0,0        ; entry 0 is always unused
flatdesc    db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:

现在的问题是我们正在使用0x07c0段和GDT(全局描述符表)的基地址相对于0x0000偏移量(而不是0x7c00)。将加载到 GDT寄存器中的结构中的基地址是线性地址(而不是段:偏移地址)。在实模式下,线性地址和物理地址是相同的。为了将转换为线性地址,我们需要将0x7c00加到上。我们修改以下行:

    dd gdt                 ;start of table

所以现在它的读法是:

    dd gdt+0x7c00          ;start of table

完整代码更改后的版本

您文件的修订版本可能如下:

    BITS 16

    jmp short bootloader_start  ; Jump past disk description section
    nop             ; Pad out before disk description


; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette

OEMLabel        db "16DOSRUN"   ; Disk label
BytesPerSector      dw 512      ; Bytes per sector
SectorsPerCluster   db 1        ; Sectors per cluster
ReservedForBoot     dw 1        ; Reserved sectors for boot record
NumberOfFats        db 2        ; Number of copies of the FAT
RootDirEntries      dw 224      ; Number of entries in root dir
                    ; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors      dw 2880     ; Number of logical sectors
MediumByte      db 0F0h     ; Medium descriptor byte
SectorsPerFat       dw 9        ; Sectors per FAT
SectorsPerTrack     dw 18       ; Sectors per track (36/cylinder)
Sides           dw 2        ; Number of sides/heads
HiddenSectors       dd 0        ; Number of hidden sectors
LargeSectors        dd 0        ; Number of LBA sectors
DriveNo         dw 0        ; Drive No: 0
Signature       db 41       ; Drive signature: 41 for floppy
VolumeID        dd 00000000h    ; Volume ID: any number
VolumeLabel     db "16DOS      "; Volume Label: any 11 chars
FileSystem      db "FAT12   "   ; File system type: don't change!

; ------------------------------------------------------------------
; Main bootloader code

bootloader_start:

    ; Modify all segment setup code to assume an ORG of 0x0000
    mov ax, 07C0h           ; Set up 4K of stack space above buffer
    mov ds, ax           ; Set DS segment to where we're loaded

    add ax, 544         ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
    cli                 ; Disable interrupts while changing stack
    mov ss, ax
    mov sp, 4096

    ; Enter unreal mode
    ; Keep interrupts off while we switch to real mode

    push ds                ; Switch to real mode detroys DS. We need to save it
    lgdt [gdtinfo]         ; load gdt register

    mov  eax, cr0          ; switch to pmode by
    or al,1                ; set pmode bit
    mov  cr0, eax

    jmp $+2                ; Clear the instruction pre-fetch queue

    ; Set DS=ES=FS=GS to descriptor with 4gb limit
    mov  bx, 0x08          ; select descriptor 1
    mov  ds, bx            ; 8h = 1000b
    mov  es, bx            ; 8h = 1000b
    mov  fs, bx            ; 8h = 1000b
    mov  gs, bx            ; 8h = 1000b

    and al,0xFE            ; back to realmode
    mov  cr0, eax          ; by toggling bit again

    sti                    ; enable interrupts
    pop ds                 ; Retsore DS to original value

    ;***********END OF UNREAL MODE SWITCH ***********;

    ; NOTE: A few early BIOSes are reported to improperly set DL

    cmp dl, 0
    je no_change
    mov [bootdev], dl       ; Save boot device number
    mov ah, 8           ; Get drive parameters
    int 13h
    jc fatal_disk_error
    and cx, 3Fh         ; Maximum sector number
    mov [SectorsPerTrack], cx   ; Sector numbers start at 1
    movzx dx, dh            ; Maximum head number
    add dx, 1           ; Head numbers start at 0 - add 1 for total
    mov [Sides], dx

no_change:
    mov eax, 0          ; Needed for some older BIOSes



; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33

floppy_ok:              ; Ready to read first block of data
    mov ax, 19          ; Root dir starts at logical sector 19
    call l2hts

    mov si, buffer          ; Set ES:BX to point to our buffer (see end of code)
    mov bx, ds
    mov es, bx
    mov bx, si

    mov ah, 2           ; Params for int 13h: read floppy sectors
    mov al, 14          ; And read 14 of them

    pusha               ; Prepare to enter loop


read_root_dir:
    popa                ; In case registers are altered by int 13h
    pusha

    stc             ; A few BIOSes do not set properly on error
    int 13h             ; Read sectors using BIOS

    jnc search_dir          ; If read went OK, skip ahead
    call reset_floppy       ; Otherwise, reset floppy controller and try again
    jnc read_root_dir       ; Floppy reset OK?


search_dir:
    popa

    mov ax, ds          ; Root dir is now in [buffer]
    mov es, ax          ; Set DI to this info
    mov di, buffer

    mov cx, word [RootDirEntries]   ; Search all (224) entries
    mov ax, 0           ; Searching at offset 0


next_root_entry:
    xchg cx, dx         ; We use CX in the inner loop...

    mov si, kern_filename       ; Start searching for kernel filename
    mov cx, 11
    rep cmpsb
    je found_file_to_load       ; Pointer DI will be at offset 11

    add ax, 32          ; Bump searched entries by 1 (32 bytes per entry)

    mov di, buffer          ; Point to next entry
    add di, ax

    xchg dx, cx         ; Get the original CX back
    loop next_root_entry

    mov si, file_not_found      ; If kernel is not found, bail out
    call print_string


found_file_to_load:         ; Fetch cluster and load FAT into RAM
    mov ax, word [es:di+0Fh]    ; Offset 11 + 15 = 26, contains 1st cluster
    mov word [cluster], ax

    mov ax, 1           ; Sector 1 = first sector of first FAT
    call l2hts

    mov di, buffer          ; ES:BX points to our buffer
    mov bx, di

    mov ah, 2           ; int 13h params: read (FAT) sectors
    mov al, 9           ; All 9 sectors of 1st FAT

    pusha               ; Prepare to enter loop


read_fat:
    popa                ; In case registers are altered by int 13h
    pusha

    stc
    int 13h             ; Read sectors using the BIOS

    jnc read_fat_ok         ; If read went OK, skip ahead
    call reset_floppy       ; Otherwise, reset floppy controller and try again
    jnc read_fat            ; Floppy reset OK?

; ******************************************************************
fatal_disk_error:
; ******************************************************************
    mov si, disk_error


read_fat_ok:
    popa

    mov ax, 2000h           ; Segment where we'll load the kernel
    mov es, ax
    mov bx, 0

    mov ah, 2           ; int 13h floppy read params
    mov al, 1

    push ax             ; Save in case we (or int calls) lose it


; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
;               = (cluster number) + 31

load_file_sector:
    mov ax, word [cluster]      ; Convert sector to logical
    add ax, 31

    call l2hts          ; Make appropriate params for int 13h

    mov ax, 2000h           ; Set buffer past what we've already read
    mov es, ax
    mov bx, word [pointer]

    pop ax              ; Save in case we (or int calls) lose it
    push ax

    stc
    int 13h

    jnc calculate_next_cluster  ; If there's no error...

    call reset_floppy       ; Otherwise, reset floppy and retry
    jmp load_file_sector


    ; In the FAT, cluster values are stored in 12 bits, so we have to
    ; do a bit of maths to work out whether we're dealing with a byte
    ; and 4 bits of the next byte -- or the last 4 bits of one byte
    ; and then the subsequent byte!

calculate_next_cluster:
    mov ax, [cluster]
    mov dx, 0
    mov bx, 3
    mul bx
    mov bx, 2
    div bx              ; DX = [cluster] mod 2
    mov si, buffer
    add si, ax          ; AX = word in FAT for the 12 bit entry
    mov ax, word [ds:si]

    or dx, dx           ; If DX = 0 [cluster] is even; if DX = 1 then it's odd

    jz even             ; If [cluster] is even, drop last 4 bits of word
                    ; with next cluster; if odd, drop first 4 bits

odd:
    shr ax, 4           ; Shift out first 4 bits (they belong to another entry)
    jmp short next_cluster_cont


even:
    and ax, 0FFFh           ; Mask out final 4 bits


next_cluster_cont:
    mov word [cluster], ax      ; Store cluster

    cmp ax, 0FF8h           ; FF8h = end of file marker in FAT12
    jae end

    add word [pointer], 512     ; Increase buffer pointer 1 sector length
    jmp load_file_sector


end:                    ; We've got the file to load!
    pop ax              ; Clean up the stack (AX was pushed earlier)
    mov dl, byte [bootdev]      ; Provide kernel with boot device info

    jmp 2000h:0000h         ; Jump to entry point of loaded kernel!


; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES


print_string:               ; Output string in SI to screen
    pusha

    mov ah, 0Eh         ; int 10h teletype function

.repeat:
    lodsb               ; Get char from string
    cmp al, 0
    je .done            ; If char is zero, end of string
    int 10h             ; Otherwise, print it
    jmp short .repeat

.done:
    popa
    ret


reset_floppy:       ; IN: [bootdev] = boot device; OUT: carry set on error
    push ax
    push dx
    mov ax, 0
    mov dl, byte [bootdev]
    stc
    int 13h
    pop dx
    pop ax
    ret


l2hts:          ; Calculate head, track and sector settings for int 13h
            ; IN: logical sector in AX, OUT: correct registers for int 13h
    push bx
    push ax

    mov bx, ax          ; Save logical sector

    mov dx, 0           ; First the sector
    div word [SectorsPerTrack]
    add dl, 01h         ; Physical sectors start at 1
    mov cl, dl          ; Sectors belong in CL for int 13h
    mov ax, bx

    mov dx, 0           ; Now calculate the head
    div word [SectorsPerTrack]
    mov dx, 0
    div word [Sides]
    mov dh, dl          ; Head/side
    mov ch, al          ; Track

    pop ax
    pop bx

    mov dl, byte [bootdev]      ; Set correct device

    ret


; ------------------------------------------------------------------
; STRINGS AND VARIABLES

    kern_filename   db "KERNEL  SYS"    ; MikeOS kernel filename

    disk_error  db "Error.", 0
    file_not_found  db "Error.", 0

    bootdev     db 0    ; Boot device number
    cluster     dw 0    ; Cluster of the file we want to load
    pointer     dw 0    ; Pointer into Buffer, for loading kernel

    gdtinfo:
        dw gdt_end - gdt - 1   ;last byte in table
        dd gdt+0x7c00          ;start of table
    gdt         dd 0,0        ; entry 0 is always unused
    flatdesc    db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
    gdt_end:
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START

    times 510-($-$$) db 0   ; Pad remainder of boot sector with zeros
    dw 0AA55h       ; Boot signature (DO NOT CHANGE!)


buffer:             ; Disk buffer begins (8k after this, stack starts)

; ==================================================================

赏金

在赏金中,您这样说:

我希望赏金获得者向我解释为什么这段代码是错误的,并帮助我修复它(通过切换到非保护模式即平面实模式,并加载名为KERNEL.SYS的内核文件并执行它。内核将使用中断,因此保护模式不是选项。)

保护模式支持软件和硬件中断。我相信您想表达的是您的内核将使用BIOS中断。除非创建VM86任务或切换回实模式,否则在保护模式下运行时不可用BIOS中断。

可以制作实模式/非保护模式内核,但它们将具有缺乏虚拟内存、内存保护和分页机制等保护模式所提供的功能。

进入非保护模式需要暂时进入保护模式;使用指向16位数据描述符的选择器设置DS/ES/GS/FS,该描述符具有4GB限制(而不是64K);然后关闭保护模式。在切换到保护模式的过程中,必须禁用中断,因为没有设置保护模式中断向量。


KERNEL.SYS

MikeOS引导程序需要将名为KERNEL.SYS的文件放置在格式为FAT12的磁盘映像的根目录中。我假设您知道如何执行此操作。在Windows和Linux之间执行此操作的方法不同,超出了本答案的范围。以下是一个测试虚幻模式是否启用并正常工作的示例kernel.asm
bits 16
; MikeOS bootloader loads our code at 0x2000:0x0000 so we need org of 0x0000
; for the kernel code to work properly.
org 0x0000

kernel_start:
    ; Set DS, ES, FS, GS to 0x0000. In Unreal mode these segment registers
    ; are not limited to 64kb. We can address full 4gb of memory
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; This code will not work in normal real mode. We emit instructions
    ; that use 0xb8000 as an offset. This offset is >= 65536 and normally
    ; isn't addressable directly in real mode. This should display MDP white 
    ; on purple to upper left of screen. 0xb8000 is pointer to first cell 
    ; of the text mode video display
    ;
    ; In Real AND Unreal mode on a 386 you are allowed to use 32-bit registers
    ; and memory operands. Real mode is limited to an offset that computes
    ; to a value below 64kb (65536) unlike unreal mode with a 4gb limit
    mov edi, 0xb8000
    mov word [edi],   0x57<<8 | 'M';
    mov word [edi+2], 0x57<<8 | 'D';
    mov word [edi+4], 0x57<<8 | 'P';
    cli
.endloop:
    hlt
    jmp .endloop

可以使用以下命令将其组装成KERNEL.SYS

nasm -f bin kernel.asm -o KERNEL.SYS

当使用此引导程序和KERNEL.SYS文件生成磁盘映像并在QEMU中运行时(Bochs类似),输出将类似于:

enter image description here

如果处理器不在虚幻模式下,则写入左上角的字符将不会出现,或者硬件/仿真器可能会进入某种未定义状态。

其他观察和信息

  • 在MikeOS引导程序中实际上不需要将开关置于Unreal模式。您可以将MikeOS引导程序保持原样,并将开关移到KERNEL.SYS中的Unreal模式。
  • 如果您打算访问1MiB以上的内存数据在任何奇数编号的Mebibyte内存区域(0x100000-0x1fffff、0x300000-0x3fffff等),那么您还需要确保A20门已启用。
  • 您在引导程序中使用的Unreal模式代码设置了具有4GB限制的数据段。它没有以同样的方式设置CS。这个版本的Unreal模式称为Big Unreal Mode
  • 处理或调用中断(即BIOS中断)在Big Unreal模式下与实模式相同
  • 如果您希望创建中断处理程序,则其位置受限于0x0000:0x0000和0xFFFF:0xFFFF之间的低内存区域。代码段中的所有代码都具有与实模式相同的限制。
  • @RossRidge的评论说这意味着您不能在Unreal模式下启用中断或使用BIOS调用,因为它们可以更改DS中的值对于Big Unreal模式是不正确的。
  • 在代码段上使用4GB限制是可能的,但是由于中断仅保存CS:IP而不是CS:EIP,因此无法可靠地使用64kb以上的代码。 EIP可以是0到4GB之间的任何值,但是除了第一个64kb以外的任何值都不能可靠地完成,除非在运行此类代码时禁用中断。这相当具有限制性,这就是为什么很少使用此模式的原因。此模式通常称为Huge Unreal mode
  • @AlexeyFrunze拥有多个引导程序,支持加载内核并支持Unreal模式。Alex还开发了Smaller C Compiler,可用于生成可以从其引导程序启动并支持生成Unreal模式代码的代码。

a20已启用,但我能把我的代码放在1MB标记上吗? 我听说有两种虚幻模式的变体,如果我希望拥有ip> 64kB,这是可能的吗?此外,我认为您会向我展示中断在那里被使用。很好的答案,当您回答有关所提供示例的这个小问题时,我会接受它。 - Kamila Szewczyk
真实模式下,CS有4GB的限制通常被称为巨大非真实模式。但是你在问题中提供的代码与巨大非真实模式无关。那是另一种设置方式。如果您对该模式有疑问,您可能希望提出另一个问题。 - Michael Petch
KrzysztofSzewczyk:去年@RossRidge的评论说不能在中断中使用虚拟模式代码(您正在使用的虚幻版本)是不正确的。实际上,您可以这样做,他犯了错误。在那个评论中,他错误地说“这意味着您不能在虚拟模式下启用中断或使用BIOS调用,因为它们可以更改DS中的值”。 - Michael Petch
我之前说的大部分是不正确的,仅仅重新加载DS寄存器并不能改变段限制,但是中断和BIOS调用可以进入保护模式,当它们返回到实模式时会重置段限制。 - Ross Ridge
在DOS环境下运行时,Unreal模式的问题更加棘手。在这种环境中,许多第三方DOS设备驱动程序会进入保护模式来处理中断,因此很可能需要随时担心切换回来。 - Michael Petch
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接