How to write your own Operating System (x86_64): Bootloader, 2nd Stage (long mode)
Πως να γράψετε το δικό σας Λειτουργικό Σύστημα (x86_64): Εκκινητής, 2o Στάδιο (μακρά κατάσταση)
IntroductionΕισαγωγή
In our previous article, we showed how to implement the first stage of the bootloader. Now, let's proceed to the second stage. First of all, in order for the execution to continue to the second stage, we have to remove the relevant comment from the jmp instruction in stage1/bootstage1.asm:
Σε προηγούμενο άρθρο μας, δείξαμε πως υλοποιούμε το πρώτο στάδιο του εκκινητή. Εδώ, θα εξετάσουμε το δεύτερο στάδιο. Πρώτα από όλα, για να προχωρήσει η εκτέλεση στο δεύτερο στάδιο, πρέπει να αφαιρέσουμε το σχετικό σχόλιο από την εντολή jmp στο stage1/bootstage1.asm:
...
; Jump to the entry point of stage 2
jmp Stage2_entrypoint
...Stage 2 codeΚώδικας του 2ου σταδίου
In the x86_64 computer architecture, long mode is the architecture's intended primary mode of operation, where a 64-bit operating system can access 64-bit instructions and registers. Let's see some things we need in order to enter the long mode:
- We check if the long mode is supported.
- We activate the A20 gate / address line.
- We prepare the memory paging.
- We remap the PIC (Programmable Interrupt Controller).
- Then, we enter the long mode and pass the control to the Kernel.
Let's examine the code for the second stage:
Στην αρχιτεκτονική υπολογιστών x86_64, η προορισμένη κατάσταση λειτουργίας είναι η λεγόμενη μακρά κατάσταση (long mode) όπου ενα 64-bit λειτουργικό σύστημα έχει πρόσβαση στις 64-bit εντολές και τους 64-bit καταχωρητές. Ας δούμε τι χρειάζεται να κάνουμε ώστε να μπούμε στην λειτουργία μακράς κατάστασης:
- Ελέγχουμε αν η λειτουργία μακράς κατάστασης υποστηρίζεται.
- Ενεργοποιούμε την πύλη / γραμμή διεύθυνσης A20.
- Προετοιμάζομε τη σελιδοποίηση της μνήμης.
- Επανακαθορίζουμε τον Προγραμματιζόμενο Ελεγκτή Διακοπών (PIC).
- Τέλος, εισερχόμαστε στη λειτουργία μακράς κατάστασης και δίνουμε τον έλεγχο στον Πυρήνα.
Ας δούμε, λοιπόν, τον κώδικα για το δεύτερο στάδιο:
stage2/bootstage2.asm
BITS 16
;---Initialized data------------------------------------------------------------
stage2_message dw 19
db 'Entering Stage 2...'
longmode_supported_message dw 23
db 'Long mode is supported.'
longmode_not_supported_message dw 27
db 'Long mode is not supported.'
;---Code------------------------------------------------------------------------
Stage2_entrypoint:
; Print 'Entering Stage 2...' message
mov si, stage2_message
call Real_mode_println
; Check if long mode is supported
call Is_longmode_supported
test eax, eax
jz .long_mode_not_supported
mov si, longmode_supported_message
call Real_mode_println
; Enable Gate A20
call Enable_A20
; Prepare paging
call Prepare_paging
; Remap PIC
call Remap_PIC
; Enter long mode
call Enter_long_mode
.long_mode_not_supported:
mov si, longmode_not_supported_message
call Real_mode_println
.halt: hlt ; Infinite loop.
jmp .halt ; (It prevents us from going off in memory and executing junk).
; Include
%include "stage2/a20.asm"
%include "stage2/paging.asm"
%include "stage2/pic.asm"
%include "stage2/longmode.asm"stage2/a20.asm
When IBM PC AT was released in 1984 (designed around the Intel 80286 microprocessor), for the sake of backward compatibility, a quirk in the 8086 microprocessor architecture (a memory wraparound) had to be replicated. You can read more about this issue here. Thus, the A20 line on the address bus was (and still is in many systems) disabled by default. This means that if we want to gain access to the full memory range, we have to enable the A20 line. There are several different ways (that may or may not be supported) and some of them may cause problems on some computers. Therefore, the recommended method is to try all of them, in the order of least risk, until one works:
- We test if A20 is already enabled. If it is, we don't need to do anything at all.
- We try to enable A20 using the BIOS function (we ignore the returned status).
- We test if A20 is enabled (to see if the BIOS function actually worked or not).
- We try to enable A20 using the keyboard controller method.
- We test if A20 is enabled in a loop with a time-out (as the keyboard controller method may work slowly).
- We try to enable A20 using the "Fast A20" method last.
- We test if A20 is enabled in a loop with a time-out (as the "Fast A20" method may work slowly).
- If none of the above worked, we give up.
Το 1984 κυκλοφόρησε ο IBM PC AT, σχεδιασμένος γύρω από τον μικροεπεξεργαστη 80286 της Intel. Για χάρη της προς τα πίσω συμβατότητας, μια ιδιοτροπία στην αρχιτεκτονική του μικροεπεξεργαστή 8086 (μια κυκλική επικάλυψη στην πρόσβαση της μνήμης) έπρεπε να αναπαραχθεί και σε αυτόν. Μπορείτε να διαβάσετε περισσότερα για αυτό το θέμα εδώ. Η ουσία είναι ότι η γραμμή Α20 στο δίαυλο διευθύνσεων ήταν (και εξακολουθεί να είναι σε πολλά συστήματα) αρχικά απενεργοποιημένη. Αυτό σημαίνει ότι αν θέλουμε να αποκτήσουμε πρόσβαση στο πλήρες εύρος της μνήνης, πρέπει να ενεργοποιήσουμε τη γραμμή Α20. Υπάρχουν αρκετοί διαφορετικοί τρόποι για να το επιτύχουμε αυτό (που μπορει να υποστηρίζονται ή όχι) και κάποιοι από αυτούς μπορεί να δημιουργήσουν προβλήματα σε κάποια συστήματα. Ως εκ τούτου, η συνιστώμενη μέθοδος είναι να δοκιμάσουμε όλους τους τρόπους κατά σειρά αυξανόμενου ρίσκου, μέχρι κάποιος να δουλέψει:
- Ελέγχουμε αν η A20 είναι ήδη ενεργοποιημένη. Αν είναι, δε χρειάζεται να κάνουμε κάτι άλλο.
- Δοκιμάζουμε να ενεργοποιήσουμε την Α20, χρησιμοποιώντας τη λειτουργία του BIOS (αγνοούμε την τιμή που μας γυρίζει).
- Ελέγχουμε αν η A20 ενεργοποιήθηκε (για να δούμε αν η λειτουργία του BIOS δούλεψε στην πράξη ή όχι).
- Δοκιμάζουμε να ενεργοποιήσουμε την Α20, χρησιμοποιώντας τον ελεκτή του πληκτρολογίου.
- Ελέγχουμε αν η A20 ενεργοποιήθηκε σε ένα βρόχο με κάποιο χρονικό τέλος (μιας και η μέθοδος με τον ελεκτή του πληκτρολογίου μπορεί να πάρει κάποιο χρόνο για να δουλέψει).
- Δοκιμάζουμε να ενεργοποιήσουμε την Α20, χρησιμοποιώντας τη μέθοδο "Fast A20" τελευταία.
- Ελέγχουμε αν η A20 ενεργοποιήθηκε σε ένα βρόχο με κάποιο χρονικό τέλος (μιας και η μέθοδος "Fast A20" μπορεί να πάρει κάποιο χρόνο για να δουλέψει).
- Αν δε δούλεψε κανένα από τα παραπάνω, τα παρατάμε.
BITS 16
;---Initialized data------------------------------------------------------------
a20_enabled_message dw 15
db 'A20 is enabled.'
a20_disabled_message dw 16
db 'A20 is disabled.'
a20_trying_bios dw 34
db 'Trying to enable A20 using BIOS...'
a20_trying_keyb dw 49
db 'Trying to enable A20 using Keyboard Controller...'
a20_trying_io92 dw 40
db 'Trying to enable A20 using IO port 92...'
;---Code------------------------------------------------------------------------
Enable_A20:
call Check_A20 ; Check if A20 is already enabled.
test ax, ax
jnz .end
; Try to enable A20 using BIOS.
mov si, a20_trying_bios
call Real_mode_println
call Enable_A20_using_BIOS
call Check_A20 ; Check if A20 is enabled.
test ax, ax
jnz .end
; Try to enable A20 using eEyboard Controller.
mov si, a20_trying_keyb
call Real_mode_println
call Enable_A20_using_Keyboard_Controller
call Check_A20 ; Check if A20 is enabled.
test ax, ax
jnz .end
; Try to enable A20 using IO port 92h (Fast A20 method).
mov si, a20_trying_io92
call Real_mode_println
call Enable_A20_using_Keyboard_Controller
call Check_A20 ; Check if A20 is enabled.
test ax, ax
jnz .end
.halt: hlt
jmp .halt ; Infinite loop.
.end:
ret
Check_A20:
;********************************************************************;
; Check the status of the A20 line ;
;********************************************************************;
call Real_mode_check_A20
test ax, ax
jnz .a20_enabled
mov si, a20_disabled_message
call Real_mode_println
ret
.a20_enabled:
mov si, a20_enabled_message
call Real_mode_println
ret
Real_mode_check_A20:
;**************************************************************************;
; Check the status of the A20 line (in real mode) ;
;--------------------------------------------------------------------------;
; Returns: ax = 0 if the a20 line is disabled (memory wraps around) ;
; ax = 1 if the a20 line is enabled (memory does not wrap around) ;
;**************************************************************************;
pushf
push ds
push es
push di
push si
cli ; clear interrupts
xor ax, ax ; ax = 0
mov es, ax ; es = 0
not ax ; ax = 0xFFFF
mov ds, ax ; ds = 0xFFFF
mov di, 0x0500 ; 0500 and 0510 are chosen since they are guaranteed to be free
mov si, 0x0510 ; for use at any point of time after BIOS initialization.
; save the original values found at these addresses.
mov dl, byte [es:di]
push dx
mov dl, byte [ds:si]
push dx
mov byte [es:di], 0x00 ; [es:di] is 0:0500
mov byte [ds:si], 0xFF ; [ds:si] is FFFF:0510
cmp byte [es:di], 0xFF ; if the A20 line is disabled, [es:di] will contain 0xFF
; (as the write to [ds:si] really occured to 00500).
mov ax, 0 ; A20 disabled ([es:di] equal to 0xFF).
je .a20_disabled
mov ax, 1 ; A20 enabled.
.a20_disabled:
; restore original values
pop dx
mov byte [ds:si], dl
pop dx
mov byte [es:di], dl
pop si
pop di
pop es
pop ds
popf
sti ; Enable interrupts.
ret
Enable_A20_using_BIOS:
;*************************************************************;
; Try to enable A20 gate using the BIOS (int 15h, ax = 2401h) ;
;-------------------------------------------------------------;
; Returns: ax = 0 (Failure) ;
; ax = 1 (Success) ;
;*************************************************************;
mov ax,2403h ; Query A20 gate Support (later PS/2s systems)
int 15h
jb .failure ; INT 15h is not supported
cmp ah, 0
jnz .failure ; INT 15h is not supported
mov ax, 2402h ; Get A20 gate Status
int 15h
jb .failure ; Couldn't get status
cmp ah, 0
jnz .failure ; Couldn't get status
cmp al, 1
jz .success ; A20 is already activated
mov ax, 2401h ; Enable A20 gate
int 15h
jb .failure ; Couldn't enable the A20 gate
cmp ah, 0
jnz .failure ; Couldn't enable the A20 gate
.success:
mov ax, 1
ret
.failure:
mov ax, 0
ret
Disable_A20_using_BIOS:
;**************************************************************;
; Try to disable A20 gate using the BIOS (int 15h, ax = 2400h) ;
;**************************************************************;
mov ax, 2400h
int 15h
ret
Enable_A20_using_Keyboard_Controller:
;******************************************************************;
; Try to enable A20 line using the Keyboard Controller (chip 8042) ;
;------------------------------------------------------------------;
; Returns: ax = 0 (Failure) ;
; ax = 1 (Success) ;
;******************************************************************;
cli ; Clear interrupts.
call a20wait
mov al, 0xAD ; Disable keyboard.
out 0x64, al
call a20wait
mov al, 0xD0 ; Read from input.
out 0x64, al
call a20wait2
in al,0x60
push eax
call a20wait
mov al, 0xD1 ; Write to output.
out 0x64, al
call a20wait
pop eax
or al, 2
out 0x60, al
call a20wait
mov al, 0xAE ; Enable keyboard.
out 0x64, al
call a20wait
sti ; Enables interrupts.
ret
a20wait:
in al, 0x64
test al, 2
jnz a20wait
ret
a20wait2:
in al, 0x64
test al, 1
jz a20wait2
ret
;*********************************************************************;
; Enable A20 Line via IO port 92h (Fast A20 method) ;
;---------------------------------------------------------------------;
; This method is quite dangerous because it may cause conflicts with ;
; some hardware devices forcing the system to halt. ;
;=====================================================================;
; Bits of port 92h ;
;---------------------------------------------------------------------;
; Bit 0 - Setting to 1 causes a fast reset ;
; Bit 1 - 0: disable A20, 1: enable A20 ;
; Bit 2 - Manufacturer defined ;
; Bit 3 - power on password bytes. 0: accessible, 1: inaccessible ;
; Bits 4-5 - Manufacturer defined ;
; Bits 6-7 - 00: HDD activity LED off, 01 or any value is "on" ;
;*********************************************************************;
Enable_A20_using_IO_port_92:
in al, 0x92 ; Read from port 0x92
test al, 2 ; Check if bit 1 (i.e. the 2nd bit) is set.
jnz .end ; If bit 1 (i.e. the 2nd bit) is already set don't do anything.
or al, 2 ; Activate bit 1 (i.e. the 2nd bit).
and al, 0xFE ; Make sure bit 0 is 0 (it causes a fast reset).
out 0x92, al ; Write to port 0x92
.end:
retstage2/paging.asm
Here is the code that sets the memory paging. Note that we will place the kernel in the high half of the virtual address space. This ensures that user space always occupies the lower canonical range, simplifying paging structures and context switching. This separation enforces stronger privilege boundaries, since user code cannot directly address high-half regions. It also enables all processes to map the same kernel space identically, reducing TLB flushes and improving overall memory management consistency.
Εδώ είναι ο κώδικας που καθορίζει τη σελιδοποίηση της μνήμης. Σημειώστε ότι θα τοποθετήσουμε τον πυρήνα στο υψηλό μισό του εικονικού χώρου διευθύνσεων. Αυτό εξασφαλίζει ότι ο χώρος χρήστη θα καταλαμβάνει πάντα το χαμηλό κανονικό εύρος, απλοποιώντας τις δομές σελιδοποίησης και την εναλλαγή διεργασιών. Ο διαχωρισμός αυτός επιβάλλει ισχυρότερα όρια προνομίων, καθώς ο κώδικας χρήστη δεν μπορεί να προσπελάσει άμεσα τις περιοχές του υψηλού μισού. Επιπλέον, επιτρέπει σε όλες τις διεργασίες να χαρτογραφούν τον χώρο του πυρήνα με τον ίδιο τρόπο, μειώνοντας τις εκκαθαρίσεις/ανανεώσεις του TLB και βελτιώνοντας τη συνοχή της διαχείρισης της μνήμης.
BITS 16
;---Constants-------------------------------------------------------------------
; Paging flag bits
PAGE_PRESENT equ (1 << 0) ; Bit 0 => Page present
PAGE_WRITE equ (1 << 1) ; Bit 1 => Page writable
PAGE_HUGE_PS equ (1 << 7) ; Bit 7 => 2MB page size, ignore PT
; Addresses to store the paging structures
PML4 equ 0xB000
PDPT_LOW equ (PML4 + 0x1000)
PD_LOW equ (PML4 + 0x2000)
PDPT_HIGH equ (PML4 + 0x3000)
PD_HIGH equ (PML4 + 0x4000)
; Kernel virtual address
KERNEL_VIRT_BASE equ 0xFFFFFFFF80000000 ; High-half base (48-bit address)
; Extract indices from KERNEL_VIRT_BASE for page table structure
PML4_INDEX equ ((KERNEL_VIRT_BASE >> 39) & 0x1FF) ; Bits 47-39
PDPT_INDEX equ ((KERNEL_VIRT_BASE >> 30) & 0x1FF) ; Bits 38-30
PD_INDEX equ ((KERNEL_VIRT_BASE >> 21) & 0x1FF) ; Bits 29-21
PT_INDEX equ ((KERNEL_VIRT_BASE >> 12) & 0x1FF) ; Bits 20-12
; Segment Selector = (Index * 8) + (TI * 4) + RPL = (1 * 8) + (0 * 4) + 0 = 8
; RPL (Requestor Privilege Level - Bits 1-0): 0 is highest, 3 is lowest
; TI (Table Indicator - Bit 2): 0 => GDT, 1 => LDT
; Index (Bits 15-3): The index of the segment descriptor within the GDT.
CODE_SEL equ 8
DATA_SEL equ 16
;---Initialized data------------------------------------------------------------
;******************************************************************************;
; Global Descriptor Table (GDT) ;
;******************************************************************************;
; The Global Descriptor Table (GDT) is a data structure used by x86-family ;
; processors (starting with the 80286) in order to define the characteristics ;
; of the various memory areas (segments) used during program execution, ;
; including the base address, the size, and access privileges like ;
; executability and writability. ;
;******************************************************************************;
GDT:
.Null:
dq 0x0000000000000000 ; Null Descriptor (should be present).
.Code:
dq 0x00209A0000000000 ; 64-bit code descriptor (exec/read).
dq 0x0000920000000000 ; 64-bit data descriptor (read/write).
ALIGN 4 ; Align the pointer field on a 4-byte boundary.
dw 0 ; Padding.
GDTR:
.Length dw $ - GDT - 1 ; 16-bit Size (Limit) of GDT.
.Base dd GDT ; 32-bit Base Address of GDT
; (CPU will zero extend to 64-bit)
;---Code------------------------------------------------------------------------
Prepare_paging:
;******************************************************************************;
; Prepare paging ;
;------------------------------------------------------------------------------;
; ES:EDI Should point to a valid 4096-aligned 16KiB buffer. ;
; SS:ESP Should point to memory that can be used as a small stack. ;
;******************************************************************************;
; Zero out the 20KiB buffer (PML4, PDPT_LOW, PD_LOW, PDPT_HIGH, PD_HIGH)
mov edi, PML4 ; Point to 20KiB buffer for the paging structures.
push di ; Store di (rep stosd alters di).
mov ecx, 5*0x1000/4 ; Count should be 5*4096/4 = 5120 dwords
xor eax, eax ; eax = 0
cld ; Set forward direction
rep stosd ; Fill with 0s
pop di ; Restore di
;************************* Low (identity) mapping **************************
; Build the PML4 (Page Map Level 4): PML4[0] -> PDPT_LOW
lea eax, [PDPT_LOW] ; eax = address of the PDPT_LOW
or eax, PAGE_PRESENT | PAGE_WRITE ; Set the flags (present and writable)
mov [PML4], eax ; PML4E[0] = eax
; Build the PDPT_LOW (Page Directory Pointer Table): PDPT_LOW[0] -> PD_LOW
lea eax, [PD_LOW] ; eax = address of the PD_LOW
or eax, PAGE_PRESENT | PAGE_WRITE ; Set the flags (present and writable)
mov [PDPT_LOW], eax ; PDPT_LOW[0] = eax
; Fill the PD_LOW (Page Directory) with entries (each maps a 2 MiB page)
mov eax, 0 ; eax = 0 (0-2MiB identity)
or eax, PAGE_PRESENT | PAGE_WRITE | PAGE_HUGE_PS ; Set flags (2MiB page)
mov [PD_LOW], eax ; PD_LOW[0] = eax
; ...
; ************************ High-half kernel mapping ************************
; PML4[PML4_INDEX] -> PDPT_HIGH
lea eax, [PDPT_HIGH] ; eax = PDPT_HIGH
or eax, PAGE_PRESENT | PAGE_WRITE ; Set the flags (present and writable)
mov [PML4+PML4_INDEX*8], eax ; PML4[PML4_INDEX] = eax
; PDPT_HIGH[PDPT_INDEX] -> PD_HIGH
lea eax, [PD_HIGH] ; eax = PD_HIGH
or eax, PAGE_PRESENT | PAGE_WRITE ; Set the flags (present and writable)
mov [PDPT_HIGH+PDPT_INDEX*8], eax ; PDPT_HIGH[PDPT_INDEX] = eax
; Fill the PD_HIGH (Page Directory) with entries (each maps a 2 MiB page)
mov di, PD_HIGH
xor eax, eax ; eax = 0
mov ecx, 512 ; Fill 512 entries (i.e. map 1 GiB)
.MapLoop:
; Set flags: Present, Writable, 2 MiB page
or eax, PAGE_PRESENT | PAGE_WRITE | PAGE_HUGE_PS
mov [di], eax ; Store low 32 bits (entry is 8 bytes)
mov [di + 4], dword 0 ; Store high 32 bits
add di, 8 ; Advance to next PD_HIGH entry
add eax, 0x200000 ; Increment address by 2MiB
loop .MapLoop
retstage2/pic.asm
The Programmable Interrupt Controller (PIC) is one of the most important chips in the x86 architecture. Its function is to manage hardware interrupts and send them to the appropriate system interrupts. This allows the system to respond to devices without the need for polling them all the time. In protected mode and long mode, the Interrupt Requests (IRQs) 0-15 are in conflict with the CPU exceptions which reserve the first 32 positions (0-31 or 0x00-0x1F). Thus, prior to switching to the long mode, we have to change the PIC's offsets (a.k.a. remapping the PIC) so that IRQs use non-reserved vectors:
O Προγραμματιζόμενος Ελεγκτής Διακοπών (Programmable Interrupt Controller, PIC) είναι ένα από τα πιο σημαντικά ολοκληρωμένα κυκλώματα στην αρχιτεκτονικη x86. Η λειτούργια του είναι να διαχειρίζεται τις διακοπές υλικού (hardware interrupts) και να τις στέλνει στις κατάλληλες διακοπές συστήματος. Αυτό επιτρέπει στο σύστημα να ανταποκρίνεται στις συσκευές χωρίς την ανάγκη να τις βολιδοσκοπεί περιοδικά (polling) όλη την ώρα. Στην προστατευμένη κατάσταση λειτουργίας (protected mode) και στην μακρά κατάσταση λειτουργίας (long mode), οι αιτήσεις διακοπών (Interrupt Requests, IRQs) 0-15 συγκρούονται με τις εξαιρέσεις του επεξεργαστή (CPU exceptions) που δεσμεύουν τις πρώτες 32 θέσεις (0-31 ή 0x00-0x1F). Ως εκ τούτου, πριν τη μετάβαση στην λειτουργία μακράς κατάστασης, πρέπει να μετατοπίσουμε τα IRQs σε μη δεσμευμένες θέσεις στον PIC (αυτή η διαδικασία είναι γνωστή και ως "remapping the PIC"):
; PIC (Programmable Interrupt Controller)
BITS 16
;---Constants----------------------------------------------------------------------
PIC1_COMMAND equ 0x20 ; Command port of 1st PIC
PIC1_DATA equ 0x21 ; Data port of 1st PIC
PIC2_COMMAND equ 0xA0 ; Command port of 2nd PIC
PIC2_DATA equ 0xA1 ; Data port of 2nd PIC
PIC_EOI equ 0x20 ; EOI (End of interrupt) command (= 0x20)
ICW1_ICW4 equ 0x01 ; Initialization Command Word 4 is needed
ICW1_SINGLE equ 0x02 ; Single mode (0: Cascade mode)
ICW1_INTERVAL4 equ 0x04 ; Call address interval 4 (0: 8)
ICW1_LEVEL equ 0x08 ; Level triggered mode (0: Edge mode)
ICW1_INIT equ 0x10 ; Initialization - required!
ICW4_8086 equ 0x01 ; 8086/88 mode (0: MCS-80/85 mode)
ICW4_AUTO_EOI equ 0x02 ; Auto End Of Interrupt (0: Normal EOI)
ICW4_BUF_SLAVE equ 0x08 ; Buffered mode/slave
ICW4_BUF_MASTER equ 0x0C ; Buffered mode/master
ICW4_SFNM equ 0x10 ; Special Fully Nested Mode
;---Code---------------------------------------------------------------------------
Remap_PIC:
;***************************************************************************;
; In protected / long mode, the IRQs 0-15 conflict with the CPU exceptions ;
; (which are reserved up until 0x1F). It is thus recommended to change the ;
; PIC's offsets (remapping the PIC) so that IRQs use non-reserved vectors. ;
; A common choice is to move them to the beginning of the available range: ;
; IRQs 0..15 -> INT 0x20..0x2F (30..47). For that, we need to set the 1st ;
; PIC's offset to 0x20 (32) and the 2nd's to 0x28 (40). ;
;***************************************************************************;
push ax
; Disable IRQs
mov al, 0xFF ; Out 0xFF to 0xA1 and 0x21 to mask/disable all IRQs.
out PIC1_DATA, al
out PIC2_DATA, al
nop
nop
; Remap PIC
mov al, ICW1_INIT | ICW1_ICW4 ; ICW1: Send initialization command (= 0x11) to both PICs
out PIC1_COMMAND, al
out PIC2_COMMAND, al
mov al, 0x20 ; ICW2: Set vector offset of 1st PIC to 0x20 (i.e. IRQ0 => INT 32)
out PIC1_DATA, al
mov al, 0x28 ; ICW2: Set vector offset of 2nd PIC to 0x28 (i.e. IRQ8 => INT 40)
out PIC2_DATA, al
mov al, 4 ; ICW3: tell 1st PIC that there is a 2nd PIC at IRQ2 (= 00000100)
out PIC1_DATA, al
mov al, 2 ; ICW3: tell 2nd PIC its "cascade" identity (= 00000010)
out PIC2_DATA, al
mov al, ICW4_8086 ; ICW4: Set mode to 8086/88 mode
out PIC1_DATA, al
out PIC2_DATA, al
mov al, 0xFF ; OCW1: We mask all interrupts (until we set a proper IDT in Kernel)
out PIC1_DATA, al
out PIC2_DATA, al
pop ax
retstage2/longmode.asm
Here is the code for checking if the long mode is supported and the code for entering the long mode:
Εδώ βρίσκεται ο κώδικας που έχει να κάνει με τον έλεγχο υποστήριξης και τη μετάβαση στην λειτουργία μακράς κατάστασης (long mode):
BITS 16
;---Code------------------------------------------------------------------------
Is_longmode_supported:
;********************************************************************;
; Check if Long mode is supported ;
;--------------------------------------------------------------------;
; Returns: eax = 0 if Long mode is NOT supported, else non-zero. ;
;********************************************************************;
mov eax, 0x80000000 ; Test if extended processor info in available.
cpuid
cmp eax, 0x80000001
jb .not_supported
mov eax, 0x80000001 ; After calling CPUID with EAX = 0x80000001,
cpuid ; all AMD64 compliant processors have the longmode-capable-bit
test edx, (1 << 29) ; (bit 29) turned on in the EDX (extended feature flags).
jz .not_supported ; If it's not set, there is no long mode.
ret
.not_supported:
xor eax, eax
ret
Enter_long_mode:
;********************************************************************;
; Enter long mode ;
;********************************************************************;
mov edi, PML4 ; Point edi at the PML4
mov eax, 10100000b ; Set the PAE and PGE bit.
mov cr4, eax
mov edx, edi ; Point CR3 at the PML4.
mov cr3, edx
mov ecx, 0xC0000080 ; Read from the EFER MSR.
rdmsr
or eax, 0x00000100 ; Set the LME bit.
wrmsr
mov ebx, cr0 ; Activate long mode
or ebx,0x80000001 ; by enabling paging and protection simultaneously.
mov cr0, ebx
lgdt [rel GDTR] ; Load GDT.Pointer
jmp CODE_SEL:LONGMODE ; Mode-Switch Jump (Load CS & flush instruction cache)
LONGMODE:
BITS 64 ; We have entered the long mode! :)
mov ax, DATA_SEL ; Set all segments registers to DATA_SEL.
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov rax, KERNEL_VIRT_BASE + HIGH_KERNEL
jmp rax ; Jump to high-address mapped kernel
HIGH_KERNEL:
lea rax, [rel GDT] ; Load our high-address GDT
mov [rel GDTR.Base], rax
lgdt [rel GDTR]
add rsp, KERNEL_VIRT_BASE ; Point rsp (stack) to its high address
mov qword [abs KERNEL_VIRT_BASE + PML4], 0 ; Clear PML4[0] (identity entry)
mov rax, cr3 ; Flush the TLB (Translation Lookaside Buffer)
mov cr3, rax ; Reload CR3 to flush TLB
jmp Kernel_entrypoint ; Jump to Kernel entrypointkernel/kernel.asm
Here is some dummy code that write a message directly into the video RAM, just to test if our bootloader is functional:
Εδώ έχουμε έναν απλό κώδικα, ο οποίος γράφει έναν μήνυμα απευθείας στην περιοχή της μνήμης των γραφικών, απλά για να δούμε αν ο εκκινητή μας είναι λειτουργικός:
BITS 64 ; We have entered the long mode! :)
;---Constants-------------------------------------------------------------------
VRAM equ KERNEL_VIRT_BASE + 0xB8000
;---Code------------------------------------------------------------------------
Kernel_entrypoint:
;********************************************************************;
; Just some dummy code for now ;
;********************************************************************;
; Set RDI to point to Video RAM (KERNEL_VIRT_BASE + 0xB8000)
mov rdi, VRAM
; Print "Hello World!"
mov rax, 0x1F6C1F6C1F651F48
mov [rdi], rax
mov rax, 0x1F6F1F571F201F6F
mov [rdi + 8], rax
mov rax, 0x1F211F641F6C1F72
mov [rdi + 16], rax
.halt: hlt
jmp .halt ; Infinite loop.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

Full codeΠλήρης κώδικας
You can download the full code from here: bootloader code
Μπορείτε να κατεβάσετε τον πλήρη κώδικα απο εδώ: κώδικας εκκινητή