Nachladen des Kernels durch eigenen Bootloader


  • Mod

    jmp dword 08:0x10000

    Thx! 👍
    Gibt man "jmp dword 08:0x10000" in Google ein, findet man nur uns hier:
    http://www.google.de/search?hl=de&q="jmp+dword+08%3A0x10000"&btnG=Suche&meta= 😕 (Ich liebe solche Unikate)

    Du solltest auf deine Ausgaben gucken

    werde ich beherzigen.


  • Mod

    Allerdings dürfte der nächste Schlag nun bei einem Kernel mit mehr als 128 Sektoren vorprogrammiert sein (EDIT: ich habe es ausprobiert: es stimmt!). Weiß da jemand etwas Genaues, wie man diese harte Begrenzung umgeht?



  • Wie in dem Verlinkten Thema steht, duerfte das an dem antike DMA-Controller fuer Floppys scheitern. Einen direkten Workarround bei diesen ISA-Fossilien kenne ich auch nicht.
    Es wird dir also wohl nichts anderes uebrig bleiben, als dich damit zu arrangieren. Moeglichkeiten gibt es schliesslich viele (von denen einige eigentlich auch vom BIOS selbst umgesetzt werden koennen sollten, aber egal...).
    Naheliegend waere es zB., in einen guenstig gelegenen Buffer einzulesen und von dort Sektor fuer Sektor an die gewuenschte Adresse zu kopieren oder eben selbst genau darauf zu achten, dass du dem DMA mit seinen 64K-Pages nicht in's Gehege kommst.


  • Mod

    Merkwürdigerweise finde ich zu diesem Thema keine Vorbilder. Bootloader für solche Anwendungen sind offenbar ein eher "verstecktes" Thema. In Foren wird man sofort auf GRUB zugesteuert.

    Naja, 64KB (128 Sektoren) ist schon eine Menge für einen Hobby-Kernel. 😃
    Immerhin wurde nun die durch 6 Bit bedingte 63-Sektoren-Grenze durch die eigene Berechnung der CHS geschafft. Immer step-by-step oder "festina lente" wie der Lateiner sagt.



  • Evtl hilft auch der offen liegende Code von GRUB weiter? Hab ihn mir interessehalber gerade auch mal angesehen, dies hier müsste der Boot-Code sein (ansonsten DL für alles hier).


  • Mod

    Danke für den Link zu GRUB!

    Ärgerlich finde ich, dass ich den Kernel nun ab 0x10000 beginnen musste. Wir hatten ihn ja extra nach 0x8000 gelegt, um den Speicher zu "schonen". 😉 Naja, immerhin versteht man nun besser, warum man typischerweise dort beginnt.

    MitGRUBwärDasNichPassiert

    In meinem Tutorial versuche ich GRUB solange zu vermeiden, bis es wirklich notwendig wird. Bisher wurde immer ein work-around gefunden. Man lernt mit eigenem Bootloader auch eindeutig mehr über die Basics (z.B. die harte Grenze durch ISA DMA oder die eigene CHS-Berechnung 0,0,1...18 | 0,1,1...18 | 1,0,1...18 | 1,1,1...18 | 2,0,1...18 | ... ). 🙂

    Damit kann man auch große Kernel nachladen:

    org 0x7C00  ; set up start address of bootloader
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; setup a stack and segment regs ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
        xor ax, ax
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, ax
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;   read kernel from floppy disk                           ;
    ;   starting from C(ylinder) H(ead) S(ector): 0 0 2        ;
    ;   17 + 18 + 18 + 18 + 57 = 128 (hard segment limit)      ; 
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
        mov si,loadmsg     ; show loading message
        call print_string
        call Waitingloop
        mov [bootdrive], dl ; boot drive stored by BIOS in DL.
                            ; we save it to [bootdrive] to play for safety.
    load_kernel:
        xor ax, ax         ; mov ax, 0  => function "reset"
        int 0x13         
        jc load_kernel     ; trouble? try again
    
    	mov bx, 0x1000     
    	mov es, bx         ; target in memory: segment
        mov bx, 0x0000     ; target in memory: offset 
        mov dl,[bootdrive] ; select boot drive
        mov al, 17         ; read n sectors
        mov ch,  0         ; cylinder = 0
        mov cl,  2         ; sector   = 2
        mov dh,  0         ; head     = 0
        mov ah,  2         ; function "read" 
        int 0x13         
        jc load_kernel     ; trouble? try again
    
    load_kernel2:
        xor ax, ax         ; mov ax, 0  => function "reset"
        int 0x13         
        jc load_kernel2    ; trouble? try again
    
        mov bx, 0x2200     ; target in memory: offset 
        mov dl,[bootdrive] ; select boot drive
        mov al, 18         ; read n sectors
        mov ch,  0         ; cylinder 
        mov cl,  1         ; sector   
        mov dh,  1         ; head     
        mov ah,  2         ; function "read" 
        int 0x13         
        jc load_kernel2    ; trouble? try again
    
    load_kernel3:
        xor ax, ax         ; mov ax, 0  => function "reset"
        int 0x13         
        jc load_kernel3    ; trouble? try again
    
        mov bx, 0x4600     ; target in memory: offset 
        mov dl,[bootdrive] ; select boot drive
        mov al, 18         ; read n sectors
        mov ch,  1         ; cylinder 
        mov cl,  1         ; sector   
        mov dh,  0         ; head     
        mov ah,  2         ; function "read" 
        int 0x13         
        jc load_kernel3     ; trouble? try again	
    
    load_kernel4:
        xor ax, ax         ; mov ax, 0  => function "reset"
        int 0x13         
        jc load_kernel4    ; trouble? try again
    
        mov bx, 0x6A00     ; target in memory: offset 
        mov dl,[bootdrive] ; select boot drive
        mov al,  18        ; read n sectors
        mov ch,  1         ; cylinder 
        mov cl,  1         ; sector   
        mov dh,  1         ; head     
        mov ah,  2         ; function "read" 
        int 0x13         
        jc load_kernel4     ; trouble? try again	
    
    load_kernel5:
        xor ax, ax         ; mov ax, 0  => function "reset"
        int 0x13         
        jc load_kernel5    ; trouble? try again
    
        mov bx, 0x8E00     ; target in memory: offset 
        mov dl,[bootdrive] ; select boot drive
        mov al,  57        ; read n sectors (max. 57!!! due to max. 128)
        mov ch,  2         ; cylinder 
        mov cl,  1         ; sector   
        mov dh,  0         ; head     
        mov ah,  2         ; function "read" 
        int 0x13         
        jc load_kernel5     ; trouble? try again	
    
    ; new segment
    load_kernel10:
        xor ax, ax         ; mov ax, 0  => function "reset"
        int 0x13         
        jc load_kernel10     ; trouble? try again
    
    	mov bx, 0x2000     ; next segment
    	mov es, bx         ; target in memory: segment
        mov bx, 0x0000     ; target in memory: offset 
        mov dl,[bootdrive] ; select boot drive
        mov al, 63         ; read n sectors
        mov ch,  3         ; cylinder 
        mov cl,  4         ; sector   
        mov dh,  1         ; head     
        mov ah,  2         ; function "read" 
        int 0x13         
        jc load_kernel10     ; trouble? try again	
    
    	call Waitingloop
    
    ;;;;;;;;;;;;;
    ; A20-Gate  ;
    ;;;;;;;;;;;;;	
    
        in  al, 0x92      ; switch A20 gate via fast A20 port 92
        cmp al, 0xff      ; if it reads 0xFF, nothing's implemented on this port
        je .no_fast_A20
    
        or  al, 2         ; set A20_Gate_Bit (bit 1)
        and al, ~1        ; clear INIT_NOW bit (don't reset pc...)
        out 0x92, al
        jmp .A20_done
    
    .no_fast_A20:         ; no fast shortcut -> use the slow kbc...
        call empty_8042 
    
        mov al, 0xD1      ; kbc command: write to output port
        out 0x64, al
        call empty_8042
    
        mov al, 0xDF      ; writing this to kbc output port enables A20
        out 0x60, al
        call empty_8042
    
    .A20_done:
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Load GDT, switch to PM, and jump to kernel ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;		
    
        cli               ; clear interrupts
        lgdt [gdtr]       ; load GDT via GDTR (defined in file "gtd.inc")	
    
        mov eax, cr0      ; switch-over to Protected Mode
        or  eax, 1        ; set bit 0 of CR0 register
        mov cr0, eax      ;	
    
        jmp dword 08:0x10000    ; this is a 16-bit-FAR-Jmp, but CS is loaded with "index" 8 in GDT 
                          ; hence, the code will be interpreted as 32 bit from here on
    					  ; the address can be found in the linker script (phys)
    
    ;;;;;;;;;
    ; Calls ;
    ;;;;;;;;;
    
    print_string:
        mov ah, 0x0E      ; VGA BIOS fnct. 0x0E: teletype
    .loop:   
        lodsb             ; grab a byte from SI
        test al, al       ; NUL?
        jz .done          ; if the result is zero, get out
        int 0x10          ; otherwise, print out the character!
        jmp .loop
    .done:
        ret
    
    empty_8042:
        call Waitingloop
        in al, 0x64
        cmp al, 0xff      ; ... no real kbc at all?
        je .done
    
        test al, 1        ; something in input buffer?
        jz .no_output
        call Waitingloop
        in al, 0x60       ; yes: read buffer
        jmp empty_8042    ; and try again
    
    .no_output:
        test al, 2        ; command buffer empty?
        jnz empty_8042    ; no: we can't send anything new till it's empty
    .done:
    ret
    
    Waitingloop:                   
        mov cx,0xFFFF
    .loop_start:
        dec cx     
        jnz .loop_start
        ret       
    
    ;;;;;;;;;;;;
    ; Includes ;
    ;;;;;;;;;;;;
    
    %include "gdt.inc"
    
    ;;;;;;;;
    ; data ;
    ;;;;;;;;
    
        bootdrive db 0    
        loadmsg db "bootloader message: loading kernel ...",13,10,0
    
        times 510-($-$$) hlt
        db 0x55
        db 0xAA
    

    Nun fehlt nur noch ein netter Algorithmus, der ES:BX und CHS sektorenweise berechnet, dann könnte man dies in einer Schleife für n Sektoren erledigen.

    Den Teil "LBA2CHS" findet man hier:
    http://en.wikipedia.org/wiki/CHS_conversion#Assembler_code_example

    GetCHS proc
                mov         AX, LBA
                xor         DX, DX
                mov         BX, SPT
                div         BX
                inc         DX
                mov         SECT, DL
                xor         DX, DX
                mov         BX, HPC
                div         BX
                mov         CYL, AL
                mov         HEAD, DL
                ret	
    GetCHS endp
    

  • Mod

    Nun fehlt nur noch ein netter Algorithmus, der ES:BX und CHS sektorenweise berechnet, dann könnte man dies in einer Schleife für n Sektoren erledigen.

    Read-Algorithmus wurde von PorkChicken (LowLevel-Forum) vorgeschlagen.

    org 0x7C00  ; set up start address of bootloader
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; setup a stack and segment regs ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
        xor ax, ax
        mov ds, ax
        mov es, ax
        mov ax, 0x7000   ; stack address 
        mov ss, ax       
        xor sp, sp       ; set stackpointer to 0 
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;   read kernel from floppy disk                           ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    read_kernel: 
        mov si,loadmsg     
        call print_string
        mov [bootdrive], dl ; boot drive stored by BIOS in DL.
    
        ; di: number of sectors, bx: segment, ch, dh, cl: cylinder, head, sector
        mov bx, 500
    	mov di, bx 
    	mov bx, 0x0800 ; kernel start address (cf. linker script)
    	mov ch, 0
    	mov dh, 0
    	mov cl, 2
        call read_sectors
    
    ;;;;;;;;;;;;;
    ; A20-Gate  ;
    ;;;;;;;;;;;;;	
    
    switch_a20:
        in  al, 0x92      ; switch A20 gate via fast A20 port 92
        cmp al, 0xff      ; if it reads 0xFF, nothing's implemented on this port
        je .no_fast_A20
    
        or  al, 2         ; set A20_Gate_Bit (bit 1)
        and al, ~1        ; clear INIT_NOW bit (don't reset pc...)
        out 0x92, al
        jmp .A20_done
    
    .no_fast_A20:         ; no fast shortcut -> use the slow kbc...
        call empty_8042 
    
        mov al, 0xD1      ; kbc command: write to output port
        out 0x64, al
        call empty_8042
    
        mov al, 0xDF      ; writing this to kbc output port enables A20
        out 0x60, al
        call empty_8042
    
    .A20_done:
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Load GDT, switch to PM, and jump to kernel ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    load_gdt:	
        cli               ; clear interrupts
        lgdt [gdtr]       ; load GDT via GDTR (defined in file "gtd.inc")	
    
    switch_to_PM:	
        mov eax, cr0      ; switch-over to Protected Mode
        or  eax, 1        ; set bit 0 of CR0 register
        mov cr0, eax      ;	
    
    jmp_to_PM:
        jmp dword 08:0x00008000    ; this is a 16-bit-FAR-Jmp, but CS is loaded with "index" 8 in GDT 
                          ; hence, the code will be interpreted as 32 bit from here on
    					  ; the address can be found in the linker script 
    ;;;;;;;;;
    ; Calls ;
    ;;;;;;;;;
    
    print_string:
        mov ah, 0x0E      ; VGA BIOS fnct. 0x0E: teletype
    .loop:   
        lodsb             ; grab a byte from SI
        test al, al       ; NUL?
        jz .done          ; if the result is zero, get out
        int 0x10          ; otherwise, print out the character!
        jmp .loop
    .done:
        ret
    
    empty_8042:
        call Waitingloop
        in al, 0x64
        cmp al, 0xff      ; ... no real kbc at all?
        je .done
    
        test al, 1        ; something in input buffer?
        jz .no_output
        call Waitingloop
        in al, 0x60       ; yes: read buffer
        jmp empty_8042    ; and try again
    
    .no_output:
        test al, 2        ; command buffer empty?
        jnz empty_8042    ; no: we can't send anything new till it's empty
    .done:
        ret
    
    Waitingloop:                   
        mov cx,0xFFFF
    .loop_start:
        dec cx     
        jnz .loop_start
        ret       
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;    read sectors from floppy disk to buffer ES:BX in memory    ;
    ;                                                               ; 
    ;    input:                                                     ;
    ;    di         - number of sectors                             ;
    ;    bx         - segment                                       ;
    ;    ch, dh, cl - cylinder, head, sector                        ; 
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    read_sectors:
    .next:
        mov es, bx
        xor bx, bx
    
    .again:
        mov dl, [bootdrive]
        mov ax, 0x0201
        int 0x13
        jc .again
    
        inc cl
        cmp cl, 19
        jl .loop
        mov cl, 1
    
        inc dh
        cmp dh, 2
        jl .loop
        mov dh, 0
    
        inc ch
        cmp ch, 80
        jae .error
    
    .loop:
        mov bx, es
        add bx, 512/16
        sub di, 1
    	jnz .next
    
    .done:
        ret	
    
    .error
        mov si, errormsg   ; show error message
        call print_string
    .end
        jmp .end	
    
    ;;;;;;;;;;;;
    ; Includes ;
    ;;;;;;;;;;;;
    
    %include "gdt.inc"
    
    ;;;;;;;;
    ; data ;
    ;;;;;;;;
    
        bootdrive db 0    
        loadmsg db "bootloader message: loading kernel ...",13,10,0
        errormsg db "bootloader message: sector read error ...",13,10,0
    
        times 510-($-$$) hlt
        db 0x55
        db 0xAA
    

    Stack wurde von 0x10000 nach 0x7000 verlegt, weil er im Weg war.



  • cool 👍 Aber das klappt nur, wenn der Kernel maximal 128KB groß ist? Das mit dem ISA DMA hab ich noch nicht ganz verstanden, soweit ich das verstanden hatte, konnte man "nur" nicht über eine 64KB-Grenze im RAM lesen?


  • Mod

    Aber das klappt nur, wenn der Kernel maximal 128KB groß ist?

    Nein, mit diesem Code kann der Kernel größer sein. Hier werden mit einem "zum Testen künstlich aufgepumpten" Kernel z.B. 500 Sektoren gelesen: http://www.henkessoft.de/OS_Dev/Downloads/20090712_69b.zip
    http://www.henkessoft.de/OS_Dev/OS_Dev3.htm#mozTocId882541

    Das mit dem ISA DMA hab ich noch nicht ganz verstanden, soweit ich das verstanden hatte, konnte man "nur" nicht über eine 64KB-Grenze im RAM lesen?

    Ja, das stimmt, wird z.B. bei Linux 0.01 auch bereits erwähnt. Kernel wurde damals nach 0x10000 geladen.

    Linux 0.01 boot.s:
    http://www.linuxgrill.com/anonymous/kernel/Archive/historic/linux-0.01.tar.bz2

    | This routine loads the system at address 0x10000, making sure
    | no 64kB boundaries are crossed. We try to load it as fast as
    | possible, loading whole tracks whenever we can.
    |
    | in: es - starting address segment (normally 0x1000)

    Ich bin noch nicht völlig sicher, ob der Aufbau ausreichend robust ist, aber das Design mit dem Stack bei 0x7000, dem Bootloader bei 0x7C00 und dem Kernel ab 0x8000 gefällt mir nach wie vor recht gut.



  • Dieser Thread wurde von Moderator/in Nobuo T aus dem Forum Assembler in das Forum Projekt: OS-Development verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.


Anmelden zum Antworten