]> git.dujemihanovic.xyz Git - nameless-os.git/commitdiff
Document the code
authorDuje Mihanović <duje.mihanovic@skole.hr>
Tue, 7 Sep 2021 18:33:39 +0000 (20:33 +0200)
committerDuje Mihanović <duje.mihanovic@skole.hr>
Tue, 7 Sep 2021 18:33:39 +0000 (20:33 +0200)
boot.s
how-to-compile [new file with mode: 0644]
kernel/entry.s
kernel/kernel.c

diff --git a/boot.s b/boot.s
index a40b7508781e9dbc59b82587e003f2ccca6a21f8..de5887dddc3101695a5ddcdc7e5deeca7f1d3796 100644 (file)
--- a/boot.s
+++ b/boot.s
@@ -1,32 +1,33 @@
-       bits 16
-       org 7C00h
+       bits 16 ; boot sectors run in real mode
+       org 7C00h ; BIOS loads us at 0x7c00
 
-KERNEL_OFFSET equ 1000h
+KERNEL_OFFSET equ 1000h ; where we will load our kernel
 
-       mov [BOOT_DRIVE], dl
+       mov [BOOT_DRIVE], dl ; BIOS puts the number of our boot drive in dl, so we will want to remember this
 
-       mov bp, 9000h
+       mov bp, 9000h ; initialize stack at a spot sufficiently distanced from this code
        mov sp, bp
 
        mov di, 0
 
-       xor ax, ax
-       int 13h
-       jc reset_error
-
-       mov ah, 2
-       mov al, 10
-       xor ch, ch
-       mov cl, 2
-       xor dh, dh
-       xor bx, bx
-       mov es, bx
-       mov bx, KERNEL_OFFSET
-       int 13h
-       jc read_error
-       cmp al, 10
-       jl read_error
-       jmp switch_to_pm
+       xor ax, ax ; clear accumulator
+       int 13h ; BIOS disk services, with a cleared accumulator this will reset the disk controller
+       jc reset_error ; halt if controller reset fails
+
+       mov ah, 2 ; instruct BIOS's interrupt 13h to read sectors
+       mov al, 10 ; load 10 sectors, might be excessive (for now) but it's still good to do so
+       xor ch, ch ; read from 1st cylinder
+       mov cl, 2 ; start reading from 2nd sector, right after the boot sector
+       xor dh, dh ; read from 1st head
+       xor bx, bx ; clear B register
+       mov es, bx ; clear extended segment
+       mov bx, KERNEL_OFFSET ; put the sectors in our desired offset
+       ; dl holds the number of the drive to read from, but BIOS already filled this in
+       int 13h ; do the read
+       jc read_error ; halt if read fails
+       cmp al, 10 ; make sure we actually read 10 sectors
+       jl read_error ; halt if we didn't
+       jmp switch_to_pm ; if all is good, begin the switch to 32-bit protected mode
 
 reset_error
        mov bx, 0B800h
@@ -47,64 +48,77 @@ halt
 
 switch_to_pm
        mov bx, 0B800h
-       mov es, bx
-       mov byte [es:0], 'L'
+       mov es, bx ; set extra segment to starting address of video RAM
+       mov byte [es:0], 'L' ; print an L to screen, to let us know that we actually got here
 
-       cli
-       xor ax, ax
-       mov ds, ax
-       lgdt [gdt_desc]
+       cli ; disable interrupts
+       xor ax, ax ; clear accumulator
+       mov ds, ax ; clear data segment, this makes sure the next instruction reads from the right place
+       lgdt [gdt_desc] ; load the Global Descriptor Table
        
-       mov eax, cr0
-       or eax, 1
-       mov cr0, eax
-       jmp CODE_SEG:protected
+       mov eax, cr0 ; move Control Register 0 to accumulator
+       or eax, 1 ; flip the bit which controls memory protection
+       mov cr0, eax ; move the accumulator back to CR0
+       jmp CODE_SEG:protected ; not quite there yet, need to do a far jump to clean the pipeline from any 16-bit instructions
+                                                  ; note that the jump does not have to be physically far away, it just needs to use a segmented address
 
-       bits 32
+       bits 32 ; we are finally in 32-bit mode
 protected
