How to write your own Operating System (x86_64): Bootloader, 1st Stage

Πως να γράψετε το δικό σας Λειτουργικό Σύστημα (x86_64): Εκκινητής, 1o Στάδιο

· Coding Προγραμματισμός · assembly assembly os λειτουργικό σύστημα


First of all, we need a bootloader which will prepare the environment and will load our kernel. On the x86, the BIOS loads only one sector (a.k.a. the boot sector) from disk into RAM. This means that we are limited to 512 bytes for the bootloader. To bypass this restriction, we are going to use a two-stage bootloader. The first stage will be very small (less than 512 bytes) with the sole purpose of loading the second stage. The second stage will contain all the code we need in order to prepare the environment and load our kernel.

Πρώτα από όλα, χρειαζόμαστε έναν εκκινητή (bootloader) ο οποίος θα προετοιμάσει το περιβάλλον και θα φορτώσει τον πυρήνα μας. Στην αρχιτεκτονική x86, το BIOS φορτώνει μόνο έναν τομέα δίσκου (γνωστό ως boot sector) από τον δίσκο στη μνήμη RAM. Αυτό σημαίνει ότι θα πρέπει να χωρέσουμε τον εκκινητή μας σε λιγότερα από 512 bytes. Για να ξεπεράσουμε αυτόν τον περιορισμό, θα χρησιμοποιήσουμε έναν εκκινητή δύο σταδίων. Το πρώτο στάδιο θα είναι πολύ μικρό (μικρότερο από 512 bytes) και θα έχει ως μοναδικό σκοπό να φορτώσει το δεύτερο στάδιο. Το 2ο στάδιο θα εμπεριέχει όλο τον κώδικα που χρειαζόμαστε για να προετοιμάσουμε το περιβάλλον και να φορτώσουμε τον πυρήνα μας.

For this project, we are going to use the Netwide Assembler (NASM). Our initial Makefile is like that:

Για αυτό το έργο, θα χρησιμοποιήσουμε τον Netwide Assembler (NASM). Το αρχικό μας Makefile είναι ως εξής:

.PHONY: clean, .force-rebuild
all: bootloader.bin

bootloader.bin: os.asm .force-rebuild
	nasm -fbin os.asm -o os.bin

	rm *.bin

The contents of os.asm are:

Τα περιεχόμενα του os.asm είναι:

    times 90 db 0 ; BPB (BIOS Parameter Block) will go here
    %include "stage1/bootstage1.asm"

    %include "stage2/bootstage2.asm"
    align 512, db 0

    %include "kernel/kernel.asm"
    align 512, db 0

Stage 1 of the BootloaderΣτάδιο 1 του Εκκινητή


Let's see the code for the first stage:

Ας δούμε τον κώδικα για το πρώτο στάδιο:

BITS 16    ; On the x86, the BIOS (and consequently the bootloader) runs in 16-bit Real Mode.
ORG 0x7C00 ; We are loaded/booted by BIOS into this memory address.

Stage1_entrypoint:         ; Main entry point where BIOS leaves us. Some BIOS may load us at 0x0000:0x7C00 while others at 0x07C0:0x0000. We do a far jump to accommodate for this issue (CS is reloaded to 0x0000).
    jmp 0x0000:.setup_segments 
        ; Next, we set all segment registers to zero.
        xor ax, ax
        mov ss, ax
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        ; We set up a temporary stack so that it starts growing below Stage1_entrypoint (i.e. the stack base will be located at 0:0x7c00).        
        mov sp, Stage1_entrypoint   
        ; Clear direction flag (go forward in memory when using instructions like lodsb).

    ; Loading stage 2 from disk into RAM
    mov [disk], dl ; Storing disk number. BIOS loads into dl the "drive number" of the booted device.
    mov ax, (stage2_start-stage1_start)/512 ; ax: start sector
    mov cx, (kernel_end-stage2_start)/512   ; cx: number of sectors (512 bytes) to read
    mov bx, stage2_start                    ; bx: offset of buffer
    xor dx, dx                              ; dx: segment of buffer
    call Real_mode_read_disk
    ; Print "Stage 1 finished." message.
    mov si, stage1_message
    call Real_mode_println
    ; Jump to the entry point of stage 2 (commented out for now)
    ;;; jmp Stage2_entrypoint

    .halt: hlt   ; Infinite loop.
    jmp .halt    ; (It prevents us from going off in memory and executing junk).

; Include
%include "stage1/disk.asm"  
%include "stage1/print.asm"  

times 510-($-$$) db 0 ; Padding
dw 0xAA55 ; The last two bytes of the boot sector should have the 0xAA55 signature.
; Otherwise, we'll get an error message from BIOS that it didn't find a bootable disk.
; This signature is represented in binary as 1010101001010101. The alternating bit 
; pattern was thought to be a protection against certain (drive or controller) failures.


