Zpět na seznam článků     Číst komentáře (30)     Verze pro tisk

Tvoříme OS [3.díl]

Autor: DjH   
19.8.2008

V tomto díle provedeme mnoho změn. Přidáme si třeba paměťový manažer, přidáme si funkci printf(), ale jako hlavní, vypíšeme si náš první obsah složky


Tvoříme OS

Díl 3

Jako hlavní cíl tohoto dílu si dáme vypsání obsahu složky root. Ale jako hlavní změnu celého OS si přidáme jednoduchý paměťový manažer a funkci printf(). Napsal jsem „přidáme“, to znamená, že si je nebudeme programovat, poněvadž jsme líní a neschopní a chceme si okamžitě vypsat slíbený obsah složky. Práci jsem udělal za vás, funkce malloc(), free() a printf() jsem našel na internetu. Omlouvám se těm, kterým jsem zkazil radost tím, že jsem je připravil o programování správce paměti...

ještě než začneme...
...tak vám tu vypíšu funkce, které si ještě přidáme (a kam)
  1.  
  2. memcpy(), memcmp(), memset()      > do souboru mem.h
  3. již zmíněný printf() a sprintf()  > do printf.h
  4. malloc(), realloc(), free()       > do malloc.h

vytvoříme si soubor _null.h, a tam si definujeme pouze NULL: #define NULL 0
vytvoříme si soubor types.h, a tam budeme definovat své datové typy:
typedef unsigned char byte;
typedef unsigned int word;
typedef unsigned long dword;
typedef unsigned size_t;

Hezky rychle jsme přešli práci s pamětí, někteří to možná budou mít za zlé, ale proč se namáhat s paměťovým manažerem? Vždyť to je nuda...

Hurá na disk!
Disk? To je aspoň zábava! Budeme číst z disku přes BIOSový interrupt int 13h, ale nejdříve si povíme něco o struktuře diskety. Disketa má hlavy, disketa má stopy, disketa má sektory. Klasická 3,5“ 1.44 MB disketa má 2 hlavy, každá hlava má 80 stop, každá stopa má 18 sektorů a každý sektor má 512 bajtů. Lze tak potom vypočítat velikost disku: 2*80*18*512 = 1'474'560 bajtů. Bootloader se nachází na prvních 512ti bajtech v disketě, tedy hlava 0, stopa 0, sektor 1. BIOSový int 13h však nerozumí tomu, že chceme přečíst data od 27‘136-tého bajtu po 31‘232-hý bajt. S funkcí AH 2 (read), interruptu 13h se zachází takto:
DL = číslo mechaniky
DH = číslo hlavy
CH = číslo stopy
CL = číslo sektoru
AL = kolik sektorů číst (do jedné stopy)
ES:BX = adresa bufferu


Před samotným čtením z disku je nejlepší mechaniku vyresetovat funkcí AH 0, intu 13h:
  1. <i>/*
  2.  *  resetdisk()
  3.  *  restartuje floppy A:
  4.  */</i>
  5. void resetdisk() {
  6. resetd:
  7.   <b>asm</b> mov ax, 0  ;
  8.   <b>asm</b> mov dl, 0  ;
  9.   <b>asm</b> int 13h    ;
  10.   <b>asm</b> jc resetd  ;
  11. }

Našimi aktuálními znalostmi snadno vytvoříme funkci bios_read_disk():
  1. <i>/*
  2.  *  bios_read_disk()
  3.  *  nahraje do pameti &lt;offset&gt; &lt;sectors&gt; sektoru, na pozici CHS
  4.  */</i>
  5. void bios_read_disk(byte cylinder, byte head, byte sector, byte sectors, void *offset) {
  6.   resetdisk();
  7.   <b>asm</b> cli;
  8.   _ES = _DS;            <i>/* BUFF                      */</i>
  9.   _BX = (<b>int</b>)offset;    <i>/* BUFF                       */</i>  
  10.  
  11.   _AH = 2;              <i>/* READ                      */</i>
  12.   _AL = sectors;        <i>/* pocet sektoru  */</i>
  13.  
  14.   _CH = cylinder;       <i>/* cislo stopy    */</i>
  15.   _DH = head;           <i>/* cislo hlavy    */</i>
  16.   _CL = sector;         <i>/* cislo sektoru  */</i>
  17.  
  18.   _DL = 0;              <i>/* cislo drive    */</i>
  19.   <b>asm</b> sti;
  20.   __int__(0x13);
  21.  
  22.   <b>return</b>;
  23. }
