   #Start Contents

                      Pisanie boot-sektorow pod Linuksem

   Gdy juz choc srednio znacie asemblera, to po pewnym czasie pojawiaja
   sie pytania (moga one byc spowodowane tym, co uslyszeliscie lub Wasza
   wlasna ciekawoscia):
    1. Co sie dzieje, gdy ma zostac uruchomiony jest system operacyjny?
    2. Skad BIOS ma wiedziec, ktora czesc systemu uruchomic?
    3. Jak BIOS odroznia systemy operacyjne, aby moc je uruchomic?

   Odpowiedz na pytanie 2 brzmi: nie wie. Odpowiedz na pytanie 3 brzmi:
   wcale. Wszystkie Wasze watpliwosci rozwieje odpowiedz na pytanie 1.

   Gdy zakonczyl sie POST (Power-On Self Test), wykrywanie dyskow i
   innych urzadzen, BIOS przystepuje do czytania pierwszych sektorow tych
   urzadzen, na ktorych ma byc szukany system operacyjny (u mnie jest
   ustawiona kolejnosc: CD-ROM, stacja dyskietek, dysk twardy).
   Gdy znajdzie sektor odpowiednio zaznaczony: bajt nr 510 = 55h i bajt
   511 = AAh (pamietajmy, ze 1 sektor ma 512 bajtow, a liczymy od zera),
   to wczytuje go pod adres bezwzgledny 07C00h i uruchamia kod w nim
   zawarty (po prostu wykonuje skok pod ten adres). Nie nalezy jednak
   polegac na tym, ze segment kodu CS = 0, a adres instrukcji IP=7C00h
   (choc najczesciej tak jest).

   To wlasnie boot-sektor jest odpowiedzialny za ladowanie odpowiednich
   czesci wlasciwego systemu operacyjnego. Na komputerach z wieloma
   systemami operacyjnymi sprawa tez nie jest tak bardzo skomplikowana.
   Pierwszy sektor dysku twardego, zwany Master Boot Record (MBR),
   zawiera program ladujacy (Boot Manager, jak LILO czy GRUB), ktory z
   kolei uruchamia boot-sektor wybranego systemu operacyjnego.

   My oczywiscie nie bedziemy operowac na dyskach twardych, gdyz byloby
   to niebezpieczne. Z dyskietkami zas mozna eksperymentowac do woli...
   A instrukcja jest prosta: umieszczamy nasz programik w pierwszym
   sektorze dyskietki, zaznaczamy go odpowiednimi ostatnimi bajtami i
   tyle. No wlasnie... niby proste, ale jak o tym pomyslec to ani to
   pierwsze, ani to drugie nie jest sprawa banalna.

   Do zapisania naszego bootsektorka na dyskietke mozemy oczywiscie uzyc
   gotowcow - programow typu dd itp. Ma to pewne zalety - program byl juz
   uzywany przez duza liczbe osob, jest sprawdzony i dziala.
   Przykladowy sposob uzycia (po skompilowaniu bootsektora):
                dd count=1 if=boot.bin of=/dev/fd0

   Ale cos by bylo nie tak, gdybym w kursie programowania w asemblerze
   kazal Wam uzywac cudzych programow. Do napisania swojego wlasnego
   programu zapisujacego dany plik w pierwszym sektorze dyskietki w
   zupelnosci wystarczy Wam wiedza uzyskana po przeczytaniu czesci mojego
   kursu poswieconej operacjom na plikach.

   Schemat dzialania jest taki:
     * Otworz plik zawierajacy skompilowany bootsektor
     * Przeczytaj z niego 512 bajtow (do zadeklarowanej tablicy w
       pamieci)
     * Zamknij ten plik
     * Otworz plik /dev/fd0 do zapisu
     * Zapisz do niego wczesniej odczytane dane
     * Zamknij ten plik

   Sprawa jest tak prosta, ze tym razem nie podam gotowca.

   Gdy juz mamy program zapisujacy bootsektor na dyskietke, trzeba sie
   postarac o to, aby nasz programik (ktory ma stac sie tym bootsektorem)
   mial dokladnie 512 bajtow i aby 2 ostatnie jego bajty to 55h, AAh.
   Oczywiscie, nie bedziemy recznie dokladac tylu bajtow, ile trzeba, aby
   dopelnic nasz program do tych 512. Zrobi to za nas kompilator.
   Wystarczy po calym kodzie i wszystkich danych, na szarym koncu,
   umiescic takie cos:
        times 510 - ($ - start) db 0
        dw 0aa55h

   To wyrazenie mowi tyle: od biezacej pozycji w kodzie odejmij pozycje
   poczatku kodu (tym samym obliczajac dlugosc calego kodu), otrzymana
   liczbe odejmij od 510 - i doloz tyle wlasnie bajtow zerowych. Gdy juz
   mamy program dlugosci 510 bajtow, to dokladamy jeszcze znacznik i
   wszystko jest dobrze.

   Jest jednak jeszcze jedna sprawa, o ktorej nie wspomnialem -
   ustawienie DS i wartosci ORG dla naszego kodu. Otoz, jesli
   stwierdzimy, ze nasz kod powinien zaczynac sie od offsetu 0 w naszym
   segmencie, to ustawmy sobie "org 0" i DS=07C0h (tak, liczba zer sie
   zgadza), ale mozemy tez miec "org 7C00h" i DS=0. Zadne z tych nie
   wplywa w zaden sposob na dlugosc otrzymanego programu, a nalezy o to
   zadbac, gdyz nie mamy gwarancji, ze DS bedzie pokazywal na nasze dane
   po uruchomieniu bootsektora.

   Teraz, uzbrojeni w niezbedna wiedze, zasiadamy do pisania kodu naszego
   bootsektora. Nie musi to byc cos wielkiego - tutaj pokaze cos, co w
   lewym gornym rogu ekranu pokaze cyfre jeden (o bezposredniej
   manipulacji ekranem mozecie przeczytac w moim innym artykule) i po
   nacisnieciu dowolnego klawisza zresetuje komputer (na jeden ze
   sposobow podanych w jeszcze innym artykule...).

   Oto nasz kod (NASM):
   (przeskocz przykladowy bootsektor)
        ; nasm -o boot.bin -f bin boot.asm

        org 7c00h                       ; lub     "org 0"

        start:
                mov     ax, 0b800h
                mov     es, ax          ; ES = segment pamieci ekranu

                mov     byte [es:0], "1" ; piszemy "1"

                xor     ah, ah
                int     16h             ; czekamy na klawisz

                mov     bx, 40h
                mov     ds, bx
                mov     word [ds:72h], 1234h    ; 40h:72h = 1234h -
                                                ; wybieramy goracy reset

                jmp     0ffffh:0000h            ; reset

        times 510 - ($ - start) db 0            ; dopelnienie do 510 bajtow
        dw 0aa55h                               ; znacznik

   Nie bylo to dlugie ani trudne, prawda? Rzecz jasna, nie mozna w
   bootsektorach uzywac zadnych funkcji systemowych, na przyklad
   linuksowego int 80h, bo zaden system po prostu nie jest uruchomiony i
   zaladowany. Tak napisany programik kompilujemy do formatu binarnego.
   Po kompilacji umieszczamy go na dyskietce przy uzyciu programu
   napisanego juz przez nas wczesniej. Resetujemy komputer (i upewniamy
   sie, ze BIOS sprobuje uruchomic system z dyskietki), wkladamy
   dyskietke i.... cieszymy sie swoim dzielem (co prawda ta jedynka moze
   byc malo widoczna, ale rzeczywiscie znajduje sie na ekranie).

   Zauwazcie tez, ze system nie rozpoznaje juz naszej dyskietki, mimo iz
   przedtem byla sformatowana. Dzieje sie tak dlatego, ze w bootsektorze
   umieszczane sa informacje o dysku.
   Bootsektor typu FAT12 (vfat) powinien sie zaczynac mniej-wiecej tak:
   (przeskocz systemowy obszar bootsektora)
        org 7c00h                       ; lub org 0, oczywiscie

        start:
                jmp short kod
                nop

                db "        "   ; nazwa OS i wersja OEM (8B)
                dw 512          ; bajtow/sektor (2B)
                db 1            ; sektory/jednostke alokacji (1B)
                dw 1            ; zarezerwowane sektory (2B)
                db 2            ; liczba tablic alokacji (1B)
                dw 224          ; liczba pozycji w katalogu glownym (2B)
                                ; 224 to typowa wartosc
                dw 2880         ; liczba sektorow (2B)
                db 0f0h         ; Media Descriptor Byte (1B)
                dw 9            ; sektory/FAT (2B)
                dw 18           ; sektory/sciezke (2B)
                dw 2            ; liczba glowic (2B)
                dd 0            ; liczba ukrytych sektorow (4B)
                dd 0            ; liczba sektorow (czesc 2),
                                ; jesli wczesniej bylo 0 (4B)
                db 0            ; numer dysku (1B)
                db 0            ; zarezerwowane (1B)
                db 0            ; rozszerzona sygnatura bloku ladujacego
                dd 0bbbbddddh   ; numer seryjny dysku (4B)
                db "           "; etykieta (11B)
                db "FAT 12  "   ; typ FAT (8B), zwykle  "FAT 12  "

        kod:
                ; tutaj dopiero kod bootsektora

   Ta porcja danych oczywiscie uszczupla ilosc kodu, ktora mozna umiescic
   w bootsektorze. Nie jest to jednak duzy problem, gdyz i tak jedyna
   rola wiekszosci bootsektorow jest uruchomienie innych programow
   (second stage bootloaders), ktore dopiero zajmuja sie ladowaniem
   wlasciwego systemu.

   Jeszcze ciekawostka: co wypisuje BIOS, gdy dysk jest niewlasciwy
   (niesystemowy)?
   Otoz - nic! BIOS bardzo chetnie przeszedlby do kolejnego urzadzenia.
   Dlaczego wiec tego nie robi i skad ten napis o niewlasciwym dysku
   systemowym??
   Odpowiedz jest prosta - sformatowana dyskietka posiada bootsektor!
   Dla BIOSu jest wszystko OK, uruchamia wiec ten bootsektor. Dopiero ten
   wypisuje informacje o niewlasciwym dysku, czeka na nacisniecie
   klawisza, po czym uruchamia int 19h. O tym, co robi przerwanie 19h
   mozecie przeczytac w artykule o resetowaniu.

   Milego bootowania systemu!

   P.S. Jesli nie chcecie przy najdrobniejszej zmianie kodu resetowac
   komputera, mozecie poszukac w Internecie programow, ktore symuluja
   procesor (w tym faze ladowania systemu). Jednym z takich programow
   jest Bochs.
     _________________________________________________________________

