更改为"虚幻"模式,处理器崩溃

Krz*_*zyk 6 c x86 assembly bootloader

我正在写一个简单的bootloader.它的主要任务是加载内核,并将处理器切换到虚幻模式.我的问题是当我打开虚幻模式时,处理器崩溃了.这是我的代码(从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)


; ==================================================================
Run Code Online (Sandbox Code Playgroud)

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

Mic*_*tch 4

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

看起来,当您使用 MikeOS 时,您从 OSDev Wiki 中拼接了一些虚幻模式代码,该代码假设原点基于段:偏移地址 0x0000:0x7c00。这也表示物理地址 0x07c00 (0x0000<<4+0x7c00=0x7c00)。在这种情况下,您将需要NASMORG代码中的 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
Run Code Online (Sandbox Code Playgroud)

可以改为:

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
Run Code Online (Sandbox Code Playgroud)

然后,您可以删除完成虚幻模式设置后出现的所有重复代码。需要消除这些行:

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
Run Code Online (Sandbox Code Playgroud)

通常虚幻模式将所有数据寄存器设置为平面内存模型。您不仅可以将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
Run Code Online (Sandbox Code Playgroud)

完成此操作后,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:
Run Code Online (Sandbox Code Playgroud)

现在的问题是我们正在使用 0x07c0 的段,并且 GDT 的基址现在相对于 0x0000(而不是 0x7c00)的偏移量。gdtinfo我们将加载到GDT 寄存器的结构中的基地址是线性地址(不是段:偏移地址)。在实模式下,线性地址和物理地址是同一件事。为了成为gdt线性地址,我们将 0x7c00 添加到gdt. 我们修改该行:

    dd gdt                 ;start of table
Run Code Online (Sandbox Code Playgroud)

现在它是这样写的:

    dd gdt+0x7c00          ;start of table
Run Code Online (Sandbox Code Playgroud)

经过更改的完整代码

文件的修订版本可能是:

    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)

; ==================================================================
Run Code Online (Sandbox Code Playgroud)

赏金

在赏金中你这样说:

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

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

可以制作实/虚模式内核,但它们会受到限制,因为缺乏保护模式下可用的虚拟内存、内存保护和分页机制。

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


内核系统

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
Run Code Online (Sandbox Code Playgroud)

它可以组装成KERNEL.SYS

nasm -f bin kernel.asm -o KERNEL.SYS
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

如果处理器未处于虚幻模式,则写入左上角的字符将不会出现,或者硬件/模拟器可能会达到某种其他类型的未定义状态。


其他观察和信息

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