Nachladen des Kernels durch eigenen Bootloader
-
Bei deinem Sprung ("jmp 0x8:0x10000") gibt mein Assembler (NASM) sogar eine Warnung aus: warning: word data exceeds bounds
Er assembliert das also zu jmp word 0x8:0x0000. Was ja auch klar ist, wenn man im Real Mode ist, weil Segment und Offset halt 16 Bit sind.
Du solltest auf deine Ausgaben gucken, oder deinen Assembler updaten.
Aber was für ein Glück, dass man das Offset auf 32 Bit erweitern kann: jmp dword 0x8:0x10000
-
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.
-
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.
-
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).
-
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_exampleGetCHS 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
-
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?
-
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#mozTocId882541Das 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.