Pierwsze kroki
Jak na razie dla wygody nasz system będziemy umieszczać na dyskietce. W końcu jak na razie pewnie nikt nie chce sobie zepsuć boot sektora, który jest na dysku twardym :P. A na początku póki nie opisze systemu plików (o tym będzie osobny art), zepsujemy cały system plików, który jest na dyskietce, albo inaczej „stworzymy własny system plików” – tak ładniej brzmi :]
Zanim zaczniemy pisać nasz pierwszy system operacyjny, musimy napisać boot loadera, który nam to jądro załaduje. No to zaczynamy:
Co to w ogóle jest boot loader?
Boot loader jest to mały program który ma za zadanie załadować i uruchomić system operacyjny.
Bios ładuje do ramu tak zwany boot sektor (inaczej MBR – Master Boot Record), czyli pierwszy sektor pod zerową głowicą i na zerowej ścieżce pierwszego ustawionego urządzenia bootującego, pod warunkiem, że znajduje się tam boot loader. Boot loader musi zajmować dokładnie 512 bajtów (czyli tyle ile jeden segment w trybie rzeczywistym) a dwa ostatnie bajty musza mieć wartość AA55h. Jeżeli nie ustawimy takiej ostatniej wartości to Bios uzna, że nie ma boot loadera. Jeżeli jednak wszystko pójdzie dobrze to w pamięci pod adresem 0000:7C00H będzie boot loader. Gdy bios oddaje kontrolę boot loaderowi wszystkie rejestry segmentowe są wyzerowane, a DL zawiera numer dysku, z którego pochodzi boot sektor (0 to dyskietka, 80h – pierwszy dysk twardy).
A oto przykład najprostszego boot loadera:
Boot.asm:
1 2 3 4 |
aa: ;wykonujemy nieskończoną pętle jmp aa ;aby nie wywołać błędu. times 510-($-$$) db 0 ;zapełniamy plik do końca, aby miał 510bajtow dw 0AA55h ;No i na końcu dodajemy wartość 0AA55h |
A teraz kompilujemy:
nasm boot.asm -o boot.bin
Teraz programem RawWrite kopiujemy nasz plik boot.bin na dyskietkę(traktujemy go jako obraz). No i uruchamiamy emulator i widzimy czarny ekran :] Czyli wszystko się udało, nasz pierwszy boot loader został załadowany bez żadnych błędów.
Wcześniej wspomniałem o „własnym formacie plików”, jest on bardzo prosty – w pierwszy sektorze będzie znajdował się boot loader a zaraz w drugim początek naszego kernela. Czyli teraz rozbudujemy boot loadera o to ze będzie wczytywał do pamięci drugi sektor i przekazywał mu kontrole. Do czytania użyjemy przerwania biosu 13h.
Kod na wczytanie drugiego sektora z dyskietki pod adres 0800h:
1 2 3 4 5 6 7 8 9 |
mov ah, 2 ;funkcja 2 przerwania 13h mov al, 10 ;ilość sektorów do przeczytania (10*512 = 5kb) mov ch, 0 ;cylinder mov cl, 2 ;sektor (w 1 jest bootsector) mov dh, 0 ;głowica mov bx, 0800h ;gdzie załadować kernel (es:bx) mov es, bx ;dane do ES możemy umieścić tylko przez inny rejestr xor bx, bx ;bx równy 0 int 13h ;wywołujemy przerwanie |
Aby teraz boot loader uruchomił to, co załadował do pamięci (czyli coś, co nazywamy kernelem) musimy wykonać skok do tego miejsca, czyli krótko i na temat:
1 |
jmp 0800h:0000h |
I to już cały boot loader. A oto cały kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
org 7C00h start: mov ah, 2 mov al, 10 mov ch, 0 mov cl, 2 mov dh, 0 mov bx, 0800h mov es, bx xor bx, bx int 13h jmp 0800h:0000h times 510 - ($ - start) db 0 dw 0AA55h |
Aby sprawdzić czy nasz boot loader działa musimy jeszcze napisać jakiegoś przykładowego kernela. A oto on:
Kernel.asm:
1 2 3 4 5 6 7 |
mov ah, 9 mov al, '=' mov bx, 7 mov cx, 10 int 10h hang: jmp hang |
Dobra, mamy boot loader i kernela, pozostaje tylko jedno pytanie jak skopiować na dyskietkę boot loadera do pierwszego sektora a kernela do drugiego?
Teraz napiszemy sobie w delphi prosty program, który połączy nam te dwa pliki w jeden (krótko mówiąc zrobimy sobie obraz dyskietki). Cały czas kompilujemy z linii poleceń, więc dla wygody napiszemy sobie tez taki programik, nie będziemy się tu bawić komponentami VCL, bo w tym przypadku to strata czasu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
program zlacz; {$APPTYPE CONSOLE} var final, f: file of byte; buf, q: byte; begin if (paramcount < 3) then writeln('uzycie: zlacz ') else begin assignfile(final, paramstr(paramcount)); rewrite(final); for q := 1 to paramcount -1 do begin writeln(paramstr(q)); assignfile(f, paramstr(q)); reset(f); while not eof(f) do begin blockread(f, buf, sizeof(buf)); blockwrite(final, buf, sizeof(buf)); end; closefile(f); end; closefile(final); writeln('Gotowe!!!'); end; end. |
A teraz kompilujemy całość:
nasm boot.asm -o boot.bin
nasm kernel.asm -o kernel.bin
zlacz boot.bin kernel.bin image.img
I teraz RawWritem nagrywamy plik image.img. Odpalamy emulator i widzimy naszego pierwszego kernela :]
Autor: Michał Ślaga (Blind)