Nic zajímavého na kódu není, ale přece jenom vysvětlím řádky
  1.  
  2.   _ES = _DS;            <i>/* BUFF                      */</i>
  3.   _BX = (<b>int</b>)offset;    <i>/* BUFF                       */</i>  
Program v Turbo C ukládá poitery v ds:[offset]. Pokud tedy chceme načíst data tam, kam ukazuje ukazatel na offset, musíme ES (Extra Segment) nastavit na DS (Data Segment). BX už jen nastavíme na ukazatel „offset“, protože v podstatě „void *“, nebo „char *“ je „int“, tedy číslo - ukazatel, kde v paměti v daném segmentu začínají data.
My ale budeme chtít s diskem pracovat jako s „rovnou plochou“. Musíme si tedy udělat funkci pro převod z fyzické adresy na CHS (Cylindr (stopa), Hlava, Sektor):

  1. <i>/*
  2.  *  readdisk()
  3.  *  nacte do &lt;buffer&gt; cast disku od &lt;start&gt; s poctem &lt;sectors&gt; sektoru
  4.  *  - start je fyzicka adresa disku, prevadi se na CHS  
  5.  */</i>
  6. void readdisk(<b>int</b> start, <b>int</b> sectors, void *buffer) {
  7.   unsigned <b>int</b> c=0,
  8.                h=0,
  9.                s=start <i>/*((addr/512)+1)*/</i> ;
  10.  
  11.   if(s &gt; 36) {
  12.     c = (s/36);
  13.     s = (s%36);
  14.   }
  15.  
  16.   if(s &gt; 18) {
  17.     h = (s/18);
  18.     s = (s%18);
  19.   }
  20.      
  21.   bios_read_disk((byte)c,(byte)h,(byte)s, sectors, buffer);
  22.   <b>return</b>;
  23. }

Myslím, že není co dodat – zadáme počáteční sektor (což je (fyzicka_adresa/512)+1), buffer a počet sektorů k načtení, funkce zjistí CHS:
Pokud je sektor vyšší než 36 -> stopu (c) zjistí vydělením sektorů 36ti (18 sektorů * 2 hlavy), zbytek sektorů je ponecháno.
Pokud je (stále) sektorů více než 18, zjistíme hlavu vydělením 18ti (vyjde nám buď 1 nebo 0, protože sektor nemůže být vyšší jak 35) a zbytek sektorů se opět ponechává. Tyto hodnoty se proženou funkcí bios_read_disk(), která načte do bufferu data z požadované pozice.
Abychom mohli číst z disku s FAT, musíme si vytvořit pár struktur – třeba bootloaderu, protože tam jsou důležité ůdaje o FAT12 - a pár globálních proměnných – například kde na disketě začínají data -.

  1. <i>/* struktura MasterBootRecordu */</i>
  2. typedef struct {
  3.         word    jump;
  4.         byte    nop;
  5.         byte    oem[8];
  6.         word    sector_size;
  7.         byte    cluster_size;
  8.         word    sector_reserved;
  9.         byte    fat_count;
  10.         word    root_size;
  11.         word    total_sectors16;
  12.         byte    media;
  13.         word    fat_size16;
  14.         word    sectors_per_track;
  15.         word    head_count;
  16.         dword   sectors_hidden;
  17.         dword   total_sectors32;
  18.         byte    phys_drv;
  19.         byte    reserved;
  20.         byte    ext_sign;
  21.         dword   serial;
  22.         byte    label[11];
  23.         byte    fs_id[8];
  24.         byte    data[512-62-2];
  25.         word    boot_sign;
  26.         } BootRecord;
  27.  