Co dalej?

   Mimo iz bootsektor jest ograniczony do 512 bajtow, to moze w dosc
   latwy sposob posluzyc do wczytania do pamieci o wiele wiekszych
   programow. Wystarczy uzyc funkcji czytania sektorow. W spisie Przerwan
   Ralfa Brown'a czytamy:
   (przeskocz opis int 13h, ah=2)
        INT 13 - DISK - READ SECTOR(S) INTO MEMORY
                AH = 02h
                AL = number of sectors to read (must be nonzero)
                CH = low eight bits of cylinder number
                CL = sector number 1-63 (bits 0-5)
                     high two bits of cylinder (bits 6-7, hard disk only)
                DH = head number
                DL = drive number (bit 7 set for hard disk)
                ES:BX -> data buffer
        Return: CF set on error
                CF clear if successful

   Wystarczy wiec wybrac nieuzywany segment pamieci, na przyklad ES=8000h
   i poczawszy od offsetu BX=0, czytac sektory zawierajace nasz kod,
   zwiekszajac BX o 512 za kazdym razem. Kod do zaladowania nie musi byc
   oczywiscie w postaci pliku na dyskietce, to by tylko utrudnilo prace
   (gdyz trzeba wtedy czytac tablice plikow FAT). Najlatwiej zaladowac
   kod tym samym sposobem, co bootsektor, ale oczywiscie do innych
   sektorow. Najlatwiej to zrobic, sklejajac skompilowany plik
   bootsektora ze skompilowanym kodem i potem nagrac na dyskietke:
                cat boot.bin system.bin > wszystko.bin
                dd if=wszystko.bin of=/dev/fd0

   Po zaladowaniu calego potrzebnego kodu do pamieci przez bootsektor,
   wystarczy wykonac skok:
        jmp     8000h:0000h

   Wtedy kontrole przejmuje kod wczytany z dyskietki.

   Ale jest jeden kruczek - trzeba wiedziec, jakie numery cylindra,
   glowicy i sektora podac do funkcji czytajace sektory, zeby
   rzeczywiscie odczytala te wlasciwe.
   Struktura standardowej dyskietki jest nastepujaca: 512 bajtow na
   sektor, 18 sektorow na sciezke, 2 sciezki na cylinder (bo sa dwie
   strony dyskietki, co daje 36 sektorow na cylinder), 80 cylindrow na
   glowice. Razem 2880 sektorow po 512 bajtow, czyli 1.474.560 bajtow.

   Majac numer sektora (bo wiemy, pod jakimi sektorami zapisalismy swoj
   kod na dyskietce), odejmujemy od niego 1 (tak by zawsze wszystkie
   numery sektorow zaczynaly sie od zera), po czym dzielimy go przez 36.
   Uzyskany iloraz to numer cylindra (rejestr CH), reszta zas oznacza
   numer sektora w tymze cylindrze (rejestr CL). Jesli ta reszta jest
   wieksza badz rowna 18, nalezy wybrac glowice numer 1 (rejestr DH), zas
   od numeru sektora (rejestr CL) odjac 18. W przeciwnym przypadku nalezy
   wybrac glowice numer 0 i nie robic nic z numerem sektora.
   W ten sposob otrzymujemy wszystkie niezbedne dane i mozemy bez
   przeszkod w petli czytac kolejne sektory zawierajace nasz kod.

   Cala te procedure ilustruje ten przykladowy kod:
   (przeskocz procedure czytania sektorow)
secrd:
;wejscie: ax=sektor, es:bx wskazuje na dane

        dec ax          ; z numerow 1-36 na 0-35
        mov cl,36       ; liczba sektorow na cylinder = 36
        xor dx,dx       ; zakladamy na poczatek: glowica 0, dysk 0 (a:)
        div cl          ; AX (numer sektora) dzielimy przez 36
        mov ch,al       ; AL=cylinder, AH=przesuniecie wzgledem
                        ;       poczatku cylindra, czyli sektor
        cmp ah,18       ; czy numer sektora mniejszy od 18?
        jb .sec_ok      ; jesli tak, to nie robimy nic
        sub ah,18       ; jesli nie, to odejmujemy 18
        inc dh          ; i zmieniamy glowice
.sec_ok:
        mov cl, ah      ; CL = numer sektora
        mov ax,0201h    ; odczytaj 1 sektor
        inc cl          ; zwieksz z powrotem z zakresu 0-17 do 1-18

        push dx         ; niektore biosy niszcza DX, nie ustawiaja
                        ;       flagi CF, lub zeruja flage IF
        stc
        int 13h         ; wykonaj czytanie
        sti
        pop dx

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
