Sisteme de fișiere unionale: Implementări, partea I


Acest articol vă este oferit de abonații LWN

Abonarii la LWN.net au făcut posibil acest articol – și tot ceea ce îl înconjoară. Dacă apreciați conținutul nostru, vă rugăm să cumpărați un abonament și să faceți posibil următorul set de articole.

25 martie 2009

Acest articol a fost realizat de Valerie Aurora (fostă Henson)

În articolul de săptămâna trecută, am trecut în revistă cazurile de utilizare, conceptele de bază și problemele comune de proiectare a sistemelor de fișiere unionate. Săptămâna aceasta, voi descrie mai multeimplementații ale sistemelor de fișiere unioniste în detalii tehnice. Sistemele de fișiere unioning pe care le voi aborda în acest articol sunt Plan 9 uniondirectories, BSD union mounts, Linux union mounts. Următorul articol va acoperi unionfs, aufs și, eventual, unul sau două alte sisteme de fișiere unioniste și va încheia seria.

Pentru fiecare sistem de fișiere, voi descrie arhitectura de bază, caracteristicile și implementarea acestuia. Discuția despre implementare se va axa în special pe albire și citirea directoarelor. Voi încheia cu o privire asupra aspectelor de inginerie software ale fiecărei implementări;de exemplu, dimensiunea și complexitatea codului, invazivitatea și povara pe care o reprezintă pentru dezvoltatorii de sisteme de fișiere.

Înainte de a citi acest articol, poate doriți să consultați scrierea lui AndreasGruenbacher, care tocmai a fost publicată, despre workshopul union mount care a avut loc în noiembrie anul trecut. Este un bun rezumat al caracteristicilor sistemelor de fișiere unioniste care sunt cele mai presante pentru dezvoltatorii de distribuții. Din introducere: „Toate cazurile de utilizare care ne interesează se rezumă practic la același lucru: să ai o imagine sau un sistem de fișiere care este folosit numai pentru citire (fie pentru că nu se poate scrie, fie pentru că nu se dorește scrierea pe imagine) și să pretinzi că această imagine sau sistem de fișiere se poate scrie și să stochezi modificările în altă parte.”

Directoarele de uniune Plan 9

Sistemul de operare Plan 9 (răsfoiți codul sursă aici) implementează uniunea în felul său special Plan 9. În directoarele de uniune Plan 9, doar spațiul de nume al directorului de nivel superior este fuzionat, nu și subdirectoarele. Fără a fi constrânse de standardele UNIX, directoarele de uniune Plan 9 nu implementează whiteouts și nici măcar nu elimină intrările duplicate – dacă același nume de fișier apare în două sisteme de fișiere, acesta este pur și simplu returnat de două ori în listele de directoare.

Un director de uniune Plan 9 este creat astfel:

 bind -a /home/val/bin/ /bin

Aceasta ar face ca directorul/home/val/binsă fie montat în uniune „după” (opțiunea-a)/bin; alteopțiuni sunt de a plasa noul director înaintea directorului existent,sau de a înlocui în întregime directorul existent. (Mie mi se pare o ordonare ciudată, deoarece îmi place ca comenzile dinbin/personal să aibă întâietate față de comenzile la nivel de sistem, dar acesta este exemplul din documentația Plan 9). Brian Kernighanexplică una dintre utilizările directoarelor de uniune: „Acest mecanism de uniondirectories înlocuiește calea de căutare a shell-urilor UNIX convenționale. În ceea ce vă privește, toate programele executabile se află în /bin”. Uniondirectories pot înlocui teoretic multe utilizări ale blocurilor fundamentaleUNIX ale legăturilor simbolice și ale căilor de căutare.

Fără albire sau eliminare de duplicate, readdir() onunion directories este trivial de implementat. Decalajele intrărilor în directoare de la sistemul de fișiere de bază corespund direct decalajului în octeți al intrării în directoare de la începutul directoarei. Un director unit este tratat ca și cum conținutul directoarelor subiacente ar fi concatenat împreună.