My si vytvoříme pár globálních proměnných. Dále do init.h inkludujeme fat.h, kde bude výše uvedená struktura, inkludujeme malloc.h a global.h, a dodáme tuto funkci:
  1. <b>int</b> iBootLoader(void) {
  2.   mbr = malloc(sizeof(BootRecord));
  3.   readdisk(1, 1, mbr);
  4.  
  5.   RootDirSect = (((mbr-&gt;root_size*32)+(mbr-&gt;sector_size-1))/mbr-&gt;sector_size); <i>/* vypocitej kolik sektoru ma root*/</i>
  6.   FATSectors     = mbr-&gt;fat_count*mbr-&gt;fat_size16;                                       <i>/* kolik ma fat sektoru   */</i>
  7.   DataStart      = mbr-&gt;sector_reserved+FATSectors+RootDirSect;          <i>/* zacatek dat    */</i>
  8.   StartSector    = (DataStart+1);
  9.   <b>return</b> 0;
  10. }

Tím si zinicalizujeme celý bootsektor do ukazatele na pointer proměnné mbr. Nyní si vytvoříme strukturu takového souboru ve FAT:

  1. <i>/* struktura "casu" u souboru */</i>
  2. typedef struct {
  3.         word
  4.                 sec          :5,
  5.                 min          :6,
  6.                 hour         :5;
  7.         } FileTime;
  8.  
  9. <i>/* struktura "datumu" u souboru */</i>
  10. typedef struct {
  11.   word
  12.         day          :5,    <i>/* 5 je pocet bitu kolikami se vyjadruje pocet dni v mesici 2^5 = 32-1 = 31dni */</i>
  13.         month        :4,    <i>/* 2^4-1=15 max 15 mesicu, nam staci jen 12 :) */</i>
  14.         year         :7;
  15.   } FileDate;
  16.  
  17. <i>/* struktura celeho souboru ve FAT */</i>
  18. typedef struct {
  19.   byte name[8];
  20.   byte ext[3];
  21.   byte attr;            <i>/* atribut */</i>
  22.   byte reserved[0x0A];
  23.   FileTime time;
  24.   FileDate date;
  25.   word ClstrNo;
  26.   dword filesize;
  27.   byte realname[12];   <i>/* skutecne jmeno + pripona */</i>
  28.   } DirectoryEntry;
  29.  

Nyní se můžeme pustit do funkcí pro čtení z disku na úrovni souborů. Můžeme si tedy vytvořit funkci na vypsání složky ROOT. Vytvoříme si funkci, která nám převede 8.3 název na klasický název souboru:
  1. <i>/*
  2.  *  getRealFileName()
  3.  *  vytvori nazev souboru s teckou a bez mezer z &lt;name&gt; a &lt;ext&gt;
  4.  *  a vrati ho v &lt;realname&gt;
  5.  */</i>  
  6. <b>char</b> *getRealFileName(<b>char</b> *name, <b>char</b> *ext, <b>char</b> *realname) {
  7. <b>int</b> cykl = 0;
  8.    while((*name != ' ') && (*name != '\0') && (cykl != 8)) {        
  9.     *realname = *name;
  10.     realname++;
  11.     name++;
  12.     cykl++;
  13.   }
  14.   if(!(*(ext)==' ' && *(ext+1)==' ' && *(ext+2)==' ')) {
  15.     *realname++ = '.';
  16.  
  17.     cykl = 0;  
  18.     while((*ext != ' ') && (*ext != '\0') && (cykl != 3)) {    
  19.       *realname = *ext;
  20.       realname++;
  21.       ext++;
  22.       cykl++;
  23.     }
  24.   }
  25.   *realname = '\0';
  26.   <b>return</b> realname;  
  27. }

Vytvoříme si funkci, která nám asociuje blok paměti se souborem:
  1. <i>/*
  2.  *  assocfile()
  3.  *  asociuje hodnoty pro &lt;file&gt;
  4.  */</i>  
  5. void assocfile(<b>char</b> *mem, DirectoryEntry *file) {
  6.   memcpy(file-&gt;name,         (mem), 8);
  7.   memcpy(file-&gt;ext,          (mem+8), 3);
  8.   file-&gt;attr     = *(byte*)  (mem+11);
  9.   file-&gt;ClstrNo  = *(word*)  (mem+0x1A);
  10.   file-&gt;filesize = *(dword*) (mem+0x1C);
  11.   getRealFileName(file-&gt;name, file-&gt;ext, file-&gt;realname);
  12.   <b>return</b>;
  13. }

