.pl 60 .po 12 .mt 2 .mb 2 .pn 137 KAPITOLA 8: SYSTEMOVE SOUVISLOSTI S OPERACNIM SYSTEMEM UNIX ------------------------------------------------------------ Tato kapitola se zabyva souvislostmi jazyka C a operacniho systemu UNIX. Protoze mnoho uzivatelu jazyka C pracuje na sys- temech typu UNIX, bude tato kapitola uzitecna vetsine ctenaru. Dokonce i tem uzivatelum, kteri pracuji s jinymi systemy, bude uzitecne pro pochopeni programovani v jazyku C prostudovani prikladu uvedenych v teto kapitole. Kapitola je rozdelena do tri hlavnich casti: - vstup a vystup - system souboru - pridelovani pameti. Prve dve casti predpokladaji minimalne znalost vnejsich vlastnosti systemu UNIX. Kapitola 7 se zabyvala systemovym interfacem, ktery je spo- lecny pro mnoho operacnich systemu. Na jakemkoliv z techto systemu jsou funkce standardni knihovny napsany pomoci vstupne /vystupnich vlastnosti skutecneho systemu. V nasledujicich odstavcich vysvetlime zakladni systemove vstupni body pro vstupy a vystupy operacniho systemu UNIX a ukazeme si, jak s jejich pomoci mohou byt implementovany nektere casti standardni knihovny. 8.1. Deskriptory souboru ------------------------- V operecnim systemu UNIX jsou vsechny vstupy a vystupy usku- tecnovany pomoci cteni nebo zapisu do souboru, protoze vsechna periferni zarizeni, dokonce terminal operatora, jsou chapana jako soubory ze systemu souboru. To znamena, ze jediny, homo- genni interface ridi veskerou komunikaci mezi programem a perifernim zarizenim. Pred ctenim nebo zapisem do souboru je nejdrive nutne o teto skutecnosti informovat system procesem zvanym "otevreni soubo- ru". Jestlize chcete do souboru zapisovat je take nutne ho nej- prve vytvorit. System sam zkontroluje, je-li vse v poradku /Existuje soubor? Je do neho dovolen pristup?/ a jestlize ano, vrati vasemu programu nizke kladne cele cislo zvane "deskriptor souboru". Toto cislo je pak vzdy uzivano pri pristupu k souboru namisto jmena souboru. /Tato vlastnost je podobna zvyklostem ve FORTRANU napr: READ(5,...) nebo WRITE(6,...)./ Vsechny in- formace o otevrenem souboru jsou ovladany systemem, uzivateluv program pouziva pouze deskriptor souboru. Protoze vstup a vy- stup terminalu operatora je spolecny s ovladanim souboru, existuje moznost jak teto vyhody vyuzit. Kdykoliv interpretator prikazove radky operacniho systemu /tzv. "shell"/ spousti vas program otevre vzdy tri soubory, ktere maji deskriptory souboru 0, 1 a 2 a jsou nazvany nazvy: standardni vstup, standardni vystup a standardni vystup chybovych hlaseni. Vsechny tyto soubory jsou normalne prirazeny k terminalu operatora, takze program, ktery cte ze souboru urceneho deskriptorem 0 a ktery pise do souboru urcenych deskriptory: 1 a 2 muze toto uskutec- nit, aniz by bylo nutne tyto soubory otevrit. Uzivatel muze zmenit vstupne vystupni prirazeni do a ze Š.po 3 souboru pomoci znaku < a > : prog outfile Ve vyse uvedenem pripade zmeni shell puvodni prirazeni deskrip- toru souboru 0 a 1 z terminalu na jmenovane soubory. Deskriptor souboru 2 vetsinnou byva nezmeneny, a chybova hlaseni vystupuji na terminal operatora. Podobne konvence jsou platne v pripade, kdy je vstup a vystup uzivan pri zreteznem vykonavani programu /pipe/. Je nutne poznamenat, ze ve vsech vyse uvedenych pripa- dech byla zmena provedena shellem a nikoliv programem uzivate- le. Program uzivatele nevi, odkud prichazi vstup, ani kam smeruje vystup, dokud pouziva soubory 0 pro vstup a soubory 1 a 2 pro vystup. 8.2. Vstup a vystup nejnizsi urovne - READ a WRITE --------------------------------------------------- Nejnizsi uroven vstupu a vystupu v operacnim systemu UNIX nevykonava zadne bufferovani ani jine sluzby, je to ve skutec- nosti primy vstup do operecniho systemu. Vsechny vstupy a vy- stupy jsou uskutecneny pomoci dvou funkci: READ a WRITE. Pro obe dve je prvnim argumentem deskriptor souboru. Druhy argument je buffer ve vasem programu, do ktereho budou data zapisovana, ci budou z neho ctena. Treti argument udava pocet slabik, ktere jsou preneseny. Pouziti je nasledujici: n_read=read(fd,buf,n); n_writen=write(fd,buf,n); Kazde vyvolani vyse uvedenych funkci vrati pocet slabik, ktere byly skutecne systemem preneseny. Pri cteni muze byt toto cislo nizsi, nez-li je zadana hodnota. Pokud je vracena hodnota 0, je to znameni konce souboru a hodnota -1 znamena, ze doslo k chybe pri vykonani funkce. Pri zapisu je vracena hodnota rovna poctu skutecne zapsanych slabik, pokud je ruzna od hodnoty zadane, je to znameni chyby. Pocet slabik, ktere se mohou zapsat ci precist je libovolny. Dve nejcasteji pouzivane hodnoty jsou 1 slabika /tj. zapis po jednotlivych znacich/ a 512 slabik /tato hodnota odpovida fyzikalni delce bloku na mnoha perifernich zarizenich/. Druhy pripad je vice efektivni, ale ani prvni neni zcela nevhodny. Na zaklade vyse uvedenych faktu muzeme nyni napsat jednodu- chy program, ktery kopiruje data ze vstupu na vystup. Je ekvi- valentni programu, ktery je uveden v prvni kapitole. Pod operacnim systemem UNIX tento program kopiruje data odkudkoliv kamkoliv, protoze muze byt prerazen jeho vstup i vystup na li- bovolne zarizeni nebo do souboru. #DEFINE BUFSIZE 512 /*vhodna velikost pro system PDP-11 UNIX*/ main () /*kopiruje vstup na vystup*/ { char buf [BUFSIZE]; int n; Š.po 12 while ((n = read(0,buf,BUFSIZE)) > 0) write(1,buf,n,); } Jestlize delka souboru neni nasobkem konstanty BUFSIZE bude pri nekterem cteni vraceno cislo mensi nezli hodnota BUFSIZE, tako- vy pocet slabik bude zapsan funkci write. Pristi volani funkce vrati hodnotu 0. Je poucne prostudovat, jak pomoci fukci read a write napsat podprogram vyssi urovne, jako treba jsou podprogramy getchar, putchar atd. Napriklad uvedeme si jednoduchou verzi funkce get- char, ktera nepouziva vstup s vyuzitim buferu. #DEFINE CMASK 0377 /*maska, po jejimz pouziti maji znaky kladnou hodnotu*/ getchar() /*vstup jednoho znaku, bez pou- ziti buferu*/ { char c; return ((read(0,&c,1) > 0) ? c & CMASK : EOF); } Promenna c musi byt deklarovana jako promenna typu char, proto- ze funkce read vyzaduje argument typu ukazovatko na promennou typu char. Vraceny znak musi byt maskovan hodnotou 0377, aby bylo zaruceno, ze je pozitivni. Jinak by se mohlo stat, ze bude diky znamenkovemu rozsireni negativni. Konstanta 0377 je vhodna pro system PDP-11,ale pro jine systemy muze byt rozdilna. Dalsi verze funkce getchar uskutecnuje vstup vice znaku na- jednou, ale vystup je uskutecnovan po jednom znaku. ???? TADY CHYBI STR. 162 PUVODNIHO (ANGLICKEHO?) TEXTU. ???? Jako priklad si uvedeme jednoduchou verzi pomocneho programu systemu UNIX, ktery ma nazev cp. Jedna se o program, ktery ko- piruje jeden soubor do souboru jineho. Podstatne zjednoduseni je to, ze nas program kopiruje pouze jeden soubor a neni mozne, aby druhym argumentem byl adresar. #DEFINE NULL 0 #DEFINE BUFSIZE 512 #DEFINE PMODE 0644 /*R/W pro vlastnika, R pro ostatni*/ main (argc,argv) /*cp: kopiruj f1 do f2*/ int argc; char argv[]; { int f1,f2,n; char buf [BUFSIZE]; if(argc != 3) error("Usage: cp from to", NULL); if((f1 = open(argv[1],0)) == -1) error("cp: can't open %s", argv[1]); if((f2 = creat(argv[2],PMODE)) == -1) error ("cp:can't create %s", argv[2]); Š.po 3 while((n = read(f1,buf,BUFSIZE)) > 0) if(write(f2,buf,n,) != n) error("cp:write error", NULL); exit(0); } error(s1, s2) /*vytiskni chybovou zpravu a ukonci cinnost*/ char *s1,*s2; { printf(s1, s2); printf("\n"); exit(1); } Existuje maximalni pocet soucasne otevrenych souboru, typic- ky 15 az 25. Program, ktery pouziva mnoho souboru , musi byt schopen vicenasobneho pouziti deskriptoru souboru. Funkce close ukonci spojeni mezi deskriptorem souboru a otevrenym souborem. Tim se uvolni deskriptor souboru pro pouziti s dalsim souborem. Ukonceni programu funkci exit nebo navratem z hlavniho programu uzavre vsechny otevrene soubory. Funkce unlink filename zrusi soubor se jmenem filename v sy- stemu souboru. C v i c e n i 8-1. Napiste znovu program cat z kapito- ly 7 s pouzitim funkci read, write, open a close na misto je- jich ekvivalentu ze standardni knihovny. Porovnejte rychlosti obou verzi. 8.4 Nahodny pristup - SEEK a LSEEK ----------------------------------- Normalni pristup k souborum je sekvencni. Kazdy novy zapis nebo cteni ze souboru se uskutecni na nasledujici pozici v sou- boru. Kdyz je nezbytne, je mozne soubory cist nebo do nich za- pisovat na libovolne pozici. Systemova funkce lseek umoznuje pristup do libovolneho mista souboru, aniz by bylo nutne soubor skutecne cist, ci do neho zapisovat. lseek (fd,offset,origin); Funkce lseek nastavi pozici v souboru, ktery je urceny deskrip- torem souboru fd, na misto urcene posunutim offset, ktery je urcovan od mista v souboru, ktere je urcene argumentem origin. Nasledujici cteni nebo zapis do souboru se uskutecni na teto pozici. Arument offset je typu long, argumenty fd a origin jsou typu int. Argument origin muze nabyt hodnoty 0, 1 a 2, ktera urcuje, ze posunuti je mereno od pocatku, od prave aktualni pozice v souboru nebo od konce souboru. Napr. nastaveni na konec souboru /append/ se uskutecni nasledovne : lseek(fd,0L,2); Nastaveni na zacatek souboru /rewind/: lseek(fd,0L,0); Š.po 12 Je nutne poznamenat, ze argument 0L je mozne psat ve tvaru (long)0. S pomoci funkce lseek je mozne se soubory pracovat jako s rozlehlymi poli, na ukor pomalejsiho pristupu k jednotlivym polozkam. Napriklad nasledujici funkce cte libovolny pocet sla- bik z jakehokoliv mista v souboru: get(fd,pos,buf,n) /*cti n slabik od pozice pos*/ int fd, n; long pos; char *buf; { lseek(fd,pos,0); /*nastav ukazatel na pozici pos*/ return(read(fd,buf,n)); } V operacnim systemu UNIX V7 je hlavni systemovou funkci, ktera umoznuje pristup k souborum, funkce seek. Funkce seek je analogicka funkci lssek, vyjma te skutecnosti, ze argument je typu int. Protoze u systemu PDP-11 maji cisla typu int velikost 16 bitu, je mozna nejvetsi hodnota argumentu offset 32767. Proto existuji dalsi mozne hodnoty argumentu /3, 4, 5/, ktere zpusobuji vynasobeni hodnoty offset konstantou 512 /tj. poctem slabik v jednom fyzickem bloku dat/ a samotny argument origin je zpracovan, jako kdyby jeho hodnota byla 0, 1nebo 2. Je tedy nutne pri pristupu do rozlehlych souboru uskutecnit dve vyvola- ni funkce seek. Prve urci blok a druhe, jehoz argument origin ma hodnotu 1 urci pozadovanou slabiku uvnitr bloku. C v i c e n i 8-2. Funkce seek muze byt napsana pomoci funkce lseek a naopak. Provedte. 8.5 Priklad - Implementace funkci FOPEN a GETC ---------------------------------------------- Nyni si ukazeme, jak pomoci vyse uvedenych informaci imple- mentovat funkce fopen a getc ze standardni knihovny. Pripominame, ze soubory jsou ve standardni knihovne urceny ukazovatkem souboru a nikoliv deskriptorem souboru. Ukazovatko na soubor, je ukazovatkem na strukturu, ktera obsahuje nezbytne informace o souboru, ukazovatko na bufer, takze souboru muze byt cten po vetsich castech; citac poctu znaku , ktere v buferu zbyvaji; ukazovatko na znak v buferu, ktery ma byt zpracovan: nekolik klicu urcujicich mody cteni/zapis atd. a deskriptor souboru. Datova struktura popisujici soubor je definovana v souboru STDIO.H, ktery musi byt pripojen /pomoci #include/ ke vsem zdrojovym souborum, ktere pouzivaji podprogramy ze standardni knihovny. Je take zaclenen funkcemi teto knihovny. Nasledujici vytah ze souboru STDIO.H urcuje ty, ktere jsou uzivany pouze funkcemi standardni knihovny, znakem podtrhnuti /"_"/ tak, aby nevznikly kolize se jmeny v uzivatelove programu. #define _BUFSIZE 215 #define _NFILE 20 /*pocet soucasne otevrenych Š.po 3 souboru*/ typedef struct _iobuf { char *_ptr; /*pozice nasledujiciho znaku*/ int _cnt; /*pocet jiz zpracovanych znaku* char *_base; /*adresa I/0 buferu*/ int _flag; /*zpusob pristupu k souboru*/ int _fd; /*deskriptor souboru*/ } FILE; extern FILE _iob[_NFILE]; #define stdin (_iob[0]) #define stdout (_iob[1]) #define sterr (_iob[2]) #define _READ 01 /*soubor otevren pro cteni*/ #define _WRITE 02 /*soubor otevren pro zapis*/ #define _UNBUF 04 /*pristup bez buferu*/ #define _BIGBUF 010 /*pristup s buferem*/ #define _EOF 020 /*priznak konce souboru*/ #define _ERR 040 /*priznak chyby pri pristupu*/ #define NULL 0 #define EOF (-1) #define getc(P) (--(P) -> _cnt >= 0\ ? *(p) -> _ptr++ & 0377 : _fillbuf (p) #define getchar() getc(stdin) #define putc(x,p) (--(p) -> _cnt >= 0\ ?*(p) -> _ptr++ = (x) : _flushbuf ((x),p)) #define putchar(x) putc (x,stdout) Makro getc pouze dekrementuje citac, nastavi ukazovatko na novou pozici a vrati znak. (Dlouha definice #define je prodlouzena na dalsi radku pomoci znaku "\".) Jestlize se citac stane negativni, je vyvolana funkce _fillbuf, ktera naplni bufer, provede opetnou inicializaci obsahu struktury a vrati znak. Tyto funkce smeji obsahovat neprenositelny interface k operacnimu systemu, napr. funkce getc maskuje znak hodnotou 0377, aby predesla znamenkovemu rozsireni, ktere vzni- ka na systemu PDP-11, a tim zajisti, ze vsechny znaky budou mit kladene hodnoty. Prestoze se nebudeme zaobirat detaily, je zde take definice funkce putc analogicka funkci getc, vyvolavajici vsak funkci _flushbuf pri zaplnenem buferu. Nyni muzeme jiz napsat definici funkce fopen. Nejvetsi cast funkce fopen se zabyva vlastnim otevrenim souboru a nastavenim ukazovatka na spravne misto v souboru a nastaveni klicu na pozadovany stav. Funkce fopen neprideluje zadny prostor pro bufer, ten je pridelen funkci _fillbuf pri prvem cteni ze sou- boru. #include #define PMODE 0644 /*R/W pro vlastnika, R pro ostatni*/ FILE fopen (name,mode) /*otevri soubor a vrat ukazovatko na desk. soub*/ register char *name, *mode; { register int fd; register FILE *fp; if(*mode != 'r' && *mode != 'w' && *mode != 'a') Š.po 12 { fprintf(stderr,"illegal mode %s opening %s \n", mode,name; exit(1); } for(fp >= _iob;fp < _iob + _NFILE;fp++) if((fp -> _flag & (_READ | _WRITE)) == 0 break; /*najdi volnou polozku*/ if(fp) = _iob + _NFILE /*neni volna polozka*/ return (NULL); if(*mode == 'w') /*pristupuj do souboru*/ fd = creat(name, PMODE); else if(*mode == 'a') { if((fd = open(name,1)) == -1) fd = creat(name,PMODE); lseek(fd,0L,2); } else fd = open(name,0); if(fd == -1) /*nemohu otevrit soubor*/ return(NULL); fp -> _fd = fd; fp -> _cnt = 0; fp -> _base = NULL; fp ->_ flag &= ^(_READ | _WRITE); fp -> _flag |= (*mode == 'r') ? _READ : _WRITE; return(fp); } Funkce _fillbuf je ponekud komplikovanejsi. Hlavni problem spociva ve faktu, ze funkce _fillbuf se pokusi umoznit pristup k souboru dokonce i vpripade, kdy neni dostatek volne pameti pro vstup/vystup s pouzitim buferu. Jestlize je pamet pro novy buffer pridelena funkci calloc, je vse vporadku. Jestlize pamet pridelena neni, umozni funkce _fillbuf pristup k souboru bez buferu, tj. znak po znaku s vyuzitim sveho vnitrniho pole. #include _fillbuf (fp) /*pridel a napln vstupni bufer*/ register FILE fp; { static char smallbuf [_NFILE]; /*pro I/0 bez buferu*/ char *calloc(); if((fp -> _flag & _READ) == 0 || fp -> _flag & (_EOF | _ERR)) != 0) return(EOF); while (fp -> _base==NULL) /*najdi misto pro bufer*/ if(fp -> _flag & _UNBUF) /*I/0 bez buferu*/ fp -> _base = &smallbuf[fp -> _fd]; else if((fp -> _base = calloc(_BUFSIZE,1)) == NULL) fp -> _flag |= _UNBUF; /*nemohu najit misto pro bufer*/ else fp -> _flag |= _BIGBUF; /*misto pro bufer bylo nalezeno*/ Š.po 3 fp -> _ptr = fp -> _base; fp -> _cnt = read(fp -> _fa,fp -> _ptr, fp - >_flag & _UNBUF ? 1 : _BUFSIZE); if(--fp -> _cnt < 0) { if(fp -> _cnt == -1) fp -> _flag |= _EOF; else fp -> _flag |= _ERR; fp -> _cnt = 0; return(EOF); } return(*fp -> _ptr++ & 0377); /*vrat kladny znak*/ } Pri prvnim vyvolani funkce getc pro dany soubor bude citac _cnt roven nule, coz zpusobi vyvolani funkce _fillbuf. Jestlize funkce _fillbuf zjisti, ze soubor neni otevren pro cteni, vrati bezprostredne hodnotu EOF. Jestlize tomu tak neni, zkusi nej- prve pridelit souboru bufer. Pokud neni mozne souboru bufer pridelit, poznaci to v promenne _flag. Jakmile je bufer stanoven, je vyvolana funkce read, aby ho naplnila, je nastaven citac, ukazovatka a je vracen znak z pocatku buferu. Dalsi volani funkce _fillbuf jiz naleznou bufer souboru prideleny. Zbyva pouze jedina vec, kterou je nutne vysvetlit. Pole _iob musi byt definovano a inicializovano pro soubory stdin, stdout a stderr: FILE _iob[_NFILE]=( (NULL, O, NULL, _READ, 0) ,/*stdin*/ (NULL, 0, NULL, _WRITE,1) ,/*stdout*/ (NULL, 0, NULL, _WRITE _UNBUF, 2) /*stderr*/ ); Z inicializace promenne _flag je patrne, ze soubor stdin je urcen pouze pro cteni, souboru stdout pouze pro zapis a soubor stderr pouze pro zapis bez pouziti bufferu. C v i c e n i 8-3. Prepiste funkce fopen a _fillbuf a pouzijte pole bitu namisto explicitnich bitovych operaci. C v i c e n i 8-4. Navrhnete funkce _flushbuf a fclose. C v i c e n i 8-5. Soucasti standardni knihovny je funkce fseek fseek(fp,offset,origin) ktera je identicka s funkci lseek s tim rozdilem, ze fp je ukazovatko na soubor misto deskriptoru souboru. Napiste funkci fseek. Ujistete se o spravne funkci vasi funkce ve spo- lupraci s ostatnimi funkcemi ze standardni knihovny. .pa Š.po 12 8.6 Priklad - Vypis adresare ----------------------------- Rozdilnou ulohou pri praci se soubory je ziskavani informaci o souboru, nikoliv prace s jeho obsahem. Prikaz operacniho sys- temu UNIX ls /list directory = vypis adresar/ vytiskne jmena souboru v adresari a pokud je zadouci i dalsi informace, jako je velikost, zpusoby pristupu atd. Protoze v operacnim systemu UNIX je adresar chapan jako soubor, je prace s nim stejna jako s jinymi soubory, system nejprve soubor s adresarem precte a vyjme z neho vsechny nez- bytne informace o souborech, ktere v nem nalezne. Format infor- mace v adresari je urcen operacnim systemem, nikoliv uzivate- lovym programem, proto musi program ls znat, jaka je reprezen- tace informaci v adresari. Ukazeme si nektere vyse uvedene zasady na programu nazvanem fsize. Program fsize je specializovana forma programu ls, ktera vytiskne velikosti vsech souboru, ktere jsou uvedeny v seznamu argumentu prikazove radky. Jestlize jeden z techto argumentu je adresarem, uplatni se na nem rekurzivne opet program fsize. Jestlize nejsou zadne argumenty bude zpracovan prave pouzivany adresar. Nyni strucny prehled o systemu souboru. Adresar je soubor, ktery obsahuje seznam jmen souboru a informace o tom, kde je soubor ulozen. Informace o umisteni souboru je ve sku- tecnosti indexem v tabulce zvane "inod table". Polozka adresare se sklada pouze ze dvou casti. Ze jmena souboru a cisla urcuji- coho polozku v tabulce "inod table". Tato polozka obsahuje ves- kere informace o souboru a jeho umisteni. Presna specifikace je zaclenena v souboru sys/dir.h, ktery obsahuje: #define DIRSIZ 14 /*maximalni delka jmena souboru*/ struct direct /*struktura polozky adresare*/ { ino_t d_ino; /*hodnota inode*/ char d_name [DIRSIZ]; /*jmeno souboru*/ } ; Typ ino_t je typ definovany pomoci konstrukce typedef a popisu- je index do tabulky "inod table". Na systemu PDP-11 UNIX je ty- pu unsigned, ale na jinych systemech muze byt rozdilneho typu. Kompletni prehled vsech systemovych typu je obsazen v souboru sys/types.h. Funkce stat ma vstupnim argumentem jmeno souboru a vraci veskerou informaci, ktera je obsazena v tabulce "inod table", nebo hodnotu -1, pokud doslo k chybe. struct stat stbuf; char *name; stat(name,&stbuf); Funkce stat zaplni strukturu stbuf informaci obsazenou v tabul- ce "inod table" pro dany soubor. Struktura popisujici vracenou informaci je obsazena v souboru sys/stst.h a je nasledovana: .pa Š.po 3 struct stat /*struktura vracena funkce stat*/ { dev_t st_dev; /*typ zarizeni*/ ino_t st_ino; /*hodnota inode*/ short st_mode /*zpusob pristupu*/ short st_nlink; /*pocet smerniku souboru*/ short st_uid; /*vlastnikuv identifikator*/ short st_gid; /*skupinovy identifikator*/ dev_t st_rdev; /*priznak zvlastnich souboru*/ off_t st_size /*delka souboru ve znacich*/ time_t st_atime; /*datum posledniho pristupu*/ time_t st_mtime; /*datum posledni modifikace*/ time_t st_ctime; /*datum vytvoreni souboru*/ } ; Mnoho z vyse uvedene struktury je objasneno v komentarich obsa- zenych v definici. Polozka st_mode obsahuje soubor klicu, ktere popisuji soubor. Definice klicu je soucasti souboru sys/stat.h. #define S_IFMT 0160000 /*typ souboru*/ #define S_IFDIR 0040000 /*adresar*/ #define S_IFCHR 0020000 /*zvlastni znak*/ #define S_IFBLK 0060000 /*zvlastni blok*/ #define S_IFREG 0100000 /*regularni*/ #define S_ISUID 04000 /*nastav vlastnikuv identifi- kator*/ #define S_ISGID 02000 /*nastav skupinovy identifi- kator*/ #define S_ISVTX 01000 /*uschovej text po pouziti*/ #define S_IREAD 0400 /*cteni dovoleno*/ #define S_IWRITE 0200 /*zapis dovolen*/ #define S_IEXEC 0100 /*vykonani dovoleno*/ Teprve nyni muzeme zacit psat program fsize. Jestlize informace obdrzena funkci stat urcuje, ze soubor neni ardesarem, muze byt jeho velikost primo obdrzena a vytisknuta. Jestlize se jedna o adresar zpracujeme rekurzivne jeho polozky. Nejvyssi uroven programu se zabyva zpracovanim argumentu prikazove radky. Kazdy argument teto radky poskytne ke zpracovani funkci fsize. #include #include /*definice typu*/ #include /*struktura polozky adresare*/ #include /*struktura vracena funkci stat*/ #define BUFSIZE 256 main (argc,argv) /*fsize: tiskni velikost souboru*/ char *argv[]; { char buf[BUFSIZE]; if (argc == 1) /*default: aktualni adresar*/ { strcpy (buf,"."); fsize (buf); } else Š.po 12 while (--argc > 0) { strcpy(buf,*++argv); fsize(buf); } } Funkce fsize tiskne velikosti souboru. Jestlize se jed- na o adresar, vyvola funkce fsize funkci directory, aby mohla zpracovat soubory obsazene v adresari. Vsimnete si pouziti nazvu klicu S_IFMT a S_IDFIR ze souboru stat.h. fsize (name) /*tiskni velikost daneho souboru*/ char *name; { struct stat stbuf; if (stat (name,&stbuf) == -1) { fprintf (stderr,"fsize: can't find %s n", name); return; } if ((stbuf.st_mode & S_IFMT) == S_IFDIR) directory (name); printf ("%81d %s \n",stbuf.st_size,name); } Funkce directory je ponekud komplikovana. Jejim hlavnim uce- lem je vytvoreni uplneho jmena souboru, kterym se prave zabyva. directory (name) /*velikosti vsech souboru adresare*/ char *name; { struct direct dirbuf; char *nbp, *nep; int i, fd; nbp = name + strlen (name); *nbp++ = '/'; /*pridej znak "/" ke jmenu adresare*/ if (nbp + DIRSIZ + 2 >= name + BUFSIZE) /*jmeno je priliz dlouhe*/ return; if ((fd = open (name,0)) == -1) return; while (read (fd,(char*) & dirbuf,sizeof(dirbuf)) > 0) { if (dirbuf.d_ino == 0) /*polozka neni pouzita*/ continue; if (strcmp (dirbuf.d_name,".") == 0 ||strcmp (dirbuf.d_name,".." == 0) continue; /*preskoc sama sebe a sve rodice*/ for (i = 0, nep = nbp; i < DIRSIZ; i++) *nep++ = dirbuf.d_name[i]; *nep++ = '\0'; fsize (name); } close (fd); Š.po 3 *--nbp = '\0'; /*obnov jmeno*/ } Jestlize polozka adresare neni pouzita, protoze byl soubor zrusen, je index na tabulku "inod table" roven nule. Potom je tato polozka pri zpracovani preskocena. Kazdy adresar obsahuje take polozku pro sebe sama, ktera je oznacena "." a pro sve "otce", ktera je oznacena "..". Tyto polozky musi byt take preskoceny, protoze by doslo k zacykleni programu. Prestoze je program fsize ponekud specializovany, lze si na nem ukazat nekolik dulezitych idei. Za prve, mnoho progra- mu, prestoze nejsou programy systemovymi, pouziva informace obhospodarovane operacnim systemem. Za druhe, pro takove pro- gramy je rozhodujici, ze reprezentace takovych informaci je ulozena pouze ve standardnich souborech, jako jsou napr. stat.h a dir.h, a ze programy, ktere tyto soubory zaclenuji, nemusi skutecne deklarace v sobe obsahovat. 8.7 Priklad - Pridelovani pameti --------------------------------- V kapitole 5 byla uvedena jednoducha verze funkce alloc. Verze, ktera bude nyni uvedena ma neomezene moznosti. Volani funkci alloc a free mohou byti prostridana v libovolnem pora- di. Funkce alloc pouziva volani operacniho systemu pro pride- leni pameti. Tyto funkce take ilustruji moznost psat progra- my zavisle na technickem vybaveni systemu relativne nezavisle od tohoto vybaveni. Dale ukazuji uziti struktur, unionu a defi- nic typu typedef v praxi. Na misto pridelovani pameti z pole pevne dane velikosti urcene pri prekladu vyuziva funkce alloc zadosti k operacnimu systemu o prideleni pameti. Protoze i dalsi cinnosti programu mohou vyzadovat prideleni pameti, je pamet pridelovana nekonti- nualne. Proto musi byt volna pamet /tj. informace o ni/ udrzo- vana v retezu volnych bloku pameti. Kazdy blok obsahuje svoji velikost, ukazovatka na dalsi blok a vlastni volnou pamet. Bloky jsou zretezeny v poradi stoupajici velikosti adresy ope- racni pameti a blok posledni /s nejvyssi adresou/ ukazuje zpet na prvni blok, takze retez vytvari ve skutecnosti kruh. Pri zadosti o prideleni pameti je prohledavan seznam vol- nych bloku pameti, dokud neni nalezen dostatecne veliky blok pameti. Jestlize ma blok pozadovanou velikost, je ze seznamu volnych bloku vyrazen a pridelen uzivatelovi. Jestlize je blok prilis veliky, je rozdelen a primerena cast je pridelena uziva- telovi, zatim co cast zbyvajici je zaclenena zpet do seznamu volnych bloku. Jestlize neni nalezen zadny blok dostatecne velikosti, je zadan operacni system o prideleni dalsiho bloku pameti, ktery je zaclenen do seznamu volnych bloku. Uvolnovani bloku pameti zpusobuje prohledavani seznamu vol- nych bloku proto, aby bylo nalezeno vhodne misto pro zaclene- ni prave uvolneneho bloku pameti. Jestlize prave uvolnovany blok pameti je prilehajici k bloku jinemu, je k nemu pripojem, aby nedochazelo k prilisnemu rozdeleni pameti. Nalezeni prile- hajicich bloku je snadne, protoze seznam volnych bloku pameti je organizovan dle poradi ukladani. Jeden problem, o kterem byla zminka v kapitole c. 5, je Š.po 12 zajisteni toho, aby blok pameti, prideleny funkci alloc byl vhodny pro objekty, ktere do neho budou ulozeny. Ackoliv existuji ruzne druhy vypocetnich systemu, existuje pro kazdy z nich nejvice omezeny objekt. Nejvice omezeny objekt muze byt ukladan pouze na nektere adrese. Na tyto adresy pak mohou byt ukladany take vsechny objekty mene omezene. Napriklad, na systemech IBM 360/370, Honeywell 6000 a nekterych dalsich mohou byt libovolne objekty ulozeny na mista vhodna pro uloze- ni objektu typu double. Na systemu PDP-11 je timto objektem objekt typu int. Volny blok obsahuje ukazovatko na nasledujici blok v retez- ci, zaznam o velikosti bloku a vlastni volnou pamet. Kontrolni informace umistena na pocatku bloku se nazyva hlavickou /"hea- der"/. Pro zjednoduseni pridelovani maji vsechny bloky velikost rovnu nasobku velikosti hlavicky, ktera musi byt spravne umis- tena. Toho je dosazeno unionem, ktery obsahuje pozadovanou strukturu hlavicky a prikladem nejvice omezeneho typu. typedef int ALIGN; /*vhodne prirazeni pro PDP-11*/ union header{ /*volna hlavicka bloku*/ struc { union header *ptr; /*nasledujici volny blok*/ unsigned size; /*velikost tohoto volneho bloku*/ }s; ALIGN x; /*prirad spravne blok*/ }; typedef union header HEADER; Funkce alloc zaokrouhli pozadovanou velikost pridelene pameti v nasobcich velikosti hlavicky. Skutecna velikost bloku pridelene pameti je vetsi o jednu jednotku velikosti hlavicky, protoze blok musi obsahovat i vlastni hlavicku, tato hodnota je obsa- zena v promenne size hlavicky. Ukazovatko vracene funkci alloc, vsak ukazuje na pocatek volne pameti, nikoliv na hlavicku blo- ku volne pameti. static HEADER base; /*pocatecni prazdny seznam*/ static HEADER *allocp = NULL; /*naposledy prideleny blok*/ char *alloc (nbytes) /*zakladni funkce pridelovani pameti*/ unsigned nbytes; { HEADER *morecore(); register HEADER *p, *q; register int nunits; nunits = 1 + (nbytes + sizeof(HEADER) - 1) / sizeof(HEADER); if (( p = allocp) == NULL) /*seznam volnych bloku neexistuje*/ { base.s.ptr = allocp = q = &base; base.s.size = 0; } for (p = q -> s.ptr; ; q = p -> s.ptr) { if (p -> s.size >= nunits /*je vetsi*/ Š.po 3 { if (p -> s.size == nunits) /*je roven*/ q -> s.ptr = p -> s.ptr; else /*pridel konec bloku*/ { p -> s.size -= nunits; p += p -> s.size; p -> s.size = nunits; } allocp = q; return ((char*)(p+1)); } if (p == allocp) /*zamez opakovanemu prohledavani*/ if ((p = morecore (nunits)) == NULL) return (NULL); /*neni zadny volny blok*/ } } Promenna base je pouzita pouze pri odstartovani procesu pride- lovani pameti. Jestlize je ukazovatko allocp hodnoty NULL, pro- vadi se prve volani funkce alloc, pri kterem je vytvoren dege- nerovany seznam volne pameti, ktery obsahuje jeden blok nulove velikosti a odkazuje sam na sebe. Pri dalsich volanich funkce alloc, jiz bude blok volne pameti hledan. Vyhledani volneho bloku pameti zapocne z mista, kde byl pridelen volny blok pame- ti naposledy, to napomaha k udrzeni homogennosti seznamu volne pameti. Jestlize je nalezen prilis veliky blok pameti, je uzi- vateli pridelena pamet od konce bloku. Timto zpusobem je hla- vicka puvodniho bloku zachovana. Pouze je zmenena hodnota size udavajici velikost volne pameti bloku. Ve vsech pripadech je vsak uzivateli vraceno ukazovatko na skutecne volnou pamet, ktera pocina jednu jednotku velikosti hlavicky bloku za pocat- kem bloku /tj. za hlavickou/. Poznamenejme, ze promenna t je konvertovana na promennou typu ukazovatko, pred jejim vracenim funkci alloc. Funkce morecore obdrzi volnou pamet od operacniho systemu. Podrobnosti o tom jak, se samozrejme lisi na ruznych systemech. V systemu UNIX vrati systemove volani sbrk (n) ukazovatko na n dalsich slabik volne pameti. Ukazovatko vracene volanim uspo- kojuje vsechny pozadavky na spravne prideleni pameti. Protoze pridelovani pameti prostrednictvim operacniho systemu je pom- merne narocnejsi operace, neni zadouci, aby kazde volani fun- kce alloc pouzivalo sluzby operacniho systemu. Proto funkce morecore zaokrouhli pocet zadanych jednotek volne pameti na vetsi hodnotu. Takovy blok potom muze byt znovu pridelovan, jak je zapotrebi. Pravidlo zaokrouhleni je parametrem, ktery muze byt nastaven tak, jak je zadouci. #define NALLOC 128 /*pocet jednotek najednou pridelenych*/ static HEADER *morecore(nu) /*zadej system o volnou pamet*/ unsigned nu; { char *sbrk(); register char *cp; register HEADER *up; register int rnu; Š.po 12 rnu = NALLOC * ((nu + NALLOC - 1) / NALLOC); cp = sbrk(rnu * sizeof(HEADER)); if ((int) sp == -1) /*neni zadna volna pamet*/ return (NULL); up = (HEADER*) cp; up -> s.size = rnu; free ((char*)(up + 1)); return (allocp); } Funkce sbrk vrati hodnotu -1, jestlize uz ani system nemuze pridelit zadnou volnou pamet, ackoliv by byla vhodnejsi hodno- ta NULL. Hodnota -1 musi byt konvertovana na hodnotu typu int, aby mohla byt porovnana. Naproti tomu pouziti konstrukce 'cast' (vid. 7.2) umoznuje, aby funkce byla pomerne imunni proti ruznym reprezentacim ukazovatek na rozdilnych systemech. Funkce free jednoduse prohledava seznam volnych bloku pame- ti, pocinaje od bloku urceneho ukazovatkem allocp. Hleda mis- to pro zarazeni volneho bloku. Takove misto existuje bud mezi dvema bloky nebo za poslednim blokem v seznamu. Kazdopadne vsak budou volne bloky, ktere spolu sousedi, spojeny v blok jediny. Jedina obtiz programu je udrzet ukazovatka tak, aby ukazovala na spravna mista a velikosti volnych bloku pameti byly spravne. free (ap) /*vloz blok ap do seznamu volnych bloku*/ char *ap; { register HEADER *p, *q; p = (HEADER*) ap - 1; /*ukazovatko na hlavicku bloku*/ for (q = allocp; ! (p > q && p < q -> s.ptr); q = q -> s.ptr) if (q >= q -> s.ptr && (p > q || p < q -> s.ptr)) break; /*neprileha-li*/ if (p + p -> s.size == q -> s.ptr) /*pripoj k nasled. bloku*/ { p -> s.size += q -> s.ptr -> s.size; p -> s.ptr = q -> s.ptr -> s.ptr; } else p -> s.ptr = q -> s.ptr; if (q + q > s.size == p) /*pripoj k predch. bloku*/ { q -> s.size += p -> s.size; q -> s.ptr = p -> s.ptr; } else q -> s.ptr = p; allocp = q; } Ackoliv je pridelovani pameti v podstate zavisle na technic- kem vybaveni systemu, program vyse uvedeny ukazuje, jak tyto zavislosti mohou byt rizeny a svereny nevelke casti progra- mu. Uziti konstrukci typedef a union je vhodne pro rizeni Š.po 3 prideleni spravneho useku pameti /za predpokladu, ze funkce sbrk vrati spravne ukazovatko/. Pouziti konstrukce 'cast' zabe- zpeci explicitni uskutecneni konverze ukazovatka a to dokonce i v pripade, kdy je spatne navrzeny systemovy interface. Pres- to ze se jedna o podrobnosti uplatnene pri navrhovani funkci, zabezpecujicich pridelovani volne pameti, je jejich vseobec- ne uplatneni daleko sirsi. C v i c e n i 8-6. Funkce ze standardni knihovny calloc (n,size) vrati ukazovatko na volny blok pameti o velikosti n* size, ktera je inicializovana nulami. Napiste funkci calloc, pouzitim funkce alloc jako vzoru nebo jako volane funkce. C v i c e n i 8-7. Funkce alloc prijme zadost o prideleni vol- ne pameti dane velikosti aniz kontroluje realnost takove zadane velikosti pameti. Funkce free dale predpoklada, ze blok, ktery je zpracovavan, obsahuje korektni hodnotu v promenne size. Pozmente vyse uvedene funkce tak, aby byly schopny vice rozpoz- navat takove chyby. C v i c e n i 8-8. Napiste funkci bfree(p,n), ktera uvolni libovolny blok p o n znacich a zaradi do seznamu volne pameti. Pouzitim funkce bfree muze uzivatel priradit staticke nebo externi pole do seznamu bloku volne pameti v libovolnem okam- ziku.