Planul 9 implementează o alternativă la readdir() care merită menționată, dirread().dirread() returnează structuri de tip Dir, descrise în pagina de manual stat(). Partea importantă a Dir este membrul Qid. Un Qid este:

…o structură care conține câmpurile path și vers: path este garantat a fi unic printre toate numele de path aflate în prezent pe serverul de fișiere, iar vers se schimbă de fiecare dată când fișierul este modificat. Calea este un long long (64 de biți, vlong), iar vers este un unsigned long (32 de biți, ulong).

De ce este interesant acest lucru? Unul dintre motivele pentru care readdir() este atât de dificil de implementat este faptul că returnează membrul d_off din struct dirent, un singur off_t (32 de biți, cu excepția cazului în care aplicația este compilată cu suport pentru fișiere mari), pentru a marca intrarea în director unde o aplicație trebuie să continue citirea la următorul apel readdir(). Acest lucru funcționează bine atâta timp cât d_off este un simplu byteoffset într-un fișier plat de mai puțin de 232 de octeți, iar intrările de directoare existente nu sunt niciodată mutate – ceea ce nu este cazul multor sisteme de fișiere moderne (XFS, btrfs, ext3 cu indexuri htree). Markerul de loc pe 96 de biți Qid este mult mai util decât off_t pe 32 sau 64 de biți. Pentru un rezumat bun al problemelor implicate în implementarea readdir(), citiți TheodoreY. Ts’o’o’s excellent post on the topic to the btrfs mailing list.

Din punct de vedere al ingineriei software, Plan 9 union directories areheavenly. Fără whiteout-uri, eliminarea intrărilor duplicate, decalaje complicate ale directoarelor sau fuzionarea spațiilor de nume dincolo de directorul de nivel superior, implementarea este simplă și ușor de întreținut.Totuși, orice implementare practică a sistemelor de fișiere de uniune pentruLinux (sau orice alt UNIX) ar trebui să rezolve aceste probleme. Pentru scopurile noastre, directoarele de uniune Plan 9 servesc în primul rând ca sursă de inspirație.

Montaje de uniuneBSD

BSD implementează două forme de uniune: opțiunea"-o union"a comenziimount, care produce un director de uniune similar cu cel din Plan 9, și comandamount_unionfs, care implementează un sistem de fișiere de uniune mai complet, cu whiteouts și fuziune a întregului spațiu de nume. Ne vom concentra pe acesta din urmă.

Pentru acest articol, folosim două surse pentru detalii specifice de implementare: implementarea originală BSD union mount așa cum este descrisă în lucrarea USENIX din 1995Unionmounts in 4.4BSD-Lite și pagina de manual și codul sursă FreeBSD7.1 mount_unionfs mount_unionfs. AlteBSD pot varia.

Un director poate fi montat în uniune fie „sub”, fie „deasupra” unui director existent sau a unui montaj în uniune, atâta timp cât ramura superioară a unei uniuni inscriptibile este inscriptibilă. Sunt acceptate două moduri de whiteout-uri: fie un whiteout este întotdeauna creat atunci când un director este eliminat, fie este creat numai dacă o altă intrare de director cu acest nume există în prezent în ramura de sub ramura inscriptibilă. Sunt acceptate trei moduri de stabilire a proprietății ș i a modului de copiere a fișierelor. Cel mai simplu estetransparent, în care noul fișier păstrează același proprietar și mod ca și originalul. Modul masquerade face ca fișierele copiate să fie deținute de un anumit utilizator și suportă un set de opțiuni de montare pentru determinarea modului noului fișier.Modul traditional stabilește proprietarul la utilizatorul care a efectuat comanda union mount și stabilește modul în funcție de umask-ul din momentul union mount.

De fiecare dată când se deschide un director, se creează un director cu același nume pe stratul superior inscriptibil, dacă acesta nu există deja. Din lucrare:

Prin crearea agresivă a directoarelor umbră în timpul căutării, sistemul de fișiere de uniune evită necesitatea de a verifica și, eventual, de a crea un lanț de directoare de la rădăcina suportului până la punctul de copiere.Având în vedere că spațiul pe disc consumat de un director este neglijabil, crearea de directoare în momentul în care acestea au fost parcurse pentru prima dată a părut o alternativă mai bună.

Ca urmare, un "find /union" va avea ca rezultat copierea fiecărui director (dar nu și a intrărilor de directoare care indică non-directoare) în stratul inscriptibil. Pentru majoritatea imaginilor de sisteme de fișiere, acest lucru va utiliza o cantitate neglijabilă de spațiu (mai puțin decât, de exemplu, spațiul rezervat pentru utilizatorul root sau decât cel ocupat de nodurile neutilizate într-un sistem de fișiere de tip FFS).

Un fișier este copiat în stratul superior atunci când este deschis cu permisiunea de scriere sau când atributele fișierului sunt modificate. (Având în vedere că directoarele sunt copiate atunci când sunt deschise, se garantează că directorul care le conține există deja pe stratul cu drept de scriere). În cazul în care fișierul care urmează să fie copiat are mai multe legături dure, celelalte legături sunt ignorate ș i noul fișier are un număr de legături de unu. Acest lucru poate întrerupe aplicațiile care utilizează legături permanente și care se așteaptă ca modificările prin intermediul unui nume de legătură să apară atunci când se face referire la o altă legătură permanentă. Astfel de aplicații sunt relativ neobișnuite, dar nimeni nu a făcut un studiu sistematic pentru a vedea ce aplicații vor eșua în această situație.

Blanchetele sunt implementate cu un tip special de intrare în director, DH_WHT. Intrările de director whiteout nu se referă la nici un inode real, dar pentru o compatibilitate ușoară cu utilitarele existente în sistemul de fișiere, cum ar fi fsck, fiecare intrare de director whiteout include un număr de inode fals, numărul de inode rezervat pentru whiteout WINO. Sistemul de fișiere de bază trebuie să fie modificat pentru a suporta tipul de intrare de director whiteout. Noile directoare noi care înlocuiesc o intrare de tip whiteout sunt marcate ca fiind opace prin intermediul unui nou atribut inode „opaque”, astfel încât căutările să nu treacă prin ele (din nou, este nevoie de un sprijin minim din partea sistemului de fișiere de bază).

Înregistrările de directoare duplicate și whiteout-urile sunt tratate în implementarea în spațiul utilizatoruluireaddir(). La opendir()timpul opendir(), biblioteca C citește directorul dintr-o dată, elimină dublurile, aplică whiteouts și pune în cache rezultatele.

Montajele de uniuneBSD nu încearcă să se ocupe de modificările la ramurile de sub ramura de sus care poate fi scrisă (deși acestea sunt permise). Modul în care este tratat rename() nu este descris.

Un exemplu din pagina de manual mount_unionfs:

 The commands mount -t cd9660 -o ro /dev/cd0 /usr/src mount -t unionfs -o noatime /var/obj /usr/src mount the CD-ROM drive /dev/cd0 on /usr/src and then attaches /var/obj on top. For most purposes the effect of this is to make the source tree appear writable even though it is stored on a CD-ROM. The -o noatime option is useful to avoid unnecessary copying from the lower to the upper layer.

Un alt exemplu (observând că eu cred că controlul sursei este cel mai bine implementat în afara sistemului de fișiere):

 The command mount -t unionfs -o noatime -o below /sys $HOME/sys attaches the system source tree below the sys directory in the user's home directory. This allows individual users to make private changes to the source, and build new kernels, without those changes becoming visible to other users.

Linux union mounts