a naší nejhlavnější funkci pro hledání souborů:
  1. <i>/*
  2.  *  findfile()
  3.  *  hleda soubor &lt;filename&gt;, vraci 0 pri nenalezenem,
  4.  *  1 pri nalezenem souboru , a nastavi do &lt;file&gt; dane atributy
  5.  */</i>
  6. <b>int</b> findfile(DirectoryEntry *file, <b>int</b> pos) {
  7. <b>signed int</b> _ret   = pos;
  8. <b>int</b> _stranka      = 0;
  9. <b>int</b> _pos          = 0;
  10. <b>char</b> *bkp_sdBuff;                           <i>/* zaloha sdBuff (viz. nize) */</i>
  11. <b>char</b> *sdBuff      = (<b>char</b>*)malloc(0x200);   <i>/* alokujeme 512 bajtu pro sektor */</i>
  12.  
  13. bkp_sdBuff = sdBuff;
  14.  
  15. _stranka = (pos/16);          <i>/* ((pos*32)/512) zjistime na kolikate 512ti bajtove strance je pointer na dalsi fajl */</i>
  16. _pos     = (pos%16);          <i>/* (((pos*32)%512)*32) */</i>
  17.  
  18.   readdisk((dir_ptr+_stranka), 1, sdBuff);
  19.   sdBuff += (_pos*32);
  20.   while(1) {
  21.   if (*sdBuff == '\0') { _ret = -1; <b>break</b>; }
  22.   if ((*sdBuff == DELETED) || (*(sdBuff+0x0B) == 0x08)) { <i>/* vymazany soubor? Label? Dekuji, nechci */</i>
  23.      sdBuff += 32;
  24.      _ret++;
  25.      _pos++;
  26.      if(_pos &gt;= 16) {
  27.        _stranka = (_pos/16);
  28.        _pos     = (_pos%16);
  29.        readdisk((dir_ptr+_stranka), 1, sdBuff);
  30.        sdBuff += (_pos*32);
  31.      }
  32.     } <b>else</b> {
  33.       assocfile(sdBuff, file);
  34.       sdBuff  += 32;
  35.       _ret++;
  36.       _pos++;
  37.       <b>break</b>;
  38.     }
  39.   }
  40.   free(bkp_sdBuff);                         <i>/* zde dealokujeme puvodni sdBuff */</i>
  41.   <b>return</b> (_ret);
  42. }

Z kódu je myslím zkušenému vše jasné, jen podotknu, že proměnná dir_ptr je (int), a to číslo ukazuje na sektor, kde začíná aktuální složka. V 1,44MB disketě začíná root složka na 20 sektoru. Tato proměnná nám později pomůže s jednodušším zacházením s chdir() (tedy se změnou aktuální složky). Možnost vypsání složky zatím implementujeme pouze do shellu, tedy do kernel.c přidáme:
  1. <b>int</b> main(void) {
  2. <b>char</b>            cmd[80];
  3. <b>int</b>             i       = 0;
  4. DirectoryEntry *file    = malloc(sizeof(DirectoryEntry));
  5.  
  6.   init();
  7.   while(1) {
  8.     if(strlen(cmd)!=0) {
  9.       if(!strcmp(cmd,<u>"dir"</u>)) {
  10.         printf(<u>"Nazev souboru | Velikost\r\n"</u>);
  11.         i = 0;
  12.         while((i = findfile(file, i)) != -1)       <i>/* najdeme soubor */</i>
  13.           printf(<u>" %12s &gt; %u bajtu\r\n"</u>, file-&gt;realname, file-&gt;filesize);
  14.       } <b>else</b> {
  15.         printf(<u>"Zadali jste retezec '"</u>);
  16.           textcolor(YELLOW);
  17.         printf(<u>"%s"</u>, cmd);
  18.           textcolor(LIGHTGRAY);
  19.         printf(<u>"'\r\n"</u>);
  20.       }
  21.     }
  22.     printf(<u>"[%s@%s]%s$ "</u>, user_name, pc_name, path);
  23.     gets(cmd, 79);
  24.   }
  25.   <b>return</b> 0;
  26. }

