Mihai BUDIU -- budiu+ at cs.cornell.edu, mihaib@pub.ro
http://www.cs.cornell.edu/Info/People/budiu/budiu.html
iulie 1996
Chiar și cei mai naivi utilizatori ai unui calculator au de a face cu sistemul de fișiere în majoritatea timpului (termenul este consacrat în jargonul informatic); o mai apropiată privire asupra sa este salutară.
``Sistem de fișiere'' (filesystem sau file system) este un termen folosit în mai multe accepțiuni distincte. O preliminară revizie a acestora poate ajuta eliminarea unora din neclaritățile care ar surveni atunci cînd în discuție sensurile se schimbă ne-explicit.
O primă accepțiune a ``sistemului de fișiere'' este cea a unei structuri de date creată pe un suport de memorie, de obicei permanent, cum e discul, în care utilizatorii memorează informații într-un mod care le face mai ușor de stocat, manipulat sau regăsit.
O a doua accepțiune este cea a unui set de operații care se pot face asupra structurilor de date de mai sus: crearea, ștergerea, scrierea și citirea de fișiere.
În fine, ultima accepțiune într-un sens le înglobează pe cele două anterioare: astfel sistemul de fișiere este o bucată dintr-un sistem de operare (adică o parte dintr-un program, de fapt); această bucată este vizibilă utilizatorului ca un set de proceduri (apeluri de sistem, pentru a fi mai preciși) care operează cu fișiere, manipulînd o structură de date pe un suport permanent de memorie.
Sistemul de fișiere văzut ca o colecție de proceduri nu trebuie musai să fie parte a unui sistem de operare, însă de obicei sistemele moderne conțin un sistem de fișiere, pentru că este extrem de util; chiar dacă în anumite sisteme ``micro-nucleu'' sistemul de fișiere nu este parte a nucleului, el poate fi asimilat în sistemul de operare.
Vom încerca în secțiunile următoare să vedem cum arată sistemul de fișiere în Unix. Pe cel de la MS-DOS îl vom folosi cîteodată pentru a exemplifica o vedere alternativă.
Pentru a simplifica discuția vom presupune că sistemul de fișiere este plasat pe un disc. Variantele pe bandă, în memorie (Ram-Disk) sau prin rețea nu sunt neapărat fundamental diferite.
Discul este la nivelul cel mai de jos pentru software o instalație extrem de complexă: el este gestionat de un procesor propriu, numit controler de disc, care știe să facă oarece operații, dar care trebuie programat pentru asta. Controlerul vede discul ca o sumă de partiții independente. (Vom folosi cîteodată termenul de ``disc'' pentru a înțelege ``partiție'', pentru că partiția este un ``disc logic''.) Natura fizică a unui disc îl împarte în cilindrii, fiecare cilindru e împărțit în piste, fiecare pistă în sectoare, fiecare sector conținînd mai mulți octeți.
Pentru a fi utilizabil, un disc trebuie să fie formatat. Operația de formatare (realizată de obicei cu programul fdisk) înscrie pe o partiție o sumedenie de informații: de exemplu pe fiecare pistă este marcat numărul ei de ordine; în acest fel cînd se încearcă citirea uneia se poate verifica dacă se află capul de citire unde trebuie (detectînd erorile datorate mecanicii deficiente).
Deși subiectul este realmente fascinant, nu vom insista asupra relațiilor între aceste părți, pentru că sistemul de fișiere se construiește de obicei pe o structură ceva mai simplă, care este oferită de driverul de disc. Driverul este o colecție de proceduri care știe să manipuleze partițiile, pistele și toate alea, însă transformă discul într-un vector de blocuri, fiecare cu mai mulți octeți. Pe scurt, driverului îi indici un număr de bloc iar el decide pe care sector, pistă, cilindru se găsește acest bloc. Dacă vrei să citești ceva din bloc, indici blocul și numărul octetului din bloc iar driverul găsește unde se află octetul pe discul fizic și îl ia de acolo.
De ce blocuri și nu octeți? Pentru că raportul duratelor între a citi un octet și a citi 100 de octeți consecutivi nu este de 100 la 1 ci undeva între 1 și 2. Asta pentru că foarte mult durează să găsești primul octet; citirea următorilor este destul de rapidă. De asemenea, s-a observat că lumea rareori citește fișierele complet aleator: un octet aici altul colea; in general sunt accesați împreună mai mulți consecutivi. De aceea driverul face toate operațiile pe disc în termeni de blocuri de mărime egală.
Cît de mari? Depinde de sistem. De obicei între jumătate de kilo octet și 8k.
Iată deci cum este plasat sistemul de fișiere în cadrul sistemului de operare:
Sistemul de fișiere are deci la dispoziție mai multe partiții. Fiecare partiție este văzută ca un array de blocuri independente. Citirea/scrierea unui bloc este realizată de driverul de disc, de serviciile căruia sistemul de fișiere se folosește. (În realitate din motive de eficiență între sistemul de fișiere și driver de obicei se mai plasează un nivel: cache-ul de disc, care memorează blocurile mai frecvent utilizate în memorie). În mod normal pe fiecare partiție se construiește cîte un sistem de fișiere independent; din această cauză vom simplifica discuția presupunînd că avem la dispoziție una singură.
În DOS partițiile devin ``discuri logice'' distincte: C: D: E:, etc. În Unix ele sunt specificate prin ``fișiere speciale'' aflate în directorul /dev, dar pentru folosire sunt montate una peste alta. De ajuns pentru moment despre partiții.
Sistemul de fișiere trebuie să ne ofere operații de: creare și distrugere a fișierelor, numire a fișierelor (de fiecare dată cînd ai de-a face cu mai multe obiecte de același fel se pune problema de a le distinge prin nume diferite), de modificare și inspectare a conținutului fișierelor.
Modurile în care se pot realiza aceste operații sunt nenumărate, influențate fiind de feluritele proprietăți pe care le dorim. De exemplu sistemul de operare CP/M (din care a fost derivat și MS/DOS 1.0) are un singur director (vom vedea mai jos un pic ce este un director în realitate) în care se află toate fișierele. Din cauza asta oricare două fișiere sunt distinse numai prin numele lor, deci nu putem avea pe același disc două fișiere cu nume identice.
Unix, și MS-DOS (care a preluat de la el ideea) folosesc o ierarhie (un arbore) de directoare, fiecare avînd un nume la rîndul lui. Fiecare fișier ``se află'' într-un director, în directoare diferite putînd exista fișiere la fel numite. Identificarea unui fișier se face indicînd pe lîngă numele lui, numele directoarelor prin care se ajunge la el (calea -- path pînă la el).
Am aflat cam cum numim fișierele. Mai trebuie să știm ce putem face cu ele. Întrebarea nu e chiar banală; de exemplu limbajul Pascal oferă un model de fișier ``structurat'': fiecare fișier este un array de lungime teoretic nelimitată de obiecte identice (record de obicei). Se pot imagina cu ușurință modele mai complicate de fișiere (unele foarte utile în aplicații de baze de date); de exemplu fișiere de ``perechi'' în care fiecare dată are o ``cheie'' iar accesele se fac după valorile cheii: ``găsește cîmpul cu cheia 100'', ``incrementează cîmpul cu cheia Bibi''.
Unix oferă probabil cel mai simplu model de fișier: un array de octeți arbitrari. Același lucru îl face și DOS-ul, distingînd cîteodată între fișiere text și fișiere ``oarecare''.
Deși ar mai fi multe de spus, să ne concentrăm acum asupra modului în care sunt realizate aceste operații. Deși felurite versiuni și variante ale Unix-ului pot proceda în realitate în mod diferit, vom vedea cum arată implementările ``clasice''. Mai important decît să știm cum se fac lucrurile în detaliu este să știm cum le-am putea face noi.
Cum s-a ajuns la structura pe care o vom desfășura mai jos din cerințele pe care le-am schițat mai sus, nu vă va fi foarte limpede pînă nu veți încerca să rezolvați aceeași problemă în alt fel: atunci veți vedea cum se justifică unele construcții care par artificiale.
O ultimă observație preliminară: din comunicările prezentate la conferința USENIX (a utilizatorilor de Unix), peste 40% sunt legate de sisteme de fi'siere: implement'ari, optimiz'ari, arhitecturi. Deci domeniul este în plină evoluție.
Sistemul de fișiere folosește ca unitate de bază blocul de pe partiție. Cînd este nevoie de spațiu se alocă un bloc întreg, chiar dacă e nevoie de mai puțin. Asta simplifică gestiunea spațiului liber. Iată cum sunt folosite blocurile de pe o partiție pentru a crea un sistem de fișiere:
Nucleul însuși al sistemului de operare este de obicei ținut sub forma unui fișier. Însă procedurile care manipulează fișiere sunt parte a nucleului, așa că pentru a porni (boot) sistemul de operare însuși nu se pot folosi operațiile pe fișiere, căci ele nu sunt încă citite de pe disc! Din această cauză nucleul trebuie să fie accesibil și direct, fără a fi nevoie să fie citit ca fișier.
Blocul de boot conține tocmai informațiile necesare pentru a citi nucleul: de obicei o listă a blocurilor de pe disc ocupate de fișierul-nucleu. Cînd calculatorul pornește un program mic (din ROM de exemplu) citește nucleul în memorie folosind aceste informații.
Superblocul este rezervat pentru a descrie cum sistemul de fișiere folosește partiția. Informațiile pe care le cuprinde sunt:
Superblocul dă o descriere a informațiilor globale referitoare la întreaga partiție care conține un sistem de fișiere. Le vom pricepe cînd vom ști de fapt despre ce este vorba.
În Unix numărul de fișiere care se pot afla la un moment dat pe un sistem de fișiere este limitat precis, încă de la crearea sistemului de fișiere (vedeți secțiunea despre mkfs)! Fiecare fișier are un i-nod (i-node sau inode în engleza, abreviere de la Information Node) asociat, care conține aproape toate informațiile legate de acel fișier, mai puțin conținutul și numele. Unele i-noduri sunt nefolosite la un moment dat.
Chiar dacă pe un disc mai există blocuri libere, în momentul cînd au fost folosite toate i-nodurile nu se mai pot crea noi fișiere. Discul este plin.
Să ne uităm mai aproape la un i-nod:
Iată o scurtă descriere a semnificației fiecăruia din cîmpurile de mai sus (nu uitați: anumite implementări pot avea și altele). Cele mai multe cîmpuri pot fi văzute cu ajutorul comenzii ls. ls are ca argumente nume de fișiere, nu i-noduri, dar cum fiecare fișier are un singur i-nod (vom vedea cum se găsește ce i-nod corespunde unui anumit nume mai încolo), ls știe de unde să ia informațiile.
Pentru a vedea ce i-nod corespunde unui fișier folosiți ls -i; de exemplu la mine acasă:
$ ls -i /bin/b* 22363 /bin/bash 22905 /bin/binmail
Deci fișierul /bin/bash are i-nodul cu numărul 22363, iar fișierul /bin/binmail are i-nodul 22905.
Aici este marcat un i-nod liber. La crearea unui nou fișier un i-nod liber este alocat, marcat ocupat, și cîmpurile celelalte înscrise corespunzător. La ștergerea unui fișier i-nodul devine liber (atenție: ``ștergerea'' în Unix este un fenomen destul de complicat!).
Fiecare fișier în Unix aparține cuiva. Un identificator numeric asociat celui care posedă fișierul (de obicei cel care l-a creat) este marcat aici. Al treilea cîmp de la ls -l indică utilizatorul. Dacă vreți valoarea numerică a UID dați ls -ln.
Identificatorul acesta ridică oarecari probleme cînd un sistem de fișiere este folosit în rețea (ca sistemul NFS -- network file system), pentru că pe un calculator utilizatorul cu codul 10 poate fi cineva, iar pe alt calculator altcineva! (Aceasta este o deficiență a Unix-ului care nu a fost proiectat originar pentru a opera în rețea -- cîrpelile nu merg prea bine.)
$ ls -l /bin/bash -rwxr-xr-x 1 root bin 279556 Feb 7 1994 /bin/bash ^^^^ ^^^ UID GID
În Unix utilizatorii fac parte din unul sau mai multe grupuri. Fiecare fișier aparține și el unui anume grup, care este indicat aici. Considerații asemănătoare cu cele de la UID se aplică și aici. Al patrulea cîmp din ls -ln este GID, sub formă numerică.
Puteți vedea drepturile în coloanele 2-10 din ceea ce tipărește ls -l.
$ ls -l /bin/bash -rwxr-xr-x 1 root bin 279556 Feb 7 1994 /bin/bash ^^^^^^^^^ drepturile
Drepturile vin sub forma a trei triplete de valori Da/Nu. Primul triplet arată ce poate face cu fișierul posesorul lui (un proces al unui utilizator cu UID egal cu cel al fișierului). Al doilea arată ce pot face utilizatorii din grupul fișierului. Al treilea triplet arată ce poate face restul lumii.
Fiecare grup de trei valori Da/Nu arată așa:
r | w | x |
Read | Write | eXecute |
Citește | Scrie | Execută |
Ele arată dacă fișierul poate fi citit, modificat sau respectiv executat (pentru fișiere de tip director căutat) de persoana respectivă. Pentru un exemplu concret să luăm un fișier cu drepturile:
r-x-w---x \_/\_/\_/ \ \ \ restul lumii \ \__grupul \___posesorul
Asta înseamnă că:
Fișierele mai au trei atribute de forma Da/Nu (biți). Acestea se numesc: bitul SUID, bitul SGID și bitul sticky (lipicios). Multă informație este condensată în acești trei biți.
$ ls -l /bin/mount -rwsr-xr-x 1 root bin 21508 Jan 28 1994 /bin/mount ^ SUID
Cînd utilizatorul mihai va executa acest fișier, procesul va avea UID root, și nu mihai, din cauza bitului SUID.
$ ls -l /usr/bin/convfont -rwsr-sr-x 1 root root 2272 Mar 16 1994 convfont ^ ^ SUID si SGID
Cum se vede și din descriere, acești biți nu au sens decît dacă fișierul este executabil. Pentru fișiere ne-executabile ei sunt folosiți pentru a indica faptul că folosirea fișierului se poate face numai după încuierea (lock) lor. (Acesta este un mecanism care nu permite accesul simultan a două procese la un singur fișier.) Acești biți se vad cu litere mari S.
Biții SUID la directoare indică faptul că toate fișierele create în acele directoare moștenesc drepturile de la director și nu de la procesul care le crează.
Ar mai fi cîte ceva de spus despre lock-uri, dar ar merita un articol în sine. Să mergem mai departe cu i-nodul.
La fișiere executabile inițial acest bit indica nucleului să optimizeze folosirea fișierului: odată executat (deci citit în memorie) nu mai trebuie scos de acolo, chiar dacă procesul se termină, pentru că probabil va fi folosit din nou în curînd.
Pentru directoare el indică o calitate foarte interesantă: în directoarele în care oricine poate scrie (----w-), cum e /tmp, teoretic oricine poate șterge orice fișier (după cum vom vedea un pic mai jos). Un bit lipicios pe un astfel de director va permite însă ștergerea unui fișier numai de către posesorul lui (de către un proces avînd același UID cu i-nodul).
$ ls -ld /tmp drwxrwxrwt 3 root root 1024 Jul 26 00:10 /tmp/ ^ bit sticky
Modificatorul d al lui ls este necesar pentru a lista drepturile directorului tmp și nu ale tuturor fișierelor cuprinse în el.
ls -l va scrie tipul unui fișier în prima literă afișată. Aceasta poate fi într-un Unix normal:
d | pentru un director |
- | pentru un fișier ordinar |
l | pentru o legătură simbolică (symbolic link) |
s | pentru o mufă (socket) |
c | pentru un fișier special de tip caracter |
b | pentru un fișier special de tip bloc |
p | pentru o țeavă cu nume (named pipe) |
Cele mai multe dintre aceste tipuri de fișiere sunt de fapt doar niște nume asociate unor părți ale nucleului, și nu vor fi discutate în acest articol. Primele trei au într-adevăr legătură cu sistemele de fișiere, dar numai despre primele două vom vorbi în acest articol, pentru că altfel iese prea mare.
Fiecare i-nod înregistrează în formatul tipic Unix (numit time_t, de obicei un întreg lung reprezentînd numărul de secunde scurse de la 1 ianuarie 1970) data (și ora) creării, accesului și modificării fișierului. Timpii se pot obține astfel cu ls:
ls -l | timpul creării |
ls -ul | timpul ultimului acces |
ls -lc | timpul ultimei modificări |
în octeți este indicată de asemenea de ls -l:
$ ls -l /bin/bash -rwxr-xr-x 1 root bin 279556 Feb 7 1994 /bin/bash ^^^^^^ bash are 273K
nu poate fi văzută cu ls. Ea este ținută într-o formă foarte ingenioasă care permite accesul extrem de rapid în orice punct al fișierului. Această listă indică pentru fiecare bloc din fișier ce bloc din partiție corespunde (fiecare prim bloc din fiecare fișier ocupă alt loc în partiție, firește).
Pentru a indica ce blocuri compun un fișier se folosesc pointeri la blocuri. Un pointer la un bloc este pur și simplu numărul de pe partiție al unui bloc. Pentru că blocul 0 nu poate fi al vreunui fișier (fiind blocul de boot) avem la dispoziție și o valoare care poate indica lipsa unui bloc. (Vom vedea că in Unix un fișier poată avea ``găuri'': blocuri lipsă în mijloc!)
Schema este extrem de ingenioasă, dar un pic mai complicat de descris în cuvinte. Pe scurt i-nodul conține:
O schemă aproximativă ar arăta așa:
Cîte blocuri se pot indica astfel? Dacă într-un bloc încap X pointeri avem lungimea maximă pentru un fișier:
10 + X + X*X + X*X*X blocuri
Pentru niște valori tipice: bloc de 1024 octeți și pointer pe 4 octeți avem X=1024/4=256. Atunci valoarea de mai sus devine
(10 + 256 + 256*256 + 256*256*256) * 1K = (10 + 256 + 65536 + 16777216) K >= 16 Giga octeți.
Pînă de curînd asta era destul de mult pentru un singur fișier. Oricum, introducerea unui al patrulea nivel de indirectare ar ridica valoarea la 4 Tera octeți, ceea ce ar trebui să ajungă pentru o vreme...
Din virtuțile acestei scheme: fișiere scurte rapide și compacte (oricare din primele 10 blocuri se accesează imediat), dimensiune maximă foarte mare, acces rapid la orice bloc (maximum trei blocuri citite de pe disc pentru indirectări), fișiere care pot avea găuri.
În Unix un fișier se poate ``afla'' simultan în mai multe directoare! Cîte apariții distincte are el este indicat de numărul de legături. De exemplu:
$ ls -ld /usr/bin drwxr-xr-x 2 root bin 7168 Jul 23 12:45 /usr/bin ^ numarul de legaturi este 2!
Cu alte cuvinte directorul /usr/bin este prezent în două directoare! Vom vedea mai jos de ce.
Cu asta am epuizat structura i-nodului clasic.
Să revenim la studiul partiției: organizarea ei în sistem de fișiere. După blocurile în care se țin i-nodurile toate blocurile care urmează sunt alocate pentru a servi la memorarea conținutului fișierelor. Aceste blocuri sunt la început toate libere: nici unul nu este indicat de i-nodul vreunui fișier ca aparținînd acelui fișier. Pe măsură ce se scriu date în fișiere și acestea cresc, se iau blocuri libere și se adaugă fișierelor, făcînd i-nodurile să puncteze spre ele.
Cînd fișierele sunt șterse (sau trunchiate) blocurile revin pe lista blocurilor libere.
Sistemul de fișiere ține și o listă a blocurilor libere, pentru a se aproviziona rapid cînd are nevoie să crească fișierele. Cînd lista este goală discul este plin (în alt fel decît atunci cînd nu mai sunt i-noduri).
Lista blocurilor libere este și ea structurată într-un mod inteligent: în superbloc există un pointer la un prim bloc liber. Acesta este plin cu pointeri la alte blocuri libere. Ultimul dintre blocurile arătate de el conține la rîndul lui pointeri spre blocuri libere ș.a.m.d.
superbloc--->primul bloc liber /-->bloc liber /-->bloc liber | | | | | | | | | | | | | | | | | v v v v \-/ v v v v \--/ v v v v ... pointeri spre blocuri libere
Această listă arată ca un arbore dezechilibrat: numai nodurile de pe ramura cea mai din dreapta au fii; toate celelalte sunt frunze. De ce schema asta? Pentru că modificarea listei se face de la început: se iau din blocurile libere punctate de primul, iar cînd toate s-au terminat mai puțin ultimul (din cele punctate de primul), chiar primul bloc liber se alocă și superblocul punctează spre cel care era ultimul. Se procedează invers pentru eliberarea de blocuri.
Schema este mai eficientă decît o listă simplu înlănțuită pentru că nu trebuie citit decît un bloc liber (primul) pentru a aloca X pointeri: cei pe care îi conține.
Deși utilizatorii de DOS pot avea această impresie, nu orice partiție trebuie să conțină un sistem de fișiere! Ba chiar Unix-ul folosește în mod curent o partiție (de swap) pentru a crea mecanismul de memorie virtuală. Pe acea partiție nu se află fișiere.
Operația prin care pe o partiție formatată se crează un sistem de fișiere se numește în Unix mkfs (MaKe FileSystem) iar în DOS format (după cum se vede în DOS distincția între partiție și sistem de fișiere nu este prea clară nici la nivel de nume al programelor utilitare).
Programul mkfs folosește driverul de disc pentru a crea blocul de boot, superblocul și a inițializa toate i-nodurile și blocurile de date ca fiind goale. Abia după ce el este executat poate fi folosit sistemul de fișiere de pe partiție, montîndu-l (cu comanda-apel de sistem mount).
După ce am aflat atîtea despre structura de date cu care se descrie un fișier putem să ne imaginăm cu ușurință cum se implementează operațiile de citire/scriere în fișiere. Să modelăm operația de scriere, care este mai complicată.
Să zicem că vrem să scriem octetul nr. 100000 cu valoarea 1 într-un anumit fișier.
Prima etapă, de care încă n-am spus nimic, este de a găsi i-nodul fișierului cînd i se dă numele. (Secțiunea despre directoare a articolului de față numai cu asta se ocupă.) Această etapă verifică și dacă am permisiunea de a face operația cerută pe acest fișier. Dacă nu am voie, operația nu se face și apelul de sistem care a invocat-o se termină cu o eroare.
După ce am găsit i-nodul, trebuie să găsim blocul pe disc în care e octetul nr. 100000. Știm cîți octeți sunt într-un bloc, așa că aflăm al cîtulea bloc al fișierului este cel care are octetul 100000. Presupunînd, ca mai sus, că un bloc are 1024 de octeți, obținem blocul 100000/1024 = 98 (rotunjind în sus). Ca să ajung la blocul 98 trebuie să citesc pointerii din blocul de pointeri simplu indirecți (el conține pointerii pentru blocurile 11-266). Și anume trebuie să caut al 88-lea pointer (nu uitați că primele 10 blocuri au proprii pointeri). Deci mă uit la blocul cu pointerii spre blocuri.
Dacă fișierul a fost mai scurt de 10 blocuri pînă acum, acest bloc (cel simplu-indirect) nu exista! În acest caz îl aloc de pe lista de blocuri libere și îl umplu cu pointeri zero, ca să arăt că e gol.
După ce am găsit blocul simplu-indirect mă uit la al 88-lea pointer din el. Dacă e nenul, înseamnă că blocul 98, pe care vreau să-l modific, făcea parte deja din fișier. Altfel trebuie să îl aloc tot eu. O fac dacă e nevoie, înscriind în blocul simplu-indirect la pointerul 88 adresa blocului 98, proaspăt alocat. Blocul nou alocat îl umplu cu zerouri.
În fine, blocul la care am ajuns conține octetul cu nr. 100000. Al cîtulea este? Aflu făcînd restul împărțirii 100000 la 1024 = 672. Deci în acest bloc scriu în octetul 672 valoarea 1, după care pun blocul la loc pe disc.
Dacă lungimea fișierului era mai mică de 100000 (trecută în i-nod) o aduc la această valoare. Pun și timpul accesului și modificării fișierului la ora curentă.
Dacă a fost nevoie pe drum și nu s-a putut aloca un bloc liber, operația iar a eșuat.
Ați prins schema? Încercați să o urmăriți pe hîrtie cu creionul.
Observați că nu este obligatoriu ca blocurile 1-97 să existe! Un fișier poate avea doar octetul 100000, ocupînd pe disc doar două blocuri (cel simplu indirect și cel cu octetul) în loc să aibă 99 de blocuri cu zerouri!
Aceasta este semantica operației de scriere în fișier în Unix. Citirea unui octet care nu există se soldează cu citirea unui zero.
Observați un lucru interesant: dacă știi i-nodul unui fișier știi toate informațiile importante despre el și ai acces la tot conținutul lui.
Directoarele în Unix au un singur rol: să asocieze fiecărui nume de fișier un i-nod. Se spune despre directoare că ``leagă nume de i-noduri''. Directoarele sunt din multe privințe niște fișiere. Deosebirea esențială însă este că directoarele sunt niște fișiere care au o structură: sistemul de fișiere interpretează el însuși octeții care formează conținutul unui director și nu permite utilizatorului manipularea lor arbitrară.
Pe scurt un director este un vector de legături (links). Orice director arată așa:
Pe lîngă un bit care distinge link-urile folosite de cele libere (link-uri libere apar din cauza lui rm -- ștergerea), link-ul mai conține doar două cîmpuri: un nume de fișier (care poate conține aproape orice caractere, cea mai notabilă excepție fiind /), și un număr de i-nod. O restricție este ca toate numele aflate în link-urile dintr-un director sa fie distincte. Nici unul din caracterele care compun numele unui fișier nu are vreo semnificație specială (cum de exemplu în DOS are punctul).
Spunem că fișierul bash se află în directorul bin dacă fișierul bin, de tip director, conține un link al cărui nume este bash.
Directoarele pot conține la rîndul lor directoare: pur și simplu i-nodul dintr-un link este un i-nod de tip director.
După cum vedeți de fapt fișierele nu sunt incluse în directoare, ci directoarele țin niște pointeri (numărul de i-nod) spre fișierele propriu-zise, asociindu-le și un nume. O primă întrebare care se poate ivi: nu poate un i-nod să fie prezent în două linkuri diferite? Răspunsul este da: pot exista link-uri multiple la un același i-nod, în același director sau în directoare diferite!
Un director ar putea arăta așa:
nume | vi | ex | | i-nod | 100 | 100 | ... |
Asta înseamnă că un același fișier este prezent de două ori, odată cu numele vi, altă dată cu numele ex. Modificarea fișierului numit vi se reflectă prin modificarea fișierului ex.
Observați că datorită faptului că nu fișierul este în director ci un arătător spre fișier, arborele de directoare devine un graf: putem avea cicluri (directoare care se conțin reciproc) sau fișiere în mai multe directoare simultan.
Prin definiție un director nu este niciodată gol (deși la o adică putem să-l forțăm), ci conține întotdeauna cel puțin două link-uri. Unul se numește . (punct) și este un link la i-nodul directorului însuși, iar altul se numește .. (punct punct) și este un link la directorul tată (în care a fost creat cel despre care vorbim). Să vedem un ipotetic exemplu:
După cum vedeți în directorul /usr, care are i-nodul 20, se află un link cu numele bin și i-nodul 39. Directorul cu i-nodul 39 conține un link cu numele . spre i-nodul 39 (el însuși) și un link cu numele .. spre directorul cu i-nodul 20 (``tatăl'' lui).
Dacă mai țineți minte, i-nodul are un cîmp care arată cîte link-uri arată spre el însuși. Din cauza asta mai sus
$ ls -ld /usr/bin drwxr-xr-x 2 root bin 7168 Jul 23 12:45 /usr/bin ^
are două legături: una aflată în /usr (cu numele bin) și una în el însuși cu numele . (punct).
Dacă în /usr/bin ar mai fi fost subdirectoare, fiecare din ele ar fi avut un link cu numele .. spre /usr/bin.
Comanda pwd (print working directory), care afișează directorul curent al procesului care o execută, lucrează astfel: urmărește link-urile .. pînă la directorul rădăcină și caută i-nodul de la link-ul . în directorul .. pentru a afla numele (mai citiți odată fraza, pe-ndelete).
Orice sistem de fișiere are un director privilegiat, care are link-ul .. spre el însuși. Acesta este directorul rădăcină al sistemului de fișiere. Toate căutările de fișiere care încep cu semnul / caută întîi în acest director. Mai exact, sa vedem cum se fac
Principala operație pentru care se folosesc directoarele este pentru a găsi i-nodul unui fișier cînd știu directoarele care ``duc'' la el (calea: path).
Orice proces în Unix are ceea ce se cheamă un director
curent (în MS-DOS există un singur director curent pentru tot
sistemul de operare). Acesta este cunoscut procesului prin i-nodul
său (și se poate schimba cu apelul de sistem chdir). Cînd un
proces specifică o operație pe fișiere el indică întotdeauna un nume de fișier ca o listă de directoare separate
prin semnul / (în MS-DOS acesta a fost înlocuit din motive
obscure cu \
). Dacă primul semn dintr-un path este /
atunci primul director în care se caută este directorul rădăcină.
Altfel primul director în care se caută este cel curent.
Să vedem acum, pe un exemplu, cum nucleul găsește i-nodul fișierului /usr/bin/cc.
Primul semn este /, deci deschide directorul rădăcină. I-nodul lui este marcat în superbloc, deci conținutul lui este accesibil.
Primul nume este usr. Caută deci printre toate link-urile din directorul rădăcină unul cu numele usr. Dacă l-a găsit, se uită la numărul de i-nod al lui usr. Citește apoi acest i-nod. Pentru că mai există nume în path după usr, i-nodul curent trebuie să aibă tipul director.
Presupunînd că așa este, nucleul deschide fișierul aflat (usr) și caută un link cu numele bin. Găsește i-nodul lui, citește fișierul (bin) și caută link-ul cc. Odată găsit, a aflat i-nodul lui cc, deci poate face orice operație cu acesta.
Mai trebuie spus că orice operație de deschidere a unui director pentru căutare este precedată de verificarea dreptului de a face această căutare. Există două feluri de drepturi pentru directoare:
Dacă un director este accesibil are de obicei drepturi r-x.
Dacă are numai drepturi r- atunci se poate afla ce fișiere sunt în el, dar ele nu pot fi deschise prin aceste link-uri, și nici i-nodurile lor nu pot fi accesate. Puteți face ls sau ls -i dar nu ls -l pe un astfel de director.
Dacă are numai drepturi -x nu puteți vedea ce fișiere conține, dar dacă știți unul din nume, îl puteți deschide!
Dacă are drepturi -- pentru dumneavoastră, atunci uitați de el.
Pentru a crea un fișier în primul rînd trebuie alocat un i-nod și apoi construit un link. În Unix toate operațiile asupra fișierelor specifică numele fișierului, și niciodată i-nodul. Nu poți accesa un fișier al cărui i-nod îl cunoști: trebuie să știi un path spre el. Din cauza asta funcționează securitatea în Unix: dacă nu există nici un path în care un utilizator să aibă dreptul de a citi toate directoarele componente, atunci el nu va putea ajunge niciodată să citească fișierul indicat de acel path.
Crearea fișierelor se poate face prin mai multe apeluri de sistem: open, creat, mknod, mkdir. Toate specifică un path. Dacă procesul care le execută are dreptul de scriere în penultima componentă a path-ului (care este directorul în care se crează fișierul) atunci fișierul este creat, alocîndu-se un i-nod liber și un link în acel director.
Fișierele nu pot fi șterse în Unix! În Unix se pot doar șterge link-uri. Singurul apel de sistem pentru așa ceva este unlink. Cînd este șters un link, i-nodului spre care acesta punctează i se decrementează numărul de referințe. Dacă acest număr devine 0, înseamnă că acest fișier nu mai este legat în nici un director. Dacă acest fișier nu este în acea clipă deschis de nici un proces, atunci toate blocurile de date pe care el le posedă sunt trecute pe lista de blocuri libere, iar i-nodul lui este eliberat. (Dacă este deschis, atunci el va fi eliberat numai cînd toate procesele vor închide fișierul).
Nu faceți confuzie între operația de ștergere a unui link, care are nevoie de drept de scriere în directorul din care se șterge link-ul, și operația de trunchiere a unui fișier (la lungime zero, de pildă) care are nevoie de drept de scriere doar în acel fișier.
$ ls -la drwxr-x--x 7 mihai users 1024 Jul 29 21:46 ./ drwxr-xr-x 6 root root 1024 Jul 26 22:50 ../ -rw-rw-rw- 1 mihai users 3735 Jul 29 19:18 ss
Un alt utilizator decît mihai nu poate face rm ss, dar poate face cp /dev/null ss, cu care îl șterge!
În fine, o ultimă operație pe fișiere pe care o discutăm este cea de legare. Se poate crea o legătură nouă la un fișier existent, cu apelul de sistem link (sau comanda shell-ului ln, care se folosește de acest apel).
$ ln /bin/bash ./bb
procedează astfel:
În mod normal numai ``super-user-ul'' poate face legături la un director.
Pentru că o legătură conține un număr de i-nod, iar fiecare partiție are propria ei numerotare independentă de a celorlalte pentru i-noduri, nu se pot face legături pe o partiție care să arate la un fișier de pe alta. Pentru asta au fost inventate legăturile simbolice, despre care vom vorbi altădată.
Un sistem de fișiere se poate strica din felurite motive (cel mai comun fiind că se stinge calculatorul printr-o procedură brutală și anumite informații nu sunt salvate din cache). Ca un filesystem să fie corect, anumite relații trebuie să fie adevărate între componentele sale. De exemplu un bloc nu trebuie să fie simultan în două fișiere.
Un program sofisticat este oferit pentru a verifica integritatea unui sistem de fișiere. fsck (File System ChecK) este rulat de obicei la fiecare pornire a calculatorului și încearcă să determine toate discrepanțele care arată că un sistem de fișiere nu este integru, și să le repare cu minimum de alterații ghicind ce s-a stricat de fapt.
Acest program nu lucrează cu fișiere și directoare, ci analizează direct partiția la nivel de bloc. El nu trebuie să fie niciodată executat cînd partiția este ``montată'' pentru că s-ar putea ca fișierele să fie modificate de procesele care rulează în timp ce sunt verificate, deci verificarea să iasă prost deși totul este în regulă.
Ca să verificați că ați înțeles cum funcționează sistemul de fișiere din Unix (deși multe lucruri au fost lăsate în suspensie, v-am dat toate informațiile necesare), încercați să rezolvați următorul exercițiu pe un Unix care vă oferă mai multe ``ferestre'':
$ sh $ cd $ mkdir bb $ cd bb $ ls $ pwd
$ cd $ rm -r bb $ ls -a
$ ls $ pwd $ cd .. $ cd /
Explicați ce se întîmplă.