Ca și BSD union mounts, Linux union mounts implementează unirea sistemului de fișiere în stratul VFS, cu un sprijin minor din partea sistemelor de fișiere de bază pentru whiteouts și etichete de directoare opace. Există mai multe versiuni ale acestor patch-uri, scrise și modificate de Jan Blunck,Bharata B. Rao și Miklos Szeredi, printre alții.

Una versiune a acestui cod fuzionează doar directoarele de nivel superior,similar cu directoarele union Plan 9 și cu opțiunea BSD -o unionmount. Această versiune de union mounts, la care mă refer ca uniondirectories, este descrisă în detaliu în articolul recent al luiGoldwyn Rodrigues de la LWN și în recenta postare a lui Miklos Szeredi a unui set de patch-uri actualizate. Pentru restul acestui articol,ne vom concentra asupra versiunilor de union mount care îmbină spațiul de nume complet.

Linux union mount sunt în prezent în curs de dezvoltare activă. Acest articol descrie versiunea lansată de Jan Blunck împotriva Linux2.6.25-mm1, util-linux 2.13 și e2fsprogs 1.40.2. Seturile de patch-uri, în serie asquilt, pot fi descărcate de pe site-ul ftp al lui Jan:

Kernel patches: ftp://ftp.suse.com/pub/people/jblunck/patches/

Utilities: ftp://ftp.suse.com/pub/people/jblunck/jblunck/union-mount/

Am creat o pagină web cu link-uri către versiunile git ale patch-urilor de mai sus și o documentație de tip HOWTO la http://valerieaurora.org/union.

O uniune este creată prin montarea unui sistem de fișiere cu setul de stegulețe MS_UNION. (Seturile MS_BEFORE, MS_AFTER și MS_REPLACE sunt definite în baza de cod mount, dar nu sunt folosite în prezent). Dacă steagul MS_UNION este specificat, atunci sistemul de fișiere montat trebuie fie să fie numai pentru citire, fie să susțină whiteouts. În această versiune a montării în uniune, steagul de montare în uniune este specificat prin opțiunea „-o union” la mount. De exemplu, pentru a crea o uniune a două sisteme de fișiere de tip loopbackdevice, /img/ro și /img/rw, ar trebui să executați:

 # mount -o loop,ro,union /img/ro /mnt/union/ # mount -o loop,union /img/rw /mnt/union/

Care uniune de montaj creează unstruct union_mount:

 struct union_mount {atomic_t u_count;/* reference count */struct mutex u_mutex;struct list_head u_unions;/* list head for d_unions */struct hlist_node u_hash;/* list head for searching */struct hlist_node u_rhash;/* list head for reverse searching */struct path u_this;/* this is me */struct path u_next;/* this is what I overlay */ };

Așa cum este descris înDocumentation/filesystems/union-mounts.txt, „Toate structurile union_mount sunt stocate în memoria cache în două tabele hash, una pentru căutările din următorul nivel inferior al stivei de uniune și una pentru căutările inverse din următorul nivel superior al stivei de uniune.”

Whiteouts și directoarele opace sunt implementate cam în același mod ca în BSD. Sistemul de fișiere de bază trebuie să susțină în mod explicit whiteouts prin definirea operației .whiteout inode pentru directoare (în prezent, whiteouts sunt implementate numai pentru ext2, ext3 și tmpfs).Implementările ext2 și ext3 utilizează tipul de intrare de directoare whiteout, DT_WHT, care a fost definit în include/linux/fs.h de ani de zile, dar care nu a fost utilizat până acum în afara sistemului de fișiere Coda. Un număr rezervat de inod rezervat whiteout, EXT3_WHT_INO, este definit, dar nu este încă utilizat;intrările whiteout alocă în prezent un inod normal. Este definit un nou indicator inodeflag, S_OPAQUE, pentru a marca directoarele ca fiind opace.Ca și în BSD, directoarele sunt marcate ca fiind opace numai atunci când înlocuiesc o intrare whiteout.