Po startu se provede standardní inicializace a nastartuje se shell. Pokud zadáte „dir“, vypíše se vám obsah složky root, pokud zadáte něco jiného, žlutě vám vypíše daný řetězec. Vím, není to nic extra, ale jako dnešní ukázka to stačí. Ještě vám přiložím seznam vylepšení, které jsem provedl a nenapsal tady v postupu: přidal jsem pár funkcí do string.h, tento soubor tedy obsahuje funkce:
  1. <i>/*
  2.  *  string.h
  3.  *  ruzne prace pro praci se stringy
  4.  *  kopirovani, porovnavani, presouvani...  
  5.  *  funkce:
  6.  *    putchar()     - vytiskne znak
  7.  *    strcat()      - spoji dva stringy
  8.  *    strcmp()      - porovna dva stringy
  9.  *    strcpy()      - natavi string
  10.  *    strlen()      - vrati delku stringu
  11.  *    toupper()     - zvetsi pismeno (a-z) z maleho na velke
  12.  *    tolower()     - zmensi pismeno (A-Z) z velkeho na male  
  13.  *    upStr()       - zvetsi pismena ve stringu z malych na velke
  14.  *    downStr()     - zmensi pismena ve stringu z velkych na male
  15.  *    puts()        - vytiskne string (neformatovane)
  16.  *    pos()         - urci pozici prvniho znaku ve string
  17.  *    getchar()     - sejme znak z klavesnice, vrati jako [int]
  18.  *    gets()        - sejme string z klavesnice
  19.  *    itoa()        - prevede integer na string
  20.  */</i>

...
Přidal jsem pár funkcí do video.h:
  1. <i>/*
  2.  *  video.h
  3.  *  ruzne fce pro praci s monitorem a textovym kurzorem
  4.  *    
  5.  *  funkce:
  6.  *    gotoxy()      - posune textovy kurzor na radek x, sloupec y
  7.  *    getxy()       - zjisti v jakem radku a sloupci se nachazi textovy kurzor
  8.  *    vidmode()     - nastavi danne video mode
  9.  *    textcolor()   - "alokuje" znaky v konzoli barvou
  10.  *    prowdown()    - prida x radku ze spoda, se zadanou barvou
  11.  *    prowup()      - prida x radku ze zhora, se zadanou barvou
  12.  *    rowdown()     - prida x radku ze spoda, s implictni barvou
  13.  *    rowup()       - prida x radku ze zhora, s implictni barvou
  14.  *    clrscr()      - vymaze obrazovku, presune kurzor na 0, 0
  15.  *    scrchr()      - vrati znak, ktery je na aktualni pozici kurzoru
  16.  *    scrchrxy()    - vrati znak, ktery je na pozici x,y
  17.  *    charto()      - napise znak &lt;ch&gt; na pozici &lt;x, y&gt;
  18.  */</i>

...a dalších pár funkcí pro vaši zábavu a pohodlnost, třeba v init.h je funkce iRslt(int), která podle vstupního čísla vypíše stav akce (
0 – [  OK  ]
1 – [ERROR!]
2 – [ WARN ] atp)
Dále po startu vykreslí krásným ASCII Artem na vrch obrazovky AltairOS (viz screenshot níže). Více se taky dozvíte po shlédnutí dosud kompletního kódu. Ještě vám dám inspiraci, co se týče výpisu složky > do image našeho floppy dejte (v UltraISO) obsah (ne složku, jen obsah) složky include, aby nám „dir“ měl co vypsat. Ještě dodám, že příště bych se chtěl podívat na výpis souboru (pod linuxem známo jako „cat“, na Windows jako „type“). No a na úplný závěr přikládám 2 screenshoty:



archív zde

Všechny díly seriálu

Tvoříme OS [1.díl]
Tvoříme OS [2.díl]
Tvoříme OS [3.díl]
Tvoříme OS [4.díl]
Líbil se Vám článek?
Budeme potěšeni, pokud vás zaujme také reklamní nabídka

Social Bookmarking

     





Hodnocení/Hlasovalo: 2.34/44

1  2  3  4  5    
(známkování jako ve škole)