Here is some code for reading disk sectors (i.e. loading them into RAM). We use the BIOS interrupt call INT 13h, service AH=42h, for the extended reading (i.e. LBA addressing) of the disk sectors:

Εδώ βρίσκεται ο κώδικας για την ανάγνωση τομέων από τον δίσκο (δηλαδή τη φόρτωσή τους στη RAM). Χρησιμοποιούμε κλήση στη διακοπή INT 13h του BIOS, υπηρεσία AH=42h, που αφορά εκτεταμένη ανάγνωση (LBA addressing) των τομέων ενός δίσκου:


;---Initialized data------------------------------------------------------------

disk db 0x80

disk_error_message dw 11
db 'Disk error!'

; Disk Address Packet                                                           ;
; Offset  Size   Description                                                    ;
;   0       1    size of packet (16 bytes)                                      ;
;   1       1    always 0                                                       ;
;   2       2    number of sectors to load (max = 127 on some BIOS)             ;
;   4       2    16-bit offset of target buffer                                 ;
;   4       2    16-bit segment of target buffer                                ;
;   8       4    lower 32 bits of 48-bit starting LBA                           ;   
;  12       4    upper 32 bits of 48-bit starting LBA                           ;
              db 0x10 ; size of packet = 16 bytes
              db 0    ; always 0
.num_sectors: dw 127  ; number of sectors to load (max = 127 on some BIOS)
.buf_offset:  dw 0x0  ; 16-bit offset of target buffer 
.buf_segment: dw 0x0  ; 16-bit segment of target buffer
.LBA_lower:   dd 0x0  ; lower 32 bits of 48-bit starting LBA
.LBA_upper:   dd 0x0  ; upper 32 bits of 48-bit starting LBA


; Load disk sectors to memory (int 13h, function code 42h) ;
; ax: start sector                                         ;
; cx: number of sectors (512 bytes) to read                ;
; bx: offset of buffer                                     ;
; dx: segment of buffer                                    ;
        cmp cx, 127 ; (max sectors to read in one call = 127)
        jbe .good_size
        mov cx, 127
        call Real_mode_read_disk
        add eax, 127
        add dx, 127 * 512 / 16
        sub cx, 127
        jmp .start

        mov [DAP.LBA_lower], ax
        mov [DAP.num_sectors], cx
        mov [DAP.buf_segment], dx
        mov [DAP.buf_offset], bx
        mov dl, [disk]
        mov si, DAP
        mov ah, 0x42
        int 0x13
        jc .print_error
        mov si, disk_error_message
        call Real_mode_println
       .halt: hlt
        jmp .halt; Infinite loop. We cannot recover from disk error.


Here is our code for printing messages. We use the BIOS interrupt call INT 10h, service AH=0Eh to print each character. Note that we do not use null-terminated strings. Instead, we opted to store the string length in the first 16 bits (2 bytes) of the string structure:

Εδώ έχουμε τον κώδικά μας για την εκτύπωση μηνυμάτων. Χρησιμοποιούμε κλήση στη διακοπή INT 10h του BIOS, υπηρεσία AH=0Eh για να εκτυπώσουμε τον κάθε χαρακτήρα. Σημειώστε ότι δεν χρησιμοποιούμε το '\0' για να δηλώσουμε το τέλος των αλφαριθμητικών σειρών χαρακτήρων. Αντίθετα, επιλέξαμε να αποθηκεύουμε τον αριθμό των χαρακτήρων στα πρώτα 16 bits (2 bytes) της δομής της σειράς χαρακτήρων:


;---Initialized data------------------------------------------------------------

newline dw 2
db 13,10 ; \r\n

stage1_message dw 17
db 'Stage 1 finished.'


; Prints a string (in real mode)                                                  ;
; si: pointer to string (first 16 bits = the number of characters in the string.) ;  
    push ax
    push cx
    push si
    mov cx, word [si] ; first 16 bits = the number of characters in the string
    add si, 2
    .string_loop:     ; print all the characters in the string
        mov ah, 0eh
        int 10h
    loop .string_loop, cx
    pop si
    pop cx
    pop ax

; Prints a string (in real mode) and a newline (\r\n)       ;
; si: pointer to string                                     ;
; (first 16 bits = the number of characters in the string.) ;  
    push si
    call Real_mode_print
    mov si, newline
    call Real_mode_print
    pop si

Running our codeΕκτέλεση του κώδικά μας

We can test our code very easily using an emulator like QEMU:

Μπορούμε να δοκιμάσουμε τον κώδικά μας πολύ εύκολα, χρησιμοποιώντας έναν εξομοιωτή όπως ο QEMU:

$ make
$ qemu-system-x86_64 -drive format=raw,file=os.bin

Running Bootloader Stage 1

Full codeΠλήρης κώδικας

You can download the full code from here: stage 1 code

Μπορείτε να κατεβάσετε τον πλήρη κώδικα απο εδώ: κώδικας για το πρώτο στάδιο


See also...

Δείτε επίσης...