Filierele sunt copiate în sus atunci când fișierul este deschis pentru scriere. Dacă este necesar, fiecare director din calea către fișier este copiat în ramura de sus (copiere la cerere a directoarelor). În prezent, copierea în sus este suportată numai pentru fișiere și directoare obișnuite.

readdir() este unul dintre cele mai slabe puncte ale actualei implementări. Este implementat în același mod ca și BSD union mountreaddir(), dar în kernel. Câmpul d_off este setat la decalajul din directorul subiacent curent,minus dimensiunile directoarelor anterioare. Intrările de directoare dindirectoarele de sub stratul superior trebuie să fie verificate în raport cu intrările anterioare pentru duplicat sau albire. Așa cum este implementat în prezent, fiecare apel de sistem readdir() (din punct de vedere tehnic, getdents()) citește toate intrările de directoare anterioare într-o memorie cache în kernel, apoi compară fiecare intrare care urmează să fie returnată cu cele care se află deja în memorie cache înainte de a o copia în memoria tampon a utilizatorului. Rezultatul final este că readdir() este complex, lent șipotențial alocă o mare cantitate de memorie în kernel.

O soluție este de a adopta abordarea BSD și de a face caching-ul, whiteout-ul și procesarea duplicată în spațiul utilizatorului. Bharata B. Raois proiectează suportul pentru union mount readdir() în glibc. (Standardul POSIX permite ca readdir() să fie implementat la nivelul libc dacă apelul de sistem din kernelul gol nu îndeplinește toate cerințele). Acest lucru ar muta utilizarea memoriei în aplicație și ar face ca memoria cache să fie persistentă. O altă soluție ar fi aceea de a face ca memoria cache din nucleu să fie persistentă într-un fel sau altul.

Sugestia mea este de a lua o tehnică de la BSD union mounts și de a o extinde: copierea proactivă nu doar a intrărilor de directoare pentru directoare, ci și a tuturor intrărilor de directoare din sistemele de fișiere inferioare, procesarea duplicatelor și a celor șterse, transformarea directoarei în opacă și scrierea acesteia pe disc. De fapt, la prima deschidere a directorului, prelucrați intrările din directoare pentru a găsi albii și duplicate, iar apoi scrieți pe disc „memoria cache” rezultată a intrărilor din directoare. Intrările de director care indică fișiere din sistemele de fișiere subiacente trebuie să indice cumva faptul că sunt intrări de tip „fall-through” (opusul unui whiteout – acesta solicită în mod explicit căutarea unui obiect într-un sistem de fișiere inferior). Un efect secundar al acestei abordări este că nu mai este nevoie de whiteout-uri.

O problemă care trebuie rezolvată cu această abordare este modul de reprezentare a intrărilor din directoare care indică spre sisteme de fișiere inferioare. Se prezintă o serie de soluții: intrarea ar putea indica un număr de nod rezervat, sistemul de fișiere ar putea aloca un nod pentru fiecare intrare, dar să-l marcheze cu un nou atribut de nod S_LOOKOVERTHERE, ar putea crea o legătură simbolică către o țintă rezervată, etc. Această abordare ar folosi mai mult spațiu pe sistemul de fișiere suprapus, dar toate celelalte abordări necesită alocarea aceluiași spațiu în memorie și, în general, memoria este mai scumpă decât discul.

O problemă mai puțin presantă a implementării actuale este aceea că numerele inod nu sunt stabile la pornire (a se vedea articolul anterior despre unirea sistemelor de fișiere pentru detalii despre motivul pentru care aceasta este o problemă).Dacă directoarele „fall-through” sunt implementate prin alocarea unui inode pentru fiecare intrare de director pe sistemele de fișiere subiacente, atunci numerele inod stabile vor fi un efect secundar natural. O altă opțiune este să se stocheze undeva o hartă de inode-uri persistente – într-un fișier din directorul de nivel superior sau, poate, într-un sistem de fișiere extern.