-       mov ax, DATA_SEG
+       mov ax, DATA_SEG ; put the selector for the data segment in the accumulator
+       ; in real mode, segmentation works by taking one of these segment registers, shifting it right by 4 bits or 1 hex digit
+       ; and then adding a 16-bit offset to form a 20-bit address
+       ; example: 1234h:5678h
+       ; 1234h is shifted to 12340h, 12340h + 5678h is 179B8h
+       ; in 32-bit protected mode, these segment registers do not hold the segment address itself, but a selector in the GDT
+       ; so we have to update the segment registers accordingly
        mov ds, ax
        mov ss, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        
+       ; reinitialize the stack at a safe location
        mov ebp, 090000h
        mov esp, ebp
 
+       ; print a string to let us know that we survived the switch
        mov ebx, pm_success
        call pmprint
 
+       ; transfer control to the kernel
        call KERNEL_OFFSET
 
+       ; above call should not return in normal circumstances, but if it does hang forever
        jmp $
 
 pmprint
-       pusha
-       mov edx, video_memory
+       pusha ; save registers to stack
+       mov edx, video_memory ; initialize dx with location of VRAM
 
 .loop
-       mov al, [ebx]
-       mov ah, 0Fh
+       mov al, [ebx] ; read next char and put it in al
+       mov ah, 0Fh ; puts the VGA text mode color white on black into ah
 
-       cmp al, 0
-       je .done
+       cmp al, 0 ; if the next character is null, we reached end of string
+       je .done ; so return the instruction
 
-       mov [edx], al
-       mov [edx+1], ah
+       mov [edx], al ; otherwise put the next character in the video memory
+       mov [edx+1], ah ; do the same for its color
 
-       inc ebx
-       add edx, 2
+       inc ebx ; point to next character in string
+       add edx, 2 ; point to next character in VRAM
 
-       jmp .loop
+       jmp .loop ; go back to the loop
 
 .done
-       popa
-       ret
+       popa ; restore registers from stack
+       ret ; return
 
 pm_success db "Now in protected mode", 0
 video_memory equ 0B8000h
 
+; the actual Global Descriptor Table
+; refer to Volume 3, Chapter 2, 2.1.1 of Intel's 64 and IA-32 Software Developer's Manual for more info
 gdt_start
 null_seg
        dq 0
@@ -129,7 +143,7 @@ gdt_desc
 CODE_SEG equ code_seg - gdt_start
 DATA_SEG equ data_seg - gdt_start
 
-BOOT_DRIVE db 0
+BOOT_DRIVE db 0 ; reserve a spot in RAM for our boot drive variable
 
        times 510-($-$$) db 0
-       dw 0AA55h
+       dw 0AA55h ; MBR signature
diff --git a/how-to-compile b/how-to-compile
new file mode 100644 (file)
index 0000000..bcd0b8b
--- /dev/null
@@ -0,0 +1,10 @@
+1. Compile an i686-elf GCC and binutils following the instructions on this page:
+   https://wiki.osdev.org/GCC_Cross-Compiler
+
+2. Install yasm, or modify the makefile to use nasm if you prefer it
+
+3. Run make in the source root
+
+4. Run boot.img with some emulator, I use qemu:
+   qemu-system-i386 boot.img
+   You can use the x86_64 qemu, Bochs, VirtualBox or something else if you prefer
index 236b81e967e459e4c6f684d137726dbf42743677..a02df92d4703df4ada20654b433158c80ee1373f 100644 (file)
@@ -1,5 +1,12 @@
-       bits 32
-       extern _start
+; when our kernel source has functions before _start and we blindly transfer control to the beginning
+; of the kernel binary, it will wrongly execute the other functions
+; the solution is to link a small assembly routine (this) which executes a known label within the kernel
+; so that this routine comes before the kernel in the resulting binary
+; we cannot link the boot sector code and the kernel because the former needs to be a raw binary, while the
+; kernel is compiled into an ELF object file which contains some metadata on the kernel code
 
-       call _start
-       jmp $
+       bits 32 ; this is started in protected mode
+       extern _start ; the known label in the kernel source we will execute is, well, not in this assembly routine
+
+       call _start ; call the known label
+       jmp $ ; it should never return, but hang forever if it does
index 87fbef954a0c4703db02024c1fcf2e1383b34659..e04b875e2959b5963e7c6f4ba394abba1f5f0be5 100644 (file)
@@ -1,5 +1,5 @@
 void _start(void)
 {
-       char *video_memory = (char *) 0xB8000;
-       *video_memory = 'A';
+       char *video_memory = (char *) 0xB8000; /* VGA VRAM starts at 0xB8000 */
+       *video_memory = 'A'; /* put an A at the beginning of the VRAM */
 }