Legăturile dure sunt gestionate – sau, mai exact, nu sunt gestionate – în același mod ca și montările de uniune BSD. Din nou, nu este clar cât de multe aplicații depind de modificarea unui fișier prin intermediul unei căi cu legături dure și de vizualizarea modificărilor prin intermediul unei alte căi cu legături dure (spre deosebire de legăturile simbolice). Singura metodă pe care o pot găsi pentru a gestiona corect acest lucru este să păstrăm o memorie cache persistentă undeva pe disc a inodelor pe care le-am întâlnit cu mai multe legături dure.

Iată un exemplu de cum ar funcționa: Să zicem că pornim o copie pentru anodul 42 și aflăm că acesta are un număr de trei legături. Vom crea o intrare pentru baza de date a legăturilor permanente care include ID-ul sistemului de fișiere, numărul de nod, numărul de legături și numărul de nod al noii copii din sistemul de fișiere de nivel superior. Acesta ar putea fi stocat într-un fișier în format CSV, sau ca o legătură simbolică într-un director rezervat din directorul rădăcină (de exemplu, „/.hardlink_hack/<fs_id>/42„, care este o legătură cu „<new_inode_num> 3„), sau într-o bază de date reală. De fiecare dată când deschidem un anod pe un sistem de fișiere subiacent, îl căutăm în baza noastră de date de legături permanente; dacă există o intrare, decrementăm numărul de legături și creăm o legătură permanentă cu anodul corect pe noul sistem de fișiere. Când toate căile sunt găsite, numărul de legături scade la unu și intrarea poate fi ștearsă din baza de date. Partea bună a acestei abordări este că volumul de costuri suplimentare este limitat și va dispărea în întregime atunci când toate căile de acces la nodurile relevante au fost găsite. Cu toate acestea, acest lucru introduce totuși o cantitate semnificativă de complexitate posibil inutilă; implementarea BSD arată că multe aplicații vor funcționa cu plăcere cu un comportament hardlink nu tocmai corect din punct de vedere POSIXLY.

În prezent, rename() de directoare între ramuri returnează EXDEV, eroarea pentru încercarea de redenumire a unui fișier între diferite sisteme de fișiere. Spațiul utilizatorului se ocupă de obicei de acest lucru în mod transparent (deoarece trebuie deja să se ocupe de acest caz pentrudirectoare din sisteme de fișiere diferite) și revine la copierea conținutului directorului unul câte unul. Implementarea rename()recursivă a directoarelor între ramuri în kernel nu este o idee strălucită din aceleași motive ca și redenumirea între sisteme de fișiere regulate; probabil că returnarea EXDEV este cea mai bună soluție.

Din punct de vedere al ingineriei software, union mounts pare a fi un compromis rezonabil între caracteristici și ușurința de întreținere. Cea mai mare parte a modificărilor VFS sunt izolate în fs/union.c, un fișier de aproximativ 1000 de linii. Aproximativ 1/3 din acest fișier este implementarea readdir() din nucleu, care va fi aproape sigur înlocuită cu altceva înainte de o eventuală fuziune.Modificările aduse sistemelor de fișiere subiacente sunt destul de minime și sunt necesare doar pentru sistemele de fișiere montate ca ramuri inscriptibile. Principalul obstacol în calea fuzionării acestui cod este implementarea readdir(). Altfel, mentenanții sistemelor de fișiere au fost în mod vizibil mai pozitivi în legătură cu union mount-urile decât cu orice altă implementare de unionare.

Un rezumat frumos al union mount-urilor poate fi găsit în slide-urile union mount-ului lui Bharata B. Rao pentru FOSS.IN .

Continuare

În următorul articol, vom trece în revistă unionfs și aufs, și vom compara diferitele implementări ale unionării sistemelor de fișiere pentru Linux. Rămâneți la curent!

Întrări în index pentru acest articol
Kernel Filesystems/Union
Kernel Montaje de uniune
Articole invitate Aurora (Henson), Valerie

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.