Sunteți pe pagina 1din 227

ADRIAN COLEA

IOSIF IGNAT

ZOLTN SOMODI

SISTEME DE OPERARE
CHESTIUNI TEORETICE I PRACTICE

U.T.PRES CLUJ-NAPOCA, 2007

Coperta: Ovidiu Muraru

Cuprins
Prefaa ........................................................................................................v 1. Sistemul de fiiere n Linux........................................................ 1 1.1. Structura sistemului de fiiere.............................................................1 1.2. Tipuri de fiiere ...................................................................................2 1.3. Structura unei partiii Linux................................................................6 1.4. Localizarea datelor unui fiier.............................................................6 1.5. Drepturi de acces.................................................................................7 1.6. Comenzile ls i chmod ......................................................................10 1.7. Probleme ...........................................................................................12 2. Fiiere de comenzi n Linux ..................................................... 14 2.1. Linia de comand ..............................................................................14 2.2. Variabile............................................................................................15 2.3. Fiiere de comenzi.............................................................................18 2.4. Redirectarea fiierelor standard de intrare i ieire...........................27 2.5. Exemple de fiiere de comenzi .........................................................28 2.6. Probleme ...........................................................................................29 3. Apeluri sistem pentru lucrul cu fiiere i directoare n Linux ........................................................................................................ 32 3.1. Descriptori de fiier...........................................................................32 3.2. Apeluri sistem pentru lucrul cu fiiere ..............................................32 3.3. Funcii pentru lucrul cu directoare .................................................... 42 3.4. Exemple.............................................................................................43 3.5. Probleme ...........................................................................................46 4. Sistemul de fiiere NTFS .......................................................... 48 4.1. Prezentare general ...........................................................................48 4.2. Structura unei partiii NTFS.............................................................48 4.3. Tipuri de fiiere i drepturi de acces n NTFS ..................................51 4.4. Funcii API Win32 pentru sistemul de fiiere NTFS ........................53 4.5. Fiiere cu seturi multiple (alternative) de date..................................61 4.6. Exemple.............................................................................................63 4.7. Probleme ...........................................................................................64 5. Apeluri sistem pentru lucrul cu procese n Linux ................. 66 5.1. Procese ..............................................................................................66 5.2. Grupuri de procese ............................................................................67
i

5.3. Programe i procese .......................................................................... 68 5.4. Apelurile sistem fork i exec ............................................................. 70 5.5. Apelurile sistem wait i waitpid........................................................ 72 5.6. Apelul sistem exit.............................................................................. 74 5.7. Exemple ............................................................................................ 76 5.8. Probleme ........................................................................................... 77 6. Thread-uri n Linux .................................................................... 80 6.1. Specificaia PTHREADS .................................................................. 80 6.2. Crearea unui thread ........................................................................... 80 6.3. Identitatea unui thread....................................................................... 82 6.4. Terminarea execuiei unui thread...................................................... 82 6.5. Ateptarea terminrii unui thread...................................................... 86 6.6. Stabilirea atributelor unui thread ...................................................... 87 6.7. Relaia dintre thread-uri i procese ................................................... 89 6.8. Probleme ........................................................................................... 91 7. Procese i thread-uri n Windows 2000 ................................... 94 7.1. Prezentare general............................................................................... 94 7.2. Funcii Win32 API pentru procese i thread-uri ...................................... 95 7.3. Planificare i prioriti........................................................................... 99 7.4. Exemple .......................................................................................... 101 7.5. Probleme ......................................................................................... 103 8. Fiiere PIPE n Linux ............................................................... 105 8.1. Principiile comunicrii prin fiiere pipe ......................................... 105 8.2. Fiiere pipe cu nume ....................................................................... 107 8.3. Fiiere pipe fr nume sau anonime................................................ 109 8.4. Comunicare unidirecional i bidirecional ................................. 111 8.5. Redirectarea STDIN i STDOUT spre fiiere pipe......................... 113 8.6. Probleme ......................................................................................... 117 9. Fiiere PIPE n Windows ......................................................... 118 9.1. Prezentare general a fiierelor pipe n Windows........................... 118 9.2. Funcii Win32 pentru utilizarea pipe-urilor anonime ..................... 118 9.3. Pipe-uri cu nume ............................................................................. 120 9.4. Exemple .......................................................................................... 123 9.5. Probleme ......................................................................................... 132 10. Comunicarea prin semnale n Linux .................................... 133 10.1. Funcionalitatea semnalelor .......................................................... 133
ii

10.2. Tratarea semnalelor....................................................................... 135 10.3. Mascarea semnalelor..................................................................... 139 10.4. Trimiterea semnalelor ...................................................................141 10.5. Alte apeluri sistem legate de semnale ...........................................143 10.6. Exemple.........................................................................................145 10.7. Probleme .......................................................................................148 11. Comunicarea prin memorie partajat i cozi de mesaje n Linux............................................................................................. 149 11.1. Comunicarea ntre procese prin memorie partajat ......................149 11.2. Comunicarea ntre procese prin cozi de mesaje............................157 11.3. Exemple.........................................................................................164 11.4. Probleme .......................................................................................168 12. Sincronizarea prin semafoare n Linux ............................... 170 12.1. Sincronizarea cu semafoare........................................................... 170 12.2. Crearea semafoarelor ....................................................................173 12.3. Operaii pe semafoare ...................................................................175 12.4. Controlul seturilor de semafoarelor ..............................................180 12.5. Comenzi shell pentru seturile de semafoare..................................183 12.6. Exemple.........................................................................................184 12.7. Probleme .......................................................................................189 13. Sincronizarea thread-urilor n Linux.................................... 192 13.1. Prezentare general .......................................................................192 13.2. Lacte ............................................................................................193 13.3. Variabile condiionale ...................................................................197 13.4. Execuia unei funcii o singur dat ..............................................201 13.5. Probleme .......................................................................................203 14. Planificarea thread-urilor n Linux ....................................... 205 14.1. Planificarea thread-urilor ..............................................................205 14.2. Prioritatea i politica de planificare ..............................................205 14.3. Domeniul de planificare i domeniul de alocare...........................211 14.4. Proprietatea de motenire..............................................................212 14.5. Exemplu de utilizare ..................................................................... 213 14.6. Probleme .......................................................................................215 Bibliografie .................................................................................. 217

iii

Prefaa
Resursele unui sistem de prelucrare automat a datelor sunt: a) resurse fizice: procesorul central, procesoarele de I/E, memoria intern i extern, dispozitivele periferice; b) resursele logice: fiierele. Un sistem de operare este o colecie de programe de control rezidente n memoria intern sau extern, care asigur: a) gestionarea resurselor fizice i logice ale sistemului de calcul; b) o serie de funciuni de asisten, avnd obiectivul de a asigura funcionarea performant a sistemului. Programele de control sunt programe care conin instruciuni privilegiate, cum ar fi: mascarea ntreruperilor, protecia memoriei etc. Ele se execut n mod nucleu (master, supervizor). Programele de control sunt organizate ntr-o ierarhie cu dou niveluri: a) nivelul fizic, care primete controlul prin intermediul sistemului de ntreruperi instalat, avnd proceduri de intrare i ieire din sistemul de operare, proceduri de tratare a ntreruperilor etc. b) nivelul logic, care cuprinde proceduri pentru alocarea resurselor la procese, planificarea proceselor pentru execuie, comunicarea i sincronizarea proceselor, etc. Programele de serviciu nu conin instruciuni privilegiate i se execut n mod aplicaie (slave, utilizator). Ele conin: editoare de text, compilatoare, editorul de legturi, depanatoare etc. Sistemul de operare constituie interfaa ntre utilizator i hardware. Datorit acestui lucru, interpretorul de comenzi, dei este un program executat la fel ca oricare program utilizator, este considerat ca fcnd parte din sistemul de operare. Termenul de sistem de operare a nceput s fie folosit o dat cu crearea unor programe de control, care permiteau nlnuirea automat a lucrrilor, multiprogramarea, prelucrarea conversaional, etc. Sistemele de operare au evoluat n timp, rolul lor crescnd de la o generaie la alta a sistemelor de calcul.

nelegnd prin proces un program secvenial n execuie, funciile generale ale unui sistem de operare sunt: a) Operaii asupra proceselor: creare, execuie, terminare, mutare dintro zon de memorie n alta; b) Controlul progresului execuiei proceselor, altfel spus, asigurarea c fiecare proces acceptat logic se execut normal i c nici un alt proces s nu poat bloca la infinit progresul celorlalte; c) Alocarea resurselor fizice diferitelor procese; d) Accesul la resursele logice; e) Rezolvarea unor probleme legate de apariia unor erori hardware i software; f) Protecia, controlul accesului i securitatea informaiei; g) Sincronizarea proceselor i comunicarea ntre procese; h) Asistarea operatorului, utilizatorului, personalului de exploatare i ntreinere. Noiunile fundamentale cu care lucreaz un sistem de operare sunt procesele i fiierele. Un sistem de operare are n structura sa module de gestionare a proceselor (implicit i a procesorului), a memoriei, a fiierelor i a dispozitivelor periferice vzute ca fiiere speciale. De la introducerea cursului de Sisteme de operare (anul 1981), n planul de nvmnt al seciei de Calculatoare, nfiinat (1977) n cadrul Facultii de Electrotehnic din U.T.C.N., la lucrrile de laborator s-au introdus noiuni legate de principiile generale care au stat la baza proiectrii unui sistem de operare, cu exemplificare pe cele utilizate la noi n ar: SIRIS, SFDX-18, CP-M, DOS, RSX-11M, UNIX, Linux, Windows. Lucrarea de fa prezint ntr-o ordine didactic noiuni teoretice i practice, legate de concepte din teoria sistemelor de operare, cu exemplificare din Linux i Windows 2000. Astfel sunt tratate: a) comenzile Linux i fiierele de comenzi; b) structura sistemelor de fiiere Linux i Windows; c) apeluri de sistem Linux i Windows legate de gestionarea fiierelor; d) apeluri de sistem Linux i Windows legate de gestionarea proceselor i a threadurilor; e) comunicarea ntre procese i a mecanismelor de sincronizare ntre procese i threaduri.

vi

Lucrarea se adreseaz studenilor din anul II, de la secia de Calculatoare, din cadrul Facultii de Automatic i Calculatoare din U.T.C.N., ca ndrumtor de laborator, dar i ca material didactic pentru unele capitole din curs la disciplina de Sisteme de operare. Cei interesai, pot aprofunda aceste noiuni urmnd cursul i laboratorul disciplinei de Proiectarea sistemelor de operare din anul IV. Autorii aduc mulumiri editurii U.T.Press, pentru multiplicarea lucrrii ntro form grafic corespunztoare. 18 ianuarie 2007 Autorii

vii

1. Sistemul de fiiere n Linux


Scopul lucrrii Lucrarea descrie modul de organizare a sistemului de fiiere i caracteristicile fiierelor n sistemul de operare Linux. De asemenea, ea prezint cteva dintre comenzile de manipulare a fiierelor.

1.1. Structura sistemului de fiiere


Sistemul de fiiere este o component important a unui sistem de operare. n Linux, el este caracterizat prin trei aspecte: structura ierarhic, independena fa de hardware i o mare flexibilitate. Structura ierarhic este organizat sub forma unui arbore cu un director rdcin (root). Directorul rdcin poate conine fiiere ordinare, fiiere de tip legtur sau alte directoare, numite subdirectoare. Subdirectoarele sunt noduri n arbore, iar fiierele sunt frunze. Independena fa de hardware rezult din faptul c fiierele sunt vizibile i accesibile utilizatorului ca o succesiune de octei. Flexibilitatea se bazeaz pe posibilitatea de a monta (insera) sau demonta, la orice nivel n ierarhia de fiiere, noi structuri arborescente de directoare i fiiere. Structura standard a sistemului de fiiere Linux conine, pe lng alte directoare i urmtoarele: bin, boot, dev, etc, home, lib, proc, root, sbin, tmp, usr, var.

Figura 1. Structura arborescent a sistemului de fiiere

Sistemul de fiiere n Linux

Structura arborescent a sistemului de fiiere d posibilitatea utilizatorilor s creeze i s gestioneze un numr mare de fiiere. Nu exist limitare a numrului de noduri, eventualele restricii fiind impuse de hardware. Referirea la un fiier se face prin specificarea numelui su precedat de un ir de caractere numit cale, ir prin care se indic poziia n arbore a fiierului cobornd pe nivelurile arborelui, fie pornind de la directorul rdcin (cale absolut sau complet), fie de la cel curent (cale relativ). Prin director curent se nelege directorul la care se raporteaz la un moment dat o anumit aplicaie. Calea unui fiier poate fi de orice lungime, caracterul '/' fiind folosit pentru delimitarea numelor de directoare din cale. Caracterul '/', pe lng rolul de separator de directoare i fiiere, identific i directorul rdcin. n ceea ce privete specificarea cii, precum i unele caracteristici ale unui fiier sau director, pot fi precizate cteva diferene fa de sistemul de operare Windows, precum: caracterul '\' este nlocuit n Linux cu '/'; n Linux se face distincie ntre litere mici i litere mari; un fiier executabil n Linux este un fiier ce are setat dreptul de execuie (indiferent de extensia sa); modul de stabilire i organizare a drepturilor de acces asupra unui fiier difer n cele dou sisteme de operare, dei interpretarea lor este foarte asemntoare; un fiier text n Linux are marcat sfritul de linie ('\n') printr-un singur caracter (cel cu codul 10), n timp ce n Windows prin dou caractere (cu codurile 13 i 10).

1.2. Tipuri de fiiere


n Linux se deosebesc patru tipuri de fiiere: ordinare (obinuite), pipe, speciale i directoare. Din punct de vedere al sistemului de operare, un fiier este un ir de octei de o anumit lungime. Accesul la datele din fiiere se face n mod aleator, adic la orice poziie (deplasament) la un moment dat. Fiierele ordinare sunt pstrate pe disc. Un fiier ordinar este creat de un proces. El poate fi text sau binar (de exemplu un fiier executabil). Fiecare fiier are ataat un numr, care este interpretat ca index ntr-o list de structuri (index node), pstrat ntr-o zon rezervat pe partiia care conine sistemul de fiiere. I-node-ul unui fiier conine toate informaiile pe care sistemul de operare le folosete pentru descrierea i gestionarea fiierului, cu excepia numelui su.

Sisteme de operare. Chestiuni teoretice i practice

Fiierele pipe sunt fiiere folosite pentru comunicarea ntre procese, pe baza principiului FIFO (First-In, First-Out). Fiierele speciale sunt fiiere ataate dispozitivelor de I/E (de tip bloc sau caracter), rolul lor fiind acela de a oferi o interfa de acces la aceste dispozitive similar cu cea de lucru cu fiierele ordinare. Fiierele speciale sunt localizate de obicei n directorul /dev i modeleaz dispozitive precum: discuri, benzi magnetice, terminale, imprimante, mouse etc. De exemplu, pentru fiecare partiie a unui hard disc exist cte un fiier special. Un fiier special deine un i-node, care ns nu refer blocuri de date pe disc. n schimb, acest i-node conine un numr de dispozitiv, care este folosit ca index ntr-o tabel intern sistemului de operare de proceduri pentru dispozitive periferice. Pentru identificarea fiecrui dispozitiv se folosesc dou numere: major (identific tipul dispozitivului) i minor (identific numrul dispozitivului de tipul dat). Folosirea dispozitivelor n aceast manier confer avantajul tratrii uniforme. Din punct de vedere utilizator nu exist nici o diferen ntre lucrul cu fiiere ordinare i cele speciale. Exemplul de mai jos ilustreaz comparativ acest lucru:
cp prg.c /home/student/prg1.c cp prg.c /dev/lp0 # copiere simpla # listare la imprimanta

Un director face legtura ntre numele fiierelor i locul unde acestea sunt memorate pe disc. El nu conine efectiv fiierele care i aparin, ci doar referinele la acestea, sub forma unei succesiuni de structuri numite intrri n director. O intrare n director are cel puin dou cmpuri, i anume: numele fiierului i numrul su de i-node. Orice director are n mod implicit dou intrri create automat odat cu crearea directorului. Acestea poart numele . i .. i conin referine spre i-nodul directorului curent i, respectiv spre printele directorului curent. Un director ce conine doar cele dou intrri amintite se consider gol, putnd fi ters cu ajutorul comenzilor de tergere. Fiecare fiier are alocat un singur i-node care conine, printre altele: 1. identificatorul utilizatorului care este proprietarul fiierului; 2. tipul fiierului (ordinar, director, pipe sau special); 3. drepturile utilizatorilor de acces la fiier; 4. data i timpul ultimului acces i al ultimei modificri a fiierului, data i timpul ultimei modificri efectuate asupra i-node-ului; 5. numrul de legturi fizice ale fiierului (a se vedea comanda unlink); 6. adresele blocurilor de pe HDD ce conin datele fiierului; 7. lungimea fiierului n octei i n blocuri de octei alocate pe disc.
3

Sistemul de fiiere n Linux

O caracteristic important a sistemului de fiiere din Linux este noiunea de legtur (link). Scopul legrii este acela de partajare a fiierelor (sau directoarelor) ntre diferite directoare. Legarea unui fiier are ca efect posibilitatea referirii acelui fiier prin ci i, eventual, nume diferite n arborele de fiiere i directoare. n Linux exist dou tipuri de legturi: fizic (hard) i simbolic. Pentru a nelege modul de realizare i funcionare a lor, trebuie s sintetizm informaiile prezentate pn aici i anume, faptul c fiecare fiier are asociat un singur i-node, iar numele fiierului i numrul su de i-node sunt localizate ntr-o intrare din directorul n care a fost creat fiierul. Numrul de i-node este un identificator unic al fiierului n cadrul sistemului i nscrierea sa n cadrul unei intrri dintr-un director poate fi interpretat ca o referire sau o legtur spre fiier din acel director. Aceast interpretare st la baza nelegerii legturii fizice, care se realizeaz prin alocarea unei noi intrri, fie n directorul iniial, fie ntr-unul diferit, intrare n director care va conine numrul de i-node al fiierului legat. Numele intrrii respective poate fi identic sau diferit de cel al altei intrri care refer acelai fiier. Evident, dou intrri diferite din cadrul aceluiai director, care refer acelai fiier (conin acelai numr de i-node) trebuie s aib nume diferite. n urma stabilirii unei legturi fizice spre un fiier, acesta va fi referit prin numrul su de i-node din intrri de director diferite. Astfel, acelai fiier este accesibil pe drumuri diferite i, eventual, prin nume diferite n arborele de fiiere i directoare. Pstrarea numrului de legturi fizice spre un fiier n inode-ul acestuia este necesar pentru a nu se terge fizic fiierul i i-node-ul lui dect n momentul ajungerii acestui numr la zero, adic n momentul tergerii ultimei legturi fizice spre acel fiier. n caz contrar sistemul de fiiere ar fi adus ntr-o stare inconsistent. Dac n cazul legturii fizice, nu se creeaz un fiier nou, ci numai o intrare nou n director, n cazul legturii simbolice ambele lucruri se ntmpl, adic n directorul n care se creeaz legtura se aloc o intrare care va referi un inode nou, alocat unui nou fiier. Acest nou fiier este unul special, de tip legtur simbolic i este gestionat n mod automat i transparent pentru utilizator de ctre sistemul de operare. Coninutul unui fiier legtur simbolic este calea spre fiierul legat (referit) i accesarea lui are ca efect accesarea fiierului legat. Fiierul legtur simbolic nu conine informaii despre localizarea fizic a fiierului legat (i-node-ul su), ci doar despre o cale spre acel fiier, astfel nct tergerea acelei ci face ca referina din fiierul legtur simbolic s devin invalid, fr a aprea ns inconsistene n sistemul de fiiere. Avantajul legturilor simbolice este acela c nu este necesar memorarea numrului lor n i-node i, n plus, coninnd o cale de tip ir de caractere n arborele de fiiere i directoare, pot referi fiiere aflate pe sisteme
4

Sisteme de operare. Chestiuni teoretice i practice

de fiiere montate, fie ele chiar localizate pe o alt main, lucru care este imposibil n cazul legturilor fizice, la care un numr de i-node are semnificaie i relevan doar n cadrul sistemului de fiiere de pe o aceeai partiie. Pentru stabilirea unei legturi spre un fiier existent se poate folosi comanda ln, a crei sintax este urmtoarea, lipsa opiunii -s indicnd crearea unei legturi fizice, iar prezena ei indicnd crearea unei legturi simbolice:
ln [-s] nume_cale_veche nume_cale_noua

Nu este permis efectuarea de legturi fizice la directoare dect administratorului sistemului i aceasta doar n anumite cazuri n funcie de configuraia sistemului. tergerea unei legturi se poate realiza prin comanda unlink. O alt caracteristic important a sistemului Linux este posibilitatea de a monta un ntreg arbore de fiiere ntr-un director din ierarhia sistemului (arborelui) de fiiere principal. Sistemul de operare Linux recunoate un singur director rdcin, dar este posibil ca fiierele s se gseasc pe mai multe suporturi fizice sau logice, fiecare volum avnd un sistem de fiiere arborescent propriu. Este posibil suprapunerea rdcinii unui astfel de sistem de fiiere peste un director al sistemului de fiiere recunoscut de sistem. Aceast operaie se numete montare i poate fi realizat prin comanda mount, dar n general doar de ctre superuser. Coninutul directorului n care se realizeaz montarea devine invizibil pn la efectuarea operaiei de demontare, motiv pentru care se recomand ca el s fie gol. Dup montare, calea la punctul de montare prefixeaz orice acces la un fiier sau director de pe sistem de fiiere montat. De exemplu:
mount /dev/hda1 /mnt/hdd -r

monteaz doar pentru citire (read-only) n directorul /mnt/hdd prima partiie a primului HDD al unui sistem. Pentru a accesa o dischet aflat n unitatea 0, comanda de montare poate arta:
mount /dev/fd/0 /mnt/fd0

Dac montarea nu mai este necesar se poate proceda la operaia de demontare i eliberarea punctului de montare prin comanda umount. De exemplu, pentru demontarea dischetei comanda este:
umount /dev/fd/0

Sistemul de fiiere n Linux

1.3. Structura unei partiii Linux


Orice partiie care conine un sistem de fiiere Linux are n principiu urmtoarea structur: Superbloc Lista de inode-uri Blocuri de date Zona de swapping

Figura 2. Dispunerea informaiilor pe o partiie Linux

Superblocul conine, printre altele, urmtoarele informaii: dimensiunea sistemului de fiiere; numrul blocurilor libere din sistemul de fiiere; tablou cu blocuri disponibile din sistemul de fiiere; indexul urmtorului bloc liber din tabloul blocurilor libere; dimensiunea listei de i-node-uri; numrul total de i-node-uri libere din sistemul de fiiere; tablou cu i-node-uri libere din sistemul de fiiere; indexul urmtorului i-node liber din tabloul i-node-urilor libere; indicator (un bit) al modificrii superblocului. Superblocul este ncrcat n memorie la pornirea sistemului, sistemul de operare asigurnd pe durata funcionrii sale corespondena dintre superblocul din memorie i cel de pe HDD. Deoarece exist intervale de timp n care superblocul din memorie conine modificri ce nu au fost nc scrise pe HDD, se recomand oprirea normal a sistemului. Distrugerea superblocului poate cauza imposibilitatea accesului la sistemul de fiiere. Zona de swapping servete pentru salvarea temporar a unor zone din memoria intern a calculatorului pe HDD pentru crearea de spaiu pentru aplicaia activ.

1.4. Localizarea datelor unui fiier


Pentru a accesa un fiier din directorul curent, sistemul de operare citete fiecare intrare din directorul respectiv i compar numele fiierului cu numele intrrii, pn cnd fiierul este gsit. Dac fiierul este prezent, sistemul extrage din intrarea din director numrul de i-node i l folosete ca index n lista (tabela) i-node-urilor de pe disc, pentru a localiza structura asociat fiierului i a o aduce n memorie. n i-node se gsesc toate informaiile asociate fiierului, inclusiv adresele blocurilor alocate fiierului, deci prin i-node-ul unui fiier sistemul are acces la datele fiierului. I-nodeul este citit de pe HDD i ncrcat n tabela i-node-urilor din memorie, care
6

Sisteme de operare. Chestiuni teoretice i practice

conine toate i-node-urile fiierelor deschise. Tabela este gestionat intern de sistemul de operare. Localizarea unui fiier care nu e situat n directorul curent este un lucru puin mai dificil. De exemplu, pentru calea absolut /home/student/fis sistemul ncarc iniial directorul rdcin, al crei i-node este cunoscut, fiind stabilit la formatarea partiiei de obicei i-node-ul cu numrul 2. Dup aceasta, caut printre intrrile directorului rdcin cea cu numele home i gsete i-node-ul asociat ei. I-node-ul este adus n memorie i se determin blocurile de pe disc care conin directorul /home. Intrrile acestui director sunt citite i comparate cu irul student. O dat gsit intrarea, se extrage i-node-ul pentru directorul /home/student i se citesc de pe disc blocurile sale de date. n final, se caut irul fis printre intrrile directorului /home/student i se determin i-node-ul corespunztor fiierului fis i, implicit, blocurile sale de date. n general, utilizarea cilor relative poate fi mai convenabil nu numai pentru utilizator, dar i pentru sistem, pentru a se reduce numrul operaiile de cutare i accesurile la HDD.

1.5. Drepturi de acces


Protecia accesului la fiiere ntr-un sistem cu mai muli utilizatori este un aspect important. Linux este un sistem de operare multiuser, ceea ce nseamn c face distincie ntre utilizarea calculatorului de ctre diferii utilizatori. Identificarea unui utilizator i permiterea accesului la resursele sistemului se face prin autentificare (procesul de login), pe baza unui nume stabilit anterior n cadrul sistemului de operare i a parolei asociate. Fiecrui utilizator i este asociat un numr unic de identificare n cadrul sistemului, numit UID (User IDentifier). Utilizatorii pot fi grupai n grupuri, fiecare grup avnd, de asemenea, asociat un identificator unic, numit GID (Group IDentifier). Pentru vizualizarea identificatorilor de utilizator i grup i a altor informaii legate de acetia se pot studia fiierele /etc/passwd i, respectiv /etc/groups. n Linux fiecare fiier conine n i-node-ul asociat identificatorul utilizatorului (UID) cruia i aparine acel fiier i identificatorul de grup (GID) al proprietarului. n momentul n care un fiier este creat, el primete ca semn de recunoatere UID-ul celui care l-a creat, recunoscut ca proprietar al fiierului. n i-node mai exist un cmp care conine trei seturi de cte trei bii. Biii respectivi descriu permisiunile de acces la fiier ale proprietarului, ale utilizatorilor din acelai grup principal din care face
7

Sistemul de fiiere n Linux

parte proprietarul i respectiv, a celorlali utilizatori din sistem. Cei trei bii din fiecare set corespund drepturilor de citire (Read), de scriere (Write) i de execuie (eXecute) i indic dac un proces (ce se execut n numele unui anumit utilizator) poate efectua (bitul setat la 1) sau nu (bitul setat la zero) operaia corespunztoare asupra fiierului respectiv. Procesele care se execut n numele administratorului de sistem, utilizator cunoscut n Linux sub numele de root, au acces nerestricionat la toate fiierelor din sistem, indiferent de drepturile de acces ale acestora. Pentru fiiere ordinare semnificaia drepturilor este evident. Pentru directoare, dreptul de citire nseamn drept de consultare (de listare) a directorului (e permis, de exemplu comanda ls). Dreptul de scriere nseamn c n director se pot crea noi fiiere, terge fiiere, se poate monta un sistem de fiiere, se pot aduga sau terge legturi. Un director care are drept de execuie poate fi vizitat n timpul cutrii unui fiier, adic se poate trece prin el cutndu-se una dintre componentele cii spre fiier. Pentru fiiere speciale i pipe dreptul de citire i scriere semnific capacitatea de a executa apelurile sistem read i respectiv, write. Dreptul de execuie nu este important n acest caz. Drepturile de acces ale unui fiier sunt pstrate pe 16 bii cu urmtoarea semnificaie:
Tabelul 1. Drepturi de acces

Bitul
0-3 4 5 6

7-9 10-12 13-15

Semnificaie Tipul fiierului. Seteaz ID-ul utilizator n timpul execuiei (suid). Seteaz ID-ul grupului n timpul execuiei (sgid). Bitul sticky. Era folosit pentru a indica pstrarea n memorie a programului corespunztor fiierului executabil cu bitul sticky setat i dup terminarea sa. Actualmente, este folosit n Linux doar pentru directoare, permind doar operaia de append asupra lor. Drept de citire, scriere, execuie pentru proprietar. Drept de citire, scriere, execuie pentru grup. Drept de citire, scriere, execuie pentru alii.

Bitul sticky Dac acest bit este poziionat pentru un director, orice fiier sau subdirector din acel director poate fi ters sau redenumit numai de proprietarul fiierului sau al subdirectorului sau de root.
8

Sisteme de operare. Chestiuni teoretice i practice

Biii suid i sgid La intrarea n sesiune a unui utilizator, toate procesele (aplicaiile) sale au asociate, identificatorul su (UID-ul) i cel al grupului principal (GID-ul) din care utilizatorul face parte, ambele informaii fiind preluate din fiierul /etc/passwd. Cei doi identificatori menionai se numesc UID real i GID real, deoarece sunt reprezentativi pentru utilizatorul real (persoana care a deschis sesiunea). Fiecrui proces i mai sunt asociai nc doi identificatori numii UID efectiv i GID efectiv. Identificatorii efectivi sunt folosii de ctre sistemul de operare la verificarea drepturilor de acces. n mod normal, identificatorii efectivi i cei reali ai unui proces sunt identici, n afara cazului n care procesul execut un fiier executabil asupra cruia are drepturi de execuie obinuite, dar care are i bitul suid sau sgid poziionat. n acest caz, procesul creat va avea identificatorul efectiv (UID sau GID, corespunztor setrii biilor suid sau sgid) egal cu cel al proprietarului fiierului executabil i diferit de cel real, care a rmas nemodificat. Procesul posed, ct timp execut codul din fiierul executabil, aceleai drepturi ca i proprietarul fiierului. Dac, de exemplu, acest proprietar este root-ul, procesul posed temporar toate drepturile asupra sistemului. Dar, singura aciune care o poate realiza este cea definit n programul executat i probabil determinat de proprietarul fiierului, n acest caz root-ul. ndat ce procesul termin de executat codul unui astfel de program, UID-ul efectiv (sau GUID-ul efectiv), redevine egal cu cel real, iar procesul continu s se execute cu drepturile iniiale. Uneori se dorete ca un utilizator comun s aib, pentru un timp, privilegiile altui utilizator. Fie o configuraie ca in Figura 3, unde fiierul Fis are ca proprietar pe utilizatorul U1 i dreptul de scriere setat doar pentru el. Pentru ca utilizatorul U2 s poat modifica datele din acest fiier, U1 furnizeaz un program n fiierul executabil Prg, care are setat bitul suid, iar utilizatorul U2 are dreptul de execuie asupra lui. n aceast situaie, n timpul execuiei lui Prg, U2 are UID-ul efectiv acelai cu al lui U1 i astfel el poate modifica datele din Fis, dar numai n cadrul oferit de programul Prg. Drepturi
-rwx------rws--x--x

Proprietar
U1 U1

Grup
G1 G1

Nume fiier
Fis Prg

Figura. 3. Accesul controlat la un fiier

Sistemul de fiiere n Linux

O exemplificare a situaiei descrise mai sus este cea a schimbrii propriei parole de ctre un utilizator folosind comanda passwd (care aparine rootului, are drepturi de execuie pentru toat lumea i bitul suid setat), comand cu care se modific fiierul /etc/passwd (respectiv /etc/shadow, pe unele sisteme), asupra cruia n mod normal nu pot fi operate modificri. Bitul sgid este folosit n aceeai manier pentru grup. Un fiier care are aceti bii poziionai afieaz ca rezultat al comenzii "ls -l" pe poziia lui 'x' pentru proprietar litera 's'. Pentru setarea biilor suid i sgid se folosete comanda chmod. Proprietarul fiierului executabil i superuserul pot modifica aceti bii. Se poate spune c 's' este o extensie a permisiunii 'x' n contextul discutat. Algoritmul folosit de sistem pentru a determina dac un proces are sau nu dreptul de a efectua o operaie (citire, scriere sau execuie) asupra unui fiier dat este urmtorul: 1. Dac UID-ul efectiv este 0, permisiunea este acceptat (utilizatorul efectiv fiind root-ul); 2. Dac UID-ul efectiv al procesului i UID-ul proprietarului fiierului se potrivesc, se decide permisiunea din biii proprietarului; 3. Dac GID-ul efectiv al procesului i GID-ul proprietarului fiierului se potrivesc, se decide permisiunea din biii grupului; 4. Dac nici UID i nici GID nu se potrivesc atunci, se decide permisiunea din ultimul set de trei bii.

1.6. Comenzile ls i chmod


Comanda ls afieaz coninutul unui director. Sintaxa ei este ilustrat mai jos:
ls [opiuni] director

Opiuni posibile ale comenzii sunt:


-d -l

Afieaz numai directoarele din directorul curent. Afieaz informaii suplimentare precum: drepturile de acces, numrul de legturi, dimensiunea fiierului, data ultimei actualizri, numele fiierului. Afieaz i numrul i-node-ului fiecrui fiier. Afieaz numrul de blocuri pentru fiecare fiier. Fiierele sunt sortate dup data ultimei actualizri.

-i -s -t

10

Sisteme de operare. Chestiuni teoretice i practice


-u -r -R -h

La afiare se consider data ultimului acces n loc de data ultimei actualizri pentru opiunile -t sau -l. Inverseaz ordinea de sortare. Se face i afiarea coninutului subdirectoarelor i a subdirectoarelor acestora i aa mai departe.

Afiarea dimensiunii fiierelor se face ntr-un format mai uor de neles de ctre utilizator, ca de exemplu 1K, 2M, 3G etc. pentru dimensiuni de KB, MB sau GB respectiv. Un exemplu de utilizare al comenzii ls este:
ls -lsihR /

Comanda chmod schimb drepturile de acces la un fiier ordinar sau director. Sintaxa ei este:
chmod atr,[atr] fiier(e)

unde, atr se exprim ca un numr octal din patru cifre sau printr-o combinaie de forma:
u|g|o|a +|-|= r|w|x|s|t

unde, '+' adaug permisiune, '-' terge permisiune, '=' atribuie permisiuni pentru proprietar ('u'), grup ('g'), ceilali ('o') sau pentru toi deodat ('a'). Permisiunile de acces se specific prin 'r' pentru citire (Read), 'w' pentru scriere (Write), 'x' pentru execuie (eXecute), 't' pentru setarea bitului sticky, 's' pentru setarea bitului suid sau sgid. Drepturile de acces la un fiier se pstreaz ntr-un cuvnt, plasat n i-nodeul fiierului. Se poate specifica direct valoarea acestui cuvnt, biii avnd semnificaia:
Tabelul2. Specificarea sub forma unui numr n octal a drepturilor de acces

Drept Citire Scriere Execuie

Proprietar 0400 0200 0100

Grup 0040 0020 0010

Alii 0004 0002 0001

Exemplele de mai jos ilustreaz modul de utilizare a comenzi.


chmod go-wx f1 chmod 0764 f1 # # # # Sterge dreptul de scriere si executie pentru grup i altii pentru fisierul f1 f1 va avea permisiunile rwxrw-r--

11

Sistemul de fiiere n Linux

1.7. Probleme
1. S se testeze comparativ pe Windows i Linux urmtoarele comenzi de manipulare a fiierelor i directoarelor:
attrib cd, chdir comp copy del, erase dir fc md, mkdir ren rd, rmdir type chmod, ls -l cd, pwd cmp cp, cat rm, unlink ls cmp, diff mkdir mv rmdir cat

2. S se parcurg arborele sistemului de fiiere i s se identifice n cadrul structurii arborescente directoarele cu comenzi (fiiere executabile), cu dispozitivele periferice (fiiere speciale), cu fiiere temporare etc. 3. Comanda Linux man permite obinerea de informaii despre comenzile Linux, diferite apeluri de sistem, utilitare importante. Comanda se apeleaz avnd ca argument numele comenzii despre care se solicit informaii. Utiliznd comanda man obinei informaii suplimentare despre comenzile ls, echo, cat, chmod i man. 4. S se testeze i s se explice efectul execuiei urmtoarelor comenzi:
ls ls ls ls -l ? l * a*b li ls -la ls [a-z]*[!0-9] ls *[!o]

5. S se testeze folosirea comenzii ln pentru crearea legturilor fizice i simbolice i s se vizualizeze informaiile acestor fiiere legtur cu ajutorul comenzii ls. 6. S se vizualizeze coninutul unui director folosind comanda ls -l. S se identifice informaiile din i-node. Utiliznd comanda chmod, s se modifice drepturile de acces ale unui fiier executabil i ale unui director arbitrar din structura sistemului de fiiere. S se explice rezultatul comenzilor. 7. S se stabileasc permisiunile de acces la directoarele dintr-un subarbore de directoare ce aparin unui utilizator U1, astfel nct un alt utilizator U2 s aib acces la un anumit director (pe un nivel din adncime) fr a

12

Sisteme de operare. Chestiuni teoretice i practice

putea vedea coninutul directoarelor prin care se trece pentru a ajunge la acel director. 8. S se testeze ce se poate face cu un fiier care are doar drepturi de citire pentru toi utilizatorii, iar directorul n care se gsete drepturi de citire scriere i execuie pentru toi utilizatorii. S se testeze cazul similar pentru un fiier din directorul /tmp. 9. S se testeze efectul diferitelor combinaii a drepturilor de citire, scriere i execuie ale unui director asupra fiierelor i subdirectoarelor din acel director. 10. Se presupune c execuia programului Prg.exe are ca efect adugarea unei linii de text la sfritul unui fiier text, numele fiierului i irul de caractere fiind transmise ca parametri ai programului n linia de comand. Cele dou fiiere - executabil i text - aparin utilizatorului User1. S se stabileasc, prin modificarea drepturilor de acces ale fiierelor, contextul n care se permite execuia cu succes a programului Prg.exe de ctre un utilizator User2, care nu are drept de scriere asupra fiierului text. 11. S se modifice programul Prg.exe din problema anterioar astfel nct, ntr-un context identic, utilizatorul User2 s-l poat executa cu succes, pe cnd un alt utilizator User3 s nu poat face acest lucru. Se recomand folosirea funciei getuid n codul surs al programului, pentru obinerea identificatorului utilizatorului real al aplicaiei. 12. Dac se copiaz fiierul /bin/sh n directorul propriu, devenim proprietarul copiei. Dac se folosete comanda chmod se poziioneaz bitul suid, iar prin comanda chown se modific proprietarul fiierului ca fiind root-ul. Executnd acum copia vom deine privilegiul de a fi root. Acest lucru nu se produce, deci care este greeala n raionament?

13

2. Fiiere de comenzi n Linux


Scopul lucrrii Lucrarea prezint caracteristicile interpretorului de comenzi din Linux, o serie de comenzi recunoscute de ctre acesta i modul de scriere a fiierelor de comenzi (script-uri).

2.1. Linia de comand


Interpretorul de comenzi preia comenzile pe care trebuie s le execute sub forma unui ir de caractere, numit linie de comand, formatul unei comenzi fiind urmtorul:
nume_comand [optiuni] argumente

Orice comand executat ntoarce ca rezultat un numr, numit cod de retur, care indic modul de terminare a comenzii, zero pentru o comand executat cu succes i diferit de zero n caz de eec. Mai multe comenzi scrise pe o linie trebuie separate prin ';'. Comenzile pot fi conectate prin pipe (simbolul '|'), astfel nct ieirea unei comenzi constituie intrare pentru a doua. Codul de retur este cel corespunztor ultimei comenzi din pipe. De exemplu, comanda "ls -l | less" aplic filtrul less pe rezultatul comenzii ls. Dac linia de comand este terminat cu caracterul '&', ultima comand a secvenei de comenzi specificate n acea linie se executat asincron (n background sau concurent) relativ la interpretorul de comenzi, care va afia identificatorul procesului lansat. Continuarea unei comenzi pe linia urmtoare este posibil dac linia este terminat cu caracterul '\'. Secvena de caracterele "&&" indic faptul c execuia comenzii de dup ele se va face numai dac precedenta comand a fost executat cu succes (funcionalitate de tip AND). Pentru o funcionalitate de tip OR se poate folosi secvena "||". n exemplele de mai jos este ilustrat folosirea ctorva din caracterele descrise.
who | grep "labso" > /dev/null && \ echo "labso is logged on" ls -l file1 || ls -l file2

14

Sisteme de operare. Chestiuni teoretice i practice

2.2. Variabile
Variabilele recunoscute de ctre interpretorul de comenzi pot fi: variabile utilizator, parametri poziionali i variabile predefinite. Variabile utilizator Definirea variabilelor utilizator se face sub forma:
nume_var1=valoare

E important de precizat faptul c interpretorul de comenzi lucreaz cu iruri de caractere, att numele variabilei, ct i valoarea ei fiind interpretate ca iruri de caractere. Prin urmare pentru a putea accesa coninutul variabilei e nevoie de o construcie special, lucru ce se face prin prefixarea numelui variabilei cu caracterul '$', ca n exemplul de mai jos.
dir=/usr/include cd $dir

Variabilele utilizator sunt evaluate la valoarea lor, n afara cazului n care valoarea este delimitat de apostrofuri. Numele unei variabile nu poate fi identic cu numele unei funcii existente. Delimitarea numelui unei variabile, n cazurile n care acesta este urmat de un alt ir de caractere de exemplu dac se dorete concatenarea valorii variabilei cu acel ir de caractere se face prin ncadrarea lui ntre caracterele '{' i '}'. Exemplu de mai jos ilustreaz acest lucru.
num=3 k=${num}tmp echo $k # k=$numtmp ar fi fost # interpretat ca variabila numtmp # Se afiseaza 3tmp

Interpretorul de comenzi ofer un mecanism de substituie bazat pe urmtoarele reguli: 'ir_de_caractere': caracterele situate ntre apostrofuri sunt tratate ca i caractere obinuite fra a avea o semnificaie special; "ir_de_caractere": caracterele situate ntre ghilimele sunt tratate ca i caractere obinuite fr a avea o semnificaie special, cu excepia caracterelor '$' i '\';

15

Fiiere de comenzi n Linux

\c: nu interpreteaz n mod special caracterul 'c'. n irurile ncadrate

ntre ghilimele, caracterul '\' este caracter de evitare a tratrii speciale de ctre interpretor a caracterelor din setul {$, `, " , \}; var=`comand`: are ca efect execuia comenzii ncadrate de caracterele '`' (apostrof invers) i atribuirea rezultatului ei variabilei. De exemplu, rep=`pwd` atribuie variabilei rep rezultatul comenzii pwd.

n ceea ce privete variabilele definite n contextul unui interpretor de comenzi trebuie tiut faptul c acestea formeaz un set distinct pentru fiecare instan (proces) a interpretorului, chiar dac dou variabile cu acelai nume sunt folosite n contextul ambelor instane ale interpretorului. Acest lucru este valabil att n cazul a doi utilizatori, care n contextul propriilor instane ale interpretorului i definesc variabile cu acelai nume, ct i n cazul n care un utilizator execut fiiere de comenzi n cadrul unui interpretor, variabile cu acelai nume fiind folosite att n cadrul interpretorului, ct i n fiierul de comenzi. Imaginea de mai jos, ilustreaz acest lucru prin secvena de comenzi introduse.

Se observ c n contextul interpretorului de comenzi se atribuie variabilei x valoarea 100, valoare ce nu este modificat (la 50) prin execuia fiierului de comenzi. Explicaia const n faptul c execuia comenzilor din cadrul fiierului fis_cmd se face n contextul unei noi instane a interpretorului, instan (proces) lansat de ctre interpretorul (procesul) iniial, fiecare proces avnd propriul set de variabile. Pentru a face cunoscut o variabil din cadrul unui interpretor unor alte instane lansate ulterior din cadrul interpretorului se folosete comanda export. Exemplul de mai jos ilustreaz acest lucru.

16

Sisteme de operare. Chestiuni teoretice i practice

Parametri poziionali Parametrii poziionali notai $1, $2, $3, ... reprezint modalitatea de a accesa argumentele transmise unui fiier de comenzi n linia de comand. Variabila $0 este numele fiierului de comenzi ce se execut. Variabile predefinite i speciale Exist o serie de variabile predefinite, iniializate la intrarea n sistem i utilizate de ctre interpretor sau alte aplicaii. Cteva dintre acestea sunt:
$HOME

Desemneaz directorul n care o instan a interpretorului, proprie unui utilizator, este poziionat n momentul intrrii n sistem a utilizatorului. De asemenea, valoarea variabilei este folosit ca director implicit pentru comanda cd.
$PATH

Definete lista directoarelor parcurse de interpretor n cutarea unui fiier executabil corespunztor comenzii introduse (directoarele sunt separate prin caracterul ':').
$UID

Indic identificatorul utilizatorului n numele cruia se execut interpretorul de comenzi.


$USER

Indic numele utilizatorului interpretorul de comenzi.


$SHELL

numele

cruia

se

execut

Indic numele interpretorului curent. Variabilele speciale sunt descrise mai jos. Valoarea lor nu poate fi modificat.
$# numrul argumentelor din linia de comand (exclusiv $0). $@,$* lista parametrilor poziionali.

17

Fiiere de comenzi n Linux


$? $$ $!

starea de ieire a ultimei comenzi executate. identificatorul de proces asociat interpretorului. identificatorul ultimului proces lansat n background.

2.3. Fiiere de comenzi


Posibilitatea de a construi proceduri alctuite din comenzi ale sistemului de operare constituie una din principalele faciliti puse la dispoziie de ctre interpretor. Acesta permite execuia unor fiiere de comenzi tratate ca proceduri. Apelul unei astfel de proceduri este identic cu al unei comenzi:
procedura arg1 arg2 ... argn

Procedura corespunde numelui unui fiier de comenzi, care trebuie s aib setat dreptul de execuie, n caz contrar procedura i parametrii ei trebuind s fie specificai ca parametri ai unui alt interpretor, ca de exemplu:
/bin/bash procedura arg1 arg2 ... argn

Transmiterea parametrilor unei proceduri se face prin valoare. Reamintim faptul c execuia comenzilor din cadrul fiierului de comenzi se va face n contextul unei noi instane a interpretorului, aceasta fiind un proces fiu al interpretorului. Fiierele de comenzi pot fi apelate recursiv. Comenzi Linux Acest tip de comenzi sunt programe care apar sub forma unor fiiere executabile i sunt de regul situate ntr-unul din directoarele /bin, /sbin, /usr/bin, /usr/sbin sau altele, directoare ce sunt incluse de obicei n cadrul valorii variabilei PATH. Descriem mai jos sumar cteva dintre acestea.
man [seciune_manual] nume_comand

Afieaz pagina de manual, care conine informaii despre comanda specificat. Opional, se poate indica i seciunea de manual (sub forma unui numr) n care e situat comanda.
cp fiier_surs fiier_destinaie cp list_fiiere_surs director_destinaie cp -R director_surs director_destinaie

Copiaz un fiier sau un director ntr-un alt director, eventual sub un alt nume, sau mai multe fiiere ntr-un anumit director.
18

Sisteme de operare. Chestiuni teoretice i practice


mv fiier_surs fiier_destinaie mv list_fiiere_surs director_destinaie

Redenumete un fiier sau mut mai multe fiiere ntr-un director.


rm [-dR] fiier_sau_director

terge un fiier sau, pentru opiunea -d, un director. Opiunea -R indic intrarea n adncime (recursiv) n subdirectoare.
mkdir nume_director

Creaz un director.
cat list_fiiere

Afieaz pe ecran coninutul fiierelor specificate ca parametri.


test condiie

Evalueaz condiia i ntoarce rezultatul evalurii. Aceast comand se regsete i printre cele ncorporate n codul ncrctorului i este folosit n cazul n care e necesar evaluarea unei condiii (de exemplu pentru comanda if). Condiia poate s apar sub una din formele de mai jos:
! condiie

Neag rezultatul evalurii expresiei. Realizeaz o evaluare de tip I (AND) logic. Realizeaz o evaluare de tip SAU (OR) logic. Adevrat dac irul de caractere are lungime nenul. Adevrat dac irul de caractere are lungimea zero. Adevrat dac cele dou iruri sunt identice. Adevrat dac cele dou iruri sunt diferite.

cond1 a cond2 cond1 o cond2

-n ir_de_caractere -z ir_de_caractere

ir_de_caractere1 = ir_de_caractere2

ir_de_caractere1 != ir_de_caractere2 nr1 eq nr2

Adevrat dac cele dou numere ntregi sunt egale. Alte opiuni de comparare sunt: -lt (mai mic), -le (mai mic sau egal), -gt (mai mare), -ge (mai mare sau egal). Adevrat dac directorul specificat exist. Adevrat dac fiierul specificat exist i e fiier ordinar.
19

-d nume_director -f fiier

Fiiere de comenzi n Linux


less fiier_text

Permite printre altele, afiarea pe ecran a unui fiier text, pagin cu pagin. Permite de asemenea derularea nainte i napoi a vizualizrii.
uname -a

Afieaz informaii despre sistem.


pwd

Afieaz directorul curent al instanei interpretorului din care s-a lansat comanda. Aceast comand se regsete i printre cele incorporate n codul ncrctorului. Funcii i comenzi ncorporate n interpretor n cadrul fiierelor de comenzi se pot defini funcii. Formatul general pentru definirea unei funcii este:
nume_funcie() { cmd1; ... cmd2; }

unde nume_funcie este numele funciei, parantezele marcheaz definirea funciei, iar ntre acolade este specificat corpul funciei. Se impune ca prima comand s fie separat de acolad cu un spaiu, iar ultima comand s fie terminat cu caracterul ';', dac acolada se afl pe aceeai linie cu comanda. De regul, dac un utilizator i-a definit mai multe funcii ntr-un fiier, el poate face cunoscut interpretorului curent aceste funcii prin specificarea n linia de comand a numelui fiierului precedat de un punct i un spaiu, sub forma:
. myfuncs

Execuia unei funcii este mai rapid dect a unui fiier de comenzi echivalent, deoarece interpretorul nu necesit cutarea fiierului pe disc, deschiderea lui i ncrcarea coninutului su n memorie. tergerea unei definiii de funcii este similar cu tergerea unei variabile. Se folosete comanda unset. Comenzile ncorporate n cadrul interpretorului pot fi apelate direct n fiierele de comenzi. O parte dintre ele i efectul lor este prezentat n cele ce urmeaz.
break [n]

Este comanda de prsire a celei mai interioare bucle for, while sau until. Dac n este specificat se iese din n bucle. De exemplu:

20

Sisteme de operare. Chestiuni teoretice i practice


while true do read if [ then else fi done cd [dir]

cmd "$cmd" = quit ] break "$cmd"

Schimb directorul curent la cel specificat. Directorul curent este parte a contextului curent. Din acest motiv la execuia unei comenzi cd dintr-o subinstan a interpretorului doar directorul curent al acesteia este modificat.
continue [n]

Este comanda care permite trecerea la o nou iteraie a buclei for, while sau until. De exemplu:
for file do if [ ! -f "$file" ] then echo "$file not found" continue fi # prelucrarea fisierului done echo [-n][arg]

Este comanda de afiare a argumentelor sale (care sunt cuvinte) la ieirea standard. Dac opiunea -n este specificat caracterul '\n' nu este scris la ieirea standard (nu se trece la linie nou).
eval cmd

Evalueaz o comand i o execut. De exemplu:

Se observ c eval parcurge lista de argumente de dou ori: la transmiterea argumentelor spre eval i la execuia comenzii. Lucrul acesta este ilustrat i de exemplul urmtor.
21

Fiiere de comenzi n Linux

Comanda eval este folosit n fiiere de comenzi care construiesc linii de comand din mai multe variabile. Comanda e util dac variabilele conin caractere care trebuie s fie recunoscute de interpretor nu ca rezultat al unei substituii. Astfel de caractere sunt: {;, |, &, < , >, "}.
exec prg

Execut programul prg specificat. Programul lansat n execuie nlocuiete programul curent. Dac exec are ca argument redirectarea I/E, interpretorul va avea I/E redirectate. De exemplu:
file=$1 # contorizeaz numarul count=0 # de linii dintr-un fisier exec < $file while read line do count=`expr $count + 1` done echo $count exit [(n)]

Cauzeaz terminarea interpretorului curent cu cod de ieire egal cu n. Dac n este omis, codul de ieire este cel al ultimei comenzi executate.
export [v...]

Marcheaz v ca nume de variabil exportat pentru mediul comenzilor executate ulterior. Dac nu este precizat nici un argument se afieaz o list cu toate numele exportate de ctre interpretorul curent. Funciile nu pot fi exportate.
getopts opt v

Comanda este folosit la prelucrarea opiunilor din linia de comand. Se execut, de regul, n bucle. La fiecare iteraie getopts inspecteaz urmtorul argument din linia de comand i decide dac este o opiune valid sau nu. Decizia impune ca orice opiune s nceap cu caracterul '-' i s fie urmat de o liter precizat n opt. Dac opiunea exist i este corect precizat, ea este memorat n
22

Sisteme de operare. Chestiuni teoretice i practice

variabila v i comanda returneaz zero. Dac litera nu este printre opiunile precizate n opt, comanda memoreaz n v un semn de ntrebare i returneaz zero cu afiarea unui mesaj de eroare. n cazul n care argumentele din linia de comand au fost epuizate sau urmtorul argument nu ncepe cu caracterul '-' comanda returneaz o valoare diferit de zero. De exemplu, pentru ca getopts s recunoasc opiunile "-a" i "-b" pentru o comand oarecare cmd, apelul este:
getopts ab var

Comanda cmd se poate apela:


cmd -a -b sau cmd -ab

n cazul n care opiunea impune un argument suplimentar acesta trebuie separat de opiune printr-un spaiu. Pentru a indica comenzii getopts c urmeaz un argument dup o opiune, litera opiunii trebuie postfixat cu caracterul ':'. De exemplu, dac opiunea "-b", din exemplul anterior, ar necesita un argument, atunci trebuie scris:
getopts ab: var

Dac getopts nu gsete dup opiunea "-b" argumentul, n variabila var se memoreaz un semn de ntrebare i se va afia un mesaj de eroare. n caz c argumentul exist, el este memorat ntr-o variabil special OPTARG. O alt variabil special, OPTIND, este folosit de comand pentru a preciza numrul de argumente prelucrate. Valoarea ei iniial este 1.
read list_nume_variabile

Se citete o linie din fiierul standard de intrare i se atribuie cuvintele citite variabilelor specificate. De exemplu:

23

Fiiere de comenzi n Linux


readonly [v...]

Identic cu read, dar valoarea variabilei v nu poate fi schimbat prin atribuiri ulterioare. Dac argumentul lipsete, se afieaz variabilele read-only.
return [n]

Permite revenirea dintr-o funcie cu valoarea n. Dac n este omis, codul returnat este cel al ultimei comenzi executate. Valoarea returnat poate fi accesat prin variabila $? i poate fi testat n comenzile de control if, while i until.
shift [n]

Deplasare spre stnga (cu n) a parametrilor din linia de comand.


sleep n

Suspend execuia pentru n secunde.


[ condiie ]

Comanda este echivalent cu test condiie.


type cmds

Furnizeaz informaii despre comanda sau comenzile specificate. Informaia specific dac comanda este: o funcie definit de utilizator, una intern interpretorului sau o comand Linux sub forma unui executabil.
unset v

Permite tergerea valorii unei variabile sau funcii din mediul curent. Comenzi de control a execuiei Comenzile ce permit controlul execuiei ulterioare n funcie de evaluarea unei condiii fac parte din comenzile implementate n cadrul interpretorului. Ele sunt descrise mai jos. Comanda IF Sintaxa comenzii este:
if cond1 then lista_cmd_1 [ elif cond2 then lista_cmd_2] [ else lista_cmd_3] fi

Exemplul de mai jos ilustreaz utilizarea comenzii if.


24

Sisteme de operare. Chestiuni teoretice i practice


if test -f $1 then echo $1 este un fisier ordinar elif test -d $1 then echo $1 este un director else echo $1 nu e fisier ordinar sau director fi

Este posibil scrierea comenzii if pe o singur linie, dar n acest caz condiiile i comenzile ce preced cuvintele cheie trebuie terminate cu caracterul ';'. Astfel exemplul de mai sus se poate scrie:
if test -f $1; then echo $1 este un fisier ordinar elif test -d $1; then echo $1 este un director else echo $1 este necunoscut; fi

Comanda CASE Sintaxa comenzii este:


case expresie in sablon_1) lista_comenzi_1;; sablon_2) lista_comenzi_2;; ... esac

Se compar expresie cu fiecare din abloanele prezente i se execut lista de comenzi unde se constat potrivirea. De exemplu, analiza unei opiuni din linia de comand se poate face astfel:
case $1 in -r) echo optiunea r;; -m) echo optiunea m;; *) ;; esac

Comanda FOR Sintaxa comenzii este:


for nume [in lista_cuvinte] do lista_comenzi done

Variabila de bucl nume ia pe rnd valorile din lista de cuvinte. Pentru fiecare valoare se execut ciclul for. Dac nu se specific lista de cuvinte (i nici cuvntul cheie in), ciclul for se execut pentru fiecare parametru transmis n linia de comand. Condiia poate fi specificat i sub forma
25

Fiiere de comenzi n Linux

"for in ablon", unde ablon este un ir de caractere ce conine caracterul '*', caz n care variabila nume ia pe rnd ca valoare numele fiierelor i a subdirectoarelor ce se potrivesc cu ablonul specificat. Exemplul de mai jos este echivalent cu execuia comenzii "ls /home/student/*.c".
for fis in /home/student/*.c do echo $fis done

Comanda WHILE Sintaxa comenzii este:


while conditie do lista_comenzi done

Lista de comenzi se execut atta timp ct condiia este ndeplinit, adic atta timp ct starea de ieire a ultimei comenzi din condiie este zero (terminat cu succes). n caz contrar, bucla se termin. De exemplu, pentru a testa periodic dac utilizatorul user este n sesiune se poate folosi secvena de mai jos:
while true do if who | grep "user" > /dev/null then echo "user" este prezent exit else sleep 120 done

Comanda UNTIL Comanda e similar cu comanda while, dar lista comenzilor se execut atta timp ct codul de retur al ultimei comenzi din conditie este diferit de zero (terminat fr succes), adic pn cnd condiia este ndeplinit. Sintaxa comenzii este:
until conditie do lista_comenzi done

26

Sisteme de operare. Chestiuni teoretice i practice

2.4. Redirectarea fiierelor standard de intrare i ieire


Sistemul de operare deschide automat pentru fiecare proces nou creat trei fiiere (avnd descriptori 0, 1, 2) corespunztor intrrii, ieirii i ieirii de eroare standard (STDIN, STDOUT i respectiv, STDERR). Interpretorul permite redirectarea acestor descriptori spre alte dispozitive periferice sau fiiere astfel:
comanda < nume_fisier

Descriptorul 0, care corespundea iniial intrrii standard, se asociaz fiierului nume_fiier, deschis pentru citire. Spunem c intrarea standard a comenzii a fost redirectat. Prin urmare, toate citirile care presupuneau introducerea de date de la tastatur se vor face din fiierul nume_fiier.
comanda > nume_fisier

Descriptorul 1, care corespundea iniial ieirii standard, se asociaz fiierului nume_fiier, deschis pentru scriere. Spunem c ieirea standard a comenzii a fost redirectat. Prin urmare, toate scrierile care presupuneau afiarea pe ecran se vor face n fiierul nume_fiier.
comanda >> nume_fisier

Este similar construciei de mai sus, dar fiierul este folosit n adugare, deci nu se pierde coninutul su anterior.
comanda > &nr

Indic faptul c descriptorul 1 se asociaz fiierului deschis, indicat de descriptorul nr. Presupune cunoaterea descriptorului unui fiier deschis i, prin urmare, se folosete de obicei pentru a indica faptul c ieirea de eroare e redirectat spre acelai fiier ca i cea standard, sau invers. De exemplu:
ls >fis 2>&1

Exemplele de mai jos ilustreaz modul de redirectare a ieirii standard:


cat fis > /dev/lp

Listeaz fiierul la imprimant.


cat f1 f2 > f3

Concateneaz fiierele f1 i f2 n f3. Dac fiierul f3 exist deja, prin redirectare cu '>', vechiul su coninut este pierdut.
cat f1 f2 >> f3

Dac f3 exist deja, la vechiul su coninut se adaug rezultatul concatenrii fiierului f1 i f2.
27

Fiiere de comenzi n Linux

2.5. Exemple de fiiere de comenzi


Exemplul 1. Fiierul de comenzi de mai jos creeaz un fiier de articole, un articol fiind constituit dintr-un nume de persoan. Se cere ca numele fiierului s nceap cu caracterul f i s continue cu 4 cifre. Funcia valid verific dac aceast cerin este ndeplinit.
# verifica daca numele fisierului # corespunde formatului dorit valid() { case $1 in f[3-5][1-6][1-6][1-6]);; *) echo > invalid;; esac } # inceputul programului - lista de comenzi echo Creare fiier echo Nume fisier sub forma fnnn: read fname valid $fname if test -f invalid then echo Nume invalid rm invalid exit fi echo > $fname aux=0 echo Introduceti articolele: while read string case $string in [a-zA-Z]*);; *) aux=1;; esac test $aux -eq 0 do echo $string >> $fname done sort $fname -o $fname echo Fisierul creat: echo cat $fname

28

Sisteme de operare. Chestiuni teoretice i practice

Exemplul 2. Urmtorul fiier de comenzi afieaz toate fiierele dintr-un director transmis ca argument n linia de comand, inclusiv din subdirectoarele sale. Fiierul de comenzi se numete ls_rec.sh i se apeleaz astfel:
./ls_rec.sh /home/student

Coninutul fiierului de comenzi ls_rec.sh este:


echo echo Director $1 if test -d $1 then for nume in $1/* $1/.[a-z,A-Z]* do if test -d $nume then ./ls_rec.sh $nume elif test f $nume then echo $nume fi done fi

2.6. Probleme
1. S se verifice ce afieaz secvenele de comenzi de mai jos: a. eval echo \$$# b. x=100
px=x eval echo \$$px eval $px=5 echo $x c. ls R / >fis 2>fis_err d. exec ls e. (n cadrul unui fiier de comenzi) file=$1 count=0 while read line do count=`expr $count + 1` done < $file echo $count

Not: Exemplul de la punctul e este coninutul unui fiier de comenzi, n care bucla while este executat ntr-o alt instan a interpretorului deoarece intrarea ei este redirectat spre $file.
29

Fiiere de comenzi n Linux

2. S se precizeze ce realizeaz comenzile:


who | wc -l > fis ls *.c | wc -l >> fis who | sort

3. S se scrie un fiier de comenzi numit recho.sh, care i afieaz argumentele primite n linia de comand n ordine invers a. pe aceeai linie sau b. pe linii diferite (se poate utiliza comanda eval). 4. Fiierul de comenzi de mai jos decide dac dou directoare sunt identice din punct de vedere al coninutului fiierelor terminate n .c ns conine cteva erori. Care sunt ele?
Crtdir=`pwd` if [ -d $1 ] then if [ -d $2 ] then cd $1 ls -R > $crtdir/f1 cd $crtdir cd $2 ls -R > $crtdir/f2 cd $crtdir grep .c$ f1 > f11 # raman doar fisierele grep .c$ f2 > f22 # .c ordonat alfabetic rm f1 f2 if cmp f11 f22 then echo Directoarele egale else echo Directoare diferite fi rm f11 f22 else echo $2 nu e director fi else echo $1 nu e director fi

5. S se scrie un fiier de comenzi, care verific dac dou directoare sunt egale, fr a se folosi comanda ls. Numele celor dou directoare se transmit ca argumente n linia de comand. Dou directoare se consider c sunt egale dac arborii ce le au ca rdcin sunt identici din punct de vedere al structurii, iar nodurile lor corespondente au acelai nume.
30

Sisteme de operare. Chestiuni teoretice i practice

6. S se scrie un fiier de comenzi care permite cutarea unui fiier n ntreaga structur a unui subdirector, fr a folosi comanda find sau alte comenzi similare care fac acest lucru. Argumentele se precizeaz n linia de comand. 7. S se scrie un fiier de comenzi care terge toate sursele C dintr-un director dac ele se regsesc ca i nume n structura altui director. Primul argument din linia de comand este directorul n care se afl sursele C, iar al doilea este directorul de unde ncepe cutarea. 8. S se scrie un fiier de comenzi care copiaz ntreaga structur a unui director ca structur a unui alt director. Cele dou directoare se transmit ca argumente n linia de comand. 9. S se calculeze, folosind comanda ls, numrul de fiiere i directoare dintr-un director, lund n considerare ntreaga structur a arborelui ce are acel director ca rdcin. Numele directorului se transmite ca parametru n linia de comand. 10. S se scrie un fiier de comenzi care calculeaz i afieaz numrul de fiiere, numrul de directoare i numrul de legturi simbolice dintr-un director, lund n considerare ntreaga structur a arborelui care are acel director ca rdcin. Se vor afia aceleai informaii pentru fiecare subdirector parcurs. Numele directorului se transmite ca parametru n linia de comand. 11. S se scrie un fiier de comenzi care creeaz un director a crui cale (numele) este specificat ca parametru n linia de comand, iar n acel director creeaz fiiere cu numele utilizatorilor conectai n acel moment. 12. S se scrie un fiier de comenzi care calculeaz numrul total de linii de text i cuvinte din toate fiierele dintr-un director, lund n considerare ntreaga structur a arborelui care are acel director ca rdcin. 13. S se scrie un fiier de comenzi care efectueaz ntr-o bucl urmtorii pai: (1) citete de la tastatur dou numere i un operator +, -, * sau /; (2) efectueaz operaia dorit i (3) scrie rezultatul, pe o nou linie, n cadrul unui fiier sub forma:
nr_operatie: operand1 operator operand2 = rezultat

Din bucla respectiv se iese la introducerea caracterului x pe poziia operatorului. nainte de terminare, se va scrie n fiier i numrul de operaii efectuate. Numele fiierului n care se face scrierea se primete ca parametru n linia de comand.

31

3. Apeluri sistem pentru lucrul cu fiiere i directoare n Linux


Scopul lucrrii Lucrarea prezint apelurile sistem uzuale folosite n operaiile de intrare/ieire pe fiiere i cele de manipulare a fiierelor i directoarelor n sistemul de operare Linux.

3.1. Descriptori de fiier


Sistemul de operare ataeaz intern fiecrui fiier deschis un descriptor sau identificator de fiier (n principiu, un numr ntreg pozitiv). La deschiderea unui fiier sau la crearea unui fiier nou, sistemul returneaz un descriptor de fiier procesului care a executat operaia. Fiecare proces i are propriii descriptori de fiier. Prin convenie, primii trei descriptori de fiier ai fiecrui proces sunt alocai automat la crearea lui. Descriptorul de fiier 0 este asociat intrrii standard (tastatura), 1 ieirii standard (ecranul), iar 2 ieirii standard de eroare (ecranul). Ceilali descriptori sunt folosii de proces pentru deschiderea de fiiere ordinare, pipe, speciale sau directoare. Exist cinci apeluri sistem care genereaz descriptori de fiiere: creat, open, fcntl, dup i pipe.

3.2. Apeluri sistem pentru lucrul cu fiiere


Apelul sistem OPEN Deschiderea sau crearea unui fiier se poate face prin apelul sistem open. Sintaxa acestui apel este:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *cale, int optiuni, mode_t permisiuni);

Funcia returneaz un descriptor de fiier sau -1 n caz de eroare. La apelul acestei funcii se pot specifica dou sau trei argumente, al treilea argument fiind folosit doar la crearea de fiiere noi. Apelul cu dou argumente este folosit pentru deschiderea fiierelor existente. Funcia returneaz cel mai
32

Sisteme de operare. Chestiuni teoretice i practice

mic descriptor de fiier disponibil. Acesta va fi utilizat n apelurile sistem ulterioare: read, write, lseek i close. Procesul care execut apelul sistem open trebuie s aib drepturi de citire i/sau scriere asupra fiierului pe care ncearc s-l deschid, n funcie de valoarea argumentului optiuni. Pointerul din fiier (poziia curent relativ la care se vor efectua operaiile de citire i scriere) este poziionat pe primul octet din fiier. Argumentul optiuni se formeaz printr-un SAU pe bii ntre urmtoarele constante, definite n fiierul fcntl.h:
O_RDONLY O_WRONLY O_RDWR O_APPEND

Fiierul este deschis doar pentru citire. Fiierul este deschis doar pentru scriere. Fiierul este deschis pentru citire i scriere. Are efect doar dac fiierul e deschis pentru scriere. n acest caz, scrierile n fiier se fac ntotdeauna la sfritul fiierului. Acest lucru este asigurat automat de ctre sistemul de operare, ca i cum procesul ar poziiona anterior scrierii, pointerul n fiier la sfritul fiierului. Dac fiierul nu exist, el este creat. Dac exist, este trunchiat. Dac fiierul exist i este specificat i opiunea O_CREAT, apelul open nu se execut cu succes.

O_CREAT O_EXCL

O_NONBLOCK La fiiere pipe i cele speciale pe bloc sau caracter cauzeaz O_NDELAY trecerea n modul fr blocare att pentru apelul open, ct i

pentru operaiile viitoare de I/E.


O_TRUNC O_SYNC

Dac fiierul exist, i se terge coninutul. Foreaz scrierea efectiv pe disc prin write. ntrzie mult ntregul sistem, dar e eficace n cazuri critice.

Argumentul al treilea, permisiuni, poate fi o combinaie de SAU pe bii ntre urmtoarele constante predefinite:
S_IRUSR, S_IWUSR, S_IXUSR S_IRGRP, S_IWGRP, S_IXGRP S_IROTH, S_IWOTH, S_IXOTH

Proprietar: read, write, execute. Group: read, write, execute. Alii: read, write, execute.

Aceste constante definesc drepturile de acces asupra unui fiier i sunt definite n fiierul sys/stat.h.
33

Apeluri sistem pentru lucrul cu fiiere i directoare n Linux

Apelul sistem CREAT Un fiier nou este creat cu ajutorul apelului sistem creat, a crui sintax este:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int creat(const char *cale, mode_t permisiuni);

Funcia creat returneaz descriptorul de fiier sau -1 n caz de eroare. Apelul funciei creat este echivalent cu apelul funciei open n forma:
open(cale, O_WRONLY | O_CREAT | O_TRUNC, mod);

Argumentul cale specific calea i numele fiierului, iar permisiuni drepturile de acces. Dac fiierul creat nu exist, este alocat un nou i-node i o legtur spre el este plasat n directorul unde acesta a fost creat. Proprietarul procesului (dat de UID-ul efectiv i GUID-ul efectiv) care execut acest apel trebuie s aib permisiunea de scriere n directorul unde se creeaz fiierul. Fiierul deschis va avea drepturile de acces specificate de argumentul al doilea din apel (vezi i umask). Apelul ntoarce cel mai mic descriptor de fiier disponibil. Fiierul este deschis n scriere, iar dimensiunea sa iniial este 0. Timpii de acces i modificare din i-node sunt actualizai. Dac fiierul exist (este nevoie de permisiunea de cutare n director) coninutul lui este ters, fiierul este deschis n scriere, dar nu se modific proprietarul sau drepturile de acces asupra lui. n acest ultim caz, al doilea argument este ignorat. Apelul sistem READ Pentru a citi un anumit numr de octei dintr-un fiier de la poziia curent, se folosete apelul sistem read. Sintaxa lui este:
#include <unistd.h> ssize_t read(int fd, void* buf, size_t noct);

Funcia returneaz numrul de octei citii efectiv, 0 pentru sfrit de fiier (EOF) i -1 n caz de eroare. Se ncearc citirea a noct octei din fiierul deschis referit de descriptorul fd i se depun la adresa de memorie indicat de parametrul buf. Pointerul (poziia curent) n fiier este incrementat automat dup o operaie de citire cu numrul de octei citii. Se revine din funcia read doar dup ce datele citite de pe disc (din fiier) sunt transferate n memorie. Acest tip de funcionalitate se numete sincron.
34

Sisteme de operare. Chestiuni teoretice i practice

Apelul sistem WRITE Pentru a scrie un anumit numr de octei ntr-un fiier la poziia curent, se folosete apelul sistem write. Sintaxa lui este:
#include <unistd.h> ssize_t write(int fd, const void* buf, size_t noct);

Funcia returneaz numrul de octei scrii si -1 n caz de eroare. Apelul scrie noct octei preluai de la adresa de memorie indicat de parametrul buf n fiierul al crui descriptor este fd. Interesant de remarcat referitor la acest apel este faptul c scrierea fizic pe disc este ntrziat. Ea se efectueaz la iniiativa sistemului de operare fr ca utilizatorul s fie informat. Dac procesul care a efectuat apelul sau un alt proces citete datele care nc nu au fost scrise pe disc, sistemul le citete napoi din bufferele cache. Scrierea ntrziat este mai rapid, dar are trei dezavantaje: 1. eroare pe disc sau cderea sistemului duce la pierderea datelor; 2. un proces care a iniiat o operaie de scriere nu poate fi informat n cazul apariiei unei erori de scriere; 3. ordinea scrierilor fizice nu poate fi controlat. Pentru a elimina aceste dezavantaje, n anumite cazuri se folosete opiunea O_SYNC specificat n momentul deschiderii fiierului. Dar cum aceasta scade viteza sistemului i avnd n vedere fiabilitatea sistemelor de astzi, se prefer mecanismul de lucru cu tampoane cache. Apelul sistem CLOSE Pentru nchiderea unui fiier i, implicit, eliberarea descriptorului ataat, se folosete apelul sistem close. Sintaxa lui este:
#include <unistd.h> int close(int fd);

Funcia returneaz 0 n caz de succes i -1 n caz de eroare. Un fiier deschis este oricum nchis automat la terminarea procesului. Apelul sistem LSEEK Pentru poziionarea absolut sau relativ a indicatorului poziiei curente ntr-un fiier se folosete apelul sistem lseek. Urmtoarele operaii de citire din fiier i scriere n fiier se vor efectua relativ la noua poziie curent n fiier. Sintaxa funciei lseek este urmtoarea:
35

Apeluri sistem pentru lucrul cu fiiere i directoare n Linux


#include <sys/types.h> #include <unistd.h> off_t lseek(int fd, off_t salt, int relativLa);

Funcia returneaz deplasamentul fa de nceputul fiierului al noii poziii curente din fiier sau -1 n caz de eroare. Nu se efectueaz nici o operaie de I/O i nu se trimite nici o comand controlerului de disc. Saltul relativ la punctul indicat de parametrul relativLa se face cu numrul de octei indicai de parametrul salt. Parametrul relativLa poate avea doar una dintre urmtoarele valori predefinite:
SEEK_SET SEEK_CUR SEEK_END

repoziionarea (saltul) se face relativ la nceputul fiierului (primul octet din fiier este la deplasament zero). repoziionarea se face relativ la poziia curent. repoziionarea se face relativ la sfritul fiierului.

Apelurile sistem open, creat, write i read execut implicit lseek. Dac un fiier este deschis folosind constanta simbolic O_APPEND, se efectueaz un apel lseek la sfritul fiierului naintea unei operaii de scriere. Apelul sistem LINK Pentru a lega un fiier existent la un alt director (sau acelai) se folosete apelul sistem link. Stabilirea unei legturi nseamn de fapt stabilirea unui nou nume sau a unei noi ci de acces spre un fiier existent. Sintaxa acestui apel sistem este:
#include <unistd.h> int link(const char* vecheaCale, const char* nouaCale);

Funcia returneaz 0 n caz de succes i -1 n caz contrar. Argumentul vecheaCale trebuie s indice un fiier existent. Doar root-ul are dreptul de a stabili legturi spre un director. Apelul sistem UNLINK Pentru a terge o legtur (cale) dintr-un director se folosete apelul sistem unlink. Sintaxa lui este:
#include <unistd.h> int unlink(const char* cale);

36

Sisteme de operare. Chestiuni teoretice i practice

Funcia returneaz 0 n caz de succes i -1 n caz contrar. Apelul unlink decrementeaz contorul de legturi fizice din i-node-ul fiierului specificat i terge intrarea din director corespunztoare fiierului ters. Dac numrul de legturi ale unui fiier devine 0, spaiul ocupat de fiierul n cauz i i-node-ul su este eliberat. Doar root-ul poate s tearg un director folosind acest apel sistem. Altfel, apelul sistem rmdir poate fi utilizat pentru a terge un director. Apelurile sistem STAT, LSTAT i FSTAT Pentru a obine informaii detaliate despre un fiier se pot folosi apelurile sistem stat, lstat sau fstat.
#include <sys/types.h> #include <sys/stat.h> int stat(const char* cale, struct stat* buf); int lstat(const char* cale, struct stat* buf); int fstat(int fd, struct stat* buf);

Cele trei funcii returneaz 0 n caz de succes i -1 n caz de eroare. Primele dou primesc ca parametru calea i numele spre un fiier i completeaz structura de la adresa buf cu informaii din i-node-ul fiierului. Apelul fstat e similar, dar funcioneaz pentru fiiere deschise crora li se cunoate descriptorul. Diferena ntre stat i lstat apare doar n cazul unui fiier legtur simbolic, caz n care stat returneaz informaii despre fiierul referit (legat), pe cnd lstat returneaz informaii despre fiierul legtur. Structura struct stat e definit n fiierul sys/stat.h i conine cmpurile:
struct stat { mode_t st_mode; ino_t st_ino; dev_t st_dev; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; off_t st_size; time_t st_atime; time_t st_mtime; time_t st_ctime; dev_t st_rdev; long st_blksize; long st_blocks; }; // // // // // // // // // // // // // // // // tip fisier & drepturi i-node numar de dispozitiv (SF) numarul de legaturi ID proprietar ID grup dim. pt. fisiere ordinare timpul ultimului acces timpul ultimei modificari timpul schimbarii starii nr. dispozitiv pt. fisiere speciale dimensiunea optima a blocului de I/E numar de blocuri de 512 octeti alocate

37

Apeluri sistem pentru lucrul cu fiiere i directoare n Linux

Comanda Linux care folosete cel mai des acest apel sistem este ls. Declaraiile de tipuri pentru membrii structurii se gsesc n fiierul sys/types.h. Tipul fiierului este codificat, alturi de drepturile de acces, n cmpul st_mode i poate fi determinat folosind urmtoarele macrouri:
Tabelul 1. Macrouri pentru obinerea tipului unui fiier

Macro
S_ISREG(st_mode) S_ISDIR(st_mode) S_ISCHR(st_mode) S_ISBLK(st_mode) S_ISFIFO(st_mode) S_ISLNK(st_mode)

Semnificaie Fiier obinuit Fiier director Dispozitiv special de tip caracter Dispozitiv special de tip bloc Fiier pipe sau fifo Legtura simbolic

Decodificarea informaiilor din st_mode poate fi fcut testnd rezultatul operaiei de I pe bii (&) ntre cmpul st_mode i una dintre constantele (mti de bii): S_IFIFO, S_IFCHR, S_IFBLK, S_IFDIR, S_IFREG, S_IFLNK, S_ISUID (setat bitul suid), S_ISGID (setat bitul sgid), S_ISVTX (setat bitul sticky), S_IRUSR (drept de citire pentru proprietar), S_IWUSR (drept de scriere pentru proprietar), S_IWUSR (drept de execuie pentru proprietar) etc. Apelul sistem ACCESS n momentul accesului unui fiier de ctre un proces, sistemul de operare verific permisiunea de acces la fiier a acelui proces n funcie de UID-ul i GID-ul su efectiv. Exist situaii cnd este nevoie s se testeze drepturile de acces bazndu-se pe UID-ul i GID-ul real. O situaie n care acest lucru este util este atunci cnd un proces se execut, datorit setrii biilor suid sau sgid ai fiierului executabil, cu alte drepturi dect cele ale utilizatorului care a lansat procesul, dar se dorete s se verifice dac utilizatorul real poate accesa fiierul. Apelul sistem access permite testarea acestui lucru. Sintaxa lui este:
#include <unistd.h> int access(const char* cale, int tipAcces);

Funcia returneaz 0 dac exist permisiunea i -1 n caz contrar. Parametrul tipAcces poate fi o combinaie de tipul I pe bii ntre urmtoarele constante predefinite: R_OK (dreptul de citire), W_OK (dreptul de scriere), X_OK (dreptul de execuie), F_OK (existena fiierului).
38

Sisteme de operare. Chestiuni teoretice i practice

Apelul sistem UMASK Pentru a mbunti securitatea sistemului de fiiere, sistemul de operare Linux permite stabilirea pentru fiecare proces a unei mti (filtru) de bii ce indic resetarea automat a unor drepturi de acces la crearea fiierelor. Structura pe bii a acestei mti este similar cu structura cmpului din inode-ul fiierelor care codific pe bii permisiunile de acces. Biii din masc poziionai pe 1 invalideaz, la crearea unui fiier, biii corespunztori din argumentul care precizeaz drepturile de acces. Masca nu afecteaz apelul sistem chmod, astfel nct procesele au posibilitatea de a-i fixa explicit drepturile de acces indiferent de valoarea mtii stabilite prin umask. Sintaxa apelului este:
#include <sys/types.h> #include <sys/stat.h> mode_t umask(mode_t mask);

Funcia returneaz valoarea mtii anterioare. Efectul apelului este ilustrat n exemplul de mai jos:
main() { int fd; umask(022); if ((fd=creat("temp", 0622))==-1) { perror("creat"); exit(0); } } system("ls -l temp");

Rezultatul afiat va fi de forma:


-rw------- temp

n care se observ resetarea automat a drepturilor de scriere pentru grup i ali utilizatori dect proprietarul. Apelul sistem CHMOD Pentru a modifica drepturile de acces asupra unui fiier existent se poate folosi apelul sistem chmod, a crui sintax este:
#include <sys/types.h> #include <sys/stat.h> int chmod(const char* cale, mode_t permisiuni);

39

Apeluri sistem pentru lucrul cu fiiere i directoare n Linux

Funcia returneaz 0 n caz de succes i -1 n caz contrar. Funcia chmod modific drepturile de acces ale fiierului specificat de parametrul cale n conformitate cu valoarea argumentului permisiuni. Pentru a putea modifica drepturile de acces, UID-ul efectiv al procesului trebuie s fie egal cu cel al proprietarului fiierului sau procesul trebuie s aib drepturi de administrator (root). Argumentul permisiuni poate fi specificat printr-una dintre constantele descrise mai jos i definite n sys/stat.h. Se poate obine un efect cumulat al lor folosind operatorul SAU pe bii.
Tabelul 2. Mti pe bii pentru setarea drepturilor de acces la un fiier

Mod
S_ISUID S_ISGID S_ISVTX S_IRWXU S_IRWXG S_IRWXO

Descriere Seteaz bitul suid Seteaz bitul sgid Seteaz bitul sticky Drept de citire, scriere i execuie pentru proprietar obinut din: S_IRUSR | S_IWUSR | S_IXUSR Drept de citire, scriere i execuie pentru grup, obinut din:
S_IRGRP | S_IWGRP | S_IXGRP

Drept de citire, scriere i execuie pentru alii, obinut din:


S_IROTH | S_IWOTH | S_IXOTH

Apelul sistem CHOWN Acest apel sistem este utilizat n scopul modificrii proprietarului (UID) i al grupului (GID) cruia i aparine un fiier. Sintaxa funciei este:
#include <sys/types.h> #include <unistd.h> int chown(const char* cale, uid_t proprietar, gid_t grup);

Funcia returneaz 0 n caz de succes i -1 n caz de eroare. Apelul ei schimb proprietarul i grupul fiierului precizat de parametrul cale, la noul proprietar specificat de parametrul proprietar i la noul grup specificat de parametrul grup. n afar de root, un utilizator obinuit nu poate schimba proprietarul fiierelor altor utilizatori, dar poate schimba GID-ul pentru fiierele proprii, dar numai pentru acele grupuri din care face parte.
40

Sisteme de operare. Chestiuni teoretice i practice

Apelul sistem UTIME n structura stat exist trei membri care se refer la timp, conform tabelului de mai jos.
Tabelul 3. Informaii de timp asociate unui fiier

Cmp
st_atime st_mtime st_ctime

Descriere Ultimul acces la datele fiierului Ultima modificare a datelor fiierului Schimbarea strii i-node-ului

Operaie read write chmod, chown

Diferena ntre timpul de modificare al fiierului i cel de schimbare a strii i-node-ului const n faptul c primul se refer la momentul n care coninutul fiierului a fost modificat, iar cel de-al doilea la momentul n care informaia din i-node a fost modificat. Acest lucru se datoreaz faptului c informaia din i-node este memorat separat de coninutul fiierului. Apelurile sistem care modific i-node-ul sunt cele care modific drepturile de acces asupra unui fiier, cele care schimb UID-ul, numrul de legturi etc. Sistemul de operare nu reine timpul ultimului acces la i-node. Acesta este motivul pentru care apelurile sistem access i stat nu schimb nici unul dintre aceti timpi. Timpii de acces i de modificare ai unui fiier de orice tip pot fi schimbai printr-unul dintre apelurile sistem de mai jos:
#include <sys/time.h> int utimes(const char* cale, const struct timeval* timpi); int lutimes(const char* cale, const struct timeval* timpi); int futimes(int fd, const struct timeval* timpi);

Funciile returneaz 0 n caz de succes i -1 n caz contrar. Doar proprietarul unui fiier sau root-ul pot modifica timpii asociai unui fiier. Parametrul timpi reprezint adresa (pointer) unui ir de dou structuri timeval, care corespund timpului de acces i, respectiv, de modificare. Cmpurile structurii timeval sunt descrise mai jos:
struct timeval { long tv_sec; // sec. trecute din 1.01.1970 suseconds_t tv_usec; // microsecunde };

41

Apeluri sistem pentru lucrul cu fiiere i directoare n Linux

Pentru obinerea timpului curent n forma cerut de structura timeval poate fi folosit funcia gettimeofday (a se vedea pagina de manual). Pentru diferite conversii ntre forma normal a specificrii unei date i ore i cea specific structurii timeval se poate folosi funcia ctime sau o alta din aceeai familie (a se vedea pagina de manual).

3.3. Funcii pentru lucrul cu directoare


Coninutul unui director poate fi obinut de ctre procesele care au drept de citire asupra directorului prin operaii de citire similare cu cele asupra fiierelor. Scrierea ntr-un director poate fi fcut doar de ctre sistemul de operare. Structura unui director apare utilizatorului ca o succesiune de structuri (elemente) numite intrri n director. O intrare n director conine, printre alte informaii, numele fiierului i i-node-ul acestuia. Pentru citirea intrrilor unui director exist urmtoarele funcii:
#include <sys/types.h> #include <dirent.h> DIR* opendir(const char* cale); struct dirent* readdir(DIR* dp); void rewinddir(DIR* dp); int closedir(DIR* dp);

Funcia opendir are ca efect deschiderea directorului, adic pregtirea pentru operaiile ulterioare de citire a coninutului lui. Ea returneaz un pointer valid dac deschiderea a reuit i NULL n caz de eroare. Funcia readdir citete la fiecare nou apel al ei, n ordine secvenial, urmtoarea intrare din director: primul apel readdir citete prima intrare din director, urmtorul apel citete urmtoarea intrare i aa mai departe. Funcia returneaz un pointer valid spre o structur de tip dirent, dac citirea a reuit i NULL n caz contrar (sfritul directorului). Funcia rewinddir repoziioneaz indicatorul din director spre prima intrare din director (nceputul directorului). Funcia closedir nchide un director deschis anterior. Returneaz -1 n caz de eroare. Structura dirent, definit n fiierul dirent.h, conine cel puin doi membri:
struct dirent { ino_t d_fileno; char d_name[MAXNAMLEN + 1]; } // nr. i-node // nume fiier

42

Sisteme de operare. Chestiuni teoretice i practice

3.4. Exemple
Exemplul 1. Programul de mai jos, numit CreareGauri.c, creeaz un fiier cu dou zone de 1M octei n care nu se scrie nimic. Astfel de fiiere se numesc fiiere cu guri. O gaur se obine printr-un salt fcut cu funcia lseek dup sfritul fiierului, operaie urmat de o scriere la noul deplasament.
#include #include #include #include <sys/types.h> <sys/stat.h> <fcntl.h> <unistd.h>

char buf1[]="LAB "; char buf2[]="OS "; char buf3[]="Linux"; int main(void) { int fd; if ((fd=creat("fisier.gol", 0644)) < 0) { perror("Eroare creare fisier"); exit (1); } if (write(fd, buf1, strlen(buf1)) < 0) { perror("Eroare scriere"); exit(2); } if (lseek(fd, 1024 * 1024, SEEK_SET) < 0) { perror("Eroare pozitionare"); exit(3); } if (write(fd, buf2, strlen(buf2)) < 0) { perror("Eroare scriere"); exit(2); } if (lseek(fd, 1024 * 1024, SEEK_SET) < 0) { perror("Eroare pozitionare"); exit(3); } if (write(fd, buf3, strlen(buf3)) < 0) { perror("Eroare scriere"); exit(2); }

Urmrii efectul execuiei programului cu ajutorul comenzilor:


rm -f fisier.gol df -h gcc -o CreareGauri.exe CreareGauri.c ./CreareGauri.exe ls -l fisier.gol stat fisier.gol od -c fisier.gol df -h

43

Apeluri sistem pentru lucrul cu fiiere i directoare n Linux

Exemplul 2. Programul de mai jos copiaz coninutul unui fiier existent ntr-un alt fiier. Numele celor dou fiiere se citesc ca argumente din linia de comand. Se presupune c oricare dintre apelurile sistem read sau write poate genera erori.
#include #include #include #include <sys/types.h> <sys/stat.h> <unistd.h> <fcntl.h>

#define BUFSIZE 512 int main (int argc, char** argv) { int from, to, nr, nw; char buf[BUFSIZE]; if (argc != 3) { printf("Utilizare: %s fis_sursa fis_dest\n", argv[0]); exit(0); } if ((from = open(argv[1], O_RDONLY)) < 0) { perror("Eroare deschidere fisier sursa"); exit(1); } if ((to = creat(argv[2], 0666)) < 0) { perror("Eroare deschidere fisier dest."); exit(2); } while((nr = read(from, buf, sizeof(buf))) != 0) { if (nr < 0) { perror("Eroare citire din fisier sursa"); exit(3); } if ((nw=write(to, &buf, nr)) < 0) { perror("Eroare scriere in fisier dest."); exit(4); } } close(from); close(to);

44

Sisteme de operare. Chestiuni teoretice i practice

Exemplul 3. Programul de mai jos afieaz coninutul unui director, specificnd pentru fiecare element din director tipul su. Numele directorului se transmite ca parametru n linia de comand.
#include <sys/types.h> #include <sys/stat.h> #include <dirent.h> void listDir(char *dirName) { DIR* dir; struct dirent *dirEntry; struct stat inode; char name[1000]; dir = opendir(dirName); if (dir == 0) { perror ("Eroare deschidere director"); exit(1); } while ((dirEntry=readdir(dir)) != 0) { sprintf(name,"%s/%s",dirName,dirEntry->d_name); lstat (name, &inode); // test the type of file if (S_ISDIR(inode.st_mode)) printf("dir "); else if (S_ISREG(inode.st_mode)) printf ("fis "); else if (S_ISLNK(inode.st_mode)) printf ("lnk "); else; printf(" %s\n", dirEntry->d_name); } }

int main(int argc, char **argv) { if (argc != 2) { printf ("UTILIZARE: %s nume_dir\n", argv[0]); exit(0); } printf("Continutul directorului este:\n"); listDir(argv[1]);

45

Apeluri sistem pentru lucrul cu fiiere i directoare n Linux

3.5. Probleme
1. Modificnd Exemplul 1, s se verifice dac n cazul crerii unui fiier cu guri sistemul de operare aloc spaiu pe HDD i pentru gurile din fiier. Pentru aceasta se va calcula, folosind apelul sistem lseek, dimensiunea n octei i n blocuri a unui fiier, iar apoi se vor compara rezultatele obinute cu valorile similare returnate de apelul sistem stat. Se pot folosi, de asemenea comenzile stat i df. Testele vor fi fcute pe fiiere cu guri foarte mari (sute de MB sau GB). S se testeze, de asemenea, ce returneaz o operaie de citire dintr-o gaur din fiier. 2. S se scrie un program C care scrie n ordine invers liniile unui fiier text ntr-un alt fiier. Numele ambelor fiiere se specific ca argumente ale programului n linia de comand. 3. S se scrie un program C care citete dintr-un fiier octeii de la deplasamentele 0, 20, 40 etc. (pn la sfritul fiierului) i i scrie la sfritul aceluiai fiier. S se afieze dimensiunea fiierului nainte i dup scrierea caracterelor. 4. S se testeze dac ntr-un fiier deschis n mod O_RDWR | O_APPEND, se poate citi de la i scrie la orice deplasament. S se scrie apoi un program C, care va fi lansat simultan de N ori. Programul scrie la sfritul unui fiier binar identificatorul de proces, obinut cu funcia getpid. Nici unul dintre cele N procese nu poate s-i continue execuia pn ce toate celelalte procese nu i-au scris identificatorul propriu n fiier. n final, fiecare proces afieaz urmtorul identificator din fiier. Valoarea constantei N se presupune cunoscut n momentul scrierii programului. 5. S se scrie un program C care s permit inserarea unor iruri de caractere ntr-un fiier text, ncepnd cu o anumit poziie. Apelul programului se face sub forma: insert fisier positie sir. 6. S se scrie un program care elimin tot al cincilea octet dintr-un fiier, fr a se folosi un fiier temporar i fr a citi n memorie ntregul fiier. Pentru ajustarea dimensiunii fiierului se poate folosi funcia truncate. 7. ntr-un fiier binar fis.bin sunt scrise numere ntregi. S se fac media aritmetic a fiecrui grup de numere cuprinse ntre dou zerouri. S se scrie valorile respective pe cte o linie distinct n fiierul text medii.txt. nceputul i sfritul fiierului pot fi considerate ca zerouri. 8. ntr-un fiier binar numit fis.bin sunt scrise numere i caractere sub forma: dou numere ntregi urmate de un caracter. Caracterul poate fi +, -, * sau /. S se scrie un program C care citete un anumit grup

46

Sisteme de operare. Chestiuni teoretice i practice

de numere i operatorul ataat, efectueaz operaia dintre cele dou numere i apoi scrie ntr-un fiier text res.txt o linie de forma:
nr1 operator nr2 = rezultat

Linia de rezultat se va aduga la sfritul fiierului. Numrul grupului vizat din fiierul binar se specific ca argument n linia de comand. 9. Se consider urmtorul program scris n fiierul surs C prg.c:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> void main( void) { if (open(temp, O_RDWR) < 0) { perror(Eroare open); exit(1); } if (unlink(temp) < 0) { perror(Eroare unlink); exit(2); } printf(Fisierul temp a fost sters.\n); sleep(30); printf(Terminare program.\n); }

S se explice rezultatul urmtoarelor comenzi:


dd if=/dev/zero of=temp bs=1024 count=1024 ls -lh temp; df -h # nainte de execuia progr. Gcc prg.c -o prg.exe; prg.exe & ls -lh temp; df -h # nainte de terminarea progr. Ls -lh temp; df -h # dup terminarea progr.

10. S se scrie un program C care terge un director cu tot ce conine acesta. Numele directorului se specific n linia de comand. 11. S se scrie un program C care caut un fiier n ntreaga structur a unui arbore de fiiere i directoare, care are ca rdcin un director dat. Numele fiierului i al directorului de pornire se transmit ca argumente ale programului n linia de comand. Opional, se poate considera cazul indicrii numelui de fiier sub forma unui ablon, folosind caracterul *. 12. S se scrie un program C similar cu cel de la problema precedent, dar pentru cutarea unui ir de caractere n cadrul fiierelor. 13. S se scrie un program C similar ca funcionalitate comenzii Linux mv. Programul se poate apela n formele:
move numeFisVechi numeFisNou move numeFis numeDir move numeDirVechi numeDirNou

14. S se modifice Exemplul 3, astfel nct s se parcurg ntreaga structur a directorului i s se afieze i dimensiunea i drepturile de acces. Se va ine cont de legturile simbolice.
47

4. Sistemul de fiiere NTFS


Scopul lucrrii Aceast lucrare prezint pe scurt cteva caracteristici ale NTFS, sistemul de fiiere nativ al sistemului de operare Windows 2000, i principalele funcii ale API-ului Win32 legate de gestiunea fiierelor i directoarelor.

4.1. Prezentare general


NTFS (New Technology File System) este un sistem de fiiere dezvoltat special pentru Windows NT i mbuntit pentru Windows 2000. NTFS4 este folosit la Windows NT, n timp ce sistemul de fiiere pentru Windows 2000 este NTFS5. Windows XP folosete o versiune uor mbuntit a NTFS5. Facilitile principale oferite de acest sistem de fiiere sunt urmtoarele: folosete adrese de disc de 64 de bii i poate suporta partiii de pn la 264 bytes ; permite folosirea caracterelor Unicode n numele de fiiere; permite folosirea numelor de fiiere de pn la 255 de caractere, inclusiv spaii i puncte; permite indexarea fiierelor; ofer posibilitatea managementului dinamic al sectoarelor ; datorit compatibilitii POSIX, permite crearea hard-link-uri, face distincie ntre litere mari i mici n cadrul numelor de fiiere i pstreaz informaii de timp referitoare la fiier; permite utilizarea fiierelor cu seturi multiple de date.

4.2. Structura unei partiii NTFS


La formatarea unei partiii (volum) conform NTFS se creeaz o serie de fiiere sistem, dintre care cel mai important este fiierul Master File Table (MFT), care conine informaii despre toate fiierele i directoarele de pe volumul NTFS, fiind un fel de baza de date a sistemului. Prima locaie pe o partiie NTFS este sectorul de boot, care este sectorul 0 al partiiei i conine un program (cod executabil) de pornire a sistemului. Alte informaii necesare programului de boot-are (de exemplu informaii necesare accesrii volumului) pot fi nscrise n sectoarele de la 1 la 16, care sunt rezervate n acest scop. Figura 1 ilustreaz structura unui volum NTFS la terminarea formatrii.
48

Sisteme de operare. Chestiuni teoretice i practice

Primul fiier pe un volum NTFS este fiierul MFT. Pentru fiecare fiier de pe un volum NTFS exist cel puin o intrare n MFT, inclusiv pentru MFT. Toate informaiile despre un fiier, incluznd numele, dimensiunea, informaii de timp referitoare la fiier, permisiuni i datele efective sunt pstrate n MFT sau n spaiul situat n exteriorul MFT-ului care descrie intrri n MFT. Toate aceste informaii sunt considerate atribute ale fiierului, acesta fiind tratat ca o colecie de atribute. Un atribut este o secven de octei organizai n dou componente: componenta de descriere a atributului (header) i coninutul su. Atributele de fiier sunt pstrate n MFT, atunci cnd dimensiunea lor permite s fie memorate n intrarea corespunztoare din MFT sau n zone auxiliare de pe HDD, exterioare fiierului MFT i asociate intrrii din MFT a fiierului.
Sectorul de boot Fiiere sistem

Master File Table

Zona de fiiere

Figura 1. Structura unui volum NTFS

Tabelul de mai jos conine toate tipurile de atribute definite n prezent de sistemul de fiiere NTFS. Aceste tipuri de atribute sunt folosite intern de ctre NTFS, utilizatorul neavnd acces direct la atribute i neputnd defini noi tipuri de atribute. Aceast list este extensibil, n sensul c n viitor se vor putea defini i alte atribute de fiier.
Tabelul 1. Tipuri de atribute ale fiierelor n NTFS Tipul Descriere atributului Standard Include informaii cum ar fi informaii de timp i numrul de legturi. information Attribute Listeaz locaiile tuturor nregistrrilor atributelor non-rezidente. Lists File Name Un atribut care se poate repeta att pentru denumiri scurte, ct i pentru denumiri lungi de fiiere. Numele lung al fiierului poate fi de pn la 255 de caractere Unicode. Numele scurt este n format 8.3. Nume adiionale sau hard link-uri, necesitate de POSIX, pot fi incluse ca atribute de nume adiionale ale fiierului. Security Denumete proprietarul fiierului i utilizatorii care l pot accesa. Descriptor Data Conine datele din fiier. NTFS permite atribute multiple de date pentru fiecare fiier. Fiecare fiier are ntotdeauna un atribut implicit de date. Object ID Un identificator unic n volum i utilizat de facilitatea de regsire a legturilor distribuite. Nu toate fiierele au identificatori de obiect.

49

Sistemul de fiiere NTFS


Logged Tool Similar unui flux de date, dar operaiile sunt nscrise n fiierul log al Stream NTFS ntocmai ca i modificrile de metadate. Folosit de EFS. Reparse Folosit pentru puncte de montare de pe disc i de asemenea i de drivere Point de filtrare ale IFS (Installable File System) pentru a marca anumite fiiere ca fiind speciale pentru acel driver. Index Root Folosit pentru a implementa directoare i ali indeci. Index Folosit pentru a implementa directoare i ali indeci. Allocation Bitmap Folosit pentru a implementa directoare i ali indeci (directoare f. mari) Volume Folosit doar de fiierul sistem $Volume. Conine versiunea volumului. Information Volume Folosit doar de fiierul sistem $Volume. Conine eticheta volumului. Name

Fiierele metadata sunt structurile de date folosite de NTFS pentru accesul i managementul fiierelor. NTFS se bazeaz pe principiul totul este fiier. Astfel, descriptorul de volum, informaia de boot, nregistrri ale sectoarelor defecte etc. sunt toate stocate n fiiere. Fiierele care stocheaz informaiile metadata ale NTFS sunt prezentate n tabelul de mai jos:
Tabela 2. Intrrile din MFT corespunztoare fiierelor metadata ale NTFS Numele fiierului $MFT $MFTmirr $LogFile $Volume $AttrDef $. $Bitmap $Boot $BadClus $Secure $Upcase Inreg. Descriere MFT nr. 0 MFT Fiier plasat n mijlocul discului, copie a primelor 16 1 nregistrri MFT. 2 Fiier de suport pentru jurnalizare. Informaii de gestiune eticheta volumului, versiunea 3 sistemului de fiiere etc. 4 Lista atributelor standard de fiiere pe volum. 5 Directorul rdcin. 6 Harta de bii a spaiului liber pe volum. 7 Sectorul de boot (partiie boot-abil). 8 Lista blocurilor defecte. 9 Descriptori de securitate pentru toate fiierele. Fiier ce conine tabelul de conformitate ntre majuscule i minuscule n numele de fiiere de pe volum. Acest fiier este necesar pentru c numele de fiiere NTFS sunt 10 memorate n Unicode care are 65.000 de caractere diferite i nu este simplu s se caute echivalentul de majuscul, respectiv minuscul. Fiier n care sunt nregistrate drepturile utilizatorilor 11 asupra spaiului de disc (a nceput s funcioneze doar de la NTFS5).

$Quota

50

Sisteme de operare. Chestiuni teoretice i practice

4.3. Tipuri de fiiere i drepturi de acces n NTFS


n NTFS putem identifica urmtoarele tipuri de fiiere: fiiere sistem: sunt fiierele descrise n tabelul de mai sus i conin informaii ce sunt folosite numai de ctre sistemul de operare (metadata). fiiere cu seturi multiple de date (Alternate Data Streams ADS): sunt fiiere care pe lng setul de date principal (implicit), mai conin i alte seturi distincte de date. Toate aceste seturi de date sunt reprezentate prin atribute de tip Data. Modul de creare i utilizare, pentru un fiier, a seturilor de date auxiliare celui principal, este descris mai jos. fiiere arhivate: NTFS poate arhiva i dezarhiva fiierele onthe-fly, adic n momentul efecturii operaiilor de scriere i respectiv, citire a datelor din ele. Acest mecanism este invizibil aplicaiilor ce utilizeaz astfel de fiiere. fiiere criptate: EFS (Encrypted File System) ofer suport pentru a stoca fiiere criptate pe un volum NTFS. Criptarea este transparent pentru utilizatorii care au cerut criptarea fiierului. Accesul celorlali utilizatori nu este permis la aceste fiiere. fiiere rare (sparse files): sunt fiiere n care informaia scris nu se gsete ntr-o singur zon contigu, ci zonele n care s-au scris date alterneaz cu zone mari n care nu s-au scris (guri). NTFS permite setarea unui atribut special al acestor fiiere, prin care se indic sistemului de I/E s aloce spaiu pe disc numai pentru zonele efectiv scrise din fiier. fiiere de tip hard-link: sunt fiiere speciale introduse de NTFS5. Aceste fiiere permit ca un fiier s poate fi accesat prin mai multe ci fr ca datele efective s fie duplicate. Dac tergem un fiier la care exist i o alt legtur, datele nu vor fi terse de pe disc pn cnd nu se terg toate legturile. Un fiier de tip hardlink poate fi creat folosind funcia CreateHardLink sau comanda "fsutil hardlink create" (n Windows XP). n ceea ce privete drepturile de acces n NTFS, ele sunt gestionate prin liste de control al accesului (ACL). Aceste ACL-uri conin informaii care definesc pentru fiecare utilizator sau grup de utilizatori drepturile pe care le are asupra unui fiier. Drepturile de acces se numesc permisiuni.

51

Sistemul de fiiere NTFS

NTFS definete 6 astfel de permisiuni de baz, numite permisiuni speciale. n Tabelul 3 sunt enumerate aceste permisiuni i se explic ce efect are fiecare asupra fiierelor, respectiv a directoarelor.
Tabelul 3. Permisiuni asupra fiierelor n NTFS Permisiune Read Write Execute Delete Change Permissions Take Ownership Notaie R W X D P O Drepturi acordate pt. fiiere Citire coninut fiier Modificare coninut fiier Executare (rulare) program Stergere fiier Schimbare drepturi de acces pentru fiier Schimbare proprietar Drepturi acordate pentru directoare Citire coninut director Modificare coninut director (creare fiiere sau subdirectoare) Traversare structur subdirectoare tergere director Schimbare drepturi de acces pt. director Schimbare proprietar

Pentru a avea un control mai fin i mai uor asupra drepturilor de acces, s-au introdus (ncepnd cu Windows 2000) nite grupuri de permisiuni, denumite componente de permisiuni. Fiecare dintre ele grupeaz una sau mai multe permisiuni speciale, dup cum urmeaz: Traverse Folder / Execute File setat pentru permisiunea X List Folder / Read Data setat pentru permisiunea R Read Attributes setat pentru permisiunea R + X Read Extended Attributes setat pentru permisiunea R Create Files / Write Data setat pentru permisiunea W Create Folders / Append Data setat pentru permisiunea W Write Attributes setat pentru permisiunea W Write Extended Attributes setat pentru permisiunea W Delete Subfolders and Files setat pentru permisiunea D Delete setat pentru permisiunea D Read Permissions setat pentru permisiunea R + W + X Change Permissions setat pentru permisiunea P Take Ownership setat pentru permisiunea O Setarea acestor permisiuni poate fi fcut i din interfaa grafic n seciunea Security (Advanced...) din fereastra de proprieti (Properties) ale unui fiier.
52

Sisteme de operare. Chestiuni teoretice i practice

4.4. Funcii API Win32 pentru sistemul de fiiere NTFS


Toate resursele (fiiere, procese etc.) sistemelor de operare derivate din Windows NT sunt identificate i accesate prin intermediul unor structuri de date numite handler-e. Orice proces care dorete folosirea unei resurse trebuie s obin un handler pentru acea resurs. Handler-ul este similar descriptorilor de fiier din sistemele Unix. Astfel, atunci cnd este creat sau deschis un fiier, se returneaz un handler i fiierul poate fi accesat pentru citire i scriere folosind acest handler. Funcia CreateFile Funcia este folosit pentru a crea un fiier sau pentru a deschide un fiier existent. Sintaxa funciei este urmtoarea:
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);

Semnificaia parametrilor este urmtoarea:


lpFileName

Este un pointer ctre un ir de caractere terminat cu 0, care specific numele fiierului care se creeaz sau se deschide.
dwDesiredAccess

Specific tipul de acces la fiier. O aplicaie poate obine acces doar pentru citire, doar pentru scriere, pentru scriere i citire sau acces de interogare a dispozitivelor. Cele mai importante valori pentru acest parametru sunt: 0 Obinerea caracteristicilor dispozitivelor sistemului i a fiierelor, fr accesarea acestora. De exemplu se poate verifica existena unui fiier, fr deschiderea lui. GENERIC_READ Dreptul de citire a fiierului. Datele se pot citi din fiier i pointerul de fiier poate fi deplasat. GENERIC_WRITE Dreptul de scriere a fiierului. Datele pot fi scrise n fiier i pointerul fiierului poate fi deplasat. Combinat cu GENERIC_READ indic dreptul de citire i scriere. DELETE Dreptul de a terge fiierul.
53

Sistemul de fiiere NTFS

Dreptul de a citi informaiile din descriptorul de securitate al fiierului. WRITE_OWNER Dreptul de a schimba proprietarul n descriptorul de securitate al fiierului. SYNCHRONIZE Dreptul de a folosi fiierul pentru sincronizare. Acesta i d unui thread posibilitatea de a atepta pn cnd fiierul este n starea marcat. GENERIC_EXECUTE Dreptul de execuie. GENERIC_ALL Dreptul de citire, scriere i execuie.
READ_CONTROL dwShareMode

Specific modul n care poate fi partajat fiierul ntre mai muli utilizatori. Dac dwShareMode este 0 i CreateFile se ncheie cu succes, fiierul nu poate fi partajat i nu poate fi deschis din nou pn cnd handler-ul nu este nchis. Pentru a partaja fiierul, se poate folosi o combinaie a urmtoarelor valori: FILE_SHARE_DELETE urmtoarele operaii de deschidere a fiierului vor reui numai dac este solicitat accesul de tergere. FILE_SHARE_READ urmtoarele operaii de deschidere a fiierului vor reui numai dac este solicitat accesul de citire. FILE_SHARE_WRITE urmtoarele operaii de deschidere a fiierului vor reui numai dac este solicitat accesul de scriere.
lpSecurityAttributes

Este un pointer la o structur SECURITY_ATTRIBUTES care determin dac handler-ul poate fi motenit de procesele fiu. Dac atributul lpSecurityAttributes este NULL, atunci handler-ul nu poate fi motenit.
dwCreationDisposition

Specific aciunea care se va efectua asupra fiierelor care exist i ce aciune s se efectueze dac fiierul nu exist. Acest parametru trebuie s ia una dintre valorile urmtoare: CREATE_NEW Creeaz un fiier nou. Funcia eueaz dac fiierul exist deja. CREATE_ALWAYS Creeaz un fiier nou. Dac fiierul exist, funcia suprascrie fiierul, terge atributele
54

Sisteme de operare. Chestiuni teoretice i practice

existente i combin atributele de fiier i opiunile specificate de parametrul dwFlagsAndAttributes cu opiunea FILE_ATTRIBUTE_ARCHIVE. OPEN_EXISTING Deschide un fiier. Funcia eueaz dac fiierul nu exist. OPEN_ALWAYS Deschide fiierul, dac acesta exist. Dac fiierul nu exist, funcia creeaz fiierul ca i cum parametrul dwCreationDisposition ar fi CREATE_NEW. TRUNCATE_EXISTING Deschide fiierul. O dat deschis, fiierul este trunchiat astfel nct dimensiunea lui s fie de 0 octei. Procesul apelant trebuie s deschid fiierul cel puin cu accesul GENERIC_WRITE. Funcia eueaz dac fiierul nu exist.
dwFlagsAndAttributes

Specific atributele fiierului i diferite opiuni pentru fiier. Un fiier poate avea urmtoarele atribute: archive, encrypted, hidden, normal, not content indexed, offline, read-only, system, temporary i urmtoarele opiuni: write through, overlapped, no buffering, random access, sequential scan, delete on close, backup semantics, POSIX semantics, open reparse point i open no recall.
hTemplateFile

Specific un handler cu acces GENERIC_READ la un fiier template. Fiierul template furnizeaz atributele de fiier pentru fiierul ce se creeaz. Dac funcia are succes, valoarea returnat este un handler prin care se acceseaz n continuare fiierul specificat. Dac funcia eueaz, valoarea returnat este INVALID_HANDLE_VALUE. Pentru a obine informaii detaliate despre eroarea aprut trebuie folosit funcia GetLastError. Funcia DeleteFile Funcia terge un fiier existent i are urmtoarea sintax:
BOOL DeleteFile( LPCTSTR lpFileName); // numele fiierului

Returneaz o valoare nenul n caz de succes i 0 altfel.

55

Sistemul de fiiere NTFS

Funcia CloseHandle Funcia nchide un handler de fiier obinut anterior cu funcia CreateFile.
BOOL CloseHandle( HANDLE hObject); //handler catre obiect

Returneaz o valoare nenul n caz de succes, 0 altfel. Funcia ReadFile Funcia citete date dintr-un fiier, ncepnd de la poziia indicat de ctre pointerul fiierului. Dup ce operaia de citire a fost finalizat, pointerul de fiier este ajustat cu numrul de octei citii efectiv, mai puin n cazul n care handler-ul de fiier este creat cu atributul FILE_FLAG_OVERLAPPED. Dac handler-ul de fiier este creat pentru intrare-ieire suprapus (I/O), aplicaia trebuie s ajusteze poziia pointerului de fiier dup operaia de citire.
BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); // // // // // handler ctre fisier buffer de date nr octeti de citit nr octeti cititi buffer suprapus

Semnificaia parametrilor este urmtoarea:


hFile

Handler ctre fiierul de citit. Handler-ul de fiier trebuie s fi fost creat cu accesul GENERIC_READ la fiier.
lpBuffer

Adresa de memorie unde se pun datele citite din fiier.


nNumberOfBytesToRead

Specific numrul de octei care trebuie citii din fiier.


lpNumberOfBytesToRead

Pointer la variabila n care se scrie numrul de octei efectiv citii.


lpOverlapped

Pointer la o structur OVERLAPPED. Aceast structur este solicitat dac hFile a fost creat cu FILE_FLAG_OVERLAPPED. Se revine din funcia ReadFIle dac numrul de octei cerut a fost citit sau dac a aprut o eroare. Dac funcia reuete, valoarea returnat este nenul.
56

Sisteme de operare. Chestiuni teoretice i practice

Funcia WriteFile Aceast funcie scrie date ntr-un fiier i este destinat att pentru operaii sincrone ct i pentru operaii asincrone. Funcia ncepe s scrie datele n fiier la poziia indicat de pointerul de fiier. Dup ce operaia de scriere a fost terminat, pointerul de fiier este ajustat cu numrul de octei scrii efectiv, cu excepia cazului n care fiierul este deschis cu FILE_FLAG_OVERLAPPED.
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);

Semnificaiile parametrilor sunt similare cu cele ale parametrilor funciei ReadFile. Dac funcia se termin cu succes, valoarea returnat va fi nenul. Dac funcia eueaz, valoarea returnat este 0. Funcia SetFilePointer Funcia SetFilePointer deplaseaz pointerul unui fiier deschis.
DWORD SetFilePointer( HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod);

Semnificaia parametrilor este urmtoarea:


hFile

Handler la fiierul al crui pointer se va deplasa. Handlerul de fiier trebuie s fi fost creat cu unul din urmtoarele dou tipuri de acces la fiier GENERIC_READ sau GENERIC_WRITE.
lDistanceToMove

Conine cei mai puin semnificativi 32 de bii ai valorii cu care se va deplasa pointerul fiierului. Pentru o valoare pozitiv pointerul va fi mutat spre sfritul fiierului, iar pentru una negativ spre nceput.
lpDistanceToMoveHigh

Indic cei mai semnificativi 32 de bii ai valorii cu care se va deplasa pointerul fiierului. Dac nu e nevoie de 64 de bii, ci sunt suficieni 32, valoarea acestui parametru trebuie s fie NULL.
57

Sistemul de fiiere NTFS


dwMoveMethod

Poziia relativ la care se va face deplasarea pointerului de fiier. Acest parametru poate avea una din urmtoarele valori: FILE_BEGIN nceputul fiierului. FILE_CURRENT Actuala valoare a pointerului fiierului. FILE_END Sfritul fiierului. Dac funcia SetFilePointer se termin cu success i
lpDistanceToMoveHigh este NULL, valoarea returnat este dublucuvntul (32 de bii) cel mai puin semnificativ al noii poziii a pointerului de fiier. Dac lpDistanceToMoveHigh nu este NULL, atunci funcia

scrie la adresa indicat de acest parametru dublu-cuvntul cel mai semnificativ al noii poziii a pointerului de fiier. Dac funcia eueaz, valoarea returnat este INVALID_SET_FILE_POINTER. Funcia GetFileAttributes Aceast funcie obine setul de atribute specifice sistemului de fiiere de tip FAT pentru un fiier sau un director specificat.
DWORD GetFileAttributes( LPCTSTR lpFileName);

Dac funcia se termin cu succes, valoarea returnat va conine codificat pe bii atributele fiierului sau directorului specificat. Atributele pot fi identificate cu ajutorul urmtoarelor constante (mti pe bii): FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_REPARSE_POINT FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_SYSTEM etc. Funcia LockFile Funcia LockFile blocheaz o regiune dintr-un fiier deschis pentru a asigura accesul n excludere mutual la acea zon, a procesului care o blocheaz. Sintaxa funciei LockFile este:
BOOL LockFile( HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh);

58

Sisteme de operare. Chestiuni teoretice i practice

Semnificaia parametrilor este urmtoarea:


hFile

Handler la fiierul al crei regiune se va bloca. Numele fiierului trebuie s fi fost creat cu unul din urmtoarele tipuri de acces la fiier: GENERIC_READ sau GENERIC_WRITE (sau ambele).
dwFileOffsetLow

Specific cuvntul cel mai puin semnificativ al deplasamentului n fiier ce indic nceputul zonei ce va fi blocat.
dwFileOffsetHigh

Specific cuvntul cel mai semnificativ al deplasamentului n fiier ce indic nceputul zonei ce va fi blocat.
nNumberOfBytesToLockLow

Specific cuvntul cel mai puin semnificativ al dimensiunii zonei ce va fi blocat.


nNumberOfBytesToLockHigh

Specific cuvntul cel mai semnificativ al dimensiunii zonei ce va fi blocat. Dac funcia se termin cu succes, valoarea returnat este nenul, altfel este 0. Funcia UnlockFile Funcia deblocheaz o regiune blocat anterior cu funcia LockFile ntr-un fiier deschis. Sintaxa acestei funcii este similar cu cea a funciei LockFile:
BOOL UnlockFile( HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh);

Funcia CreateDirectory Aceast funcie creeaz un nou director. Dac sistemul de fiiere existent suport opiuni de securitate pentru directoare i fiiere, funcia va aplica descriptorul de securitate specificat pentru noul director.
BOOL CreateDirectory( LPCTSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes);

Dac funcia se termin cu succes, valoarea returnat este nenul, altfel este 0.
59

Sistemul de fiiere NTFS

Funcia RemoveDirectory Funcia RemoveDirectory terge un director gol existent. Sintaxa funciei este urmtoarea:
BOOL RemoveDirectory( LPCTSTR lpPathName);

Dac funcia se termin cu succes, valoarea returnat este nenul, altfel este 0. Funcia FindFirstFile Aceast funcie caut ntr-un director un fiier sau subdirector. Sintaxa funciei este urmtoarea:
HANDLE FindFirstFile( LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);

Parametrul lpFileName indic calea spre fiierul sau subdirectorul cutat. Numele fiierului poate s conin i caracterele '*' sau '?', caz n care este interpretat ca un ablon cutndu-se primul fiier sau director care se potrivete ablonului respectiv. Parametrul lpFindFileData este adresa unei structuri de tipul WIN32_FIND_DATA, n care se vor depune informaii despre fiierul gsit (atribute, timpi etc.) Dac funcia se termin cu succes, valoarea returnat este un handler de cutare care va putea fi folosit ntr-un apel ulterior al funciei FindNextFile, pentru a se gsi urmtorul fiier care se potrivete ablonului specificat n apelul funciei FindFirstFile. Handlerul poate fi eliberat cu ajutorul funciei FindClose. Dac funcia FindFirstFile eueaz, adic nici un fiier nu este gsit, valoarea returnat este INVALID_HANDLE_VALUE. Funcia FindNextFile Aceast funcie continu cutarea fiierelor sau directoarelor, care se potrivesc ablonului specificat ntr-un apel anterior al funciei FindFirstFile. Sintaxa funciei este:
BOOL FindNextFile( HANDLE hFindFile, // handler de cutare LPWIN32_FIND_DATA lpFindFileData);

Dac funcia se termin cu succes, valoarea returnat este nenul, altfel este 0.
60

Sisteme de operare. Chestiuni teoretice i practice

Funcia MoveFile Mut un fiier sau director existent. Operaia poate fi vzut i ca o redenumire a fiierelor sau directoarelor. n cazul mutrii unui director, ntregul arbore ce are ca rdcin acel director este mutat n directorul destinaie. O restricie a acestei funcii este c nu permite mutarea unui director ntre volume diferite.
BOOL MoveFile( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName); // numele vechi // noul nume

Dac funcia se termin cu succes, valoarea returnat este nenul, altfel este 0. Funcia SetCurrentDirectory Funcia schimb directorul curent pentru procesul care o apeleaz. Sintaxa ei este urmtoarea:
BOOL SetCurrentDirectory( LPCTSTR lpPathName); // numele noului director

Dac funcia se termin cu succes, valoarea returnat este nenul, altfel 0.

4.5. Fiiere cu seturi multiple (alternative) de date


Dup cum am mai menionat, sistemul NTFS permite ca unui fiier s-i fie asociate mai multe atribute de tip Data, deci mai multe seturi de date. Fiecare fiier are asociat un set de date principal, care nu are un nume explicit, folosirea simpl a numelui fiierului indicnd n mod implicit acest set de date. Dac este necesar, se mai pot asocia fiierului alte seturi alternative cu nume explicite. Aceast facilitate permite ca unele date din fiier s fie accesate ca o unitate separat. De exemplu, o aplicaie grafic poate s stocheze pentru un fiier imagine o versiune de calitate mai slab, dar de dimensiune mult mai mic a imaginii (thumbnail) ntr-un alt set de date al fiierului, diferit de cel principal, care conine imaginea propriu-zis. Fiecare set alternativ de date al unui fiier este tratat separat de NTFS, fiind identificat n mod distinct printr-un nume de forma: nume_fiier:nume_set_alternativ. Dimensiunile seturilor alternative de date ale unui fiier nu sunt evideniate n dimensiunea fiierului, aceasta fiind dat doar de dimensiunea setului principal. Toate seturile de date ale unui fiier au aceleai permisiuni, i anume cele ale fiierului.
61

Sistemul de fiiere NTFS

Un set de date alternativ poate fi creat prin apelul funciei CreateFile sau din linia de comand, specificnd un nume de forma menionat mai sus. Exemplul de mai jos ilustreaz acest lucru pentru linia de comand:
echo "Setul principal de date" > Fisier.txt

Astfel, am creat fiierul cu numele Fisier.txt. Comanda urmtoare creeaz n acest fiier un set alternativ cu numele ADS:
echo "Set alternativ de date" > Fisier.txt:ADS

Se poate observa c setul adugat nu apare ntre fiierele din director i nici nu mrete dimensiunea fiierului principal. Pentru a citi coninutul setului principal i al celui alternativ se pot executa comenzile:
more more < Fisier.txt < Fisier.txt:ADS

Pentru a deschide un set alternativ ntr-un editor de texte, de exemplu Notepad, numele setului trebuie s aib o extensie, de exemplu: Fisier.txt:ADS.txt. n acest mod, el poate fi vizualizat i editat n editor prin comanda "notepad Fisier.txt:ADS.txt". Seturile alternative de date pot conine i date binare, adic fiiere executabile. Ele se pot executa cu comanda: "start .\Fisier.txt:fis.exe". Sistemul de operare Windows folosete seturi alternative de date atunci cnd specificm date suplimentare pentru un fiier n seciunea Summary a paginii de proprieti (Properties) ale fiierului, pentru a stoca acele date. Seturile alternative de date ofer, pe de alt parte, o bun posibilitate viruilor de a se ascunde, pentru c ele nu se vd n lista de fiiere i nu modific dimensiunea i marca de timp a fiierului principal. Sistemul de operare Windows nu ofer n mod implicit programe utilitare care detecteaz seturile alternative de date. Un astfel de utilitar poate fi ns gsit la adresa http://www.microsoft.com/technet/sysinternals/default.mspx. El se numete streams i are sintaxa urmtoare:
streams [-s] [-d] <fisier sau director>

Opiunea s indic intrarea n adncime n toate subdirectoarele directorului n care se caut fiiere cu seturi multiple de date. Opiunea d indic tergerea setului de date specificat ca argument.

62

Sisteme de operare. Chestiuni teoretice i practice

4.6. Exemple
Exemplul 1. Program de mai jos realizeaz copierea unui fiier existent ntr-un alt fiier, folosind funciile API Win32 ale Windows 2000.
#include <windows.h> #include <stdio.h> #define BUF_SIZE 10 void main() { HANDLE char int DWORD inhandle, outhandle; buffer[BUF_SIZE]; count, s; ocnt;

/* Deschide fisierele de intrare si de iesire */ inhandle = CreateFile("sursa.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); outhandle = CreateFile("dest.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); /* Copieaza fisierul */ do { s = ReadFile(inhandle, buffer, BUF_SIZE, &count, NULL); if (s && count > 0) WriteFile(outhandle, buffer, count, &ocnt, NULL); } while (s>0 && count>0); /* Inchide fisierele */ CloseHandle(inhandle); CloseHandle(outhandle);

Exemplul 2. Programul urmtor, caut toate fiiere de tipul *.txt din directorul curent i seteaz atributul lor read-only.
#include <windows.h> #include <stdio.h> WIN32_FIND_DATA FileData; HANDLE hSearch; DWORD dwAttrs; BOOL fFinished = FALSE;

63

Sistemul de fiiere NTFS


void main() { /* Caut fisiere *.txt in directorul curent */ hSearch = FindFirstFile("*.txt", &FileData); if (hSearch == INVALID_HANDLE_VALUE) { printf("Nu s-au gasit fisisere *.txt"); return; } // Seteaza fiecare fisier gasit la read-only // daca nu are deja setat acel atribut while (!fFinished) { dwAttrs = GetFileAttributes(FileData.cFileName); if (!(dwAttrs & FILE_ATTRIBUTE_READONLY)) { SetFileAttributes(FileData.cFileName, dwAttrs | FILE_ATTRIBUTE_READONLY); } if (!FindNextFile(hSearch, &FileData)) { if (GetLastError() == ERROR_NO_MORE_FILES) { printf("Nu mai sunt fisiere *.TXT"); fFinished = TRUE; } else { printf("Eroare de cautare."); return; } }

// nchide handle-ul de cutare FindClose(hSearch);

4.7. Probleme
1. S se calculeze dimensiunea unui fiier n octei i n numr de blocuri, folosind funcia SetFilePointer. S se compare rezultatul obinut cu valorile afiate n pagina de proprieti a fiierului. 2. S se scrie un program C care s afieze coninutul unui director dat, indicnd pentru fiecare element al directorului cteva proprieti (tipul,
64

Sisteme de operare. Chestiuni teoretice i practice

3.

4.

5.

6.

dimensiunea, etc.). Programul poate primi opiunea R, caz n care va afia recursiv i coninutul subdirectoarelor (i al subdirectoarelor lor i aa mai departe) directorului iniial. Numele directorului se precizeaz ca argument n linia de comand sau dac lipsete, se consider directorul curent. S se scrie un program C care pune n eviden modul de creare i manipulare a fiierelor de tip hard link (legtur fizic). Funcia de creare a unei legturi fizice este CreateHardLink. S se creeze un fiier text cruia s i se asocieze un flux alternativ de date, care s conin un cod executabil, i anume programul Calculator (aflat n directorul %SystemRoot%\system32\calc.exe). S se lanseze apoi programul respectiv din cadrul fluxului alternativ al fiierului text. S se fac o comparaie ntre modul de gestionare a fiierelor cu guri (sparse files) n sistemele de operare Linux i Windows 2000 sau XP. Acestea se creeaz fcnd salt peste sfritul fiierului i scriind ceva la acel deplasament. O prim ntrebare ar fi dac se aloc spaiu pe HDD pentru gaur (zona nescris), iar o a doua ntrebare ar fi ce returneaz o citire din acea gaur. Pe Windows tratarea unui fiier ca sparse file se face cu ajutorul funciei DeviceIoControl, cu opiunea FSCTTL_SPARCE_FILE. S se scrie un program C care s pun n eviden proprietatea de indexare (cu arbori B+) a directoarelor n NTFS. Pentru aceasta se vor crea ntr-un director un numr foarte mare de fiiere (10.000 100.000) i se vor face apoi cutri att ale tuturor fiierelor din director, ct i ale unor fiiere care nu exist n director. Se va afia timpul necesar crerii i respectiv, cutrii fiierelor, att individual, pentru fiecare fiier n parte, ct i global pentru ntregul set. Pentru msurarea timpului se pot folosi funciile GetTickCount sau clock sau QueryPerformanceCounter. Testul descris s se fac pentru dou directoare, unul avnd setat opiunea de indexare (n seciunea General Advanced..., din pagina de proprieti ale acelui director), cellalt neavnd setat acea opiune. S se realizeze testul i pe sistemul de operare Linux i s se compare rezultatele obinute.

65

5. Apeluri sistem pentru lucrul cu procese n Linux


Scopul lucrrii n cadrul acestei lucrri sunt prezentate cteva aspecte legate de crearea i gestionarea proceselor n Linux i apelurile sistem ce pot fi utilizate pentru manipularea proceselor, cum ar fi cele pentru creare, terminare, ateptare dup terminarea unui alt proces etc.

5.1. Procese
Un proces este entitatea ce reprezint un program n execuie, nelegnd prin program codul i datele aferente aflate ntr-un fiier executabil. Fiecare proces are asociat un identificator unic numit identificator de proces, prescurtat PID. PID-ul este un numr pozitiv atribuit de sistemul de operare fiecrui proces nou creat. Cum PID-ul unui proces este unic, el nu poate fi schimbat, dar numrul respectiv poate fi refolosit de ctre sistemul de operare pentru identificarea unui nou proces, cnd procesul cruia i-a fost atribuit anterior se termin. Un proces i poate obine identificatorul propriu prin apelul sistem getpid. n Linux, orice proces nou este creat de ctre un proces deja existent, dnd natere unei relaii printe-fiu. Excepie face procesul cu PID-ul 0, care este creat chiar de ctre sistemul de operare la pornirea sa. Un proces poate s determine PID-ul printelui su prin apelul sistem getppid. PID-ul procesului printe nu se poate modifica, adic un proces nu i poate schimba el nsui printele. Acest lucru se poate ntmpla totui o singur dat, dar realizat n mod automat de ctre sistemul de operare, atunci cnd un proces care are fii se termin, moment n care toi fiii si devin fii ai procesului cu PID-ul 1 (procesul init). Sintaxa celor dou apeluri sistem amintite este:
#include <sys/types.h> #include <unistd.h> pid_t getpid(); pid_t getppid();

Sistemul de operare ine evidena proceselor ntr-o structur de date intern numit tabela proceselor. Fiecare proces din sistem are alocat o intrare n aceast tabel. Lista proceselor din tabela proceselor poate fi obinut prin
66

Sisteme de operare. Chestiuni teoretice i practice

comanda ps. O comand similar este comanda top. n mod normal comanda ps afieaz doar procesele utilizatorului care a lansat-o i doar acele procese ataate terminalului (indicat n coloana TTY a tabelei afiate) n care se execut comanda. Dac se dorete i afiarea proceselor altor utilizatori, atunci se poate specifica opiunea "-a", iar pentru afiarea proceselor care nu sunt ataate unui terminal, opiunea "-x". Cu ajutorul opiunii "-l" se afieaz mai multe informaii despre un proces, ca de exemplul starea sa, PIDul printelui su, UID-ul utilizatorului cruia i aparine acel proces. Modul de afiare a acestor informaii este ilustrat mai jos.

5.2. Grupuri de procese


Sistemul de operare permite constituirea unui set de procese ca i grup distinct pentru a facilita transmiterea de semnale n cadrul acelui grup. Pe lng PID-ul asociat, fiecare proces are i un identificator de grup de procese, prescurtat PGID, care permite identificarea unui grup de procese. PGID-ul este motenit de procesul fiu de la procesul printe. Contrar PIDului, un proces poate s-i modifice PGID-ul, dar numai prin crearea unui nou grup avnd identificatorul egal cu PID-ul procesului. Acest lucru se realizeaz prin apelul sistem setpgrp, cu urmtoarea sintax:
#include <unistd.h> int setpgrp();

Funcia setpgrp actualizeaz PGID-ul procesului apelant la valoarea PIDului su i ntoarce noul PGID. Procesul apelant prsete astfel vechiul grup devenind liderul unui nou grup, urmnd ca procesele fiu pe care eventual le va crea s fac parte din acel grup. Deoarece procesul apelant este primul membru al grupului i numai descendenii si pot s aparin grupului (prin motenirea PGID-ului), el este referit ca reprezentantul (liderul) grupului. Deoarece doar descendenii liderului pot fi membri ai grupului, exist o corelaie ntre grupul de procese i arborele proceselor. Fiecare lider de grup este rdcina unui subarbore, care dup eliminarea rdcinii conine doar procese ce aparin grupului. Dac nici un proces din
67

Apeluri sistem pentru lucrul cu procese n Linux

grup nu s-a terminat lsnd fii care au fost adoptai de procesul init, acest subarbore conine toate procesele din grup. Un proces i poate determina PGID-ul su folosind apelul sistem getpgrp, cu urmtoarea sintax:
#include <unistd.h> pid_t getpgrp();

Apelul ntoarce PGID-ul procesului apelant. Deoarece PID-ul liderului este acelai cu PGID-ul, getpgrp identific liderul de grup. Un proces poate fi asociat unui terminal, care este numit terminalul de control asociat procesului. Acesta este motenit de la procesul printe la crearea unui nou proces. Un proces este deconectat (eliberat) de terminalul su de control la apelul setpgrp, devenind astfel un lider de grup de procese (nu i se nchide ns terminalul). Ca atare, numai liderul poate stabili un terminal de control, devenind procesul de control pentru terminalul n cauz. Un proces care nu este asociat unui terminal de control este numit daemon. Spooler-ul de imprimant este un exemplu de astfel de proces. Un proces daemon este identificat n rezultul afirii comenzii ps prin simbolul ? plasat n coloana TTY.

5.3. Programe i procese


Un program este o colecie de instruciuni i date pstrate ntr-un fiier executabil pe disc, avnd coninutul organizat conform unui format bine stabilit. Un program n Linux este format din mai multe segmente. n segmentul de cod se gsesc instruciuni n format binar. n segmentul de date se gsesc date predefinite (de exemplu, constante) i date iniializate. Aceste dou segmente, alturi de segmentul de stiv, care conine date alocate dinamic la execuia procesului, sunt pri funcionale ale unui proces Linux. Pentru a executa un program, se creeaz un nou proces, care nu este altceva dect un mediu n care se va executa programul. Programul este folosit pentru a iniializa primele dou segmente, dup care nu mai exist nici o legtur ntre proces i programul pe care-l execut. Datele sistem ale unui proces includ informaii ca directorul curent, descriptori de fiiere deschise, ci implicite, tipul terminalului, timp CPU consumat etc. Un proces nu poate accesa sau modifica direct propriile date sistem, deoarece acestea sunt n afara spaiului su de adresare. Exist ns multiple apeluri sistem pentru a accesa sau modifica indirect aceste informaii.

68

Sisteme de operare. Chestiuni teoretice i practice

Toate procesele active la un moment dat n Linux sunt de fapt descendeni direci sau indireci ai unui singur proces, lansat la pornirea sistemului prin comanda "/etc/init". La intrarea unui utilizator n sistem se lanseaz automat un nou proces care este interpretorul de comenzi corespunztor sesiunii acelui utilizator. Acest proces are menirea de a interpreta i executa comenzile introduse de la tastatur de ctre utilizator. Fiecare comand introdus se execut n cadrul unui nou proces creat de ctre interpretorul de comenzi. Crearea unui proces se realizeaz prin apelul sistem fork. La fiecare execuie a acestui apel, se creeaz un nou proces, distinct de procesul care apeleaz fork, avnd propriul su PID. Cele dou procese, cel care a apelat funcia fork i cel nou creat, sunt concurente, adic independente din punct de vedere al execuiei. Totui ele sunt identice n ceea ce privete codul i datele. Apelul sistem fork realizeaz astfel o copie a procesului iniial i, ca atare, imaginea proceselor n memorie este identic. Procesul care a iniiat apelul fork este identificat ca proces printe, iar procesul rezultat n urma apelului este identificat ca proces fiu. Pentru a explica modul de lucru al interpretorului de comenzi considerm spre exemplu, execuia comenzii "echo text". Interpretorul de comenzi desparte comanda de argumente i execut apoi apelul sistem fork, care are ca efect crearea unui proces fiu. Procesul printe (interpretorul), prin apelul sistem wait, i suspend execuia i ateapt terminarea procesului fiu. Procesul fiu cere nucleului, prin apelul sistem exec, ncrcarea i pornirea execuiei unui nou program (cod i date), respectiv cel memorat n fiierul executabil "/bin/echo" i comunic n acelai timp i argumentele pentru noul program. Sistemul suprascrie segmentele de cod i date ale procesului fiu cu coninutul corespunztor acelor segmente citite din fiierul executabil indicat de exec. n procesul fiu se va ncepe deci execuia noului program, care se va termina prin apelul sistem exit (apelat n mod explicit de ctre proces sau implicit de ctre sistemul de operare la sfritul funciei main a procesului). Apelul sistem exit are ca efect terminarea procesului curent (fiul, n exemplul nostru), memorarea unui cod de terminare n tabela proceselor, scoaterea procesului printe din starea de ateptare a fiului (stare n care a intrat prin apelul lui wait) i reluarea execuiei sale. Funcia wait extrage din tabela proceselor, din intrarea asociat procesului fiu, codul de terminare al acestuia, cod care este pstrat acolo pn n momentul n care procesul printe apeleaz wait pentru acel fiu. Acest mod de funcionare al interpretorului corespunde unei execuii sincrone i creeaz iluzia c interpretorul de comenzi este de fapt procesul care execut comanda. Este posibil ns i execuia n paralel (asincron) a celor dou procese, caz n care procesul printe (interpretorul) nu mai ateapt dup terminarea fiului
69

Apeluri sistem pentru lucrul cu procese n Linux

su (procesul care execut comanda), ci i continu imediat execuia sa. Acest lucru poate fi indicat n linia de comand prin specificarea caracterului '&' la sfritul liniei. n acest caz interpretorul afieaz pe ecran un numr, dup care i continu execuia reafind prompterul. Numrul afiat reprezint PID-ul procesului fiu, creat pentru a executa comanda.

5.4. Apelurile sistem fork i exec


Apelul sistem fork este folosit pentru crearea unui nou proces i are sintaxa:
#include <sys/types.h> #include <unistd.h> pid_t fork();

Procesul fiu, fiind o copie a procesului printe, conine acelai cod i i ncepe execuia revenind din funcia fork. Pentru a se face o distincie ntre revenirea din fork n procesul printe i n procesul fiu, funcia returneaz PID-ul fiului n primul caz (n printe) i respectiv 0, n cel de-al doilea (n fiu) sau -1 n caz de eroare. Codul de mai jos ilustreaz acest lucru.
pid=fork(); ... // cod executat de ambele procese switch (pid) { case -1: /* Eroare! fork nereusit */ case 0 : /* cod executat doar de fiu */ break; default: /* cod executat doar de printe */ } ... // cod executat de ambele procese

Cazul de eroare poate s apar dac s-a atins limita maxim de procese pe care le poate lansa un utilizator sau dac s-a atins limita maxim de procese care se pot executa simultan n sistem. n procesul fiu toate variabilele au iniial valoarea motenit din procesul printe, toi descriptorii de fiier sunt aceiai ca n procesul printe, se motenete acelai UID real i GUID real, acelai PGID al grupului de procese, aceleai variabile de mediu etc. Spaiile de adrese ale celor dou procese i resursele alocate de sistemul de operare sunt totui diferite, nsemnnd ca cele dou procese sunt distincte, fiul motenind doar ca valoare resursele printelui su. Din momentul revenirii din apelul fork, procesele printe i fiu se execut independent, concurnd unul cu celalalt pentru
70

Sisteme de operare. Chestiuni teoretice i practice

obinerea procesorului i a altor resurse ale sistemului. Procesul fiu i ncepe execuia din locul de unde i-o continu procesul printe, adic urmtoarea instruciune de dup fork. Nu se poate preciza care dintre procese va porni primul. Este posibil doar, aa cum a fost ilustrat mai sus, separarea execuiei n cele dou procese prin testarea valorii ntoarse de apelul fork. Raiunea crerii unui proces fiu identic (ca i coninut) cu printele su are sens dac se poate modifica segmentul de date i cel de cod al procesului rezultat, astfel nct s se poat ncrca i executa un nou program. Pentru acest lucru este pus la dispoziie familia de funcii exec (sub forma mai multor variante ale sale: execl, execlp, execv i execvp). Partea de sistem a procesului nu se modific n nici un fel prin apelul exec, deci nici PID-ul procesului nu se schimb. n acest caz procesul fiu va executa cu totul altceva dect printele su. Dup un apel exec reuit nu se mai revine n vechiul cod. Trebuie precizat totui c fiierele deschise ale tatlui se regsesc deschise i n fiu (datorit copierii coninutului tabelei de descriptori de fiier) i rmn aa chiar i dup apelul exec. nchiderea automat a unui fiier deschis ntr-un proces n urma apelului lui exec se face prin specificarea acestui lucru cu ajutorul funciei fcntl, sub forma "fcntl(fd, F_SETFD, 1)". Un apel exec nereuit returneaz valoarea -1, dar cum alt valoare nu se returneaz, ea nu trebuie testat. Insuccesul poate fi determinat prin specificarea unei ci greite spre fiierul executabil sau a unui fiier pentru care nu exist drept de execuie. Diferitele variante de exec dau utilizatorului mai mult flexibilitate la transmiterea parametrilor. Sintaxa lor este:
#include <unistd.h> int execl(const char * cale, const char * arg0, ..., NULL); int execv(const char * cale, char * argv[]); int execlp(const char * numefis, const char * arg0, ..., NULL); int execvp(const char * numefis, char * argv[]);

Toate funciile returneaz -1 n caz de eroare i nu se revine din ele n caz de succes. ntre primele dou variante i ultimele, deosebirea const n aceea c la ultimele dou fiierul executabil specificat de parametrul numefis se caut n directoarele din variabila PATH. Evident, n acest caz nu are sens specificarea unei ci, adic parametrul numefis nu trebuie s conin caracterul '/', lucru care se cere n mod normal la primele dou variante de exec, prin parametrul cale. Dac n numefis se precizeaz caracterul '/', se presupune specificarea explicit a cii i nu se face nici o
71

Apeluri sistem pentru lucrul cu procese n Linux

cutare. Calea indicat prin parametrul cale poate fi una absolut sau relativ. O alt deosebire exist ntre apelurile de genul execl i execv, deosebire care se refer la modul de specificare a argumentelor fiierului executabil (comenzii): ca list, respectiv vector de iruri de caractere. Indiferent de modul de specificare a acestor argumente, trebuie reinut faptul c ele descriu linia de comand, aa cum ar fi introdus ea de la tastatur, dac programul executabil ar fi lansat din interpretorul de comenzi. Linia de comand ncepe cu numele comenzii, adic al fiierului executabil, urmat de argumentele sale (cuvinte separate prin spaii) i de irul vid, marcat de apsarea tastei ENTER. n cazul specificrii argumentelor ca list, fiecare element al liniei de comand este precizat separat ca ir de caractere (cuvnt), iar sfritul listei este marcat prin irul vid (NULL). n cazul specificrii argumentelor ca vector, se indic doar adresa unui vector ce conine elementele liniei de comand. Ultimul element al vectorului trebuie s fie, de asemenea, NULL. Funciile execl i execlp pot fi utilizate doar cnd se cunoate numele comenzii i argumentele sale n momentul scrierii codului (informai necesare la compilare). Funciile execv i execvp se pot folosi i n cazul n care comanda i argumentele sale sunt precizate doar n timpul rulrii programului. De exemplu, este evident c interpretorul de comenzi folosete funcia execvp pentru a putea executa orice comenzi specificate n timpul rulrii sale. Se observ c fr fork, exec este limitat ca aciune, iar fr exec, fork nu are aplicabilitate practic. Dei efectul lor conjugat este cel dorit, raiunea existenei a dou apeluri distincte va rezulta din parcurgerea lucrrilor urmtoare.

5.5. Apelurile sistem wait i waitpid


Cele dou apeluri sistem pot fi folosite pentru sincronizarea execuiei proceselor printe i fiu, n sensul c procesul printe ateapt terminarea (normal sau cu eroare) procesului fiu folosind apelul sistem wait sau waitpid. Sintaxa celor dou apeluri sistem este:
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int* pstatus); pid_t waitpid(pid_t pid, int* pstatus, int opt);

Ambele funcii returneaz n caz de succes PID-ul unui fiu terminat i -1 n caz de eroare. De exemplu, dac procesul apelant nu are fii, cele dou funcii
72

Sisteme de operare. Chestiuni teoretice i practice

ntorc valoarea -1 i poziioneaz variabila sistem errno la valoarea ECHILD. Funcia waitpid returneaz 0 dac s-a precizat ca opiune WNOHANG i nu exist nici un proces fiu terminat. Argumentul pstatus este adresa la care se va copia codul de terminare al procesului fiu al crui PID este returnat. Un proces ce apeleaz wait sau waitpid poate: fi blocat n apelul funciei, dac toi fiii si sunt n execuie, relundu-i execuia n momentul terminrii unuia dintre fiii si; s primeasc imediat starea de terminare a fiului, dac cel puin unul dintre fii s-a terminat nainte de apelul funciei wait; s primeasc o eroare, dac nu are procese fiu. Diferenele ntre cele dou apeluri sistem sunt: wait blocheaz procesul apelant pn la terminarea unui fiu, n timp ce waitpid poate avea specificat opiunea WNOHANG, precizat prin argumentul opt, care evit acest lucru; waitpid nu ateapt neaprat terminarea primului fiu, ci se poate specifica prin argumentul pid procesul fiu ateptat; waitpid permite controlul programelor prin argumentul opt. Modul n care s-a terminat procesul fiu, normal sau cu eroare, este codificat n octeii de la adresa indicat de pstatus i poate fi aflat cu ajutorul macrourilor de mai jos:
WIFEXITED(*pstatus)

ntoarce TRUE dac procesul fiu s-a terminat prin apelul explicit sau implicit (la sfritul execuiei sale) al lui exit sau prin apelul instruciunii return la sfritul funciei main. Altfel, ntoarce FALSE.
WEXITSTATUS(*pstatus)

ntoarce codul de terminare specificat n procesul fiu ca parametru al funciei exit sau instruciunii return. Testarea acestei valori are sens doar n cazul n care procesul s-a terminat prin exit.
WIFSIGNALED(*pstatus)

ntoarce TRUE dac procesul fiu a fost terminat datorit recepionrii unui semnal. Altfel, ntoarce FALSE.
WTERMSIG(*pstatus)

ntoarce codul semnalului care a cauzat terminarea procesului fiu. Testarea acestei valori are sens doar n cazul n care macroul WIFSIGNALED, descris anterior, a ntors rezultatul TRUE.

73

Apeluri sistem pentru lucrul cu procese n Linux

Exist trei moduri de a termina un proces: (1) n mod voluntar, prin apelul exit, (2) recepionarea unui semnal de terminare sau a unui semnal netratat de ctre proces i (3) cderea sistemului. Codul de stare returnat prin variabila indicat de parametrul pstatus indic, prin urmare, care dintre primele dou moduri a cauzat terminarea (n al treilea mod procesul printe i sistemul de operare dispar, aa nct starea fiului nu mai conteaz). Argumentul opt al funciei waitpid poate fi 0, caz n care comportarea funciei este similar cu cea a lui wait, sau una dintre constantele simbolice WNOHANG i WUNTRACED. Dintre acestea prezint momentan interes doar prima i specificarea ei are ca efect revenirea imediat din waitpid, chiar i n cazul n care nici unul dintre procesele fii ale procesului apelant nu este terminat. In funcie de valoarea parametrului pid, comportarea funciei waitpid este urmtoarea:
pid == -1 pid > 0 pid == 0 pid < -1

Se ateapt dup terminarea oricrui proces fiu (echivalent cu wait). Se ateapt terminarea procesului cu identificatorul specificat de parametrul pid. Se ateapt orice proces cu identificatorul de grup de procese egal cu cel al apelantului. Se ateapt orice proces cu identificatorul de grup de procese egal n valoare absolut cu parametrul pid.

Funcia waitpid returneaz -1 dac nu exist proces sau grup de procese cu PID-ul specificat sau PID-ul respectiv nu este al unui fiu de al su.

5.6. Apelul sistem exit


Acest apel sistem are ca efect terminarea procesului din care este apelat. Sintaxa funciei este urmtoarea:
void exit(int* status);

Parametrul transmis funciei exit este interpretat ca i cod de terminare i poate fi obinut de un proces printe pentru a verifica modul de terminare al unui fiu de-al su. Prin convenie, codul 0 semnific terminarea normal a procesului, iar un cod diferit de zero indic apariia unei erori. Apelul lui exit dintr-un proces mai are ca efect, pe lng terminarea procesului apelant,
74

Sisteme de operare. Chestiuni teoretice i practice

i scoaterea printelui acelui proces din starea de ateptare a terminrii unui fiu, stare n care a intrat anterior prin apelul funciilor wait sau waitpid. Mecanismul de scoatere a procesului printe din starea de ateptare se bazeaz pe generarea semnalului SIGCHLD, semnal trimis unui proces printe de fiecare dat cnd unul dintre fiii si se termin. Trebuie remarcate urmtoarele trei situaii, relativ la apelurile sistem wait i exit: 1. Procesul printe se termin naintea procesului fiu. Procesul init (cu PID-ul 1) devine printele oricrui proces al crui printe iniial s-a terminat. Sistemul de operare face astfel ca fiecare proces s aib ntotdeauna un printe. 2. Procesul fiu se termin naintea procesului printe. Dac procesul fiu se termin naintea printelui su, sistemul de operare pstreaz anumite informaii (PID-ul, modul i starea de terminare, timp de utilizare a CPU etc.) i dup terminarea sa, restul resurselor alocate procesului fiind eliberate. Aceste informaii sunt accesibile printelui prin apelul funciei wait sau waitpid. n terminologia specific sistemelor de operare de tip Unix un proces care s-a terminat i pentru care printe su nu a executat nc wait se gsete n starea zombie. n aceast stare, procesul nu mai are resurse alocate (memoria alocat procesului este eliberat i fiierele deschise de el sunt nchise), ci doar intrarea sa n tabela proceselor este nc meninut. Un proces zombie se poate observa prin comanda ps -l, care afieaz pe coloana corespunztoare strii procesului (notat cu litera 'S') litera 'Z'. 3. Procesul fiu, motenit de procesul init, se termin. Dac un proces care are ca printe pe procesul init se termin, acesta nu devine zombie, deoarece procesul init apeleaz una dintre funciile wait sau waitpid pentru fiii si. Prin acest mecanism procesul init evit ncrcarea sistemului cu procese zombie. Din cele prezentate mai sus, se poate observa c exist o legtur strns ntre apelurile sistem wait (waitpid) i exit, funcionalitatea lor complet putnd fi neleas doar mpreun. Remarcm un dublu aspect al acestei funcionaliti corelate, i anume: 1. cel al sincronizrii procesului printe cu execuia fiului, n sensul c printele este blocat pn cnd fiul se termin; 2. cel de comunicare, fiul putnd transmite informaii despre modul su de terminare printelui.
75

Apeluri sistem pentru lucrul cu procese n Linux

5.7. Exemple
Exemplul 1. Programul de mai jos creeaz un proces fiu, ateapt terminarea fiului i afieaz PID-ul acestuia i starea sa de terminare (n zecimal i hexazecimal).
// parinte.c - codul parintelui #include <sys/types.h> #include <sys/wait.h> main() { int pid, stare; printf(" Parinte: inainte de fork()\n"); if ((pid=fork()) != 0) wait(&stare); else { execl("./fiu", "fiu", 0); perror("Eroare exec"); } printf("Parinte: dupa fork()\n"); stare = WEXITSTATUS(stare); printf("PID fiu=%d; terminat cu codul %d=%x\n", pid, stare, stare);

// fiu.c - codul fiului. // Obtinut prin comanda de compilare gcc fiu.c -o fiu #include <sys/types.h> #include <sys/wait.h> main() { int pid; printf("Fiul: incepe executia \n"); pid=getpid(); printf("Fiul: %d se termina\n", pid); } exit(10);

Exemplul 2. Programul de mai jos preia de la tastatur numele unor comenzi Linux fr parametri i le execut. Se folosete apelul sistem execlp. La ateptarea introducerii unei comenzi, programul afieaz prompterul >.
76

Sisteme de operare. Chestiuni teoretice i practice


#include <sys/types.h> #include <sys/wait.h> int main( void) { char buf[MAXLINE]; pid_t pid; int status; printf("> "); while (fgets(buf, MAXLINE, stdin) != NULL) { buf[strlen(buf)+1] = 0; if ((pid=fork()) < 0) { perror("Eroare fork"); exit(1); } else if (pid == 0) { execlp(buf, buf, NULL); perror("Eroare exec"); exit(2); } if ((pid=waitpid( pid, &status, 0)) < 0) { perror("Eroare waitpid"); exit(2); } printf("> "); } exit(0);

5.8. Probleme
1. S se vizualizeze efectul execuiei programului de mai jos.
int main( void) { int pid, k=7; pid=fork(); printf("Returnat %d\n", pid); if (pid) k=2; printf("k=%d\n", k);

2. S se scrie un program C prin care s se pun n eviden faptul c fiii unui proces printe care se termin devin automat fiii procesului init. 3. S se scrie un program C prin care s se creeze un proces aflat n starea zombie.

77

Apeluri sistem pentru lucrul cu procese n Linux

4. S se scrie un program C prin care s se pun n eviden faptul c dou procese aflate n relaia printe-fiu sunt concurente din punct de vedere al execuiei, c iniial procesul fiu este o copie a printelui, dar c fiecare proces i are propriile date. 5. S se execute programul de mai jos pe fiiere de test avnd dimensiunea din ce n ce mai mare (pn la civa MB) i s se explice rezultatul.
#include #include #include #include <sys/types.h> <sys/stat.h> <fcntl.h> <unistd.h>

int fdR, fdW; char c; rd_wr() { for (;;) { if (read( fdR, &c, 1) != 1) return; write( fdW, &c, 1); } } int main(int argc, char * argv[]) { if (argc != 3) { printf("Utilizare: %s sursa dest\n", argv[0]); exit(1); } if ((fdR=open( argv[1], O_RDONLY)) < 0) { perror("Eroare open"); exit(1); } if ((fdW=creat(argv[2], 0600)) < 0) { perror("Eroare create"); exit(1); } fork(); rd_wr(); } exit(0);

6. S se scrie un program C care s testeze dac exist o limit impus numrului de procese pe care le poate crea simultan un utilizator. Pentru a nu bloca sistemul, procesele create vor apela funcia sleep pentru un numr mare de secunde. 7. S se scrie un program care s creeze mai muli fii. Unul dintre ei va citi n mod continuu caractere de la tastatur i le va reafia pe ecran. Acest
78

Sisteme de operare. Chestiuni teoretice i practice

proces este un proces interactiv. Ceilali fii vor executa o bucl infinit n care vor genera primele N numere prime. Aceste procese sunt procese intens consumatoare de procesor sau computaionale. S se testeze n ce msur timpul de reacie al procesului interactiv este influenat de numrul de procese computaionale. 8. S se modifice Exemplul 2 astfel nct s se accepte introducerea unor comenzi cu parametri i s se implementeze funcionalitatea corespunztoare specificrii n linia de comand a unui interpretor din Linux a caracterelor &, <, > . 9. S se testeze funcionalitatea funciei system i s se scrie apoi un program C care s aib funcionalitatea similar acestei funcii, folosind apelurile sistem fork i execvp. 10. S se scrie dou programe C, unul numit client.c, iar celalalt server.c. Programul client va afia pe ecran un prompter i va citi de la tastatur dou numere ntregi i unul din caracterele + sau . Informaiile citite vor fi transmise, cu ajutorul apelului sistem execl unui proces fiu care va executa codul serverului. Acesta va face operaia corespunztoare i va transmite rezultatul procesului printe (client) cu ajutorul apelului sistem exit. Procesul client va afia apoi rezultatul i va reafia prompterul pentru o nou citire. 11. S se testeze codul de mai jos:
for(i=1; i<=10; i++) { fork(); printf("Procesul cu PID=%d\n", getpid()); }

S se modifice apoi codul respectiv astfel nct la sfritul execuiei tuturor proceselor create, ntr-un fiier numit proc.txt s se gseasc scris numrul total de procese care au fost create, fr a ase folosi n acest sens o formul matematic de calcul a acestui numr, ci el s fie obinut prin comunicarea proceselor create. 12. S se scrie un program C care s aib o funcionalitate asemntoare cu cea a comenzii ps. Programul va folosi informaiile furnizate de sistemul de operare n cadrul pseudo-sistemului de fiiere montat n directorul /proc. n acest director, fiecrui proces din sistem i corespunde un director avnd numele identic cu identificatorul procesului. Pentru detalii asupra structurii pseudo-sistemului proc i a semnificaiei informaiilor afiate n cadrul lui se poate studia pagina de manual afiat de comanda man 5 proc.

79

6. Thread-uri n Linux
Scopul lucrrii Scopul acestei lucrri este de a prezenta cteva dintre funciile descrise de specificaia PTHREADS, n implementarea ei sub Linux, funcii care ofer posibilitatea crerii i gestionrii thread-urilor multiple ale unui proces.

6.1. Specificaia PTHREADS


PTHREADS este un model standardizat care ofer posibilitatea divizrii unui program n subprograme a cror execuie poate fi concurent. Aceste subprograme identific n cadrul unui proces execuii independente i vor fi numite n cele ce urmeaz thread-uri. Litera P din numele modelului vine de la POSIX (Portable Operating System Interface for UniX). Pachetul PTHREADS este definit ca o colecie de tipuri, structuri de date i funcii cu o sintax specific limbajului C. Lucrarea de fa prezint implementarea modelului PTHREADS pentru sistemul de operare Linux, implementare realizat n pachete precum LinuxThreads sau NGPT (Next Generation Posix Threads). Funciile existente n modelul PTHREADS corespund operaiilor referitoare la thread-uri, precum ar fi cele de creare, terminare, stabilire a atributelor i caracteristicilor thread-urilor, comunicare i sincronizare ntre thread-uri, stabilire a politicilor i a mecanismelor de planificare. n cadrul acestei prezentri sunt descrise funciile de creare i terminare a thread-urilor unui proces, precum i alte cteva funcii i problemele legate de aceste dou momente ale execuiei unui thread.

6.2. Crearea unui thread


Funcia de creare a unui thread se numete pthread_create i are urmtoarea sintax:
#include <pthread.h> int pthread_create ( pthread_t* idThread, const pthread_attr_t* atribute, void *(*functie)(void*), void* arg);

80

Sisteme de operare. Chestiuni teoretice i practice

Semnificaia parametrilor funciei este urmtoarea:


idThread atribute

Este adresa unde va fi nscris identificatorul thread-ului nou creat. Este adresa unei structuri care conine atributele folosite la crearea thread-ului. Dac se specific NULL pe poziia parametrului respectiv, atunci thread-ul este creat cu valorile implicite ale atributelor sale. Este adresa funciei pe care o va executa thread-ul. Reprezint adresa la care se gsete argumentul transmis funciei executate de thread. Argumentul poate avea astfel orice structur dorit de utilizator.

functie arg

Funcia ntoarce rezultatul zero n caz de succes i o valoare pozitiv n caz de eroare, valoare ce reprezint codul erorii. Thread-ul creat este executat n mod concurent cu thread-ul care a apelat funcia pthread_create i cu alte eventuale thread-uri ale procesului. El i ncepe execuia apelnd funcia transmis ca parametru i i ncheie execuia n mod implicit la sfritul funciei sau explicit apelnd funcia pthread_exit descris mai jos. Thread-urile create n cadrul unui proces sunt identice din punct de vedere al relaiei care exist ntre ele, singurul diferit ntr-un anumit sens fiind cel principal, creat odat cu crearea procesului i care execut funcia main. Terminarea acestui thread duce la terminarea procesului i, implicit, la terminarea forat a tuturor thread-urilor sale. Prin urmare, n mod normal, n funcia main, se ateapt terminarea celorlalte thread-uri ale procesului. Exemplul urmtor ilustreaz modul de creare a unui thread.
void* thFunction(void* arg) { int* val = (int*) arg; } printf("Thread with argument %d\n", *val);

main() { pthread_t th1; int arg1 = 1; pthread_create(&th1, NULL, thFunction, &arg1); pthread_join(th1, NULL);

81

Thread-uri n Linux

Compilarea unui program C care folosete funciile din pachetul PTHREADS trebuie fcut specificnd biblioteca n care se gsesc acele funcii. Comanda de compilare are forma:
gcc progr.c lpthread -o progr.exe

6.3. Identitatea unui thread


Fiecrui thread i este asociat n momentul crerii sale un identificator unic. Acest identificator este obinut de ctre thread-ul apelant al funciei pthread_create. Identificatorul unui thread poate fi folosit, de exemplu, de ctre un alt thread pentru ca acesta din urm s atepte dup terminarea execuiei primului thread, apelnd funcia pthread_join. Un thread poate si obin propriul identificator cu ajutorul funciei pthread_self. Pentru compararea identitii a dou thread-uri este pus la dispoziie funcia pthread_equal. Sintaxa acestor dou funcii are urmtoarea form:
#include <pthread.h> pthread_t pthread_self(void); int pthread_equal(pthread_t thread1, pthread_t thread2);

6.4. Terminarea execuiei unui thread


n mod normal, un thread i termin execuia la sfritul funciei pe care o execut. O modalitate explicit de terminare voluntar a unui thread este prin apelarea de ctre acel thread a funciei pthread_exit, cu ajutorul creia se pot specifica i informaii legate de starea de terminare a thread-ului. Sintaxa acestei funcii este urmtoarea:
#include <pthread.h> void pthread_exit(void *retval);

n cazul n care un thread se termin fr a apela n mod explicit funcia pthread_exit, informaia de terminare referitoare la acel thread va corespunde cu valoarea ntoars ca rezultat de ctre funcia executat de thread. Prin urmare, informaia despre starea de terminare a unui thread este dat de valoarea parametrului retval al funciei pthread_exit, n cazul n care aceasta este apelat, sau de rezultatul funciei executat de ctre thread, n caz contrar.
82

Sisteme de operare. Chestiuni teoretice i practice

Printre atributele unui thread exist unul care indic dac dup terminare se va pstra sau nu informaia despre starea sa de terminare. Cele dou alternative corespund situaiilor n care un alt thread poate atepta (prin apelul funciei pthread_join) dup terminarea acelui thread pentru a obine informaia care descrie starea sa de terminare, respectiv nu poate face acest lucru. n cel de-al doilea caz, n momentul terminrii thread-ului toate resursele alocate lui sunt eliberate imediat. Modul n care se stabilete valoarea respectivului atribut este descris puin mai jos. O modalitate de terminare forat, din exterior, a unui thread este prin apelul funciei pthread_cancel de ctre un alt thread. Sintaxa funciei este:
#include <pthread.h> int pthread_cancel(pthread_t thread);

Un thread poate fi terminat doar de ctre un alt thread al aceluiai proces. Posibilitatea ca un thread s poat fi terminat de ctre un alt thread este dat de valoarea unui alt atribut al thread-ului, atribut care se numete stare de terminare (cancelability state) a thread-ului respectiv. Setarea celor dou alternative n ceea ce privete terminarea unui thread de ctre alt thread se poate face cu ajutorul funciei pthread_setcancelstate, a crei sintax este:
#include <pthread.h> int pthread_setcancelstate(int val, int *vecheaVal);

Valorile posibile pentru parametrului val sunt: PTHREAD_CANCEL_ENABLE Thread-ul va putea fi oprit. PTHREAD_CANCEL_DISABLE Thread-ul nu va putea fi oprit. n momentul n care un thread, aflat n starea n care el poate fi oprit cu pthread_cancel, primete din partea unui alt thread o cerere de terminare, el poate s ia act de ea imediat terminndu-i execuia sau poate amna momentul terminrii sale pn la ntlnirea unui aa-numit punct de terminare. Modul n care un thread se comport la apariia unei cereri de terminare din exterior este dat de valoarea unui alt atribut al thread-ului, atribut care se numete tip de terminare (cancelability type) i care poate fi setat cu ajutorul funciei pthread_setcanceltype, avnd urmtoarea sintax:
#include <pthread.h> int pthread_setcanceltype(int tip, int *vechiulTip);

83

Thread-uri n Linux

Parametrul tip poate lua urmtoarele valori, corespunztoare celor dou moduri de comportament descrise mai sus, i anume: PTHREAD_CANCEL_ASYNCHRONOUS Terminare imediat. PTHREAD_CANCEL_DEFERRED Terminare amnat. Punctele de terminare sunt acele puncte n execuia thread-ului n care se verific sosirea unei cereri de terminare i se execut operaiile corespunztoare pentru terminarea thread-ului. Standardul POSIX definete ca puncte de terminare urmtoarele funcii: pthread_join, pthread_cond_wait, pthread_cond_timedwait, pthread_testcancel, sem_wait, sigwait, sleep i n general, orice funcie care pune thread-ul n ateptare i cedeaz procesorul altui thread. Alte tipuri de funcii POSIX sunt considerate a nu fi puncte de terminare, adic n momentul apelului lor i pe durata execuiei lor, thread-ul nu se va termina. Dintre funciile descrise mai sus a fi puncte de terminare, funcia pthread_testcancel reprezint un punct de terminare stabilit de programator, pe cnd celelalte sunt considerate a fi puncte de terminare automate. Sintaxa funciei pthread_testcancel este:
#include <pthread.h> void pthread_testcancel(void);

n mod implicit un thread este creat avnd urmtoarele valori ale celor dou atribute legate de terminarea sa din exterior, de ctre un alt thread: Starea de terminare: PTHREAD_CANCEL_ENABLE; Tipul de terminare: PTHREAD_CANCEL_DEFFERRED. Terminarea unui thread de ctre un alt thread trebuie fcut cu mare atenie datorit problemelor destul de grave ce pot fi generate. Acest lucru este necesar deoarece n momentul sosirii unei cereri de terminare thread-ul respectiv s-ar putea s dein date globale aflate ntr-o stare inconsistent sau s dein resurse (lacte, semafoare etc.) ateptate i de alte thread-uri i care trebuie neaprat eliberate nainte de terminarea thread-ului. Prin urmare acele poriuni de cod care sunt critice din acest punct de vedere ar trebui protejate mpotriva unei terminri necontrolate. Acest lucru se poate face prin setarea tipul terminrii thread-ului la PTHREAD_DEFERRED i prin specificarea unor funcii care trebuie executate n momentul terminrii thread-ului, fie prin apelul lui pthread_exit, fie prin cel al lui pthread_cancel de ctre un alt thread. Funciile ce vor fi executate la terminarea unui thread sunt specifice fiecrui thread i aciunilor ntreprinse
84

Sisteme de operare. Chestiuni teoretice i practice

de ctre el i trebuie s fie, n principiu, destinate eliberrii resurselor deinute i respectiv, aducerii datelor prelucrate de ctre thread ntr-o stare consistent. Aceast pregtire a terminrii controlate a thread-ului se poate face cu ajutorul funciilor: pthread_cleanup_push, folosit pentru a se aduga o nou funcie la lista de funcii care trebuie executate la terminarea thread-ului; pthread_cleanup_pop, folosit pentru extragerea din lista funciilor ce trebuie executate la terminarea thread-ului a ultimei funcii introduse anterior. Pentru un anumit thread pot fi setate mai multe funcii care s se execute la terminarea thread-ului respectiv, ordinea execuiei acestora fcndu-se pe principiul de funcionare al unei stive (LIFO). Sintaxa celor dou funcii amintite mai sus este urmtoarea:
#include <pthread.h> void pthread_cleanup_push( void (*functie)(void*), void* arg); void pthread_cleanup_pop(int executa);

Semnificaia parametrilor este urmtoarea:


functie arg executa

Este funcia care se va executa la terminarea thread-ului. Este argumentul transmis funciei. Indic dac funcia ce se scoate de pe stiv trebuie sau nu executat.

Un amnunt practic, important de reinut, este faptul c se impune folosirea celor dou funcii ca perechi, deoarece sunt definite ca macrouri, pthread_cleanup_push folosind acolada deschis '{', iar pthread_cleanup_pop pe cea nchis '}'. Nerespectarea acestei cerine genereaz la compilare erori dificil de neles i corectat. Din pcate acest mod de definire a celor dou macrouri impune anumite restricii de utilizare, ele neputnd face parte n mod independent din blocuri diferite de instruciuni, de exemplu n corpul unor instruciuni for diferite. Exemplul de mai jos ilustreaz folosirea acestor funcii n situaia n care un thread aloc dinamic memorie pe care trebuie s o elibereze la terminarea sa.
85

Thread-uri n Linux
typedef struct m { int size; void* pMem; } MEM; void allocate_mem(void* arg) { MEM* p = (MEM*) arg; p->pMem = malloc(p->size); } void release_mem(void* arg) { MEM* p = (MEM*) arg; if (p->pMem) free(p->pMem); } void* thFunction(void* arg){ int oldType; MEM thMem; pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, &oldType); thMem.size = 100; thMem.pMem = NULL; pthread_cleanup_push( release_mem, (void *) &thMem); allocate_mem(&thMem); /* do some work with the memory*/ pthread_cleanup_pop(1); } pthread_setcanceltype(oldType, NULL);

6.5. Ateptarea terminrii unui thread


O metod destul de grosier de sincronizare a execuiilor diferitelor threaduri este aceea ca un thread s atepte terminarea unui alt thread. Acest lucru se poate face cu ajutorul funciei pthread_join. Sintaxa funciei este:
#include <pthread.h> int pthread_join(pthread_t th, void **stareTerminare);

Apelul funciei duce la blocarea thread-ului apelant pn la terminarea thread-ului indicat de parametrul th. De asemenea, se pot obine informaii legate de terminarea thread-ului dup care se ateapt, informaii organizate ntr-o structur a crei adres va fi nscris la adresa indicat de parametrul stareTerminare, n caz c valoarea acestuia nu este NULL. Tipul
86

Sisteme de operare. Chestiuni teoretice i practice

parametrului stareTerminare este dat de semntura funciei executat de un thread, funcie care returneaz un rezultat de tip void*. Dac thread-ul ateptat a fost terminat forat, atunci valoarea parametrului stareTerminare este constanta predefinit PTHREAD_CANCELED. Trebuie reinut, aa cum aminteam i mai sus, c aceast funcie poate fi apelat doar pentru thread-uri pentru care se pstreaz de ctre sistem informaii legate de terminarea lor. Funcia pthread_join poate fi apelat cu succes doar o singur dat pentru un thread. Dac un thread apeleaz funcia pthread_join i ulterior un alt thread face acelai lucru, n cel de-al doilea caz funcia nu va duce la blocarea thread-ului apelant, ci va ntoarce o valoare de eroare.

6.6. Stabilirea atributelor unui thread


Un thread are anumite proprieti pe care le numim atribute ale thread-ului respectiv. Cteva dintre aceste atribute le-am amintit deja mai sus, cum ar fi de exemplu modul de comportare a thread-ului n momentul apariiei unei cereri de terminare din exterior. Alte atribute ale unui thread sunt: proprietatea de a se putea atepta (prin apelul funciei pthread_join) dup terminarea respectivului thread; dimensiunea stivei; poziionarea stivei n spaiul de adrese al procesului; atribute legate de planificarea pentru execuie a thread-ului. Pentru a stabili valorile atributelor unui thread trebuie creat o structur de tipul pthread_attr_t i modificate apoi valorile implicite ale cmpurilor acelei structuri, folosind diverse funcii puse la dispoziie n acest sens. Adresa structurii de atribute trebuie transmis funciei pthread_create. Dac se transmite NULL, atunci thread-ul este creat cu setul de parametri implicii. Funciile de creare i distrugere a unei structuri de atribute ale unui thread sunt pthread_attr_init, respectiv pthread_attr_destroy, cu urmtoarea sintax:
#include <pthread.h> int pthread_attr_init(pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t *attr);

87

Thread-uri n Linux

Momentan, singurul atribut care prezint importan este cel legat de posibilitatea de a apela pthread_join pentru un anumit thread. Setarea valorii acestui atribut i respectiv, obinerea valorii sale curente se poate face cu ajutorul funciilor de mai jos:
#include <pthread.h> int pthread_attr_setdetachstate( pthread_attr_t *atribute, int stare); pthread_attr_getdetachstate( const pthread_attr_t* atribute, int *stare);

int

Valorile posibile ale parametrului stare sunt:


PTHREAD_CREATE_DETACHED

Nu se poate atepta dup terminarea thread-ului i, implicit, nu sunt pstrate informaii despre modul de terminare a thread-ului.
PTHREAD_CREATE_JOINABLE

Se poate atepta dup terminarea thread-ului. Informaiile despre modul de terminare a thread-ului sunt pstrate pn la obinerea acestora cu ajutorul funciei pthread_join. Aceasta este valoarea implicit a atributului thread-ului. Exist funcii cu nume similare pentru modificarea i obinerea celorlali parametri ai unui thread, i anume: pthread_attr_getstacksize i pthread_attr_setstacksize; pthread_attr_getstackaddr i pthread_attr_setstackaddr etc. Dac un thread este setat detaat, atunci, n momentul terminrii sale, sistemul nu mai pstreaz informaii legate de starea n care s-a terminat, toate resursele alocate respectivului thread fiind eliberate i, prin urmare, nu se poate apela cu succes funcia pthread_join pentru acel thread. Un comportament similar poate fi obinut cu ajutorul funciei pthread_detach, care poate fi apelat dup crearea thread-ului. Apelarea acestei funcii pentru un thread pentru care exist deja un alt thread care ateapt dup terminarea lui va fi fr efect, valoarea atributului rmnnd neschimbat. Sintaxa funciei este descris mai jos:
#include <pthread.h> int pthread_detach(pthread_t th);

88

Sisteme de operare. Chestiuni teoretice i practice

6.7. Relaia dintre thread-uri i procese


Comportamentul la semnale Una dintre problemele care se pun referitor la procesele ce conin mai multe thread-uri este aceea a modului de utilizare a semnalelor. Este vorba att despre modul de transmitere a semnalelor ctre procese ce conin mai multe thread-uri, precum i modalitatea de reacie a acestora la semnalele trimise lor de ctre alte procese. Standardul PTHREADS prevede ca trimiterea unui semnal s se poat face doar unui proces, i nu unui thread din acel proces. Acest lucru este evident, din moment ce din exterior nu se poate detecta, n mod normal, existena thread-urilor unui proces, acestea innd de structura intern a procesului. Modul de rspuns la primirea unui semnal, precum i funcia lui de tratare, n cazul n care semnalul este preluat, se stabilete, de asemenea, pentru proces i nu pentru un anumit thread din acel proces. Pe de alt parte, definirea filtrului de blocare a unor semnale este proprie fiecrui thread, putndu-se defini cte un filtru pentru fiecare thread n parte. Definirea respectivului filtru pentru un thread se face cu ajutorul funciei pthread_sigmask, iar pentru proces, per ansamblu, cu ajutorul funciei sigprocmask. Un semnal trimis unui proces este recepionat (tratat) n timpul execuiei unuia dintre thread-urile care nu au blocat primirea acelui semnal, fr a se ti care anume este acela. Exemplul de mai jos ilustreaz modul de stabilire a tratrii unui semnal i a mtii de blocare a anumitor semnale.
void sigHandler(int sig) { printf("Semnalul %d tratat in timpul executiei thread-ului %d.\n", sig, pthread_self()); } main() { pthread_t th1; signal(SIGUSR1, sigHandler); pthread_create(&th1, NULL, thFunction, NULL); pthread_join(th1, NULL); } void* thFunction(void* arg) { sigset_t sigmask; sigemptyset(&sigmask); sigaddset(&sigmask, SIGUSR1); pthread_sigmask(SIG_SETMASK, &sigmask, NULL); while(1); }

89

Thread-uri n Linux

Efectul apelului funciei fork O alt problem care apare n cazul proceselor cu mai multe thread-uri este cea corespunztoare situaiei n care unul din thread-urile procesului apeleaz funcia fork, de creare a unui nou proces. ntrebarea care se pune este dac thread-urile existente n procesul printe n momentul apelului funciei fork vor exista i n procesul fiu sau nu. Standardul PTHREADS precizeaz c, dei procesul fiu este identic, ca i coninut, cu cel printe, singurul thread activ n procesul nou creat va fi doar acela care a apelat funcia fork. Prin urmare, execuia procesului creat va corespunde cu execuia thread-ului activ i se va termina o dat cu terminarea acestuia. Avnd n vedere modul de implementare a thread-urilor n Linux cu ajutorul proceselor, acest mod de comportare este evident, fiind creat un duplicat doar al procesului (thread-ului) care apeleaz funcia fork. Datorit acestui mod de funcionare al funciei fork, n procesul fiu pot s apar anumite probleme relativ la starea thread-urilor ce nu mai sunt activate n fiu. Este posibil, spre exemplu, ca, n momentul crerii noului proces, unul din thread-urile inactive s fi blocat anterior, cu ajutorul unui lact, accesul la o resurs partajat. Eliberarea lactului i, implicit, a accesului la resursa partajat poate fi fcut doar de ctre thread-ul care a blocat lactul. El fiind, ns, inactiv n procesul fiu, este posibil ca thread-ul activ s rmn blocat definitiv ateptnd deblocarea lactului. Pentru a preveni astfel de situaii se poate folosi funcia pthread_atfork, prin care se pot specifica funcii care s se execute, att n procesul printe, ct i n cel fiu nainte i dup efectuarea execuia funciei fork. Sintaxa funciei este urmtoarea:
#include <pthread.h> pthread_atfork( void (*pregatire)(void), void (*parinte)(void), void (*fiu)(void));

Semnificaia parametrilor este urmtoarea:


pregatire parinte fiu

Funcia care se va executa n procesul printe, nainte de crearea noului proces. Funcia care se va executa n procesul printe, nainte de revenirea din funcia fork. Funcia care se va executa n procesul fiu, nainte de revenirea din funcia fork.

Funcia pthread_atfork poate fi apelat de mai multe ori, avnd ca efect stabilirea unor liste de funcii ce vor fi executate n ordinea LIFO.
90

Sisteme de operare. Chestiuni teoretice i practice

6.8. Probleme
1. S se scrie un program care testeaz dac thread-urile unui proces se execut n mod concurent sau nu. n acest scop, thread-urile create vor executa o funcie, n care, n cadrul unei bucle infinite, vor afia identificatorul de thread i identificatorul procesului cruia i aparin. 2. Folosind ca punct de pornire programul de la Problema 1, s se testeze cele dou moduri de reacie a unui thread la o cerere de terminare transmis prin apelul funciei pthread_cancel de ctre un alt thread. 3. S se scrie un program care determin numrul maxim de thread-uri ce pot fi active simultan n cadrul unui proces. Pentru a nu consuma procesor, dar i pentru a nu se termina thread-rile create, vor apela n cadrul unei bucle infinite funcia sleep. Pentru aflarea numrului dorit se va testa valoarea ntoars de funcia pthread_create, pentru a detecta momentul n care nu se mai pot crea alte thread-uri. 4. S se scrie un program de tip server care creeaz n mod periodic threaduri care simuleaz deservirea unor cereri de la clieni. Thread-urile de deservire a cererilor afieaz un mesaj, ateapt un anumit timp (cu sleep) i apoi se termin. n acelai timp, serverul accept comenzi introduse de la tastatur. S se implementeze funcionalitatea comenzii de oprire a serverului, adic la apsarea unei anumite taste (de exemplu 'x') s nu se mai creeze noi thread-uri i procesul se fie terminat. Terminarea procesului trebuie fcut ns numai dup terminarea threadurilor existente la acel moment. 5. S se modifice problema anterioar astfel nct thread-urile ce simuleaz deservirea cererilor de la clieni s execute ntr-o bucl infinit operaii intens computaionale (de exemplu calcularea primelor N numere prime). La apsarea unei taste, serverul va afia pe ecran numrul de thread-uri create pn n acel moment. S se urmreasc modul n care thread-urile computaionale influeneaz timpul de reacie al thread-ului ce citete de la tastatur. 6. S se testeze comportamentul unor thread-uri diferite ale aceluiai proces care apeleaz simultan funcii de citire de la tastatur. 7. S se testeze efectul apelului funciei pthread_join de ctre thread-ul activ dintr-un proces fiu pentru ateptarea terminrii unui thread care exista n procesul printe n momentul apelului funciei fork, dar care nu e activ n procesul fiu. ntrebarea care se pune este dac thread-ul activ va fi blocat n funcia pthread_join sau nu, iar dac nu, ce anume returneaz funcia respectiv.
91

Thread-uri n Linux

8. S se scrie un program C de copiere a coninutului unui fiier ntr-un alt fiier folosind mai multe thread-uri. Copierea se va face n zone de anumite dimensiuni (de exemplu 512 octei, 1Kb, 2Kb, 4Kb etc.). Pentru a evidenia necesitatea thread-urilor ntr-o astfel de situaie, precum i pentru a aprecia numrul de thread-uri pentru care operaia este eficient, se va face o comparaie ntre urmtoarele implementri ale problemei: a. un singur thread; b. N thread-uri create la nceputul execuiei programului, fiecare copiind o anumit zon a fiierului; c. un thread existent va crea un nou thread doar nainte de a apela funciile de acces la fiiere (read i write), care e posibil s-l pun n stare de ateptare. 9. S se efectueze aceleai teste ca n problema precedent, dar pentru cazul n care o zon copiat trebuie prelucrat nainte de a fi scris n fiierul destinaie. Un exemplu de prelucrare ar putea fi scrierea n ordine invers a octeilor. 10. Se presupune c ntr-un fiier text numbers.in, pe fiecare linie se gsesc dou numere ntregi. S se scrie programul C, care citete, pe rnd, toate liniile din fiier i pentru fiecare linie citit creeaz un nou thread, cruia i transmite ca parametri (n cadrul unei structuri) cele dou numere aflate pe linia respectiv. Fiecare thread va prelua cei doi parametri, va face media lor aritmetic, va scrie n fiierul result_threads.out, pe o linie, cele trei numere i identificatorul propriu de thread i, n plus, numrul ce reprezint media aritmetic l va transmite ca parametru funciei pthread_exit. Dup terminarea citirii fiierului numbers.in, thread-ul main va prelua toate rezultatele transmise de thread-urile create i le va scrie n fiierul results_main.out, fiecare rezultat alturi de identificatorul thread-ului care l-a produs. S se compare, apoi coninutul celor dou fiiere. 11. Se presupune c thread-ul main al unui proces este blocat n ateptarea terminrii unui alt thread al procesului. Se mai presupune referitor la modalitatea de reacie la apariia semnalului SIGUSR1, c s-a stabilit anterior o funcie de tratare i c thread-ul dup care se ateapt a mascat semnalul SIGUSR1 cu ajutorul funciei pthread_sigmask. S se testeze dac prin trimiterea semnalului SIGUSR1 ctre proces, thread-ul main va reaciona sau nu i, n caz afirmativ, dac va rmne n starea de ateptare a celuilalt thread sau nu. 12. S se scrie dou programe C corespunztoare a dou procese, fiecare cu cte dou thread-uri, astfel nct comunicarea prin semnalele SIGUSR1 i SIGUSR2 dintre cele dou procese s se fac la nivelul unei perechi de
92

Sisteme de operare. Chestiuni teoretice i practice

thread-uri pentru fiecare semnal. Astfel prin semnalul SIGUSR1 vor comunica doar cele dou thread-uri care au fost create primele n cele dou procese, iar prin SIGUSR2 vor comunica doar celelalte dou. 13. Se consider c o anumit variabil global var dintr-un proces este ntr-o stare consistent doar dac valoarea sa este una prestabilit (de exemplu var=10). Presupunem c procesul va crea mai multe thread-uri care apeleaz toate aceeai funcie threadFunct, funcie care modific valoarea variabilei var. Modificrile valorii variabilei se pot face doar prin intermediul funciilor aduna(int val) i respectiv, scade(int val). S se scrie funcia threadFunc ce va fi executat de ctre threaduri, astfel nct la sfritul execuiei tuturor thread-urilor create valoarea variabilei s rmn cea iniial, considerat ca stare consistent a variabilei, indiferent de cazul n care thread-urile se termin n mod normal sau sunt terminate de ctre thread-ul main prin apelul funciei pthread_cancel. 14. n contextul descris de problema precedent, s se asigure, folosind funcia pthread_atfork, c n procesul fiu valoarea variabilei var este cea corespunztoare strii consistente. Se consider c este posibil ca i thread-ul care apeleaz funcia fork s fi modificat valoarea variabilei nainte de apelul lui fork. 15. S se scrie un program care s genereze n mod continuu thread-uri. Se presupune c alocarea structurii pthread_t asociat unui thread i a parametrilor funciei executate de thread se face n mod dinamic. Pentru eliberarea memoriei alocat thread-urilor care s-au terminat se va crea un alt thread, numit garbage collector.

93

7. Procese i thread-uri n Windows 2000


Scopul lucrrii Lucrarea descrie structurile de date i strategia folosit pentru gestionarea proceselor i thread-urilor n Windows 2000. Sunt prezentate, de asemenea, cteva funcii ale API-ului Win32 legate de procese i thread-uri.

7.1. Prezentare general


Ca i n alte sisteme de operare, i n Windows conceptul care caracterizeaz execuia unui cod executabil este procesul. Procesul reprezint cadrul asigurat de ctre sistemul de operare pentru execuia unui cod executabil, acest cadru cuprinznd starea mainii i toate resursele alocate, necesare execuiei respective. Conceptul care identific n acest cadru execuia propriu-zis este cel de thread. n Windows 2000, un proces const din unul sau mai multe thread-uri de execuie mpreun cu resursele alocate acestora. Modelul proces din Windows 2000 include urmtoarele elemente: codul i datele programului executat; spaiul virtual de adrese al procesului, distinct de cel al unui alt proces; resurse de sistem (semafoare, filtre etc.) alocate de sistemul de operare procesului, pe msur ce thread-urile acestuia le solicit; identificatorul de proces (PID); cel puin un thread. Fiecare thread al unui proces are asociat un contor de program (numrtor de instruciuni) care ine evidena instruciunilor care urmeaz s se execute. n cazul Windows 2000, thread-ul este o entitate controlat de planificatorul de execuie. Thread-urile au urmtoarele componente: regitri care descriu starea procesorului; unul dintre aceti regitri este contorul de program; dou stive: una pentru execuia n mod nucleu, cealalt pentru execuia n mod utilizator; zon privat de memorie pentru dll-uri i biblioteci run-time; identificatorul thread-ului (TID). Identificatorii de thread-uri i procese sunt alocai din acelai spaiu valoric, astfel nct toate procesele i thread-urile vor avea identificatori unici.
94

Sisteme de operare. Chestiuni teoretice i practice

Primele trei elemente din lista de mai sus poart mpreun denumirea de contextul thread-ului. Planificarea thread-urilor este sarcina exclusiv a nucleului sistemului de operare i se bazeaz pe prioritatea thread-urilor. ntr-un proces, thread-urile sunt executate independent unul de altul i nu se vd reciproc. API-ul Win32 definete conceptul de fibr, care poate fi asociat unui thread. Fibrele sunt similare thread-urilor, dar sunt gestionate de ctre utilizator i nu de ctre sistemul de operare. Managementul fibrelor presupune crearea, terminarea i inclusiv planificarea lor, trecerea execuiei de la o fibr la alta fcndu-se explicit la cererea utilizatorului. Fiecare thread poate avea una sau mai multe fibre. Fibrele sunt cea mai mic entitate executabil care poate fi creat i executat la nivel utilizator. Fiecare resurs folosit de un proces (de exemplu thread-urile unui proces) este reprezentat de un obiect. Procesul n sine este tratat ca un obiect. Un obiect poate fi accesat printr-un handle obinut pentru acel obiect. Pentru securitate i managementul resurselor, fiecare proces are asociat o structur de date (un token de acces), care conine identificatorul de securitate i drepturile de acces ale procesului.

7.2. Funcii Win32 API pentru procese i thread-uri


Crearea unui proces Crearea proceselor se poate face folosind funcia Win32 CreateProcess. Aceast funcie are 10 parametri, fiecare avnd la rndul lui mai multe opiuni. Nu vom face o prezentare complet a parametrilor acestei funcii (i nici pentru celelalte funcii), ci vom da numai o scurt descriere a lor. Informaii complete asupra sintaxei i comportamentului funciilor abordate se pot gsi n documentaia MSDN. Sintaxa funciei CreateProcess este urmtoarea:
BOOL CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation);

95

Procese i thread-uri n Windows 2000

Semnificaia parametrilor este urmtoarea:


lpApplicationName lpCommandLine lpProcessAttributes

Calea spre fiierul executabil. Linia de comand. Pointer la un descriptor de securitate pentru proces. Pentru NULL se vor lua valorile implicite. Pointer la un descriptor de securitate pentru thread-ul iniial. Un bit care spune dac noul proces motenete handle-urile procesului creator. Diferite flag-uri (de exemplu, modul de eroare, prioritate, depanare, consola etc.). Pointer la irul variabilelor de mediu. Dac este NULL, procesul fiu va primi valorile variabilelor de mediu din procesul printe. Pointer la directorul de lucru al noului proces. Pointer la o structur de date care descrie fereastra iniial de pe ecran (culoare, numr de linii, titlu, icoana, forma cursorului etc.). Pointer la o structur de date care returneaz informaii despre procesul creat.

lpThreadAttributes

bInheritHandles

dwCreationFlags

lpEnvironment

lpCurrentDirectory lpStartupInfo

lpProcessInformation

Urmtorii pai sunt executai la un apel al funciei CreateProcess: 1. deschide fiierul executabil; 2. creeaz obiectul executiv de proces, EPROCESS; 3. creeaz thread-ul iniial; 4. notific subsistemul Win32 despre existena noului proces; 5. lanseaz n execuie thread-ul iniial; 6. iniializeaz spaiul de adrese al noului proces (de exemplu, se ncarc DLL-urile necesare) i se lanseaz n execuie programul.

96

Sisteme de operare. Chestiuni teoretice i practice

Crearea thread-urilor Un thread poate fi creat ntr-un proces existent cu funcia CreateThread, funcie care are sintaxa de mai jos:
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);

Semnificaia parametrilor este urmtoarea:


lpThreadAttributes dwStackSize lpStartAddress lpParameter dwCreationFlags lpThreadId

Pointer la un descriptor de securitate pentru thread. Dimensiunea iniial n octei a stivei threadului. Pointer la funcia principal a thread-ului. Argumentul funciei principale. Un thread poate fi suspendat sau poate fi executat imediat dup creare. Pointer la identificatorul thread-ului.

Cnd funcia CreateThread este apelat, urmtoarele operaii sunt executate de ctre sistem: 1. Creeaz o stiv utilizator pentru thread n spaiul de adrese al procesului. 2. Seteaz valorile iniiale pentru hardware-ul legat de thread. 3. Apeleaz funcia NTCreateThread care creeaz obiectul executiv thread. Aceti pai sunt executai n mod kernel. 4. Notific subsistemul Win32 despre noul thread, care l foreaz s fac unele setri pentru thread. 5. Returneaz handler-ul i ID-ul thread-ului apelantului funciei CreateThread. 6. Thread-ul este pus n starea gata de rulare (ready-to-run). O variant interesant i util a acestei funcii este CreateRemoteThread, care creeaz un thread care va rula n spaiul de adrese al unui alt proces.
97

Procese i thread-uri n Windows 2000

Sintaxa funciei este similar cu cea a funciei CreateThread cu diferena c are un parametru adiional, un handle la un proces n care thread-ul va fi creat. Handle-ul trebuie s aib setate urmtoarele drepturi de acces: PROCESS_CREATE_THREAD PROCESS_QUERY_INFORMATION PROCESS_VM_OPERATION PROCESS_VM_WRITE PROCESS_VM_READ. Funcia CreateRemoteThread are ca rezultat execuia unui nou thread n spaiul de adrese al procesului dat. Thread-ul are acces la toate obiectele deschise de procesul care l-a creat. Crearea unei fibre Pentru a crea o fibr, trebuie s apelm funcia CreateFiber. Ea aloc un obiect fibr, i asociaz o stiv i seteaz adresa specificat prin funcia de fibr ca nceput al execuiei fibrei. Apelul funciei CreateFibre nu planific fibra pentru execuie. Sintaxa funciei este urmtoarea:
LPVOID CreateFiber( SIZE_T dwStackSize, LPFIBER_START_ROUTINE lpStartAddress, LPVOID lpParameter);

Semnificaia parametrilor este urmtoarea:


dwStackSize

Dimensiunea iniial a stivei. Argumentul funciei principale a fibrei.

lpStartAddress Pointer la funcia principal a fibrei. lpParameter

Terminarea proceselor, thread-urilor i a fibrelor n condiii normale, un proces i termin execuia apelnd funcia ExitProcess, prin care transmite i starea de terminare. n cazul n care se dorete terminarea unui proces (mpreun cu toate thread-urile lui) dintr-un alt proces, se poate apela funcia TerminateProcess, cu urmtoarea sintax:
BOOL TerminateProcess( HANDLE hProcess, UINT uExitCode);

98

Sisteme de operare. Chestiuni teoretice i practice

Dup revenirea din funcie, n uExitCode vom avea codul de ieire pentru procesul terminat ca urmare a apelului funciei. Similar, funcia TerminateThread este folosit pentru terminarea forat a unui thread. Cnd aceast funcie este apelat, thread-ul nu are posibilitatea s mai execute cod utilizator, stiva sa iniial nu este dealocat, iar DLLurile ataate thread-ului nu sunt anunate de terminarea thread-ului. Sintaxa funciei este urmtoarea:
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode);

Datorit faptului c apelul funciei TerminateThread poate duce la situaii neprevzute, e bine s fie utilizat numai n cazuri extreme. Se recomand s se apeleze TerminateThread numai dac se tie exact ce face thread-ul int. Dac thread-ul terminat este ultimul thread dintr-un proces, la apelul funciei se va termina i procesul thread-ului. Pentru a termina o fibr, se apeleaz funcia DeleteFiber care terge structurile de date alocate fibrei.

7.3. Planificare i prioriti


Funcia SetPriorityClass seteaz clasa de prioritate pentru un proces specificat. Aceast valoare mpreun cu valoarea prioritii fiecrui thread din proces determin nivelul prioritii de baz pentru acel thread. Sintaxa funciei este urmtoarea:
BOOL SetPriorityClass( HANDLE hProcess, DWORD dwPriorityClass);

Parametrul dwPriorityClass poate avea una dintre urmtoarele valori: ABOVE_NORMAL_PRIORITY_CLASS BELOW_NORMAL_PRIORITY_CLASS HIGH_PRIORITY_CLASS IDLE_PRIORITY_CLASS NORMAL_PRIORITY_CLASS REALTIME_PRIORITY_CLASS. Dac funcia se termin cu succes, ea returneaz o valoare pozitiv. n caz contrar, valoarea returnat este 0.
99

Procese i thread-uri n Windows 2000

Funcia SetThreadPriority seteaz valoarea prioritii unui thread specificat. Aceast valoare, mpreun cu clasa de prioritate a procesului thread-ului determin nivelul baz de prioritate pentru thread-ul respectiv.
BOOL SetThreadPriority( HANDLE hThread, int nPriority);

Parametrul nPriority poate avea urmtoarele valori: THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_IDLE THREAD_PRIORITY_LOWEST THREAD_PRIORITY_NORMAL THREAD_PRIORITY_TIME_ CRITICAL. Dac funcia se termin cu succes, ea returneaz o valoare pozitiv. n caz contrar, valoarea returnat este 0. Pentru a afla prioritatea unui thread, se poate apela funcia GetThreadPriority. Alte funcii legate de planificarea thread-urilor i a fibrelor sunt urmtoarele:
VOID Sleep(DWORD milsecunde);

Suspend execuia thread-ului curent pentru un interval specificat.


DWORD SuspendThread(HANDLE thread);

Suspend execuia thread-ului specificat.


BOOL SwitchToThread(void);

Thread-ul apelant va ceda execuia unui alt thread pe procesorul curent.


VOID SwitchToFiber(LPVOID fibra);

Planific o fibr. Fibrele se creeaz cu funcia CreateFiber. nainte de a putea planifica o fibr pentru execuie, trebuie apelat funcia ConvertThreadToFiber pentru a iniializa zona de memorie unde se vor salva informaiile de stare ale fibrei. Thread-ul devine fibra executat. Funcia SwitchToFiber salveaz informaiile de stare pentru fibra curent i rencarc starea fibrei specificate. Se poate apela SwitchToFiber i cu adresa unei fibre create de un alt thread. Pentru a face acest lucru, trebuie s avem o referin la adresa returnat n cellalt thread cnd acesta a apelat CreateFiber i trebuie s folosim o sincronizare potrivit.
100

Sisteme de operare. Chestiuni teoretice i practice

7.4. Exemple
Exemplul 1. Programul C de mai jos ilustreaz modul de creare a unui proces. Codul procesului fiu este ilustrat dup cel al printelui su.
// Codul procesului parinte: ProcesParinte.cpp #include <windows.h> #incude <stdio.h> void main(VOID) { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); // Porneste procesul fiu if(! CreateProcess( ".\\ProcesFiu.exe", // calea spre executabil "ProcesFiu arg1 arg2", // linia de comanda NULL, // Handle-ul procesului nu poate // fi mostenit NULL, // Handle-ul thread-ului nu poate // fi mostenit FALSE, // Nu se mostenesc handle-urile 0, // Fara flag-uri de creare NULL, // Foloseste variabilele de mediu // ale parintelui NULL, // Foloseste directorul curent al // parintelui &si, // Pointer la structura STARTUPINFO &pi)) // Pointer la structura // PROCESS_INFORMATION { printf("Eroare CreateProcess.\n"); exit(0); } // Asteapta terminarea fiului WaitForSingleObject(pi.hProcess, INFINITE); // Inchide handle-urile spre fiu CloseHandle(pi.hProcess); CloseHandle(pi.hThread);

101

Procese i thread-uri n Windows 2000


// Codul procesului fiu: ProcesFiu.cpp int main(int argc, char **argv) { if (argc != 3) { printf("Sunt necesari doi parametri!\n"); exit(1); } printf("Parametrii programului fiu: %s %s\n", argv[1], argv[2]);

Exemplul 2. Exemplul urmtor prezint modul cum se creeaz un nou thread care execut o funcia ThreadFunc definit local.
#include <windows.h> #include <conio.h> DWORD WINAPI ThreadFunc(LPVOID lpParam) { char szMsg[80]; wsprintf(szMsg, "Parametru=%d.",*(DWORD*)lpParam); MessageBox(NULL, szMsg, "ThreadFunc", MB_OK); } return 0;

VOID main(VOID) { DWORD dwThreadId, dwThrdParam = 1; HANDLE hThread; char szMsg[80]; hThread = CreateThread( NULL, // atribute implicite 0, // dimens. implicita a stivei ThreadFunc, // functia de executat &dwThrdParam, // argumentele functiei 0, // flag-uri implicite de creare &dwThreadId); // identificatorul thread-ului // Verifica valoarea returnata if (hThread == NULL) { wsprintf(szMsg, "Eroare CreateThread."); MessageBox(NULL, szMsg, "main", MB_OK); } else { _getch(); CloseHandle(hThread); }

102

Sisteme de operare. Chestiuni teoretice i practice

7.5. Probleme
1. S se scrie un program C care demonstreaz c execuia mai multor procese este concurent. 2. S se scrie un program C care demonstreaz c execuia mai multor thread-uri este concurent. 3. S se scrie un program C care s impun o planificare a fibrelor unui thread ntr-o ordine prestabilit. S se verifice ce se ntmpl dac una dintre fibre intr ntr-o bucl infinit. 4. S se scrie un program C care s verifice dac exist o limit a numrului de procese ce pot fi create simultan n sistem de ctre un utilizator. Pentru a nu bloca sistemul, procesele respective vor apela ntr-o bucl infinit funcia Sleep. S se identifice apoi numrul de procese consumatoare de procesor pentru care comportarea sistemului este rezonabil. Acelai test s se efectueze pentru cazurile n care prioritatea proceselor create este una mai mic dect cea implicit. 5. S se efectueze testele descrise n problema precedent, dar n cazul thread-urilor. 6. S se testeze modul de planificare a proceselor i a thread-urilor. n acest scop vor fi create mai multe procese, respectiv thread-uri, fiecare avnd una dintre prioritile posibile. Procesele (thread-urile) vor afia pe ecran un mesaj. 7. S se scrie codul C al unui proces de tip server care s creeze dou thread-uri. Primul thread simuleaz modul de deservire a clienilor i creeaz la anumite intervale de timp cte un thread care ar deservi n mod real o nou cerere sosit de la un client. Thread-ul respectiv va fi lsat s ruleze ntr-o bucl infinit n care efectueaz anumite operaii computaionale. Opional se poate impune o limit a numrului de thread-uri astfel create. Cel de-al doilea thread al serverului va execut o bucl infinit n care ateapt apsarea unei taste i afieaz pe ecran numrul de thread-uri create de primul thread al procesului server. S se testeze modul n care thread-urile computaionale influeneaz timpul de rspuns (de reacie) la apsarea unei taste al celui de-al doilea thread al serverului. S se modifice apoi prioritatea thread-ului interactiv la valori mai mari dect cea implicit, iar a celor computaionale la valori mai mici i s se repete testele. 8. S se scrie un program C care creeaz N thread-uri cu prioritatea normal, numite thread-uri de lucru i un thread cu prioritatea THREAD_PRIORITY_IDLE, numit garbage collector. Thread-urile de lucru execut o bucl infinit n care, la fiecare pas, trebuie s gseasc
103

Procese i thread-uri n Windows 2000

M elemente ale unui ir de ntregi cu valoare 0 i s le seteze la valoarea 1. Cutarea elementelor zero se va face aleator. Dup k ncercri nereuite de gsire a unui element zero, un thread de lucru ateapt un anumit timp, apelnd Sleep, dup care ncearc din nou. Dup ce a gsit cele M elemente cutate, thread-ul de lucru afieaz poziia lor n ir i reia algoritmul de la nceput. S se testeze modul de lucru al thread-ului garbage collector, punndu-se n eviden momentele la care el este planificat. S se modifice apoi prioritile thread-urilor i s se repete testele. 9. Scriei un program care realizeaz adunarea n paralel a N numere folosind thread-uri. S se compare cu timpul de execuie al adunrii n cazul efecturii operaiei de ctre un singur thread. 10. S se vizualizeze procesele active din sistem i diferite caracteristici ale lor cu ajutorul aplicaiei Task Manager, lansat prin apsarea combinaiei de taste Ctrl-Alt-Del. Opiunea de meniu View Select Columns... ofer posibilitatea alegerii proprietilor ce vor fi afiate. Faciliti asemntoare ofer aplicaia Process Explorer disponibil la adresa http://www.microsoft.com/technet/sysinternals. S se scrie apoi un program C care s afieze pe ecran toate procesele active i cteva dintre proprietile lor. n acest scop se vor studia i folosi funciile: a. CreateToolhelp32Snapshot, CloseToolhelp32Snapshot; b. Process32First, Process32Next; c. GetCurrentProcessId, GetCurrentThreadId.

104

8. Fiiere PIPE n Linux


Scopul lucrrii Lucrarea prezint modalitatea de comunicare n Linux ntre procese aflate pe acelai sistem folosind fiiere pipe cu nume i fiiere pipe anonime sau fr nume. Sunt descrise, de asemenea, principalele apeluri sistem de creare i utilizare a fiierelor pipe.

8.1. Principiile comunicrii prin fiiere pipe


Fiierele pipe reprezint n Linux un mecanism specializat de comunicare ntre procese aflate pe acelai sistem. Fiind implementate ca fiiere, utilizarea lor se face n mod similar cu utilizarea unui fiier obinuit. Astfel, ele trebuie create, deschise pentru a putea fi utilizate, iar pentru trimiterea prin pipe i recepionarea din pipe a unei informaii (mesaj), acea informaie trebuie scris (cu write) n, respectiv citit (cu read) din fiierul pipe. Operaiile pe pipe se fac la nivel de octei. Diferenele utilizrii fiierelor pipe fa de un fiier obinuit apar sub forma unor restricii impuse modului n care se efectueaz operaiile de scriere i citire din pipe. Aceste operaii se fac respectndu-se principiul FIFO (First-In First-Out). Prin urmare, nu se pot face modificri ale poziiei curente n fiierul pipe folosind apelul sistem lseek. Pe de alt parte, ncercarea de citire dintr-un fiier pipe gol sau cea de scriere ntr-un fiier pipe plin blocheaz procesul sau thread-ul care a apelat funcia read, respectiv write. Deblocarea proceselor se face n momentul n care un alt proces scrie n pipe numrul de octei ateptai de read, n primul caz, respectiv citete din pipe un numr de octei mai mare sau egal cu cel pe care dorete s-i scrie funcia write, n cel de-al doilea caz. Trebuie remarcat astfel c nu se ajunge, n mod normal, la un sfrit al fiierului pipe, lucru care n cazul unui fiier obinuit ar fi fost detectat prin returnarea de ctre funcia read a valorii zero, ci, n situaia n care toi octeii scrii n pipe au fost citii (adic fiierul pipe e gol) i se ncearc o nou citire, procesul ce face acea operaie este blocat pn cnd un alt proces scrie n pipe. Acest mod de funcionare a citirilor n i scrierilor din pipe asigur sincronizarea proceselor care comunic prin pipe. n folosirea fiierelor pipe exist dou situaii particulare, date de numrul de procese care au deschis pipe-ul pentru citire (considerate posibili
105

Fiiere PIPE n Linux

cititori), respectiv de numrul proceselor care au deschis pipe-ul pentru scriere (considerate posibili scriitori n pipe). Trebuie remarcat faptul c un proces poate fi considerat n acelai timp att cititor, ct i scriitor, n funcie de modul n care a deschis fiierul pipe. n cazul n care un proces ncearc s citeasc dintr-un fiier pipe gol pentru care nu mai exist procese scriitor, funcia read nu se va bloca, ci va returna valoarea zero, adic similar cu situaia de detecie a sfritului de fiier pentru fiierele normale. Aceast situaie corespunde detectrii sfritului de fiier pipe, lucru care va fi interpretat de ctre procesul care face citirea din pipe ca terminare a comunicrii prin pipe. n cazul n care un proces ncearc scrierea ntr-un fiier pipe pentru care nu mai exist procese cititor, sistemul de operare genereaz o excepie sub forma unui semnal (SIGPIPE), care, n mod implicit, termin procesul care execut scrierea. Aceasta este o modalitate de a evita blocarea definitiv a unui proces care scrie ntr-un pipe plin din care nu mai citete nimeni. Dac semnalul SIGPIPE transmis acelui proces de sistemul de operare este captat de proces, el nu va mai fi oprit, ci se va executa o rutin de tratare a mesajului, indicat de proces. Pentru a evita cele dou cazuri de excepie descrise mai sus, sistemul de operare va bloca un proces care ncearc s deschid (cu open) un fiier pipe doar pentru citire, dac acel fiier nu este deja deschis de ctre un alt proces pentru scriere, deblocarea procesului fcndu-se n momentul deschiderii pipe-ului pentru scriere. De asemenea, sistemul de operare va bloca un proces care ncearc s deschid doar pentru scriere un pipe care la acel moment nu mai este deschis de ctre nici un alt proces pentru citire, deblocarea fcndu-se cnd pipe-ul este deschis de ctre un alt proces pentru citire. Pentru evitarea acestei blocri funcia open poate fi apelat cu opiunea O_NDELAY sau O_NONBLOCK pe poziia celui de al doilea parametru, caz n care funcia open nu se blocheaz, dar returneaz eroare. Evident, dac un proces deschide pipe-ul simultan att pentru scriere, ct i pentru citire el nu va fi blocat, fiind considerat att un posibil cititor, ct i un posibil scriitor, dar ambele operaii se fac pe acelai descriptor, cel returnat de funcia open. Trebuie avut ns grij pentru c ntr-o astfel de situaie, dac acel proces ncearc citirea din pipe-ul gol, el va rmne blocat, adic funcia read nu returneaz imediat rezultatul 0, pn cnd un alt proces nu va deschide pipeul pentru scriere i va scrie n pipe numrul de octei ateptai de primul proces. Dac un astfel de proces nu apare, primul proces va rmne definitiv blocat, pentru c sistemul de operare l consider i posibil scriitor, dei el, evident, nu poate s mai scrie ceva, fiind blocat n read.

106

Sisteme de operare. Chestiuni teoretice i practice

8.2. Fiiere pipe cu nume


Fiierele pipe cu nume apar ca fiiere normale, avnd un nume, fiind localizate ntr-un anumit director i avnd anumite drepturi (dintre care, evident, au sens doar cele de citire i scriere). Orice proces care cunoate numele fiierului pipe i are acces la directorul n care e creat pipe-ul poate deschide acel pipe pentru citire i/sau scriere, n funcie de drepturile pe care le are asupra fiierului pipe. Fiierele pipe cu nume mai sunt cunoscute i sub numele de fiiere FIFO. Crearea unui fiier pipe cu nume se poate face cu ajutorul funciilor mknod sau mkfifo. Sintaxa celor dou funcii este:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int mknod(const char *nume_cale, mode_t perm_acces_si_tip, dev_t disp); int mkfifo(const char *nume_cale, mode_t perm_acces);

Ambele funcii returneaz 0 n caz de succes i -1 n caz de eroare. Primul parametru, nume_cale, reprezint numele fiierului pipe, nume care poate include i calea spre directorul unde se dorete crearea pipe-ului, n caz contrar el fiind creat n directorul curent. Cel de-al doilea parametru, perm_acces, reprezint drepturile de acces la pipe, drepturi ce pot fi specificate sub forma codificrii n baza opt a biilor corespunztori drepturilor de acces pentru proprietar, grup i ali utilizatori. De exemplu o codificare de forma 0640 creeaz pipe-ul cu drepturi de scriere i citire pentru proprietar, doar cu drept de citire pentru utilizatorii din acelai grup cu proprietarul i fr nici un drept pentru restul utilizatorilor. n cazul funciei mknod, pe poziia celui de-al doilea parametru trebuie precizat i tipul fiierului care se creeaz, adic FIFO, sub forma unui SAU pe bii ntre permisiunile de acces i constanta S_IFIFO, adic ceva de forma 0640 | S_IFIFO. Cel de-al treilea parametru al funciei mknod are valoarea zero. Exemplele de mai jos ilustreaz modul de creare a unor fiiere pipe cu nume cu ajutorul celor dou funcii.
if (mkfifo("FIFO", 0600) < 0) { perror("Eroare creare FIFO"); exit(1); } if (mknod("/tmp/FIFO", 0644 | S_IFIFO, 0) < 0) { perror("Eroare creare FIFO"); exit(1); }

107

Fiiere PIPE n Linux

tergerea unui fiier pipe se face la fel ca a unui fiier obinuit, cu ajutorul funciei unlink. O dat creat, fiierul pipe cu nume poate fi deschis cu ajutorul funciei open i accesat n scriere sau citire cu funciile write, respectiv read. Reamintim c este necesar ca fiierul pipe s fie deschis att pentru scriere, ct i pentru citire pentru a putea fi folosit efectiv. n acest sens, un proces care ncearc deschiderea pipe-ului doar pentru un anumit tip de operaii (de exemplu, doar pentru citire), va rmne blocat n funcia open, pn cnd pipe-ul va fi deschis i pentru operaii complementare (pentru scriere, n exemplul nostru). Dac se dorete ca procesul s nu fie blocat, atunci funcia open trebuie apelat cu opiunea O_NONBLOCK, dar trebuie avut grij c n acest caz principiile de sincronizare pe pipe nu mai funcioneaz. Exemplul de mai jos ilustreaz modul de comunicare a dou procese prin intermediul unui fiier FIFO.
#include #include #include #include #include <sys/types.h> <sys/stat.h> <fcntl.h> <unistd.h> <string.h>

// program1.c - codul C al primului proces int main(int argc, char **argv) { int fd; if (argc != 2) { printf ("Utilizare: %s mesaj", argv[0]); exit(0); } mkfifo("FIFO", 0600); fd = open("FIFO", O_WRONLY); } write(fd, argv[1], strlen(argv[1]));

// program2.c - codul C al celui de-al doilea proces int main(int argc, char **argv) { char buf[10]; int fd; fd = open("FIFO", O_RDONLY); if (fd < 0) { perror("Eroare deschidere pipe"); exit(1); } n = read(fd, buf, 6); buf[n] = 0; } printf("S-a citit de pe pipe: %s\n", buf);

108

Sisteme de operare. Chestiuni teoretice i practice

8.3. Fiiere pipe fr nume sau anonime


Fiierele pipe anonime se creeaz cu ajutorul apelului sistem pipe. Sintaxa acestui apel este:
#include <unistd.h> int pipe(int fd[2]);

Funcia returneaz 0 n caz de succes sau -1 n caz de eroare. Argumentul fd al funciei reprezint adresa unei zone de memorie (numele unui ir) rezervat pentru dou numere ntregi. Efectul execuiei apelului sistem pipe este crearea fiierului pipe anonim, adic un fiier care nu poate fi accesibil pe baza unui nume, fiierul fiind practic invizibil. Deoarece accesul la un fiier presupune deschiderea fiierului, sistemul de operare, deschide n mod automat fiierul creat n urma apelului funciei pipe, att pentru citire, ct i pentru scriere i memoreaz n cadrul irului fd cei doi descriptori. Astfel, n primul element al irului fd[0] se va stoca descriptorul fiierului pipe deschis pentru citire, iar n cel de-al doilea element al irului fd[1] se va stoca descriptorul fiierului pipe deschis pentru scriere. Se pune acum ntrebarea ce alt proces, n afara procesului care a creat fiierul pipe, poate avea acces la acest fiier invizibil. Pipe-ul este accesibil procesului care l-a creat doar prin intermediul celor doi descriptori stocai n cadrul irului fd. Prin urmare, singura modalitate ca un alt proces s aib acces la pipe ar fi prin intermediul celor dou deschideri ale pipe-ului. Acestea sunt ns nite structuri interne ale sistemului de operare i sunt referite n mod indirect prin intermediul descriptorilor de fiier returnai de obicei de funcia open. ns, fiierul pipe neavnd nume, apelul funciei open nu este posibil. Rezolvarea acestei probleme este apelul funciei fork de ctre procesul care a creat pipe-ul, lucru care are ca efect crearea unui nou proces, care motenete de la procesul printe, n copie, toate structurile de date. Printre acestea se gsete i tabela descriptorilor fiierelor deschise, care va fi astfel identic n procesul fiu ca i n printe i, prin urmare, procesul fiu va avea acces la cele dou deschideri ale fiierului pipe. Figura 1 de mai jos ilustreaz mecanismul descris mai sus. Aici se pot observa dou tipuri de tabele ce sunt folosite de ctre sistemul de operare pentru gestionarea fiierelor deschise. Este vorba, pe de o parte, de tabela fiierelor deschise, n care o intrare descrie un fiier deschis ntr-un anumit mod, iar pe de alt parte, de tabela descriptorilor de fiier, specific fiecrui proces n parte i care conine referine spre intrrile din tabela fiierelor deschise.

109

Fiiere PIPE n Linux

Figura 1. Modul de accesare a pipe-lui anonim de ctre procese printe-fiu

n concluzie, comunicarea prin fiiere pipe fr nume se poate face doar ntre procese aflate n relaia printe-fiu sau ntre procese descendente din acelai proces, cel care a creat pipe-ul. Avantajul utilizrii unui astfel de pipe, care precum se vede, are o utilitate limitat, este faptul c fiierul pipe nu este vizibil i accesibil altor procese, dect celui care a creat pipe-ul i descendenilor si, constituind astfel un fel de canal privat de comunicare ntre aceste procese. Codul de mai jos ilustreaz modul de comunicare dintre dou procese printr-un fiier pipe fr nume.
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int fd[2], pid, n, buf[100]; int main(int argc, char *argv) { pipe(fd); pid = fork(); if (pid == 0) { // proces fiu n = read(fd[0], buf, 7); buf[n] = 0; printf("Fiul a receptionat: %s\n", buf);

110

Sisteme de operare. Chestiuni teoretice i practice


n = write(fd[1], "FIU", 3); printf("Fiul a transmis: FIU\n");

} else { // poces parinte write(fd[1], "PARINTE", 7); printf("Parintele a transmis: PARINTE\n"); n = read(fd[0], buf, 3); buf[n] = 0; printf("Parintele a receptionat: %s\n", buf); }

8.4. Comunicare unidirecional i bidirecional


n exemplul anterior se poate observa o comunicare ntre cele dou procese printe i fiu care are loc n ambele direcii, i anume: de la printe spre fiu, i de la fiu spre printe. Numim o astfel de comunicare bidirecional, iar fiierul pipe bidirecional. Trebuie ns precizat c acest tip de comunicare i denumirea pe care i-am dat-o este o convenie pe care o stabilete utilizatorul i este specific unui anumit tip de aplicaii (de genul client-server), fr ca sistemul de operare s restricioneze n vreun fel comunicarea prin pipe-uri. Astfel, pe pipe ar putea comunica mai mult de dou procese, unele citind, altele prelund date (comunicare multidirecional) sau, ntr-un caz nerealist, dar teoretic posibil, un singur proces ar putea s scrie i s citeasc din pipe. Un alt caz particular de comunicare pe care l-am putea evidenia este cel n care comunicare prin pipe se realizeaz ntre dou procese, unul doar scriind date n pipe, iar cellalt doar citind date din pipe. O astfel de comunicare o numim unidirecional. n cazul comunicrii bidirecionale ntre dou procese, este posibil apariia urmtoarei situaii nedorite i, practic vorbind, eronate. n exemplul de mai sus, se observ c procesul printe efectueaz o scriere (cu write) n pipe i imediat o citire (cu read) din pipe. Ne-am putea imagina c procesul respectiv joac rol de client al unui server procesul fiu n exemplul nostru i trimite o cerere la care ateapt un rspuns. Situaia eronat care poate s apar ntr-un astfel de scenariu este cea n care procesul printe citete din pipe informaii pe care le-a scris chiar el puin mai nainte. Evident, sistemul de operare i permite s fac acest lucru, netiind care este intenia real a procesului. Eroarea apare datorit faptului c nu exist sincronizarea dorit ntre execuiile proceselor printe (client) i fiu (server). O astfel de sincronizare nu este asigurat de ctre sistemul de operare i trebuie realizat de ctre utilizator. Exist mecanisme speciale de sincronizare a
111

Fiiere PIPE n Linux

execuiei proceselor. Aici vom indica o soluie care necesit doar folosirea fiierelor pipe. Pentru aceasta trebuie s ne reamintim c exist o sincronizare inclus i n cadrul funcionalitii fiierelor pipe, i anume: dac un proces ncearc s citeasc dintr-un pipe gol, el este blocat (pus n ateptare) pn cnd un alt proces scrie n pipe numrul de octei necesar. Astfel, n contextul descris, un proces ateapt ca un alt proces s fac un anumit lucru, aceasta i nsemnnd, de altfel sincronizare. Folosindu-ne de acest tip de sincronizare specific comunicrii prin fiiere pipe, vom evita situaia eronat descris mai sus utiliznd pentru comunicarea ntre cele dou procese dou fiiere pipe folosite n mod unidirecional, adic: unul pentru comunicarea dinspre procesul printe (client) spre fiu (server), iar cellalt pentru direcia opus. Codul corespunztor acestei soluii de comunicare bidirecional este:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int fdSpreStanga[2], fdSpreDreapta[2]; int pid, n; int buf[100]; int main(int argc, char **argv) { pipe(fdSpreDreapta); // pipe de la parinte spre fiu pipe(fdSpreStanga); // pipe de la fiu spre parinte pid = fork(); if (pid == 0) { // proces fiu close(fdSpreStanga[0]); close(fdSpreDreapta[1]); n = read(fdSpreDreapta[0], buf, 7); buf[n] = 0; printf("Fiul a receptionat: %s\n", buf); n = write(fdSpreStanga[1], "FIU", 3); printf("Fiul a transmis: FIU\n");

} else { // poces parinte close(fdSpreStanga[1]); close(fdSpreDreapta[0]);

write(fdSpreDreapta[1], "PARINTE", 7); printf("Parintele a transmis: PARINTE\n"); n = read(fdSpreStanga[0], buf, 3); buf[n] = 0; printf("Parintele a receptionat: %s\n", buf);

112

Sisteme de operare. Chestiuni teoretice i practice

Se observ acum c i n cazul n care procesul printe ncearc, imediat dup ce a scris n primul pipe, citirea rspunsului de la fiu, nainte ca procesul fiu s fi reuit citirea mesajului transmis de printe i scrierea mesajului de rspuns, el va fi pus n ateptare, pentru c cel de-al doilea pipe, cel de pe care citete printele, este gol. Procesul printe va rmne blocat pn cnd procesul fiu va scrie n cel de-al doilea pipe.

8.5. Redirectarea STDIN i STDOUT spre fiiere pipe


n acest capitol dorim s discutm o situaie particular de comunicare prin fiiere pipe. Este vorba de impunerea din exterior, dar n mod transparent, a comunicrii ntre procese care nu comunic cu alte procese i cu att mai puin prin fiiere pipe, dar care preiau date de la intrarea standard i afieaz rezultatele la ieirea standard. Un exemplu de procese de acest fel sunt majoritatea comenzilor recunoscute de ctre interpretorul de comenzi, a cror cod executabil exist, dar nu poate fi modificat. De exemplu, comanda ls afieaz pe ecran adic la ieirea standard informaii despre coninutul unui director. Comenzile sort sau cat, lansate fr parametri, ateapt datele de intrare de la tastatur, adic de la intrarea standard. Interpretorul de comenzi permite, pentru comenzi de genul celor amintite, specificarea unor construcii (comenzi compuse) de forma urmtoare:
ls | sort cat fis1 fis2 | sort

Caracterul '|' din exemplele anterioare se numete pipe i indic interpretorului de comenzi c trebuie s stabileasc condiiile unei comunicri ntre cele dou comenzi, n sensul c ieirea (rezultatul) primei comenzi s devin intrare pentru cea de-a doua. Interpretorul de comenzi permite specificarea mai multor comenzi separate de '|' n aceeai linie de comand, comunicarea prin fiiere pipe avnd loc simultan ntre fiecare dou procese ce corespund la dou comenzi consecutive. Vom ncerca mai jos s ilustrm modul n care interpretorul de comenzi reuete s stabileasc o comunicare ntre dou procese care nu tiu unul de cellalt i care nu intenioneaz s comunice unul cu cellalt. Pentru a putea face acest lucru este nevoie de folosirea unui apel sistem special numit dup, respectiv o versiune mbuntit a sa, dup2, a cror sintax este urmtoarea:
#include <unistd.h> int dup(int fdExistent); int dup2(int fdExistent, int fdNou);

113

Fiiere PIPE n Linux

Efectul acestor dou apeluri sistem este acela de creare a unui descriptor de fiier duplicat pentru un descriptor deja existent, acest lucru nsemnnd c acelai fiier deschis (aceeai deschidere a unui fiier) poate fi accesat prin doi descriptori diferii, fdExistent i fdNou. n tabelele din Figura 1, acest lucru va apare ca dou intrri diferite din tabela descriptorilor de fiiere ai procesului care apeleaz funcia dup sau dup2 intrrile cu indecii fdExistent i fdNou referind aceeai intrare din tabela fiierelor deschise. Dac se efectueaz o operaie de read, write sau lseek folosind unul dintre descriptori, modificarea poziiei curente din fiier este vizibil i prin folosirea celuilalt descriptor. Ambele apeluri sistem returneaz valoarea noului descriptor alocat, n caz de succes i -1, n caz de eroare. Diferena dintre funciile dup i dup2 este aceea c la cea din urm se indic i valoare noului descriptor care se dorete alocat. Dac acesta este deja alocat unei alte deschideri a unui fiier, atunci se nchide aceast deschidere i se aloc descriptorul (intrarea din tabela descriptorilor) pentru a realiza duplicarea. Reamintim faptul c tabela descriptorilor de fiiere deschise este specific fiecrui proces n parte i, prin urmare, apelurile dup i dup2 afecteaz doar procesul care le apeleaz, fr a avea vreo influen asupra altor procese. Mai trebuie menionat faptul c apelul sistem dup va aloca ntotdeauna cel mai mic descriptor de fiier disponibil, adic prima intrare disponibil din tabela descriptorilor. Folosind fiierele pipe fr nume i apelul sistem dup2, descriem n codul de mai jos modul de lucru al interpretorului de comenzi la introducerea n linia de comand a dou comenzi separate prin caracterul '|'. Se presupune introducerea unor comenzi fr parametri.
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(int argc, char **argv) { int fdPipe[2]; int pid1, pid2; char cmd1[30], cmd2[30], cmd[30]; char *pos; while (1) { printf(">"); fgets(cmd, 30, stdin); // afisare prompter // citire linie de cmd.

if (!strcmp(cmd, "exit\n")) exit(0);

114

Sisteme de operare. Chestiuni teoretice i practice


pos = index(cmd, '|'); if ((pos == NULL) || (pos == cmd)) { printf("Introduceti 2 cmd. separate de |.\n"); continue; } // obtine comanda 1 if (*(pos-1) == ' ') { strncpy(cmd1, cmd, pos - cmd - 1); cmd1[pos-cmd-1] = 0; } else { strncpy(cmd1, cmd, pos - cmd); cmd1[pos-cmd] = 0; } // obtine comanda 2 if (*(pos+1) == ' ') strcpy(cmd2, pos+2); else strcpy(cmd2, pos+1); if (cmd2[strlen(cmd2) - 1] == '\n') cmd2[strlen(cmd2) - 1] = 0; pipe(fdPipe); // creare pipe

pid1 = fork(); // creare fiu 1 if (pid1 < 0) { perror("Eroare creare fiu 1");exit(1); } if (pid1 == 0) { // fiu 1 close(fdPipe[0]); dup2(fdPipe[1], 1); // redirectare STDOUT close(fdPipe[1]); // in pipe execlp(cmd1, cmd1, NULL); perror("Eroare executie comanda 1"); exit(0);

pid2 = fork(); // creare fiu 2 if (pid2 < 0) { perror("Eroare creare fiu 2"); exit(1); } if (pid2 == 0) { // fiu 2 close(fdPipe[1]); dup2(fdPipe[0], 0); // redirectare STDIN close(fdPipe[0]); // din pipe execlp(cmd2, cmd2, NULL); perror("Eroare executie comanda 2"); exit(0);

115

Fiiere PIPE n Linux


close(fdPipe[0]); close(fdPipe[1]); waitpid(pid1, NULL, 0); // asteapta primul fiu waitpid(pid2, NULL, 0); // asteapta al doilea fiu } // de la while } // de la main

Observm n primul rnd, n codul de mai sus, c interpretorul de comenzi creeaz procese noi pentru fiecare comand pe care o citete din linia de comand. Observm apoi, c pentru a realiza comunicarea prin pipe a proceselor care execut cele dou comenzi, se face redirectarea ieirii standard spre pipe a procesului care execut prima comand i respectiv, redirectarea intrrii standard dinspre acelai pipe a procesului care execut a doua comand. Pentru aceasta trebuie tiut faptul c la crearea unui proces, n mod automat, primele trei intrri din tabela descriptorilor de fiiere ai acelui proces, cu alte cuvinte, primii trei descriptori 0, 1 i 2 sunt automat asociate fiierului standard de intrare (STDIN) descriptorul 0 , fiierului standard de ieire (STDOUT) descriptorul 1 i fiierului standard de eroare (STDERR) descriptorul 2. Prin urmare, pentru redirectarea intrrii standard spre pipe, de exemplu, trebuie fcut n aa fel ca descriptorul 0 s indice fiierul pipe deschis pentru citire, adic s indice spre aceeai intrare din tabela fiierelor deschise spre care indic i descriptorul corespunztor fiierului pipe deschis pentru citire. Aceasta nseamn c descriptorul 0 trebuie s fie un duplicat al descriptorului fdPipe[0], lucru care se face prin apelul funciei dup2. Un alt lucru important care trebuie observat n codul de mai sus este nchiderea descriptorilor de citire i scriere ai pipe-ului de ctre toate cele trei procese (interpretorul i cei doi fii). Pipe-ul va rmne astfel accesibil doar prin descriptorul 0 pentru citire de ctre al doilea proces i prin descriptorul 1 pentru scriere de ctre primul proces. Este esenial n aceast privin s se nchid toi descriptorii de scriere pe pipe neutilizai. n caz contrar, la sfritul comunicrii dintre cele dou procese, cel de-al doilea proces care citete din pipe, ntr-o bucl, pn la detecia sfritului de fiier, cnd funcia read ntoarce 0 va rmne blocat n apelul funciei read (apelat pentru descriptorul 0, redirectat la pipe). Acest lucru se ntmpl pentru c sistemul de operare sesizeaz nc posibili scriitori n pipe, i anume procesul printe i/sau al doilea proces fiu, dei singurul proces care practic ar fi putut scrie era primul proces fiu, dar acesta s-a terminat.
116

Sisteme de operare. Chestiuni teoretice i practice

8.6. Probleme
1. S se determine dimensiunea maxim a unui fiier pipe, pentru ambele tipuri de fiiere pipe. 2. S se modifice codul interpretorului de comenzi dat ca exemplu n cadrul lucrrii pentru a accepta comunicarea prin fiiere pipe a unor comenzi crora li se specific i parametrii, sub forma:
cmd1 arg1 arg2 ... | cmd2 arg1 arg2

3. Se presupune c n dou fiiere nume.txt i prenume.txt sunt scrise pe cte o linie numele i respectiv, prenumele unor persoane, existnd o coresponden la nivel de numr de linie ntre cele dou fiiere. S se scrie dou programe C care citesc date din cele dou fiiere, primul din fiierul nume.txt, iar al doilea din fiierul prenume.txt i scriu ceea ce au citit ntrun fiier numit persoane.txt. Cele dou procese trebuie s-i sincronizeze execuia folosind fiiere pipe astfel nct n fiierul perosoane.txt s apar pe fiecare linie numele i prenumele aceleiai persoane. 4. S se scrie dou programe C, unul numit server.c, iar al doilea anumit client.c. Cele dou procese nu sunt n relaia printe-fiu. Procesul client citete de la tastatur dou numere ntregi i unul din caracterele '+' sau '-', pe care le trimite printr-un pipe cu nume procesului server. Acesta efectueaz operaia corespunztoare i trimite napoi prin pipe procesului client rezultatul. Clientul va afia rezultatul pe ecran, apoi reia din nou ntregul algoritm. S se precizeze i s se pun n eviden problemele care pot apare n cadrul comunicaiei dintre client i server i s se modifice apoi codul celor dou programe astfel nct problemele respective s nu mai apar. 5. S se scrie un program C numit server.c, care s accepte pe un fiier pipe cu nume numit PIPE_SERVER cereri de conexiune din partea unor procese clieni, rezultate fiecare dintr-un program C numit client.c. Mesajele de conectare trimise de clieni pe pipe trebuie s conin cel puin un cmp de identificare a cererii de conexiune (de exemplu tipul 0) i un cmp n care fiecare client s-i transmit PID-ul propriu. Pentru fiecare client conectat serverul va lansa cte un thread, care va rspunde apoi clientului la cereri de genul: creare fiier, citire din fiier, scriere n fiier, tergere a unui fiier etc. Se presupune c fiierele sunt create i cutate n directorul curent al procesului server. S se realizeze, folosind fiiere pipe cu nume comunicarea ntre procesele client i server, pe de o parte i ntre fiecare client i thread-ul asociat, pe de alt parte.

117

9. Fiiere PIPE n Windows


Scopul lucrrii Aceast lucrare explic mecanismul de comunicare ntre procese n Windows folosind fiiere pipe. Sunt prezentate principalele funcii ale APIului Win32 legate de gestionarea fiierelor pipe cu nume i anonime.

9.1. Prezentare general a fiierelor pipe n Windows


Pipe-ul este un flux de octei care poate fi accesat prin interfaa obinuit de lucru cu fiiere. Pipe-urile Windows ofer operaii de I/E printr-un singur handle. n Windows, se regsesc att pipe-urile cu nume, ct i cele fr nume sau anonime. Variantele fr nume sunt de fapt pipe-uri simple cu nume la care nu li se face public numele. Sistemul de operare asigur operaii de I/E sincrone pe pipe-uri. Pentru accesul asincron la pipe-uri este necesar folosirea unei interfee diferite. Pipe-urile cu nume din Windows sunt de dou tipuri: de tip octet (byte) i de tip mesaj. Tipul pipe-ului determin modul n care datele sunt scrise n pipe. Pipe-urile de tip octet sunt similare pipe-urilor din Unix, datele fiind scrise ca un ir de octei. n cazul pipe-urilor de tip mesaj, sistemul trateaz octeii scrii n pipe ca uniti de mesaj. Tipul pipe-ului se specific la creare (funcia CreateNamedPipe) prin constantele PIPE_TYPE_BYTE, respectiv PIPE_TYPE_MESSAGE. Procesul care creeaz un pipe se numete server de pipe. Procesul care se conecteaz la un pipe deja creat se numete client de pipe. Unul dintre procese scrie datele n pipe, apoi cellalt proces le citete din pipe. Pipeurile pot fi unidirecionale (unul dintre procese numai scrie, cellalt numai citete) sau bidirecionale (ambele procese pot citi din pipe i scrie n pipe).

9.2. Funcii Win32 pentru utilizarea pipe-urilor anonime


Pipe-urile anonime sunt pipe-uri care se folosesc de obicei n mod unidirecional i prin care se transfer, de regul, date ntre un proces printe i un proces fiu al acestuia. Pipe-urile anonime sunt ntotdeauna locale, ele nu pot fi utilizate pentru transferul datelor prin reea.

118

Sisteme de operare. Chestiuni teoretice i practice

Crearea pipe-urilor anonime Funcia CreatePipe creeaz un pipe anonim i returneaz dou handle-uri: un handle de citire din pipe i unul de scriere n pipe. Pentru a realiza comunicarea ntre dou procese folosind un pipe anonim, serverul de pipe trebuie s transmit unul dintre handle-uri unui alt proces. Aceasta se poate face prin motenire sau prin folosirea unui alt mod de comunicare interproces. Funcia are urmtorul prototip:
BOOL CreatePipe( PHANDLE phRead, PHANDLE phWrite, LPSECURITY_ATTRIBUTES lpsa, DWORD nSize);

Semnificaia parametrilor este urmtoarea:


phRead phWrite lpsa nSize

Indic adresele de memorie la care vor fi scrii handle-urile de citire din, respectiv de scriere n pipe. Pointer la o structur SECURITY_ATTRIBUTES, care specific dac handle-ul poate fi motenit de procesele fiu. Dimensiunea, n octei, a pipe-ului.

Citirea din i scrierea n pipe-urile anonime Funciile standard de lucru cu fiiere, ReadFile i WriteFile, sunt folosite pentru citirea i respectiv, scrierea pipe-ului. Pentru a citi dintr-un pipe, procesul trebuie s apeleze funcia ReadFile folosind handle-ul de citire al pipe-ului. Se revine din funcia ReadFile atunci cnd un alt proces scrie n pipe-ul respectiv, dac se nchid toate handle-urile de scriere la pipe sau dac apare o eroare. Scrierea n pipe se realizeaz cu funcia WriteFile, prin handle-ul de scriere. Funcia nu i termin execuia pn cnd nu se scriu toi octeii n pipe sau dac apare o eroare. Dac pipe-ul este plin i mai sunt octei de scrii, se ateapt pn cnd un alt proces citete din pipe elibernd suficient spaiu liber din el. Operaiile de citire i scriere asincrone nu sunt permise pentru pipe-urile anonime. Din acest motiv funciile ReadFileEx i WriteFileEx nu se pot folosi cu pipe-uri fr nume.

119

Fiiere PIPE n Windows

Un pipe anonim exist pn cnd toate handle-urile (de citire i de scriere) vor fi nchise. Un proces poate s-i nchid un handle cu funcia CloseHandle. Motenirea handle-urilor de acces de ctre procesele fiu Handle-ul returnat serverului de ctre funcia CreatePipe poate fi motenit n trei moduri: Dac cmpul bInheritHandle din structura SECURITY_ATTRIBUTES este setat la TRUE, handle-urile create de CreatePipe pot fi motenite. Serverul de pipe poate folosi funcia DuplicateHandle pentru a schimba modalitatea de motenire. Serverul poate crea un duplicat care nu poate fi motenit, chiar dac handle-ul surs putea fi motenit. Duplicarea poate schimba modul de motenire i n sens invers. Funcia are urmtorul prototip:
BOOL DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions); // // // // // // // procesul surs handle-ul surs procesul dest. handle-ul dest. permisiuni indic. motenire opiuni

Prin funcia CreateProcess, serverul poate specifica dac procesul fiu creat va primi sau nu handle-urile care pot fi motenite.

9.3. Pipe-uri cu nume


Pipe-urile cu nume sunt pipe-uri unidirecionale sau bidirecionale, care pot fi utilizate pentru comunicarea ntre serverul de pipe i unul sau mai muli clieni de pipe. Toate instanele unui pipe cu nume partajeaz acelai nume de pipe, dar fiecare are propriul su buffer i handle, deci apar ca i canale de comunicaie distincte. Orice proces poate accesa pipe-urile cu nume (n funcie de drepturile de acces), putnd aciona att ca server, ct i ca i client. Astfel, comunicarea ntre orice dou procese devine foarte simpl. Pipe-urile cu nume ofer posibilitatea de comunicare ntre procese aflate pe aceeai main sau ntre procese aflate pe maini diferite, legate n reea.
120

Sisteme de operare. Chestiuni teoretice i practice

Crearea pipe-urilor cu nume Un pipe cu nume este creat de serverul de pipe prin apelul funciei CreateNamedPipe. Sintaxa acestei funcii este urmtoarea:
HANDLE CreateNamedPipe( LPSTR pipeName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD dwBufferOut, DWORD dwBufferIn, DWORD timeOut, LPSECURITY_ATTRIBUTES lpsa);

Semnificaia parametrilor este urmtoarea:


pipeName

Este un ir de caractere, care indic numele pipe-ului. n Windows, numele pipe-ului trebuie s respecte urmtoarea sintax:
\\ServerName\pipe\PipeName ServerName este numele mainii din reea (specificat prin nume sau

prin adres IP) unde ruleaz serverul de pipe sau caracterul '.', n cazul n care serverul este pe aceeai main cu clientul. Cuvntul pipe din nume este impus, adic trebuie s apar ntotdeauna.
dwOpenMode

Specific modul de acces la pipe i poate avea valorile: PIPE_ACCESS_INBOUND : indic acces n citire; PIPE_ACCESS_OUTBOUND : indic acces n scriere; PIPE_ACCESS_DUPLEX : comunicare bidirecional. Prin acelai parametru se mai poate indica dac operaiile cu pipe-ul vor fi sincrone sau asincrone (FILE_FLAG_OVERLAPPED) i dac scrierile se realizeaz prin buffer sau fr (FILE_FLAG_WRITE_THROUGH).
dwPipeMode

Indic tipul pipe-ului i poate avea valorile: PIPE_TYPE_BYTE: pipe de tip octet pentru scriere; PIPE_READMODE_BYTE: pipe de tip octet pentru citire; PIPE_TYPE_MESSAGE: pipe de tip mesaj pentru scriere; PIPE_READMODE_MESSAGE: pipe de tip mesaj pentru citire. Prin valorile PIPE_WAIT i PIPE_NOWAIT se poate specifica dac operaiile cu pipe-ul s fie sincrone (cu blocare) sau nu (asincrone).
nMaxInstaces

Numrul maxim de procese client care se pot conecta la pipe-ul creat.


121

Fiiere PIPE n Windows


dwBufferOut dwBufferIn

Dimensiunea n octei a buffer-elor de ieire, respectiv de intrare. Dac este necesar, sistemul poate modifica mrimea acestor buffer-e.
timeOut

Indic timpul maxim de ateptare pentru terminarea unei operaii de I/E pe pipe.
lpsa

Indic adresa unei structuri de tipul SECURITY_ATTRIBUTES prin care se specific atribute de securitate. Citirea din i scrierea n pipe-urile cu nume Pentru a putea face operaii pe pipe, procesele trebuie s se conecteze la el. Serverul de pipe, dup crearea pipe-ului, trebuie s apeleze funcia ConnectNamedPipe. Sintaxa acestei funcii este urmtoarea:
BOOL ConnectNamedPipe( HANDLE hNamedPipe, LPOVERLAPPED lpo);

Parametrul hNamedPipe este handle-ul returnat de funcia CreateNamedPipe, iar parametrul lpo este folosit doar dac pipe-ul a fost deschis cu opiunea FILE_FLAG_OVERLAPPED. Altfel, valoarea lui trebuie s fie NULL. Funcia returneaz o valoare pozitiv n caz de succes, iar n caz contrar 0. Ca s se conecteze la un pipe cu nume creat de un alt proces, un proces client trebuie s apeleze funcia CreateFile, care va primi ca parametru numele pipe-ului n formatul prezentat mai sus. Comunicarea proceselor prin pipe-uri cu nume se face la fel ca i la pipeurile anonime, adic prin funciile ReadFile i WriteFile. Se pot folosi i funciile ReadFileEx i WriteFileEx pentru comunicarea asincron, caz n care procesele nu sunt blocate cnd efectueaz operaii de citire sau scriere pe pipe, care n mod normal s-ar bloca, dar se va executa o funcie specificat la terminarea operaiei respective. Folosind funcia PeekNamedPipe se pot citi date din pipe fr a se terge octeii citii din pipe. Funcia TransactNamedPipe scrie un mesaj de cerere ntr-un pipe bidirecional de tip mesaj i citete rspunsul ntr-o singur operaie (tranzacie). Astfel se pot mbuntii performanele reelei.
122

Sisteme de operare. Chestiuni teoretice i practice

9.4. Exemple
Exemplul 1. Folosindu-se de dou fiiere pipe anonime, un proces comunic cu un fiu al su redirectndu-i intrarea i ieirea standard spre cele dou pipeuri. Fiul citete de la intrarea standard i afieaz ceea ce citete la ieirea standard, dar, avnd aceste fiiere redirectate, va citi de pe un pipe i va scrie pe cellalt, comunicnd astfel cu printele su. Procesul printe primete n linia de comand numele unui fiier text pe care l citete i l scrie pe primul pipe i afieaz ceea ce fiul i transmite pe cel de-al doilea pipe.
// Codul surs al serverului #include <stdio.h> #include <windows.h> #define BUFSIZE 4096 HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup; HANDLE hChildStdoutRd, hChildStdoutWr; HANDLE hChildStdoutRdDup, hInputFile, hStdout; BOOL VOID VOID VOID VOID CreateChildProcess(VOID); WriteToPipe(VOID); ReadFromPipe(VOID); ErrorExit(LPTSTR); ErrMsg(LPTSTR, BOOL);

DWORD main(int argc, char *argv[]) { SECURITY_ATTRIBUTES saAttr; BOOL fSuccess; // Asteapta numele unui fis. text in linia de cmd. if (argc == 1) ErrorExit("Please specify an input file"); // Seteaz campul bInheritHandle astfel incat // handle-urile pot fi motenite saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Obtine handle-ul iesirii standard (STDOUT) hStdout = GetStdHandle(STD_OUTPUT_HANDLE); // Creeaza un pipe pentru iesirea standard // a procesului fiu if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) ErrorExit("Stdout pipe creation failed\n");

123

Fiiere PIPE n Windows


// Creeaza un handle de citire care nu poate fi // mostenit si inchide handle-ul care poate // fi mostenit pt. a se asigura ca fiul // nu poate citi din acest pipe ifSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd, GetCurrentProcess(), &hChildStdoutRdDup , 0, FALSE, DUPLICATE_SAME_ACCESS); if( !fSuccess ) ErrorExit("DuplicateHandle failed"); CloseHandle(hChildStdoutRd); // Creeaza un pipe pentru intrarea standard (STDIN) // a procesului fiu if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) ErrorExit("Stdin pipe creation failed\n"); // Duplica handle-ul de scriere a pipe-ului astfel // incat sa nu poata fi mostenit fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr, GetCurrentProcess(), &hChildStdinWrDup, 0,FALSE, DUPLICATE_SAME_ACCESS); if (! fSuccess) ErrorExit("DuplicateHandle failed"); CloseHandle(hChildStdinWr); // Creeaz procesul fiu. fSuccess = CreateChildProcess(); if (! fSuccess) ErrorExit("Create process failed"); hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); if (hInputFile == INVALID_HANDLE_VALUE) ErrorExit("CreateFile failed\n"); // Scrie in pipe-ul care este intrarea // standard a fiului WriteToPipe(); // Citeste din pipe-ul care este iesirea // standard al fiului ReadFromPipe(); } return 0;

124

Sisteme de operare. Chestiuni teoretice i practice


BOOL CreateChildProcess() { PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; BOOL bFuncRetn = FALSE; ZeroMemory(&piProcInfo,sizeof(PROCESS_INFORMATION)); ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = hChildStdoutWr; siStartInfo.hStdOutput = hChildStdoutWr; siStartInfo.hStdInput = hChildStdinRd; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; // Creeaz procesul fiu bFuncRetn = CreateProcess(NULL, "child.exe", NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); if (bFuncRetn == 0) ErrorExit("Eroare CreateProcess"); else { CloseHandle(piProcInfo.hProcess); CloseHandle(piProcInfo.hThread); return bFuncRetn; }

VOID WriteToPipe(VOID) { DWORD dwRead, dwWritten; CHAR chBuf[BUFSIZE]; // CiteSte din fisierul text si scrie // continutul lui intr-un pipe for (;;) { if (! ReadFile(hInputFile, chBuf, BUFSIZE, &dwRead, NULL) || dwRead == 0) break; if (! WriteFile(hChildStdinWrDup, chBuf, dwRead, &dwWritten, NULL)) break;

// Inchide handle-ul de scriere la pipe pentru ca // procesul fiu termine citirea din pipe if (! CloseHandle(hChildStdinWrDup)) ErrorExit("Close pipe failed");

125

Fiiere PIPE n Windows


VOID ReadFromPipe(VOID) { DWORD dwRead, dwWritten; CHAR chBuf[BUFSIZE]; // Inchide hadle-ul de scriere n pipe inainte sa // citeasc de din el if (!CloseHandle(hChildStdoutWr)) ErrorExit("CloseHandle failed"); // Citeste iesirea procesului fiu si scrie la // iesirea standard for (;;) { if(!ReadFile(hChildStdoutRdDup, chBuf, BUFSIZE, &dwRead, NULL) || (dwRead == 0)) break; if (! WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL)) break; } }

VOID ErrorExit (LPTSTR lpszMessage) { fprintf(stderr, "%s\n", lpszMessage); ExitProcess(0); }

// Codul surs al procesului fiu este: #include <windows.h> #define BUFSIZE 4096 VOID main(VOID) { CHAR chBuf[BUFSIZE]; DWORD dwRead, dwWritten; HANDLE hStdin, hStdout; BOOL fSuccess; hStdout = GetStdHandle(STD_OUTPUT_HANDLE); hStdin = GetStdHandle(STD_INPUT_HANDLE); if ((hStdout == INVALID_HANDLE_VALUE) || (hStdin == INVALID_HANDLE_VALUE)) ExitProcess(1);

126

Sisteme de operare. Chestiuni teoretice i practice


for (;;) { // Citeste de la STDIN fSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL); if (! fSuccess || dwRead == 0) break; // Scrie la STDOUT fSuccess = WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL); if (! fSuccess) break;

Exemplul 2. Folosind un pipe cu nume se implementeaz comunicarea ntre un proces server i mai multe procese client. Pentru fiecare client serverul creeaz un nou thread care va deservi acel client pe o instan distinct a pipe-ului. Comunicarea pe instanele pipe-ului este bidirecional.
// Codul #include #include #include sursa al server-ului <windows.h> <stdio.h> <tchar.h>

#define BUFSIZE 4096 VOID InstanceThread(LPVOID); VOID GetAnswerToRequest(LPTSTR, LPTSTR, LPDWORD); int _tmain(VOID) { BOOL fConnected; DWORD dwThreadId; HANDLE hPipe, hThread; LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mypipe"); /* Bucla principala: se creeaza o instanta pipe-ului cu nume si apoi asteapta conectarea unui client. Cand se conecteaz un client, se creeaza un thread care va comunica cu clientul pe instanta creata a pipe-ului */

127

Fiiere PIPE n Windows


for (;;) { hPipe = CreateNamedPipe( lpszPipename, // numele pipe-ului PIPE_ACCESS_DUPLEX, // acces citire/scriere PIPE_TYPE_MESSAGE | // pipe de tip mesaj PIPE_READMODE_MESSAGE | // mod citire-mesaj PIPE_WAIT, // modul blocant PIPE_UNLIMITED_INSTANCES, BUFSIZE, // dim. buffer output BUFSIZE, // dim. buffer input NMPWAIT_USE_DEFAULT_WAIT, NULL); // atribute de securitate implicite if (hPipe == INVALID_HANDLE_VALUE) { printf("CreatePipe failed"); return 0; } /* Asteapta un client s se conecteze; daca reuseste, functia returneaza a valoare diferita de 0. Dac functia returneaz 0, si GetLastError() returneaza valoarea ERROR_PIPE_CONNECTED atunci clientul e deja conectat; altfel nu se poate face conexiunea */ fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : GetLastError() == ERROR_PIPE_CONNECTED); if (fConnected) { // Creeaza un thread pentru acest client hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) InstanceThread, (LPVOID) hPipe, 0, &dwThreadId); if (hThread == NULL) { printf("Eroare creare thread."); return 0; } else CloseHandle(hThread);

} return 1;

} else // Clientul nu se poate conecta CloseHandle(hPipe); // inchide pipe-ul

128

Sisteme de operare. Chestiuni teoretice i practice


VOID InstanceThread(LPVOID lpvParam) { TCHAR chRequest[BUFSIZE]; TCHAR chReply[BUFSIZE]; DWORD cbBytesRead, cbReplyBytes, cbWritten; BOOL fSuccess; HANDLE hPipe; // Parametrul thread-ului este un handle // la o instanta de pipe hPipe = (HANDLE) lpvParam; while (1) { // Citeste cererile clientului din pipe fSuccess = ReadFile( hPipe, chRequest, BUFSIZE*sizeof(TCHAR), &cbBytesRead, NULL); if (! fSuccess || cbBytesRead == 0) break; HandleReq(chRequest, chReply, &cbReplyBytes); // Scrie raspunsul in pipe fSuccess = WrieFile( hPipe, chReply, cbReplyBytes, &cbWritten, NULL); if (! fSuccess || cbReplyBytes != cbWritten) break;

/* Goleste pipe-ul pentru a permite clientului sa citeasc continutul pipe-ului inainte de a se deconecta. Apoi se deconecteaza pipe-ul si se inchide handle-ul la el. */ FlushFileBuffers(hPipe); DisconnectNamedPipe(hPipe); } CloseHandle(hPipe);

VOID HandleReq(LPTSTR chRequest, LPTSTR chReply, LPDWORD pchBytes) { _tprintf( TEXT("%s\n"), chRequest ); lstrcpy(chReply, TEXT("Raspuns de la server")); *pchBytes = (lstrlen(chReply)+1)*sizeof(TCHAR); }

129

Fiiere PIPE n Windows


// Codul #include #include #include #include sursa al clientului <windows.h> <stdio.h> <conio.h> <tchar.h>

#define BUFSIZE 512 int _tmain(int argc, TCHAR *argv[]) { HANDLE hPipe; LPTSTR lpvMessage=TEXT("Raspuns de la server"); TCHAR chBuf[BUFSIZE]; BOOL fOk; DWORD cbRead, cbWritten, dwMode; LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mypipe"); if( argc > 1 ) lpvMessage = argv[1]; // Incearca sa deschida un pipe; // asteapta daca este necesar while (1) { hPipe = CreateFile( lpszPipename, // GENERIC_READ | // GENERIC_WRITE, // 0, NULL, OPEN_EXISTING, // 0, // NULL); // numele pipe-ului acces in citire si scriere deschide un pipe existent atribute implicite

if (hPipe != INVALID_HANDLE_VALUE) break; // Iese dac apare o eroare alta // decat ERROR_PIPE_BUSY if (GetLastError() != ERROR_PIPE_BUSY) { printf("Nu se poate deschide pipe-ul"); return 0; } // Daca toate instantele pipe-ului sunt ocupate, // ateapt 20 de secunde if (!WaitNamedPipe(lpszPipename, 20000)) { printf("Nu se poate deschide pipe-il"); return 0; }

130

Sisteme de operare. Chestiuni teoretice i practice


// Pipe-ul s-a conectat; // schimba tipul la modul citire-mesaj dwMode = PIPE_READMODE_MESSAGE; fOk = SetNamedPipeHandleState( hPipe, // handle la pipe &dwMode, // modul pipe nou NULL, // nu se seteaz nr. maxim de octeti NULL); // nu se seteaz time-out if (!fOk) { printf("Eroare SetNamedPipeHandleState "); return 0; } // Trimite mesaj la server-ul de pipe fOk = WriteFile( hPipe, lpvMessage, (lstrlen(lpvMessage)+1)*sizeof(TCHAR), &cbWritten, NULL); if (!fOk) { printf("Eroare WriteFile "); return 0; } do {

// Citeste din pipe fOk = ReadFile( hPipe, chBuf, BUFSIZE*sizeof(TCHAR), &cbRead, NULL); if (!fOk && GetLastError() != ERROR_MORE_DATA) break; _tprintf( TEXT("%s\n"), chBuf );

} while (!fOk); getch(); CloseHandle(hPipe); } return 0;

131

Fiiere PIPE n Windows

9.5. Probleme
1. S se scrie codul C al unui proces care creeaz un pipe anonim i apoi un proces fiu. Procesul printe scrie n pipe coninutul unui fiier text, al crui nume l primete n linia de comand. Procesul fiu afieaz coninutul pipe-ului executnd comanda more. 2. S se scrie, folosind pipe-uri, funcii de sincronizare ntre dou procese, funcii care s fie apelate la intrarea, respectiv ieirea din regiunile critice ale proceselor i care s asigure o sincronizare de genul: a. excludere mutual; b. execuie alternat. 3. Folosind pipe-uri cu nume s se asigure o comunicare de tip inel a N procese. n inelul respectiv va circula un mesaj de tip token, procesul care deine la un moment dat token-ul afindu-i identificatorul i a cta oar a primit token-ul. Transferul token-ului n inel se face de un numr de ori cunoscut de fiecare dintre procese. Construirea inelului i generarea token-ului se vor face de ctre un proces distinct de cele N, care va porni i cele N procese. S se extind apoi funcionalitatea inelului, astfel nct la un moment dat un proces s-i poat anuna terminarea (ieirea din inel), n timp ce celelalte s poat continua. 4. S se scrie programul cu funcionalitatea unui interpretor de comenzi care accept introducerea n linia de comand a unor comenzi compuse de forma comanda1 | comanda2. Prima comand afieaz n mod normal un rezultat de tip text pe ecran, iar cea de-a doua comand i citete datele de intrare de la tastatur. Efectul comenzii compuse este acela de comunicare printr-un pipe anonim a celor dou comenzi, n sensul c ieirea standard a primei comenzi este redirectat spre pipe, iar intrarea standard a celei de-a doua este redirectat dinspre pipe. 5. S se implementeze codul C al unui proces client i al unui server concurent multithread. Serverul furnizeaz clienilor servicii de efectuare a operaiilor aritmetice cu dou numere. Fiecare client este deservit de cte un thread diferit al serverului, thread creat la conectarea clientului. El recepioneaz cereri de la procesele client i transmite rezultatul napoi prin intermediul unui pipe cu nume. Structura unui mesaj este:
typedef struct { long idClient; int x; int y; int operatie; int rez; } Mesaj;

132

10. Comunicarea prin semnale n Linux


Scopul lucrrii Lucrarea prezint aspecte legate de utilizarea semnalelor n Linux, privite ca un mecanism de control a execuiei proceselor i de comunicare ntre procese. Sunt descrise principalele apeluri sistem necesare generrii i tratrii semnalelor.

10.1. Funcionalitatea semnalelor


Semnalele POSIX corespund unui mecanism special de control al execuiei proceselor. Acest mecanism const n transmiterea unor mesaje speciale proceselor de ctre sistemul de operare. Semnalele pot fi considerate un fel de ntreruperi software, deoarece mecanismul de generare i tratare a lor este asemntor cu cel specific ntreruperilor. Semnalele sunt generate i trimise unui proces de ctre sistemul de operare ca urmare a apariiei unor situaii speciale n execuia acelui proces sau datorit unei cereri explicite n acest sens a unui alt proces. Situaiile speciale care duc la generarea unor semnale sunt fie cele care reflect excepii generate de hardware (instruciune ilegal, mprire la zero etc.), fie cele constatate de ctre sistemul de operare (scrierea ntr-un pipe nchis, apsarea unor combinaii speciale de taste etc.). Cererile explicite adresate de ctre un proces pentru trimiterea unui semnal ctre un alt proces se fac prin apeluri sistem specializate, puse la dispoziie de ctre sistemul de operare. n acest ultim caz, semnalele pot fi privite ca un mecanism de comunicare ntre procese, ns unul nu foarte specializat pentru acest scop. Din acest motiv ele sunt folosite nu att pentru transmiterea de informaii ntre procese, ct pentru controlul i sincronizarea execuiei proceselor. Pentru a se putea face distincie ntre multiplele situaii care pot duce la generarea unui semnal, sistemul de operare clasific semnalele n mai multe clase sau tipuri. Fiecrui semnal i este astfel asociat un identificator, care indic tipul semnalului respectiv. Conform standardului POSIX, exist dou tipuri de semnale: semnale standard i semnale de tip real. Sistemul de operare Linux suport ambele tipuri de semnale, dar lucrarea de fa face referire doar la setul standard de semnale, ale cror nume, identificator de tip, mod de tratare implicit i semnificaie sunt date n Tabelul 1. Deoarece asocierea unui identificator pentru un anumit tip de semnal este dependent
133

Comunicarea prin semnale n Linux

de arhitectur, n tabel sunt indicate, unde este cazul, toate cele trei variante posibile pentru identificatorul de tip al unui semnal, n modul urmtor: primul numr corespunde sistemelor cu arhitectur alpha i sparc, numrul din mijloc sistemelor cu arhitectur i386, ppc i sh, iar cel de-al treilea arhitecturii mips. n cazul specificrii unui singur numr, acesta e valabil pentru toate cele trei variante. n cazul tratrii implicite a unor semnale care au ca efect terminarea procesului cruia i sunt trimise, sistemul de operare poate salva n mod automat pe HDD imaginea din memorie a procesului respectiv, sub forma unui aa-numit fiier core, caz n care n tabel, n coloana Tratare implicit, vom indica acest lucru prin termenul Core.

Tabelul 1. Tipurile standard de semnale din Linux

Semnal
SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP SIGABRT SIGBUS SIGFPE SIGKILL SIGSEGV SIGPIPE SIGALRM SIGTERM SIGUSR1 SIGUSR2

Identificator Tratare de tip implicit


1 2 3 4 5 6 10, 7, 10 8 9 11 13 14 15 30, 10, 16 31, 12, 17

Semnificaie Deconectarea terminalului de care depinde procesul ntrerupere de la tastatur (CTRL+C) Terminare de la tastatur (CTRL+\) Instruciune ilegal ntrerupere la execuia n mod de depanare Generat de apelul funciei abort Specificarea unei adrese de memorie invalid Excepie de virgul mobil Semnal de terminare forat Acces la adres de memorie invalid ncercare de scriere ntr-un pipe nchis (fr cititori) Generat la expirarea timpului stabilit de funcia alarm Semnal de terminare Rezervat utilizatorilor Rezervat utilizatorilor

Terminare Terminare Core Core Core Core Core Core Terminare Core Terminare Terminare Terminare Terminare Terminare

134

Sisteme de operare. Chestiuni teoretice i practice


SIGCHLD SIGCONT SIGSTOP SIGSTP SIGTTIN SIGTTOU SIGPWR SIGPROF SIGVTALRM 20, 17, 18 19, 18, 25 17, 19, 23 18, 20, 24 21, 21, 26 22, 22, 27 29, 30, 19 27, 27, 29 26, 26, 28

Ignorare

Suspendare Suspendare Suspendare Suspendare Terminare Terminare Terminare

Terminarea sau oprirea unui proces fiu Continuarea execuiei suspendat cu SIGSTOP Suspendarea execuiei Suspendarea execuiei de la tastatur (CTRL+Z) TTY input pentru proces ce ruleaz n background TTY output pentru proces ce ruleaz n background Cderea tensiunii de alimentare Generat la expirarea timpului stabilit de funcia setitimer Generat la expirarea timpului stabilit de funcia setitimer

10.2. Tratarea semnalelor


Tratarea semnalelor se face n mod asincron, n sensul c nu exist o funcie de recepionare a unui semnal, funcie pe care apelnd-o un proces s rmn blocat pn la primirea acelui semnal. n mod normal procesul i precizeaz modalitatea de reacie la primirea unui semnal, apoi i execut codul su. La primirea unui semnal execuia procesului este suspendat imediat i se face salt la codul (utilizator sau sistem) prin care se reacioneaz la semnalul primit. Modul de reacie la apariia unui anumit semnal poate fi stabilit de ctre fiecare proces n parte, existnd trei astfel de modaliti: tratare implicit, stabilit n mod automat de ctre sistemul de operare, avnd n mod normal ca efect terminarea execuiei procesului; ignorare, avnd ca efect continuarea execuiei procesului ca i cnd semnalul nu ar fi aprut; tratare explicit, avnd ca efect execuia unei funcii specificat de ctre utilizator. Semnalele SIG_KILL i SIG_STOP nu pot fi ignorate sau tratate n mod explicit.

135

Comunicarea prin semnale n Linux

Pentru stabilirea modului de reacie la apariia unui semnal pot fi folosite apelurile sistem signal sau sigaction, a cror sintax este prezentat mai jos:
#include <signal.h> typedef void (*sighandler_t)(int); struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; sighandler_t signal(int semnal, sighandler_t functie); int sigaction (int semnal, const struct sigaction *noua_setare, struct sigaction *vechea_setare);

n cazul ambelor funcii, parametrul semnal reprezint identificatorul tipului de semnal pentru care se stabilete modul de tratare. n cazul funciei signal, parametrul functie descrie modul de tratare a semnalului, putnd avea ca valoare: adresa unei funcii utilizator, dac se dorete tratarea explicit a semnalului, SIG_IGN, pentru ignorarea semnalului sau SIG_DFL, pentru tratarea implicit de ctre sistemul de operare a semnalului. Funcia utilizator de tratare a unui semnal primete ca parametru, n momentul apelului ei de ctre sistemul de operare, ca urmare a apariiei semnalului, un ntreg reprezentnd identificatorul de tip al semnalului aprut. Funcia signal returneaz valoarea anterioar corespunztoare tratrii semnalului sau SIG_ERR n caz de eroare. Parametrul noua_setare al funciei sigaction reprezint adresa unei structuri struct sigaction, care descrie modul de tratare al semnalului. Dac valoarea parametrului vechea_setare este diferit de NULL, atunci la adresa respectiv este salvat structura ce descrie modul de tratare anterior al semnalului. Funcia sigaction returneaz 0 n caz de succes i -1 n caz de eroare.
136

Sisteme de operare. Chestiuni teoretice i practice

n cadrul structurii struct sigaction cmpurile sa_handler i sa_sigaction trebuie completate n mod exclusiv, cu adresele unor funcii utilizator de tratare a semnalului. Primul cmp corespunde stabilirii unei funcii utilizator cu un singur parametru, similar apelului sistem signal, pe cnd cel de-al doilea corespunde unei funcii cu trei parametri, primul parametru fiind identificatorul de tip al semnalului, cel de-al doilea adresa unei structuri struct siginfo_t, iar cel de-al treilea adresa unei structuri de tipul struct ucontext_t (definit n sys/ucontext.h), care conine contextul execuiei procesului existent n momentul apariiei semnalului. Aceti parametri sunt transmii funciei utilizator n momentul apelului ei de ctre sistemul de operare. Cele dou structuri menionate vor fi alocate i completate automat de ctre sistemul de operare pe stiva procesului n momentul apelului funciei de tratare a semnalului. Structura siginfo_t ofer informaii suplimentare legate de generarea semnalului, cmpurile ei fiind descrise mai jos.
struct siginfo_t { int si_signo; int si_errno; int si_code; pid_t si_pid; uid_t si_uid; int si_status; clock_t si_utime; clock_t si_stime; sigval_t si_value; int si_int; void * si_ptr; void * si_addr; int si_band; int si_fd; }; // // // // // // // // // // // // Numar semnal Cod de eroare Cod semnal ID proces ID real utilizator Valoare de terminare Timp utilizator consumat Timp sistem consumat Valoare atasat semnalului Semnal POSIX.1b Semnal POSIX.1b Adresa memorie

// Descriptor fisier

Cmpurile si_signo, si_errno i si_code sunt completate pentru toate semnalele, dintre celelalte unele avnd semnificaie doar pentru anumite semnale. Pentru semnalele trimise cu ajutorul funciei kill (descris mai jos) i pentru semnalul SIGCHLD se completeaz i cmpurile si_pid i si_uid, care reprezint identificatorul procesului care a trimis semnalul i respectiv, identificatorul real al utilizatorului cruia i aparine respectivul proces. Cmpul si_code indic motivul generrii semnalului. Tabelul de mai jos descrie cteva dintre posibilele valori ale acestui cmp.

137

Comunicarea prin semnale n Linux


Tabelul 2. Valori posibile ale cmpului si_code

Valoare
SI_USER SI_KERNEL SI_QUEUE SI_TIMER CLD_EXITED CLD_KILLED CLD_STOPED CLD_CONTINUED

Semnificaie Trimis cu kill sau raise Generat de kernel Trimis cu sigqueue Expirarea unui timer Un proces fiu s-a terminat Un proces fiu a fost terminat forat Un proces fiu a fost oprit Un proces fiu oprit anterior i-a reluat execuia mprire ntreag la zero mprire real la zero Depire numr ntreg

Observaii Valabil pentru toate semnalele Valabil pentru toate semnalele Valabil pentru toate semnalele Valabil pentru toate semnalele Valabil semnalul
SIGCHLD SIGCHLD SIGCHLD SIGCHLD

Valabil semnalul Valabil semnalul Valabil semnalul Valabil semnalul


SIGFPE SIGFPE SIGFPE

FPE_INTDIV FPE_FLTDIV FPE_INTOVF

Valabil semnalul Valabil semnalul

Cmpul sa_mask al structurii struct sigaction indic tipurile de mesaje ce vor fi mascate pe durata tratrii semnalului indicat n apelul funciei sigaction. Semnalul tratat este i el mascat, cu excepia cazurilor n care se folosesc valorile SA_NODEFER sau SA_NOMASK pentru cmpul sa_flags al structurii struct sigaction. Valorile cmpului sa_flags modific comportamentul funciei de tratare a semnalului n modul urmtor:
SA_NOCLDSTOP

Dac semnalul tratat este SIGCLD, nu se vor primi alte semnale de acest tip cnd se termin procese fiu.

138

Sisteme de operare. Chestiuni teoretice i practice


SA_ONESHOT sau SA_RESETHAND

Modul de tratare a semnalului devine cel implicit dup terminarea tratrii semnalului.
SA_NODEFER sau SA_NOMASK

Nu blocheaz semnalul tratat, astfel nct el poate fi primit chiar n timpul tratrii sale, ceea ce nseamn ntreruperea funciei de tratare a semnalului i reapelarea ei n noul context.
SA_SIGINFO

Funcia utilizator de tratare a semnalului trebuie s primeasc trei argumente. n acest caz trebuie specificat cu o valoare nenul cmpul sa_sigaction, iar cmpul sa_handler trebuie setat la NULL.

10.3. Mascarea semnalelor


Aa cum am menionat i mai sus la descrierea funciei sigaction, semnalele pot fi mascate sau blocate pe durata tratrii unui semnal, semnalul care tocmai este tratat fiind mascat n mod implicit, iar celelalte n funcie de filtrul indicat n apelul funciei sigaction. Mascarea semnalelor se poate face ns de ctre un proces i n afara funciilor de tratare a semnalelor. Astfel, pe lng modalitile de reacie la semnale descrise mai sus, un proces poate stabili un filtru pentru semnalele pe care dorete s le blocheze. Mascarea unui semnal este diferit de ignorarea semnalului. n primul caz sistemul de operare menine informaia despre apariia semnalului, dar amn tratarea lui pn n momentul deblocrii lui, pe cnd n cel de-al doilea caz semnalul este definitiv pierdut. Apeluri sistem utile pentru blocarea semnalelor sunt cele descrise mai jos.
#include <signal.h> int sigprocmask(int mod, const sigset_t *masca, sigset_t *vechea_masca); int sigpending(sigset_t *set_semnale); int sigsuspend(const sigset_t *masca);

Funcia sigprocmask are ca efect schimbarea filtrului (masca) ce indic semnalele care vor fi mascate pe durata tratrii unui semnal. Valoarea parametrului mod indic modul de stabilire a noului filtru, astfel:
SIG_BLOCK

Setul semnalelor blocate este obinut prin reuniunea setului curent cu cel indicat de parametrul masca.
139

Comunicarea prin semnale n Linux


SIG_UNBLOCK

Setul semnalelor indicate n parametrul masca sunt demascate, adic sunt eliminate din setul curent al semnalelor mascate.
SIG_SETMASK

Setul semnalelor blocate va fi setat la valoarea celui indicat de parametrul masca. Dac pe poziia celui de-al treilea parametru, vechea_masca, se specific o valoare nenul, atunci la adresa respectiv va fi returnat valoarea actual a setului semnalelor mascate (cea de dinaintea apelului funciei sigprocmask). Apariia unui semnal mascat S1 n timpul tratrii unui alt semnal S2 va fi memorat, dar tratarea lui este amnat pn la terminarea funciei curente de tratare a semnalului S2. Dac semnalul mascat apare de mai multe ori ct timp este blocat, sistemul de operare memoreaz doar o singur apariie a sa i nu mai multe. sigpending stocheaz la adresa indicat de parametrul set_semnale setul semnalelor blocate, pentru care sistemul de operare ateapt deblocarea pentru a fi tratate. Funcia sigsuspend seteaz filtrul de mascare a semnalelor unui proces la valoarea parametrului masca, iar apoi suspend execuia procesului pn la apariia unui semnal. Aceast funcie poate fi util n cazul n care procesul trebuie s i suspende execuia pn la apariia unui anumit semnal, fr ca aceast ateptare s fie influenat de apariia altor semnale. Exemplul de mai jos ilustreaz modul de folosire a funciei ntr-o astfel de situaie, cnd se ateapt trimiterea semnalului SIGUSR1.
#include <stdio.h> #include <signal.h> void functie(int semnal) { printf("Tratare semnal %d\n", semnal); } main() { sigset_t masca; sigfillset(&masca); sigdelset(&masca, SIGUSR1); if (signal(SIGUSR1, functie) < 0) { perror("Eroare setare semnal 1"); exit(1); } if (signal(SIGUSR2, functie) < 0) { perror("Eroare setare semnal 2"); exit(1); } sigsuspend(&masca);

Funcia

140

Sisteme de operare. Chestiuni teoretice i practice

Pentru stabilirea filtrului de mascare a semnalelor pentru un proces, pot fi utilizate urmtoarele funcii:
#include <signal.h> int int int int int sigemptyset(sigset_t* masca); sigfillset(sigset_t* masca); sigaddset(sigset_t* masca, int semnal); sigdelset(sigset_t* masca, int semnal); sigismember(const sigset_t* masca, int semnal);

Funcia sigemptyset marcheaz toate semnalele posibile ca neaparinnd filtrului de semnale masca. Funcia sigfillset include toate semnalele posibile n filtrul de semnale masca. Funciile sigaddset i sigdelset adaug la filtrul de semnale indicat de parametrul masca, respectiv elimin din acel filtru, semnalul specificat prin parametrul semnal. Funcia sigismember testeaz dac semnalul indicat prin parametrul semnal aparine filtrului de semnale indicat de parametru masca.

10.4. Trimiterea semnalelor


Apelurile sistem care au ca efect trimiterea unui semnal ctre un proces sunt cele descrise mai jos:
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int semnal); int raise(int semnal); int sigqueue(pid_t pid, int semnal, const union sigval valoare); union sigval { int sival_int; void* sival_ptr; };

Apelul sistem kill este folosit pentru trimiterea unui semnal de ctre un proces ctre un alt proces. Atenionm asupra faptului c numele funciei poate crea confuzie n ceea ce privete utilitatea sa. Apelul sistem kill are ca efect trimiterea unui semnal, al crui tip este specificat prin parametrul semnal, ctre un proces, al crui identificator este indicat prin parametrul pid, i nu omorrea (terminarea forat) acelui proces, acesta fiind doar cazul particular al trimiterii semnalului SIGKILL.
141

Comunicarea prin semnale n Linux

Pentru a putea trimite un semnal unui alt proces, procesul care apeleaz funcia kill trebuie s aib drepturi de administrator sau s aib acelai identificator de utilizator real sau efectiv ca i procesul cruia vrea s-i trimit semnalul. Dac valoarea parametrului semnal este 0 (zero), atunci nu se trimite nici un semnal, ci doar se verific dac procesul cu identificatorul pid exist i dac exist drepturile necesare pentru trimiterea unui semnal spre procesul indicat. n funcie de valoarea parametrul pid, semnalul trimis cu funcia kill poate ajunge la unul sau mai multe procese, astfel: a. dac pid > 0, atunci semnalul este trimis procesului cu identificatorul pid; b. dac pid == 0, atunci semnalul este trimis tuturor proceselor din acelai grup cu procesul curent; c. dac pid == -1, atunci semnalul este trimis tuturor proceselor, cu excepia procesului cu identificatorul 1 (procesul init); d. dac pid < -1, atunci semnalul este trimis tuturor proceselor din grupul de procese cu identificatorul |pid|. Apelul sistem raise are ca efect trimiterea semnalului indicat de parametrul semnal ctre procesul apelant. Efectul este similar cu cel al funciei kill n forma urmtoare:
kill(getpid(), semnal);

Apelul sistem sigqueue trimite semnalul indicat de parametrul semnal procesului indicat prin parametrul pid. Funcia permite transmiterea, o dat cu semnalul, a unei informaii auxiliare, indicat de parametrul valoare. Aceast valoare poate fi interpretat ca un ntreg sau ca un pointer. Recepionarea acestei informaii de ctre procesul care primete semnalul este posibil dac acesta i-a stabilit modul de reacie la primirea semnalului cu funcia sigaction cu valoarea cmpului sa_flags al structurii struct sigaction setat la SA_SIGINFO, caz n care valoarea respectiv este accesibil prin cmpul si_value al structurii struct siginfo_t, transmis ca al doilea argument al funciei de tratare a semnalului. De asemenea, cmpul si_code al respectivei structurii este setat la valoarea SI_QUEUE.

142

Sisteme de operare. Chestiuni teoretice i practice

10.5. Alte apeluri sistem legate de semnale


Exist o serie de alte funcii, pe lng cele descrise mai sus, care pot fi utilizate n cadrul comunicrii proceselor prin semnale. Cteva dintre acestea sunt descrise mai jos. Funcia alarm Sintaxa ei este urmtoarea:
#include <unistd.h> unsigned int alarm(unsigned int secunde);

Efectul apelului funciei alarm este acela c semnalul SIGALRM va fi transmis procesului apelant dup intervalul de timp indicat de parametrul secunde. Dac acest parametru are valoarea zero, atunci nu va fi generat semnalul SIGALRM. Un apel al funciei anuleaz vechea setare relativ la generarea semnalului SIGALRM stabilit printr-un apel anterior. Funcia returneaz numrul de secunde rmase pn la apariia semnalului SIGALRM programat printr-un apel anterior sau 0 (zero) n caz c nu a existat un astfel de apel. Funciile setitimer i getitimer Sintaxa celor dou funcii este urmtoarea:
#include <sys/time.h> struct itimerval { struct timeval it_interval; struct timeval it_value; }; struct timeval { long tv_sec; long tv_usec; }; // secunde // microsecunde

int getitimer(int contor, struct itimerval *valoare); int setitimer(int contor, const struct itimerval *valoareNoua, struct itimerval *valoareVeche);

Funciile setitimer i getitimer sunt folosite pentru a stabili i respectiv, a obine valorile a trei contoare de timp pe care sistemul de operare le
143

Comunicarea prin semnale n Linux

asociaz fiecrui proces. La expirarea fiecruia dintre cele trei contoare sistemul de operare trimite un anumit semnal procesului. Parametrul tipContor indic contorul asupra cruia se efectueaz operaia i poate avea urmtoarele trei valori corespunztor celor trei tipuri de contor:
ITIMER_REAL

Este decrementat n timp real, n funcie de avansul ceasului sistem, iar la expirarea sa este generat semnalul SIGALRM. Este decrementat doar cnd procesul se afl n execuie, iar la expirarea sa este generat semnalul SIGVALRM. Este decrementat n timpul execuiei procesului sau cnd se execut cod sistem n contextul procesului, iar la expirarea sa este generat semnalul SIGPROF. Acest contor poate fi folosit n combinaie cu contorul ITIMER_VIRTUAL pentru a obine informaii despre timpul n care execuia unui proces are loc n spaiul utilizator sau n spaiul nucleu.

ITIMER_VIRTUAL

ITIMER_PROF

Iniializarea unui contor se face prin precizarea valorii sale (cmpul it_value al structurii struct itimerval) i a valorii implicite (cmpul it_interval al structurii struct itimerval) pe care contorul o va primi n momentul expirrii sale (atingerea valorii zero). Dac valoarea contorului este 0 (zero), atunci contorul este anulat. Similar, dac la expirarea unui contor valoarea sa implicit este zero, contorul nu va mai fi repornit. Dac valoarea parametrului valoareVeche al funciei setitimer este diferit de NULL, atunci la adresa respectiv se va nscrie vechea setare a contorului. Contoarele de timp nu expir niciodat mai repede de timpul programat, ns e posibil s expire puin mai trziu, n funcie de rezoluia ceasului sistem, care este de 10 ms. n momentul expirrii contorul este resetat i semnalul corespunztor este trimis procesului. Dac expirarea unui contor are loc n timp ce procesul este n execuie (lucru valabil ntotdeauna pentru contorul ITIMER_VIRT), semnalul generat va fi trimis imediat procesului. Altfel, el este amnat pn n momentul relurii execuiei procesului.

144

Sisteme de operare. Chestiuni teoretice i practice

Funcia pause Sintaxa funciei este urmtoarea:


#include <unistd.h> int pause(void);

Funcia pause are ca efect suspendarea execuiei procesului apelant pn n momentul sosirii unui semnal. Revenirea din funcia pause se face doar dac apare un semnal care nu este ignorat de ctre proces i numai dup execuia funciei de tratare a semnalului respectiv. Valoarea returnat este -1, iar codul erorii memorat n variabila de sistem errno este EINTR. Funcia siginterrupt Sintaxa funciei este urmtoarea:
#include <signal.h> int siginterrupt(int semnal, int intrerupere);

Funcia siginterrupt stabilete comportamentul sistemului relativ la procesul apelant n situaia n care apariia semnalului indicat de parametrul semnal are ce efect ntreruperea unui apel sistem. Dac valoarea parametrului intrerupere este 0 (zero), atunci apelul sistem ntrerupt va fi reexecutat. Acesta este comportamentul implicit al sistemului de operare Linux. Dac ns pentru semnalul respectiv s-a stabilit prin apelul sistem signal o funcie de tratare, atunci comportamentul implicit al sistemului este de a nu relua execuia apelului sistem ntrerupt. Dac valoarea parametrului intrerupere este 1 i nu s-au efectuat transferuri de date n cadrul apelului sistem ntrerupt pn n momentul ntreruperii lui, atunci respectivul apel sistem nu va fi reluat, ci va returna imediat valoarea -1, setndu-se de asemenea, variabila errno la valoarea EINTR. Dac valoarea parametrului intrerupere este 1 i s-au efectuat transferuri de date n cadrul apelului sistem ntrerupt pn n momentul ntreruperii lui, atunci respectivul apel sistem nu va fi reluat, ci va returna imediat numrul de octei transferat.

10.6. Exemple
Exemplul 1. Exemplul de mai jos ilustreaz modalitatea de tratare a tuturor semnalelor printr-o funcie utilizator, cu excepia semnalelor SIGKILL i
145

Comunicarea prin semnale n Linux SIGSTOP, care nu pot fi tratate n acest mod. Trimiterea de semnale spre

procesul care va executa codul din exemplu se poate face dintr-un alt proces sau din linia de comand cu ajutorul comenzii kill, identificatorul procesului fiind obinut cu ajutorul comenzii ps.
#include #include #include #include <stdio.h> <signal.h> <string.h> <errno.h>

int terminare; void functie(int sig, siginfo_t *siginfo, void* v) { printf("Tratare semnal %d\n", sig); if (sig == SIGQUIT) // CTRL+\ terminare = 1;

int main(int argc, char **argv) { struct sigaction sigact; int semnal; // pregatirea strcuturii sigaction sigact.sa_handler = NULL; sigact.sa_sigaction = functie; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_SIGINFO; for (semnal=1; semnal<32; semnal++) if ((semnal != SIGKILL) && (semnal != SIGSTOP)) if (sigaction(semnal, &sigact, NULL) < 0) { printf("Eroare tratare semnal %d:%s\n", semnal, strerror(errno)); exit(1); } terminare = 0; while (!terminare) pause();

Exemplul 2. Exemplul de mai jos ilustreaz modalitatea de sincronizare prin semnale a execuiei a dou procese aflate n relaia printe-fiu. Cele dou procese trebuie s afieze alternativ un mesaj, respectnd o ordine bine stabilit: mai nti procesul printe, apoi procesul fiu, apoi din nou printele i aa mai departe.
146

Sisteme de operare. Chestiuni teoretice i practice


#include #include #include #include <stdio.h> <signal.h> <sys/types.h> <unistd.h>

int asteapta; void functie(int semnal) { if (semnal == SIGUSR1){ asteapta = 1; } } int main(int argc, char **argv) { sigset_t masca; int pidPereche, i; // mascheaza toate semnalele sigfillset(&masca); sigprocmask(SIG_SETMASK, &masca, 0); if (signal(SIGUSR1, functie) < 0) { perror("Eroare setare SIGUSR1"); exit(1); } pidPereche = fork(); // creare fiu if (pidPereche < 0){ perror("Eroare creare proces fiu"); exit(1); } if (pidPereche == 0) { // fiu printf("Start: Proces fiu\n"); pidPereche = getppid(); asteapta = 1; } else { // parinte printf("Start: Proces parinte\n"); asteapta = 0; } for (i=1; i<100; i++) { // isi asteapta randul if (asteapta) { // fiul asteapta primul sigdelset(&masca, SIGUSR1); sigsuspend(&masca); } printf("Proces:%d, pas:%d\n", getpid(), i); asteapta = 1; // transfera randul procesului pereche kill(pidPereche, SIGUSR1);

147

Comunicarea prin semnale n Linux

10.7. Probleme
1. S se scrie un program C utilizat pentru copierea intrrii standard ntr-un fiier, specificat prin linia de comand. Dac n intervalul de 3N secunde nu este introdus nici un caracter de la tastatur, programul este terminat. La fiecare interval de N secunde neutilizate, utilizatorul va fi atenionat. Valoarea constantei N este specificat ca argument al programului n linia de comand. 2. Dintr-un fiier text fis.txt dou procese, care nu sunt n relaia printefiu, trebuie s citeasc pe rnd (alternativ) cte un caracter. Pentru sincronizarea execuiei lor, cele dou procese folosesc semnale. Comunicarea reciproc a PID-urilor se va face prin fiiere pipe cu nume. Caracterele citite de fiecare proces sunt scrise n fiierele fis1.txt (primul proces) i fis2.txt (al doilea proces). 3. Un proces creeaz n mod continuu la anumite perioade timp cte un proces fiu. Un proces fiu doarme pentru un numr aleator de secunde (sau milisecunde) i apoi se termin. La apsarea combinaiei de taste CRL+C procesul va afia ci fii a creat i ci s-au terminat pn n acel moment. S se scrie programul C corespunztor procesului printe i a fiilor si. 4. Se dau trei fiiere cu numele nume.txt, pren.txt i nota.txt n care sunt nregistrate, cte unul pe linie, numele, prenumele i nota obinut de mai muli studeni. S se scrie un program C, prin lansarea cruia se genereaz 3 procese, fiecare proces citind datele dintr-un fiier dintre cele trei. S se sincronizeze funcionarea celor trei procese astfel nct ntr-un fiier tabel.txt s se scrie pe cte o linie informaiile citite de ele de pe liniile cu acelai numr de ordine sub forma: NUME PRENUME NOTA. Sincronizarea se va face prin semnale. 5. S se scrie un program C prin care s se testeze modalitatea de funcionare a apelurilor sistem ce au ca efect punerea procesului apelant n stare de ateptare n situaia ntreruperii lor de ctre semnale. Astfel de apeluri sistem sunt read i write. 6. S se scrie un program C care s msoare pentru o anumit perioad, timpul petrecut de acel proces n execuie n mod nucleu, timpul petrecut de acel proces n execuie n mod utilizator i timpul ct acel proces a ateptat dup procesor.

148

11. Comunicarea prin memorie partajat i cozi de mesaje n Linux


Scopul lucrrii Lucrarea prezint modalitatea de comunicare prin memorie partajat i cozi de mesaje n Linux ntre procese aflate pe acelai sistem. Sunt descrise att principiile care stau la baza celor dou mecanisme de comunicare ntre procese, ct i apelurile sistem prin care ele sunt folosite i gestionate de ctre utilizator.

11.1. Comunicarea ntre procese prin memorie partajat


Spaiile de adrese virtuale ale proceselor sunt mapate n memoria fizic n zone disjuncte pentru a asigura protecia resurselor alocate unui proces de accesul neautorizat al unui alt proces. Prin urmare, nu exist, n mod normal, o intersecie, la nivelul memoriei fizice, ntre spaiile de adrese ale proceselor. O excepie de la aceast regul poate fi maparea segmentului de cod al unor procese diferite, dar care ruleaz acelai cod executabil (rezultate n urma lansrii n execuie a aceluiai program), n aceeai zon de memorie fizic pentru o utilizare mai eficient a acesteia. O astfel de intersectare a dou spaii de adrese ale proceselor formeaz ceea ce se numete memorie partajat sau memorie folosit n comun. n cazul segmentelor de cod aceast partajare are ca scop partajarea de ctre mai multe procese a unei zone de memorie cu coninut comun: segmentul de cod. Acesta fiind accesat n mod normal doar n citire, partajarea lui nu are ca efect influenarea n vreun fel a execuiei vreunui proces de ctre un alt proces dintre cele implicate. Nu se poate vorbi aadar de o comunicare ntre procese n acest caz. Pe de alt parte ns, scrierea ntr-o zon de memorie partajat poate influena execuia altor procese care citesc date din acea zon de memorie i prin urmare aceast operaie poate fi privit ca o modalitate de transmitere de date ntre procese. Partajarea unei zone de memorie ntre procese, cu posibilitatea de scriere i citire din acea zon, este o tehnic cunoscut sub numele de comunicare ntre procese prin memorie partajat. Menionm c acest mecanism de comunicare poate fi folosit doar pentru procese care au acces la acelai spaiu de memorie fizic, fie ea memoria local a unui sistem caz n care
149

Comunicarea prin memorie partajat i cozi de mesaje n Linux

comunicarea se poate face doar ntre procesele ce ruleaz pe acel sistem, fie o memorie distribuit caz n care comunicarea poate avea loc i ntre procese ce ruleaz pe sisteme diferite, dar pentru care memoria apare ca fiind local. Modul n care sistemul de operare realizeaz partajarea unei zone de memorie de ctre dou procese este ilustrat n Figura 1.

Figura 1. Partajarea unei zone de memorie ntre dou procese

Partajarea memoriei este o tehnic specific sistemelor multi-thread, threadurile aceluiai proces partajnd acelai spaiu de adrese, cel al procesului cruia i aparin. Accesul la zone de memorie comun prezint avantajul unei comunicri directe i rapide ntre procese, fiind de fapt cel mai rapid mecanism de comunicare ntre procese. Practic, comunicarea se produce n mod automat i transparent pentru procese, prin scrierea i citirea din zona de memorie partajat. n acelai timp, acest mod transparent de comunicare prezint i pericolul unor accesri nesincronizate ale zonei de memorie de ctre procese diferite, lucru ce poate duce la generarea unui coninut inconsistent al zonei de memorie i, implicit, la comunicarea de date incorecte ntre procese. Din acest motiv, accesul la zonele de memorie partajat trebuie fcut n mod controlat, prin respectarea unor reguli de acces, impuse, n general, prin folosirea unor mecanisme speciale de sincronizare a execuiei proceselor.

150

Sisteme de operare. Chestiuni teoretice i practice

Utilizarea unei zone de memorie partajat n Linux necesit efectuarea urmtorilor pai: crearea ei de ctre un proces, lucru ce are ca efect alocarea ei n memoria fizic; maparea unei zone din spaiul virtual de adrese al procesului care dorete accesul la zona de memorie partajat peste memoria fizic alocat acelei zone partajate; aceast operaie se numete n terminologia Linux ataare a zonei partajate la spaiul virtual de adrese ale procesului; detaarea memoriei partajate n momentul n care un proces nu mai dorete accesul le ea; tergerea zonei, atunci cnd nu se mai dorete utilizarea ei. Crearea zonei de memorie partajat Primul pas pe care trebuie s-l efectueze orice proces care dorete s acceseze o zon de memorie partajat existent este cel de obinere a unui identificator de acces la zona fizic de memorie partajat. Aceast operaie se face folosind acelai apel sistem ca i cel de creare a zonei, i anume shmget, a crui sintax i funcionalitate sunt prezentate mai jos:
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t cheie, int dimensiune, int optiuni);

O zon de memorie partajat fiind privit ca un obiect fizic al sistemului de operare, identificarea sa se face pe baza unui identificator sau a unei chei a crei valoare este specificat de parametrul cheie. Toate procesele care doresc accesul la o aceeai zon de memorie partajat, deci care doresc s foloseasc n comun acea zon, trebuie s foloseasc aceeai cheie ca parametru al funciei shmget. Dac zona de memorie exist deja, funcia shmget ntoarce ca rezultat un identificator de acces la acea zon (un descriptor), descriptor care va fi folosit pentru ataarea zonei de ctre procese. Dac zona de memorie nu exist, ea poate fi creat, n funcie de valoarea parametrului optiuni. La crearea zonei se vor aloca numrul minim de pagini de memorie necesare pentru a cuprinde numrul de octei indicai prin parametrul dimensiune. Indiferent de valoarea acestui parametru, dimensiunea unei zone de memorie partajat va fi un multiplu de dimensiunea unei pagini. Pentru aflarea dimensiunii unei pagini se poate folosi funcia getpagesize. Valoarea celui de-al treilea parametru al funciei
151

Comunicarea prin memorie partajat i cozi de mesaje n Linux

poate fi exprimat ca o operaie SAU pe bii ntre urmtoarele constante predefinite:


IPC_CREAT

Are ca efect crearea unei noi zone de memorie partajat. Dac aceast opiune nu este folosit, funcia shmget va verifica existena zonei de memorie indicat de primul parametru i permisiunea procesului apelant de a accesa acea zon. Dac aceast opiune este folosit n cazul n care zona de memorie exist deja, se va verifica dac noua dimensiune indicat de parametrul dimensiune este mai mic sau egal cu actuala dimensiune a zonei i dac exist drepturi de acces la ea. n caz afirmativ se va ntoarce ca rezultat un descriptor de acces la acea zon, iar n caz negativ funcia se va termina fr succes i va ntoarce ca rezultat valoarea -1.
IPC_EXCL

Indic faptul c zona de memorie nu trebuie creat dac ea exist i s-a folosit opiunea IPC_CREAT, caz n care funcia shmget nu va fi executat cu succes i va returna valoarea -1.
Drepturi de acces

Sunt reprezentarea n cifre octale a celor nou bii care indic permisiunile de acces la zona de memorie, similar cu drepturile de acces la fiiere, cu deosebirea c dreptul de execuie este ignorat. Deoarece orice proces poate folosi orice valoare ca i cheie a unei zone de memorie partajat, n cazul n care se dorete crearea unei noi zone de memorie partajat i nu se cunoate n mod sigur valoarea unei chei neutilizate, se poate folosi pe poziia primului parametru al funcie shmget constanta predefinit IPC_PRIVATE. Evident, n acest caz zona de memorie creat nu poate fi identificat printr-o anumit cheie i prin urmare, nu va putea fi folosit dect la comunicarea ntre procesul care a creat-o i descendeni ai si (procese create de el i de fiii si). O funcie util n ncercarea de a asigura unicitatea unei chei ntr-un anumit context este funcia ftok, descris mai jos:
# include <sys/types.h> # include <sys/ipc.h> key_t ftok(const char *caleFisier, int identificator);

152

Sisteme de operare. Chestiuni teoretice i practice

Funcia ftok ntoarce ca rezultat o cheie obinut prin combinarea numrului de i-node al fiierului indicat prin parametrul caleFisier i cei mai puin semnificativi 8 bii ai parametrului identificator. Cheia obinut este aceeai n cazul folosirii aceluiai fiier i identificator, indiferent de calea specificat pentru acel fiier. Dei funcia ftok ncearc obinerea unei chei unice pentru valori diferite ale celor doi parametri, ea nu prezint totui aceast garanie. Ataarea i detaarea zonei de memorie partajat Pentru accesarea unei zone de memorie partajat de ctre un proces, ea trebuie ataat spaiului de adrese al acelui proces, lucru ce poate fi fcut prin apelul sistem shmat. Detaarea zonei de memorie partajat se face cu ajutorul apelului sistem shmdt. Sintaxa celor dou funcii este urmtoarea:
#include <sys/types.h> #include <sys/shm.h> void *shmat(int id, const void *adresa, int optiuni); int shmdt(const void *adresa);

Parametrul id al funciei shmat reprezint descriptorul de acces la zona de memorie partajat, descriptor ntors ca rezultat de ctre shmget. Parametrul adresa indic locaia din spaiul virtual de adrese al procesului n care se dorete ataarea (maparea) memoriei partajate. Dac valoarea lui este NULL, atunci sistemul de operare va alege o adres nefolosit din spaiul virtual de adrese, adres care s fie multiplu de dimensiunea unei pagini. Dac valoarea lui este diferit de NULL i opiunea SHM_RND este indicat n parametrul optiuni, atunci adresa la care se va ataa memoria partajat este cel mai mare multiplu de dimensiunea unei pagini (constanta SHMLBA) mai mic dect valoarea parametrului adresa, iar dac opiunea SHM_RND nu este folosit, atunci adresa indicat trebuie s fie aliniat la adres de pagin. Parametrul optiuni poate avea o valoare rezultat dintr-un SAU pe bii ntre urmtoarele constante predefinite: SHM_RND, SHM_RDONLY (ataarea doar pentru citire a memoriei partajate), SHM_REMAP (maparea zonei de memorie peste un spaiu pe care deja a fost mapat o alt zon de memorie partajat). Funcia shmat ntoarce ca rezultat adresa de memorie un pointer la care a fost ataat (mapat) zona de memorie partajat. Accesul la zona de
153

Comunicarea prin memorie partajat i cozi de mesaje n Linux

memorie se va face prin pointer-ul respectiv, care poate fi interpretat ca indicnd elemente de un anumit tip. n caz de eroare funcia ntoarce -1. Funcia shmdt are ca efect detaarea din spaiul virtual de adrese al procesului apelant a zonei de memorie partajat indicat de adresa specificat prin parametrul adresa. Rezultatul ntors este 0 (zero) n caz de succes, respectiv -1 n caz de eroare. O zon de memorie detaat nu este tears, acest lucru fcndu-se numai cu apelul sistem shmctl, descris mai jos. Controlul zonei de memorie partajat O dat creat o zon de memorie partajat, sistemul de operare menine informaii despre ea n cadrul unei structuri de date de tipul shmid_ds, ale crei cmpuri sunt descrise mai jos:
struct shmid_ds { struct ipc_perm int time_t time_t time_t unsigned short unsigned short short }; struct ipc_perm { key_t key; ushort uid; ushort gid; ushort cuid; ushort cgid; ushort mode; ushort seq; }; // // // // // // // // // // // // // shm_cpid; // shm_lpid; // // shm_nattch; // // shm_perm; shm_segsz; shm_atime; shm_dtime; shm_ctime; permisiuni dim. in octeti ultimul attach ultimul detach ultima modificare a structurii pid proces creator pid ultim proces ce a accesat zona nr. de procese ce au atasata zona

cheia zonei uid efectiv proprietar gid efectiv proprietar uid efectiv utilizator creator uid efectiv utilizator creator permisiuni numar de secventa

Valorile acestor cmpuri sunt modificate automat de ctre sistemul de operare n timpul crerii zonei i pe msur ce aceasta este ataat i accesat de diferite procese. Exist ns i posibilitatea citirii acestor informaii de ctre utilizator sau chiar a modificrii unora dintre ele cu ajutorul apelului sistem shmctl.

154

Sisteme de operare. Chestiuni teoretice i practice

Acelai apel sistem poate fi folosit i pentru tergerea zonei de memorie partajat. Sintaxa lui este:
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int id, int cmd, struct shmid_ds *buf);

Parametrul id este descriptorul zonei de memorie partajat obinut de procesul apelant prin apelul anterior al funciei shmget. Parametrul cmd indic operaia (comanda) care se va efectua asupra structurii shmid_ds asociat zonei de memorie partajat, putnd lua una dintre urmtoarele valori:
IPC_STAT

Indic operaia de obinere a informaiilor despre zona de memorie partajat, informaii care vor fi stocate n cmpurile unei structuri shmid_ds, a crei adres este indicat prin parametrul buf. Procesul apelant trebuie s aib drepturi de citire asupra zonei de memorie partajat pentru a putea efectua aceast operaie.
IPC_SET

Indic operaia de modificare a unor informaii ce descriu zona de memorie partajat. Modificrile sunt reflectate de valorile cmpurilor structurii shmid_ds, a crei adres este indicat prin parametrul buf. Cmpurile care pot fi modificate sunt: shm_perm.uid (proprietar), shm_perm.gid (grup proprietar) i shm_perm.mode (permisiuni de acces). Cmpul shm_ctime (timpul ultimei modificri a structurii shmid_ds a zonei de memorie partajat) este automat actualizat la valoarea curent a ceasului sistem. Procesul apelant trebuie s aparin utilizatorului proprietar sau creator al zonei de memorie partajat sau s aib privilegii de administrator.
IPC_RMID

Indic operaia de tergere a zonei de memorie partajat. tergerea efectiv a zonei se va face doar dup ce ea nu va mai fi ataat nici unui proces, adic atunci cnd valoarea cmpului shm_nattch devine 0, ns ntre timp ea nu va mai putea fi accesat i ataat de alte procese n afara celor care deja o au ataat. Procesul apelant trebuie s aparin utilizatorului creator sau proprietar al zonei de memorie partajat sau s aib privilegii de administrator. Valoarea parametrului buf nu este folosit n cazul acestei operaii i de obicei ea este NULL.
155

Comunicarea prin memorie partajat i cozi de mesaje n Linux

Comenzi shell pentru memorie partajat Zonele de memorie partajat create n sistem pot fi vizualizate cu ajutorul comenzii ipcs. Numele comenzii este format din iniialele cuvintelor InterProcess Communication Status i ea are ca efect afiarea de informaii despre diferite resurse de tipul mecanisme de comunicare ntre procese, printre care sunt i zonele de memorie partajat. Opiunea comenzii care are ca efect afiarea doar a informaiilor despre zonele de memorie partajat este m. Alte opiuni utile, valabile pentru toate mecanismele de comunicare ntre procese, deci i pentru zonele de memorie partajat, sunt:
-t -p -l -u -i id

Afiarea informaiilor legate de timpul crerii, modificrii, ultimului acces. Afiarea identificatorilor proceselor care au folosit mecanismele de comunicare ntre procese i modul n care le-au folosit. Afiarea unor limite de dimensiune impuse mecanismelor de comunicare ntre procese. Afiarea unei scurte statistici legate de utilizarea mecanismelor de comunicare ntre procese. Afiarea de informaii doar despre resursa al crei descriptor este cel dat de valoarea parametrului id. Aceast opiune poate fi folosit n combinaie cu cele prezentate mai sus.

Comanda ipcs afieaz att cheia pe baza creia a fost creat o resurs de tip comunicare ntre procese, n cazul nostru zona de memorie partajat, ct i identificatorul returnat proceselor care obin accesul la acea zon prin apelul funciei shmget. O alt comand util este cea de tergere a resurselor create de sistemul de operare pentru comunicarea ntre procese. Comanda se numete ipcrm (IPC Remove) i necesit specificarea cheii sau a identificatorului zonei de memorie partajat, sub una din formele urmtoare:
ipcrm m identificator ipcrm M cheie

Zona de memorie specificat va fi tears doar dup ce toate procesele care o aveau ataat n momentul apelului comenzii o detaeaz de spaiul lor de adrese. tergerea unei zone de memorie partajat poate fi fcut doar de ctre administratorul sistemului, utilizatorul proprietar sau cel creator al respectivei zone de memorie.
156

Sisteme de operare. Chestiuni teoretice i practice

11.2. Comunicarea ntre procese prin cozi de mesaje


Cozile de mesaje sunt un alt mecanism pe care sistemul de operare Linux l pune la dispoziia proceselor de pe acelai sistem, care doresc s comunice, adic s-i transmit reciproc informaii. Comunicarea folosind acest mecanism se face la nivel de blocuri de octei, care formeaz structuri de date interpretate i manipulate ca mesaje, sistemul de operare fcnd distincie ntre un mesaj i un altul. Transmiterea informaiei ntre procese prin cozi de mesaje este similar celei folosite n cazul fiierelor pipe, coada de mesaje putnd fi privit ca un pipe specializat. Astfel, ordinea operaiilor de scriere n coada de mesaje i de citire din ea este impus de principiul FIFO (First-In First-Out), iar comunicarea este n mod implicit una de tip sincron, adic operaia de citire a unui mesaj este sincronizat cu cea de scriere, n sensul c se suspend execuia procesului care vrea s preia un mesaj pn n momentul n care cineva scrie mesajul ateptat n coad. Mesajele care se transmit prin coada de mesaje sunt structurate sub forma a dou componente: o component de descriere a mesajului (header) i una de coninut (corpul mesajului). Nu exist o structur impus la nivel de coninut al mesajelor, utilizatorul putndu-i defini propriile tipuri de mesaje, cu structuri i dimensiuni diferite. Din cadrul componentei de descriere a mesajelor utilizatorul are acces doar la un cmp, care i i este, de altfel, impus n structura mesajului. Acest cmp este interpretat ca identificator al mesajului, identificator folosit de ctre sistemul de operare pentru a face distincie ntre diferite clase de mesaje. Este evident c exist i alte informaii incluse n cadrul header-ului unui mesaj cum ar fi, de exemplu, lungimea mesajului , pe care ns utilizatorul nu le vede ca aparinnd mesajului. Figura 2 ilustreaz modul de transmitere a mesajelor printr-o coad de mesaje i structura mesajelor.

Figura 2. Structura unei cozi de mesaje i a mesajelor

157

Comunicarea prin memorie partajat i cozi de mesaje n Linux

Accesul la coada de mesaje este sincronizat n mod automat de ctre sistemul de operare, n sensul c dac mai multe procese efectueaz simultan operaii de scriere i citire asupra cozii de mesaje, coninutul ei i al mesajelor manipulate rmne consistent. n Linux exist posibilitatea ca un proces s cear recepionarea dintr-o coad de mesaje a unui mesaj de un anumit tip, care s-ar putea s nu fie mesajul care urma n mod normal s fie citit, lucru care evident nu corespunde principiului FIFO ce st la baza comunicrii prin cozile de mesaje. Totui, chiar i n acest caz principiul FIFO se aplic mesajelor din clasa cerut de procesul cititor. Ca i n cazul zonelor de memorie partajat, cozile de mesaje sunt create ca resurse (obiecte) ale sistemului de operare, i nu ca resurse ale proceselor. Pentru a le putea folosi, procesele trebuie s obin accesul la resursele respective, ntr-un mod similar cu accesul la un fiier sau, mai exact, n mod similar cu accesul la zonele de memorie partajat. Descriem mai jos funciile pe care sistemul de operare Linux le pune la dispoziia aplicaiilor utilizator pentru comunicarea prin cozi de mesaje. Crearea cozii de mesaje Funcia de creare a unei cozi de mesaje este msgget. Obinerea accesului la o coad de mesaje creat, operaie pe care trebuie s o fac orice proces care dorete folosirea acelei cozi de mesaje, se face tot prin intermediul funciei respective. Sintaxa ei este descris mai jos.
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t cheie, int optiuni);

Parametrul cheie reprezint identificatorul public al cozii de mesaje, cu alte cuvinte un fel de nume al cozii de mesaje. Reamintim n acest sens faptul c funcia ftok poate fi folosit pentru a obine dintr-o cale de fiier i un numr ntreg o cheie, n ncercarea de a folosi ca nume public al unei cozi de mesaje un ir de caractere i nu direct o cheie. Toate procesele care doresc s comunice printr-o anumit coad de mesaje, trebuie s obin accesul la acea coad de mesaje folosind cheia sub care a fost creat coada respectiv. Precizm faptul c aceeai valoare poate fi folosit ca i cheie, att pentru o coad de mesaje, ct i pentru o zon de memorie partajat, fr a se realiza interferene sau confuzii ntre ele, cele dou categorii de resurse (i altele,
158

Sisteme de operare. Chestiuni teoretice i practice

cum ar fi semafoarele) fiind gestionate n mod independent de ctre sistemul de operare. Valoarea parametrului cheie poate fi constanta predefinit IPC_PRIVATE, n cazul n care se dorete crearea unei noi cozi de mesaje i nu se cunoate n mod sigur valoarea unei chei neutilizate. O astfel de coad de mesaje va putea fi folosit ns doar la comunicarea ntre procesul care a creat-o i descendeni ai si (procese create de el i de fiii si). Parametrul optiuni are aceeai semnificaie ca i n cazul funciei shmget de creare a zonelor de memorie partajat, avnd valoarea 0, n cazul n care se dorete numai obinerea accesului la o coad de mesaje deja creat sau o combinaie (SAU pe bii) a urmtoarelor constante, n cazul n care se dorete crearea unei noi cozi de mesaje:
IPC_CREAT

Are ca efect crearea unei noi cozi de mesaje. Dac aceast opiune nu este folosit, funcia msgget va verifica existena cozii de mesaje indicat de parametrul cheie i permisiunea procesului apelant de a accesa acea resurs. Dac aceast opiune este folosit n cazul n care coada de mesaje exist deja, se va verifica doar dac procesul apelant are drepturi de acces la ea. n caz afirmativ se va ntoarce ca rezultat un descriptor de acces la acea coad de mesaje, iar n caz negativ funcia se va termina fr succes i va ntoarce ca rezultat valoarea -1.
IPC_EXCL

Aceast opiune se folosete doar n combinaie cu IPC_CREAT i indic faptul c nu trebuie creat coada de mesaje dac ea exist deja, caz n care funcia msgget nu va fi executat cu succes i va returna valoarea -1.
Drepturi de acces

Sunt reprezentarea n cifre octale a celor nou bii care indic permisiunile de acces la coada de mesaje, similar cu drepturile de acces la fiiere, cu deosebirea c dreptul de execuie este ignorat. Dreptul de scriere este interpretat ca permisiune de trimitere a mesajelor prin coada de mesaje, iar cel de citire ca permisiune de preluare (recepionare) a mesajelor.

159

Comunicarea prin memorie partajat i cozi de mesaje n Linux

Funcia msgget ntoarce ca rezultat, n cazul n care se execut cu succes, un numr pozitiv, interpretat ca descriptor de acces la coada de mesaje indicat sau -1, n caz de eroare. Comunicarea prin coada de mesaje Comunicarea prin cozi de mesaje nseamn trimiterea, respectiv recepionarea de mesaje. Funciile prin care se realizeaz acest lucru sunt msgsnd i msgrcv, sintaxa lor fiind descris mai jos:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> struct msgbuf { long mtype; char mtext[1]; }; // tip mesaj // continut mesaj

int msgsnd(int id, struct msgbuf *msg, size_t dimMsg, int optiuni); ssize_t msgrcv(int id, struct msgbuf *msg, size_t dimMsg, long tip, int optiuni);

Ambele funcii primesc ca prim parametru identificatorul de acces la coada de mesaje, identificator returnat de funcia msgget. Al doilea parametru este adresa unei structuri de tipul struct msgbuf, adres de la care se preia mesajul ce trebuie trimis prin coada de mesaje, n cazul funciei msgsnd, respectiv la care se depune mesajul preluat din coada de mesaje, n cazul funciei msgrcv. Structura mesajului este stabilit de ctre utilizator, singurul cmp impus fiind mtype, pe care sistemul de operare l folosete ca tip al mesajului. Cmpul mtext este folosit doar pentru a indica nceputul corpului mesajului (un ir de octei), al crui coninut (structur) este definit de ctre utilizator. De fapt, utilizatorul poate s redefineasc coninutul (i chiar i numele cmpurilor) structurii msgbuf sau poate s-i defineasc propria structur (cu alt nume), corespunztoare mesajelor pe care dorete s le transmit prin coada de mesaje. De exemplu, structura mesajului poate fi definit astfel:
struct msgbuf { long tipMesaj; char nume[100]; int varsta; };

160

Sisteme de operare. Chestiuni teoretice i practice

Parametrul dimMsg indic dimensiunea cmpurilor mesajului cu excepia cmpului care corespunde tipului mesajului, conform formulei:
dimMsg = sizeof(struct msgbuf) sizeof(long);

Aceast valoare este folosit n cazul funciei msgsnd pentru a se cunoate dimensiunea mesajului care se transmite prin coada de mesaje, iar n cazul funciei msgrcv pentru a se cunoate limita maxim a mesajului care poate fi preluat din coada de mesaje (sau ct spaiu e rezervat pentru un mesaj la adresa indicat de msg). Parametrul tip al funciei msgrcv indic ce tip de mesaj se dorete a fi preluat din coada de mesaje. Se va prelua primul mesaj n ordinea FIFO de acel tip din coad. n funcie de valoarea pe care o poate avea acest parametru exist urmtoarele cazuri: valoarea 0: se va prelua primul mesaj din coad, indiferent de tipul su; o valoare pozitiv: se va prelua primul mesaj cu tipul egal cu acea valoare; o valoare negativ: se va prelua primul mesaj cu cel mai mic tip mai mic sau egal dect valoarea absolut a parametrului tip. Parametrul optiuni poate avea valoarea 0 sau poate fi obinut printr-o combinaie (SAU pe bii) a urmtoarelor constante predefinite:
IPC_NOWAIT

Indic faptul c procesul care apeleaz funciile msgsnd i msgrcv nu trebuie s fie pus n ateptare n cazurile n care, n mod normal, apelul lor ar avea acest efect. ntr-o astfel de situaie se revine imediat din cele dou funcii, iar rezultatul ntors de ele este -1 (eroare), variabila errno fiind setat la valoarea EAGAIN, de ctre msgsnd i respectiv, ENOMSG de ctre msgrcv. Funcia msgsnd blocheaz procesul apelant n cazul n care nu mai este suficient spaiu n coada de mesaje pentru mesajul care trebuie transmis, pn n momentul n care n coad se elibereaz spaiul necesar sau se terge coada de mesaje. Funcia msgrcv blocheaz procesul apelant dac nu exist nici un mesaj de tipul indicat, pn la apariia unui mesaj de acel tip sau tergerea cozii de mesaje.
MSG_EXCEPT

Aceast opiune se utilizeaz doar pentru funcia msgrcv i doar n cazul utilizrii unei valori diferite de zero a parametrului tip. Efectul ei este acela de a indica extragerea din coada de mesaje a primului mesaj cu tipul diferit de cel indicat.
161

Comunicarea prin memorie partajat i cozi de mesaje n Linux


MSG_NOERROR

Aceast opiune se utilizeaz doar pentru funcia msgrcv i indic faptul c dac lungimea mesajului care urmeaz a fi preluat din coada de mesaje este mai mare dect dimensiunea maxim indicat de parametrul dimMsg, atunci mesajul trebuie trunchiat la lungimea maxim acceptat. Octeii trunchiai ai mesajului sunt extrai din coada de mesaje, dar sunt definitiv pierdui. ntr-o aceeai situaie, dar fr specificarea opiunii MSG_NOERROR, funcia msgrcv eueaz i ntoarce rezultatul -1, iar variabila errno este setat la valoarea E2BIG. n cazul n care funciile msgsnd i msgrcv sunt apelate pentru o coad de mesaje care este tears, ele returneaz valoarea -1, iar variabila errno este setat la valoarea EIDRM. n caz de succes, funcia msgsnd ntoarce 0, iar funcia msgrcv numrul de octei recepionai din coada de mesaje. n caz de eec, ambele funcii returneaz -1. Controlul cozii de mesaje Pentru fiecare coad de mesaje, sistemul de operare menine o structur de date de tipul struct msqid_ds, definit n fiierul <sys/msg.h>, ale crei cmpuri sunt urmtoarele:
struct msqid_ds { struct ipc_perm msg_perm; // permisiuni de acces ushort msg_qnum; // nr. mesaje din coada ushort msg_qbytes; // nr. max. de octeti permisi // pt. continutul total al msg ushort msg_lspid; // pid-ul ultimului msgsnd ushort msg_lrpid; // pid-ul ultimului msgrcv time_t msg_stime; // momentul ultimului msgsnd time_t msg_rtime; // momentul ultimului msgrcv time_t msg_ctime; // momentul ultimei modificari // a structurii msgid_ds }; struct ipc_perm { key_t key; ushort uid; ushort gid; ushort cuid; ushort cgid; ushort mode; ushort seq; }; // // // // // // // cheia cozii de mesaje uid efectiv proprietar gid efectiv proprietar uid efectiv utilizator creator uid efectiv utilizator creator permisiuni numar de secventa

162

Sisteme de operare. Chestiuni teoretice i practice

Cmpurile structurilor descrise mai sus sunt n mod automat modificate (actualizate) de ctre sistemul de operare n urma operaiilor efectuate asupra cozii de mesaje. Astfel, spre exemplu, cmpurile msg_lspid, msg_qnum i msg_stime sunt modificate n urma unui apel al funciei msgsnd n felul urmtor: msg_lspid la valoarea identificatorului procesului care a apelat funcia, msg_qnum este incrementat cu 1 i msg_stime la valoarea timpul la care a fost efectuat apelul. Cmpurile msg_lrpid, msg_qnum i msg_rtime sunt modificate n mod similar n urma unui apel al funciei msgrcv, cu deosebirea c msg_qnum este decrementat cu 1. Exist ns i posibilitatea ca aplicaiile utilizator s acceseze n mod direct cmpurile structurii msqid_ds, fie pentru citirea valorilor lor, fie chiar pentru modificarea unora dintre ele. Acest lucru este posibil folosind funcia msgctl. n mod similar cu shmctl, funcia msgctl poate fi folosit i pentru tergerea cozii de mesaje. Sintaxa ei este urmtoarea:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int id, int cmd, struct msqid_ds *buf);

Parametrul id este descriptorul cozii de mesaje obinut de procesul apelant cu ajutorul funciei msgget. Parametrul cmd indic operaia (comanda) care se va efectua asupra structurii msqid_ds asociate cozii de mesaje, putnd lua una dintre urmtoarele valori:
IPC_STAT

Indic operaia de obinere a valorilor cmpurilor structurii msqid_ds asociate cozii de mesaje, informaii ce vor fi scrise n memorie la adresa indicat de parametrul buf. Procesul apelant trebuie s aib drepturi de citire a cozii de mesaje pentru a putea efectua aceast operaie.
IPC_SET

Indic intenia de modificare a unor cmpuri ale structuri shmid_ds asociate cozii de mesaje. Valorile lor sunt luate de la adresa indicat de parametrul buf. Cmpurile care pot fi modificate sunt: msg_perm.uid (proprietar), msg_perm.gid (grup proprietar), msg_perm.mode (permisiuni de acces) i msg_qbytes (numrul maxim de octei ai coninutului mesajelor din coad). Cmpul shm_ctime (timpul ultimei modificri a structurii msqid_ds) este
163

Comunicarea prin memorie partajat i cozi de mesaje n Linux

automat actualizat la valoarea curent a ceasului sistem. Procesul apelant trebuie s aparin utilizatorului proprietar sau creator al cozii de mesaje sau s aib privilegii de administrator.
IPC_RMID

Indic operaia de tergere a cozii de mesaje. tergerea se face imediat, toate procesele care erau blocate n apeluri ale funciilor msgsnd sau msgrcv fiind deblocate, funciile returnnd -1 (eroare), iar variabila de sistem errno fiind setat la EIDRM. Procesul apelant trebuie s aparin utilizatorului creator sau proprietar al cozii de mesaje sau s aib privilegii de administrator. Valoarea parametrului buf nu este folosit n cazul acestei operaii i, de obicei, ea este setat la NULL. Comenzi shell pentru cozile de mesaje Comenzile ipcs i ipcrm descrise la capitolul despre zonele de memorie partajat pot fi folosite i n cazul cozilor de mesaje. Astfel comanda ipcs afieaz informaii i despre cozile de mesaje, iar ipcrm poate fi folosit i pentru tergerea cozilor de mesaje. Opiunea ce indic comenzii ipcs afiarea doar a informaiilor despre cozile de mesaje este -q. Pentru tergerea unei cozi de mesaje, comanda ipcrm trebuie executat n una dintre urmtoarele forme, care cer specificarea fie a identificatorului asociat cozii de mesaje, fie a cheii folosite la crearea ei:
ipcrm q identificator ipcrm Q cheie

tergerea unei cozi de mesaje poate fi fcut doar de ctre administratorul sistemului, utilizatorul proprietar sau creator al respectivei cozi de mesaje.

11.3. Exemple
Exemplul 1. Exemplul de mai jos ilustreaz modul de utilizare alternativ a unei zone de memorie partajat ca i un ir de ntregi, respectiv ca ir de octei. Scopul programului este acela de a afia reprezentarea din memorie, la nivel de octet, a ntregilor nscrii n zona de memorie partajat.
#include <sys/types.h> #include <sys/shm.h> #define N 256

164

Sisteme de operare. Chestiuni teoretice i practice


int main(int argc, char **argv) { int shmId, i, *pInt; char *pChar; // crearea zonei de memorie partajata shmId = shmget(10000, N*sizeof(int), IPC_CREAT|0600); if (shmId < 0) { perror("Eroare creare mem. partajata"); exit(1); } // atasarea zonei si accesare ei ca int pInt = (int*) shmat(shmId, 0, 0); if (pInt == -1) { perror("Eroare atasare mem. partajata"); exit(1); } // accesare zonei ca char* pChar = (char*) pInt; for (i=0; i<N; i++) pInt[i] = i; for (i=0; i<N*sizeof(int); i++) { if ((i % sizeof(int)) == 0) printf("%4d: ", pInt[i / sizeof(int)]); printf("%2x ", (unsigned char) pChar[i]); if ((i % sizeof(int)) == (sizeof(int) - 1)) printf("\n");

printf("\n"); shmdt(pInt); } shmctl(shmId, IPC_RMID, 0); // detasarea zonei // stergerea zonei

Exemplul 2. Exemplul de mai jos ilustreaz modul de mapare (ataare) a unei zone de memorie partajat n spaiul de adrese al unui proces la o adres indicat explicit de acel proces n apelul funciei shmat.
#include #include #include #include #include #include <stdio.h> <sys/types.h> <unistd.h> <sys/ipc.h> <sys/shm.h> <stdlib.h>

#define SIZE 100

165

Comunicarea prin memorie partajat i cozi de mesaje n Linux


int main(int argc, char **argv) { int *s, *pInt, id, posElemPrim; printf("SHMLBA=%d\n", SHMLBA); // dim. pagina s = (int*) calloc(SIZE, sizeof(int)); printf("Memoria dinamica alocata la adresa s=%x \n", s); id = shmget(IPC_PRIVATE, SIZE*sizeof(int), IPC_CREAT | 0644); if (id < 0) { perror("Eroare creare mem. partajata"); exit(1); } pInt=(int*) shmat(id, s,SHM_RND | SHM_REMAP); if ((int)pInt == -1){ perror("Eroare atasare mem. partajata"); exit(1); } printf("Mememoria partajata atasata la adresa pInt=%x \n", pInt); posElemPrim = s - pInt; printf("Positia primului element din s in pInt este %d \n", posElemPrim); s[0]=222; printf("s[0]=%d este egal cu pInt[%d]=%d\n", s[0], posElemPrim, pInt[posElemPrim]); shmdt(pInt); shmctl(id, IPC_RMID, 0);

Exemplul 3. Exemplul de mai jos ilustreaz comunicarea bidirecional dintre dou procese printr-o coad de mesaje folosind mesaje cu tip, astfel nct mesajele care sunt destinate unui anumit sens al comunicrii nu pot circula n sensul opus. Cu alte cuvinte nu exist riscul ca un proces care scrie n coad un mesaj destinat celuilalt proces i imediat citete din coad n ateptarea unui mesaj de rspuns de la cellalt proces s i citeasc propriul mesaj, lucru ce s-ar putea ntmpla dac nu s-ar folosi mesaje cu tip distinct pentru cele dou sensuri ale comunicrii.
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>

166

Sisteme de operare. Chestiuni teoretice i practice


#define #define #define #define TYPE1 1 TYPE2 2 N 1000 STEPS 5

typedef struct msg { long type; int val; } MSG; int main(int argc, char **argv) { int id, msgSize, i, step, rcvType; MSG m1, m2; if ((argc != 2) || ((strcmp(argv[1], "1")) && (strcmp(argv[1], "2")))) { printf("Usage: %s 1|2"); exit(1); } if (!strcmp(argv[1], "1")) { id = msgget(10000, IPC_CREAT | 0600); m1.type = TYPE1; rcvType = TYPE2; } else { id = msgget(10000, 0); m1.type = TYPE2; rcvType = TYPE1; } if (id < 0) { perror("Nu se poate accesa coada de msg."); exit(2); } msgSize = sizeof(MSG) - sizeof(long); for (step=1; step<=STEPS; step++) { for (i=1; i<=N; i++) { m1.val = i + (atoi(argv[1]) - 1) * N; msgsnd(id, &m1, msgSize, 0); printf("Msg. trimis: type=%d, val=%d\n", m1.type, m1.val); } for (i=1; i<=N; i++) { msgrcv(id, &m2, msgSize, rcvType, 0); printf("Msg. primit: type=%d, val=%d\n", m2.type, m2.val); } usleep(10000); } if (!strcmp(argv[1], "1")) msgctl(id, IPC_RMID, 0);

167

Comunicarea prin memorie partajat i cozi de mesaje n Linux

11.4. Probleme
1. S se scrie un program C care s afieze urmtoarele tipuri de adrese din spaiul de adrese al unui proces: adresa de alocare a segmentului de date (adresa unde sunt alocate variabilele globale ale programului), adresa de alocare a segmentului de stiv (adresa unde sunt alocai parametrii i variabilele funciei main), adresa de alocare a variabilelor dinamice, adresa de mapare a zonelor de memorie partajat. Pe baza informaiilor afiate i a rulrii multiple a programului s se ncerce ilustrarea organizrii spaiului de adrese al unui proces. 2. S se scrie dou programe C, prin care se acceseaz o aceeai zon de memorie partajat. Procesele corespunztoare celor dou programe sunt executate la terminale diferite i trebuie s-i ataeze memoria partajat n propriul spaiu de adrese la adresa corespunztoare unui ir de caractere pe care l aloc dinamic fiecare dintre procese. Unul dintre procese va citi apoi de la tastatur n irul propriu cte o linie, iar cel de-al doilea proces trebuie s afieze linia respectiv prin afiarea propriului ir de caractere. 3. S se scrie codul C al dou procese distincte, neaflate n relaia printefiu, care i ataeaz o aceeai zon de memorie partajat n care este stocat un numr ntreg a crei valoare iniial este 0. Cele dou procese trebuie s incrementeze strict alternativ (primul proces, al doilea proces, primul proces, al doilea proces i aa mai departe) contorul din zona de memorie partajat i s afieze pe ecran noua sa valoare. Sincronizarea execuiei proceselor trebuie fcut cu ajutorul semnalelor. Deoarece pentru acest lucru e necesar ca cele dou procese s i cunoasc reciproc identificatorul de proces, ele vor schimba aceast informaie printr-o coad de mesaje a crei cheie o cunosc ambele. 4. S se rezolve problema precedent pentru situaia n care sincronizarea celor dou procese se va face prin cozi de mesaje, caz n care nu mai este nevoie ca ele s-i cunoasc (i implicit s-i interschimbe) identificatorul de proces. 5. S se implementeze problema precedent pentru cazul generalizat al N procese. Problema e similar cu transmiterea prin intermediul unui mesaj, ntre procese aflate ntr-un inel, a unei permisiuni (un token) care d la un moment doar unui singur proces dreptul de a accesa contorul, i anume procesului care deine permisiunea. Dup accesarea contorului permisiunea este transmis urmtorului proces din inel. Aceast strategie duce la blocarea tuturor proceselor din inel n cazul opririi unuia dintre ele. S se testeze i aceast situaie i s se propun o soluie folosind semnale.

168

Sisteme de operare. Chestiuni teoretice i practice

6. S se scrie codul surs C pentru dou tipuri de procese: unul server i altul client. Serverul accept cereri de la clieni pe o coad de mesaje. Cererile trimise de clieni sunt cereri de creare/tergere a unor fiiere n cadrul unui director la care are acces procesul server, respectiv de citire /scriere din/n fiierele create. Pentru fiecare cerere sosit, serverul creeaz un thread, care va rezolva acea cerere. Thread-ul care deservete o cerere d rspuns la acea cerere folosind aceeai coad de mesaje pe care sosesc cererile. Procesul client afieaz o interfa de tip linie de comand de la care citete comenzi de tipul:
create nume_fisier read nume_fisier deplasament nr_octeti remove nume_fisier write nume_fisier deplasament sir_caractere

Corespunztor unei comenzi citite, formeaz un mesaj de cerere, pe care l scrie n coada de mesaje din care preia mesaje serverul. Va atepta apoi rspuns de la server pe aceeai coad de mesaje i va afia rezultatul cererii pe ecran. 7. S se scrie codul C al trei procese distincte. Primul proces citete un ir de numere ntregi scrise fiecare pe cte o linie ntr-un fiier text nr.in, numere pe care le transmite printr-o coad de mesaje celui de-al doilea proces, care le preia i le folosete pentru generarea unui nou ir pe baza formulei an = an-1 + an-2. irul generat este trimis apoi, prin aceeai coad de mesaje, celui de-al treilea proces, care l va scrie n fiierul nr.out, fiecare numr pe cte o linie. La terminarea fiierului nr.in, primul proces trimite un mesaj de sfrit celui de-al doilea proces, iar acesta un mesaj similar celui de-al treilea proces. 8. S se scrie dou programe C, unul numit server, iar cellalt numit client. Cele dou procese comunic printr-o coad de mesaje. Procesul client citete din fiierul text nr.in de pe fiecare linie dou numere ntregi ntre care se gsete, separat prin spaii, unul din caracterele '+', '-', '*', '/', care indic operaia ce trebuie efectuat cu cele dou numere. Clientul formeaz un mesaj pe care-l trimite serverului. Acesta preia mesajul, l decodific, efectueaz operaia i transmite clientului rezultatul. Clientul scrie n fiierul text nr.out linia citit din fiierul de intrare urmat de caracterul '=' i de rezultatul primit de la server. 9. S se foloseasc comanda ipcs cu opiunea l pentru a vizualiza limitrile impuse de sistemul de operare asupra mecanismelor de comunicare ntre procese de tip memorie partajat i cozi de mesaje.

169

12. Sincronizarea prin semafoare n Linux


Scopul lucrrii Lucrarea prezint cteva aspecte legate de sincronizarea execuiei proceselor folosind semafoare i apelurile sistem puse la dispoziie de sistemul de operare Linux pentru folosirea i manipularea semafoarelor.

12.1. Sincronizarea cu semafoare


Semaforul este un mecanism de sincronizare a execuiei proceselor care acioneaz n mod concurent asupra unor resurse partajate. Zonele de cod n care un proces acceseaz i, eventual, modific resursele partajate cu alte procese se numesc zone sau regiuni critice. Sincronizarea nseamn, la modul general, respectarea unor reguli de acces la resursele partajate. La modul concret, nseamn intrarea unui proces ntr-o zon de cod critic doar dac are acordul comun sau permisiunea tuturor celorlalte procese implicate, n caz contrar procesul fiind suspendat pn n momentul n care o astfel de permisiune e disponibil, adic condiiile sunt sigure pentru accesul la resurs. Scopul sincronizrii este evitarea situaiilor care ar duce la o stare inconsistent i nedeterminat a resurselor partajate. Cererea, respectiv acordarea unei permisiuni presupune un schimb de informaii ntre procesele concurente i, n acest sens, mecanismele de sincronizare sunt mecanisme de comunicare ntre procese. Semaforul poate fi privit aadar ca un mecanism de comunicare ntre procese, scopul comunicrii fiind sincronizarea execuiei proceselor. Semaforul const, n esen, dintr-o valoare ntreag i o coad de ateptare. Mecanismul n sine asigur acordarea de permisiuni de intrare n zonele critice, valoarea semaforului la un moment dat indicnd numrul de permisiuni disponibile la acel moment. Sincronizarea cu semafoare presupune cererea unei permisiuni nainte de intrarea ntr-o zon critic i respectiv, eliberarea permisiunii acordate la ieirea din zona critic respectiv. Toate procesele care doresc s-i sincronizeze execuia la o aceeai resurs partajat trebuie s foloseasc n acest scop acelai semafor sau set de semafoare. n urma acordrii unei permisiuni unui proces, valoarea semaforului este decrementat, iar n urma eliberrii, ea este incrementat. Dac n momentul cererii unei permisiuni valoarea semaforului este zero, execuia procesului care a cerut permisiunea este suspendat i procesul este pus n coada de ateptare a semaforului, procesorul fiind alocat altui proces. n momentul
170

Sisteme de operare. Chestiuni teoretice i practice

eliberrii unei permisiuni, unul dintre procesele care ateptau n coada semaforului este trezit i primete permisiunea de acces. n modul clasic, un proces cere i elibereaz doar o permisiune la un moment dat, dar o s vedem c sistemul de operare Linux permite cererea i eliberarea mai multor permisiuni simultan. Figura 1 ilustreaz modul de funcionare a unui semafor i utilizarea lui de ctre un proces.

Figura 1. Interaciunea unui proces cu un semafor

Operaia de cerere a unei permisiuni poate fi, aadar, vzut ca o operaie de scdere a valorii semaforului notat de obicei cu P(), iar cea de eliberare a unei permisiuni ca o operaie de incrementare a lui notat cu V(). Pentru a funciona corect, valoarea unui semafor trebuie utilizat i/sau modificat doar prin intermediul operaiilor P i V i nu n mod direct. Singura excepie de la aceast regul o constituie iniializarea semaforului, nainte de folosirea sa de ctre procesele concurente. Operaiile P i V sunt speciale prin faptul c sunt executate n mod atomic, adic toate sub-operaiile lor componente sunt executate neinteruptibil, ca i cum ar fi o singur operaie ce nu mai poate fi descompus, motiv pentru care ele sunt numite operaii primitive sau, mai scurt, primitivele semaforului. Efectul acestui mod de implementare este faptul c semaforul asigur accesul la resursa partajat doar n limita numrului de permisiuni disponibile. Chiar dac la un moment dat mai multe procese cer simultan o permisiune, numai un numr de procese egal cu valoarea semaforului la acel moment vor primi
171

Sincronizarea prin semafoare n Linux

permisiune de trecere, celelalte fiind suspendate. Acelai lucru se ntmpl i dac accesul la semafor se face n mod concurent att pentru cererea de permisiuni, ct i pentru eliberarea de permisiuni. Cu alte cuvinte, semaforul, dei este la rndul lui o resurs partajat, nu devine un punct de manifestare a nesincronizrii proceselor. Funcionarea unui semafor depinde de valoarea lui iniial i de modul de utilizare a primitivelor P i V. Exemplul de mai jos ilustreaz modalitatea clasic de asigurare a sincronizrii proceselor concurente pe regiunile lor critice folosind un semafor cu o valoare iniial pozitiv N. Toate procesele trebuie s foloseasc semaforul n modul descris n exemplu, adic apelnd primitiva P nainte de a intra n regiunea critic, pentru a primi permisiunea de acces i respectiv V, la ieirea din regiunea critic, pentru a elibera permisiunea primit.
Semaphore s = N; ... // procesul e in afara zonei critice s.P(); // cererea permisiunii de a intra // in zona critica ... // procesul e in zona critica // protejata de semafor s.V(); // eliberarea permisunii primite ... // procesul e in afara zonei critice

Dac valoarea iniial a unui semafor este 1, atunci o sincronizare de forma celei de mai sus asigur ceea ce se numete excludere mutual, adic la un moment dat doar un singur proces poate fi ntr-o regiune critic, adic un singur proces poate la un moment dat folosi resursa partajat protejat de semafor. Dac valoarea semaforului este mai mare dect 1, atunci mai multe procese, dar nu mai multe dect N, pot fi n regiunile lor critice simultan. Dac valoarea iniial a semaforului este 0 (zero), atunci codul de mai sus ar duce la blocarea definitiv a tuturor proceselor implicate n sincronizare, ceea ce evident nu are nici un sens. Semafoarele se folosesc cu valoarea iniial 0 ntr-un alt context i n alt mod. Un astfel de caz este cel n care permisiunile sunt generate ca urmare a executrii unor anumite procese, care creeaz un context necesar execuiei altor procese, semaforul jucnd rolul de contor al apariiei unui anumit tip de evenimente. Exemplul clasic este cel al proceselor productor i consumator care comunic prin intermediul unei resurse partajate (un buffer, un fiier etc.) unele depunnd mesaje n cadrul resursei, celelalte consumnd mesajele produse. Evident procesele consumator nu pot intra s acceseze resursa partajat pn cnd nu exist cel puin un mesaj depus n acea resurs de procesele productor.
172

Sisteme de operare. Chestiuni teoretice i practice

Acest aspect al sincronizrii proceselor productor/consumator este ilustrat n exemplul de mai jos, rezolvarea complet a problemei fiind descris n capitolul de exemple de la sfritul acestei lucrri.
Semaphore nrMsg = 0; // Proces productor ... nrMsg.V(); ... // Proces consumator ... nrMsg.P(); ...

Observm pe codul de mai sus c procesele consumator apeleaz doar primitiva P a semaforului nrMsg, pe cnd procesele productor doar primitiva V a aceluiai semafor, ceea ce are sens deoarece un productor produce un mesaj i implicit o permisiune pentru un consumator, pe cnd acesta din urm o dat ce consum un mesaj, nseamn c consum i o permisiune de acces pentru ceilali consumatori. Ordinea cronologic a apelurilor primitivelor semaforului din exemplul de mai sus, pentru ca aplicaia per ansamblu s evolueze este V i P, invers fa de exemplul anterior.

12.2. Crearea semafoarelor


Sistemul de operare Linux suport mecanismul de sincronizare a proceselor prin semafoare. n mod similar cu zonele de memorie partajat i cu cozile de mesaje, semafoarele sunt resurse ale sistemului i nu ale unui anumit proces. Prin urmare ele trebuie nti create, iar pentru a putea fi folosite trebuie obinut o referin spre ele (un identificator de acces). Apelul sistem folosit att pentru crearea semafoarelor, ct i pentru obinerea identificatorului de acces la ele se numete semget, avnd urmtoarea sintax:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t cheie, int nrSemafoare, int optiuni);

Funcia semget creeaz un set de semafoare, care va conine numrul de semafoare indicat de parametrul nrSemafoare. Numrul de semafoare dintr-un set este mai mare sau egal cu unu, existnd o limit maxim a acestui numr, limit impus de sistem. Parametrul cheie este un ntreg, prin care se identific n mod unic n sistem un anumit set de semafoare. Comparativ cu fiierele, aceast cheie joac rol de nume al unui set de fiiere. Procesele care doresc folosirea aceluiai set de semafoare trebuie s foloseasc aceeai valoare a cheii n
173

Sincronizarea prin semafoare n Linux

apelul funciei semget. Pentru a avea o identificare prin nume (ir de caractere) i n cazul semafoarelor, se poate folosi funcia ftok, care genereaz un numr ntreg pe baza unei ci spre un fiier i a unui alt numr ntreg. Apeluri diferite ale funciei ftok, folosind ci diferite spre un acelai fiier, dar cu un acelai numr, are ca efect generarea aceleiai chei. Din pcate funcia ftok nu asigur faptul c pentru perechi diferite de cale i numr va genera ntotdeauna o valoare unic a cheii. Valoarea parametrului cheie poate fi constanta predefinit IPC_PRIVATE, n cazul n care se dorete crearea unui nou set de semafoare i nu se cunoate n mod sigur valoarea unei chei neutilizate. Un set de semafoare creat astfel va putea fi folosit ns doar de ctre procesul care l-a creat i de descendeni ai si (procese create de el i de fiii si). n funcie de valorile parametrului optiuni, apelul funciei semget poate avea ca efect crearea setului de semafoare i obinerea unui identificator de acces la acel set sau doar obinerea identificatorului de acces. n cazul n care funcia se execut cu succes, ea ntoarce ca rezultat o valoare strict pozitiv (identificatorul de acces), iar n caz de insucces ntoarce -1, setnd variabila de sistem errno la o valoare ce corespunde erorii aprute. Valoarea parametrului optiuni poate fi 0 (zero) sau diferit de zero. n primul caz se dorete doar obinerea accesului la un set de semafoare deja creat i, n consecin, funcia semget verific existena setului de semafoare corespunztor cheii indicate i dreptul procesului apelant de a accesa acel set de semafoare. Dac aceste condiii sunt verificate, funcia ntoarce identificatorul de acces la setul de semafoare, altfel -1. n cazul n care valoarea parametrului optiuni este nenul, ea trebuie obinut printr-o combinaie (SAU pe bii) a urmtoarelor constante predefinite:
IPC_CREAT

Indic intenia de creare a unui nou set de semafoare. Dac aceast opiune nu este folosit, funcia semget va verifica existena setului de semafoare indicat de parametrul cheie i permisiunea procesului apelant de a accesa acea resurs. Dac aceast opiune este folosit n cazul n care setul de semafoare exist deja, se va verifica doar dac procesul apelant are drepturi de acces la el. n caz afirmativ, se va ntoarce ca rezultat un identificator de acces la acea zon, iar n caz negativ, funcia se va termina fr succes.
IPC_EXCL

Aceast opiune se folosete doar n combinaie cu IPC_CREAT i indic faptul c nu trebuie creat setul de semafoare dac el exist

174

Sisteme de operare. Chestiuni teoretice i practice

deja, caz n care funcia semget nu va fi executat cu succes i va returna valoarea -1.
Drepturi de acces

Sunt reprezentarea n cifre octale a celor nou bii ce indic permisiunile de acces la coada de mesaje, similar cu drepturile de acces la fiiere, cu deosebirea c dreptul de execuie este ignorat. Dreptul de scriere este interpretat ca permisiune de modificare a valorilor semafoarelor din set, iar cel de citire ca permisiune de citire a valorilor lor.

12.3. Operaii pe semafoare


Operaiile pe semafoare corespund, n principiu, modificrii valorii lor prin decrementarea sau incrementarea ei cu un anumit numr. Cazul decrementrii corespunde cererii de permisiuni i poate bloca procesul care efectueaz operaia dac valoarea curent a semaforului este mai mic dect numrul cu care se dorete decrementarea. Incrementarea valorii unui semafor corespunde eliberrii (generrii) de permisiuni pentru acel semafor. Aceast operaie nu va bloca niciodat procesul care o efectueaz, ci dimpotriv ea poate avea ca efect deblocarea unor procese care ateptau pentru creterea valorii semaforului. Funciile care permit efectuarea operaiilor menionate mai sus sunt urmtoarele:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> struct sembuf { unsigned short int sem_num; short int sem_op; short int sem_flg; }; struct timeval { time_t tv_sec; long int tv_usec; };

// numar (id) semafor // operatia pe semafor // optiuni operatie

// secunde // microsecunde

int semop(int id, struct sembuf *operatii, unsigned nrOperatii); int semtimedop(int id, struct sembuf *operatii, unsigned nrOperatii, struct timespec *timeout);

175

Sincronizarea prin semafoare n Linux

Parametrul id reprezint identificatorul setului de semafoare, identificator returnat de funcia semget. Parametrul operatii este adresa unui ir de structuri de tipul struct sembuf, fiecare structur descriind o operaie asupra unui semafor din set. Parametrul nroperatii indic numrul de structuri care trebuie folosite din irul indicat de parametrul operatii. Diferena ntre cele dou funcii se manifest doar pentru cazurile n care operaiile pe setul de semafoare pot duce la blocarea procesului apelant. Astfel, n cazul funciei semop, procesul rmne blocat pn n momentul n care operaiile pot fi efectuate, iar n cazul funciei semtimedop procesul rmne blocat cel mult un interval de timp indicat de parametrul timeout. Descrierea unei operaii pe unul din semafoarele din set necesit n primul rnd specificarea numrului de ordine al semaforului n cadrul setului cmpul sem_num al structurii struct sembuf, primul semafor avnd numrul de ordine 0, al doilea 1 i aa mai departe. Se specific apoi n cadrul cmpului sem_op numrul cu care valoarea semaforului se modific. Exist trei tipuri de valori ale acestui cmp: pozitive: valoarea semaforului este incrementat cu valoarea cmpului sem_op. Aceast operaie este ntotdeauna executat, fr a bloca procesul apelant al funciei semop. zero: se verific dac valoarea semaforului este zero. n caz afirmativ, operaia este efectuat cu succes, evident, valoarea semaforului rmnnd nemodificat. n caz contrar, procesul apelant al funciei semop este blocat, pn cnd valoarea semaforului ajunge la zero. negative: valoarea semaforului este decrementat cu valoarea absolut a cmpului sem_op. n cazul n care valoarea semaforului este mai mic dect valoarea absolut a cmpului sem_op, atunci procesul apelant al funciei semop este blocat pn n momentul n care este posibil efectuarea operaiei. Cmpul sem_flg al structurii sembuf indic anumite opiuni referitoare la operaia descris de respectiva structur. Valoarea acestui cmp poate fi una dintre urmtoarele constante predefinite:
IPC_NOWAIT

Indic faptul c nu se dorete blocarea procesului apelant al funciei semop dac operaia pe semafor ar avea n mod normal acest efect. ntr-un astfel de caz, se revine imediat din funcia semop, fr a se efectua nici una dintre operaiile indicate n irul de structuri
176

Sisteme de operare. Chestiuni teoretice i practice


operatii, funcia ntoarce rezultatul de eroare -1, iar variabila

sistem errno este setat la valoarea EAGAIN.


SEM_UNDO

Indic faptul c operaia pe semafor trebuie fcut n sens opus (decrementare n cazul incrementrii i incrementare n cazul decrementrii) i cu aceeai valoare n momentul terminrii procesului. Acest mecanism poate fi folosit pentru a evita lsarea ntr-o stare inconsistent a unui semafor n cazul n care un proces se termin brusc, lucru care ar putea duce la o blocare a altor procese care acceseaz semaforul respectiv. De exemplu, n cazul a dou procese care folosesc un semafor pentru a asigura accesul n regim de excludere mutual la o resurs partajat, dac unul dintre ele decrementeaz valoarea semaforului (ea ajunge la 0), iar apoi se termin brusc, cellalt proces va atepta la nesfrit incrementarea valorii semaforului, presupus a fi fcut de acelai proces care a decrementat-o. Mecanismul de undo (refacerea valorii semaforului) este asigurat prin faptul c unui proces i sunt n mod automat asociate de ctre sistemul de operare anumite structuri de date, corespunztor fiecrui semafor pe care l acceseaz. n cadrul unei astfel de structuri exist un contor la care se adaug, cu semn schimbat, numrul cu care se modific valoarea semaforului ntr-o operaie care specific opiunea SEM_UNDO. n momentul terminrii procesului, sistemul de operare va efectua asupra semaforului operaia de adunare a valorii acestui contor la valoarea semaforului, ceea ce, evident, poate nsemna o incrementare sau o decrementare, n funcie de valoarea pozitiv sau negativ a contorului. O problem care se poate pune este ce se ntmpl n cazul n care semaforul trebuie decrementat cu un numr (valoarea contorului de undo) care este mai mare dect valoarea curent a semaforului. n mod normal o astfel de operaie ar bloca procesul, pn cnd s-ar putea efectua operaia. Procesul fiind ns n faza de terminare, s-ar putea ca acest lucru s nu fie acceptabil. Soluia sistemului de operare Linux pentru aceast situaie este de a decrementa semaforul cu valoarea maxim posibil aducerea valorii semaforului la 0 i de a termina imediat procesul. O proprietate foarte important a funciilor semop i semtimedop este aceea c setul de operaii indicate n apelul lor se execut n mod atomic, adic sau sunt efectuate simultan toate, dac acest lucru este posibil sau nu se efectueaz nici una dintre ele i procesul este blocat. Bineneles, dac pentru operaiile care ar avea ca efect blocarea procesului se specific opiunea IPC_NOWAIT,
177

Sincronizarea prin semafoare n Linux

procesul nu este blocat i se revine imediat din apelul funciilor, dar cu cod de eroare. Acest tip de funcionalitate al celor dou funcii poate fi folosit pentru a evita interblocarea proceselor. Trebuie remarcat, nc o dat, c un apel al funciei semop cu mai multe operaii este diferit, datorit acestei execuii atomice a lor, de apelul repetat al funciei semop pentru fiecare operaie n parte. Exemplul urmtor ilustreaz aceast diferen i pune n eviden evitarea interblocrii a dou procese prin efectuarea atomic a operaiilor pe mai multe semafoare simultan. Se presupune c dou procese doresc decrementarea a dou semafoare diferite, care au valoarea iniial 1. Dac un proces decrementeaz semafoarele ntr-o anumit ordine, prin apeluri diferite ale funciei semop, iar cellalt proces n ordine invers, atunci e posibil s se ajung la o blocare reciproc a proceselor n urmtorul scenariu: primul proces decrementeaz primul semafor, apoi, nainte de a decrementa pe cel de-al doilea, este suspendat i se ncepe execuia celuilalt proces. Acesta decrementeaz mai nti cel de-al doilea semafor i ncercnd s decrementeze apoi i primul semafor va fi blocat, valoarea semaforului fiind 0. La reluarea execuiei, primul proces va ncerca decrementarea celui de-al doilea semafor, dar va fi i el blocat pentru c semaforul are deja valoarea 0. Codul de mai jos ilustreaz acest posibil scenariu, folosind apeluri ale funciei sleep pentru a surprinde n mod sigur situaiile de suspendare a proceselor corespunztoare scenariului descris mai sus:
// Primul procesul int id; struct sembuf op={0,0,0}; id = semget(10000, 0, 0); semctl(id, 0, SETVAL, 1); semctl(id, 1, SETVAL, 1); op.sem_num = 0; op.sem_op = -1; semop(id, &op, 1); // "asteapta" ca procesul 2 // sa decr. semaforul 2 sleep(1); // suspendat op.sem_num = 1; op.sem_op = -1; semop(id, &op, 1); // Al doilea proces int id; struct sembuf op={0,0,0}; id = semget(10000, 0, 0); // "asteapta" procesul 1 // sa decr. semaforul 1 sleep(1); // suspendat op.sem_num = 1; op.sem_op = -1; semop(id, &op, 1); op.sem_num = 0; op.sem_op = -1; semop(id, &op, 1);

Implementarea prin care se evit interblocarea proceselor specific ambele operaii ntr-un singur apel al funciei semop, caz n care nu conteaz
178

Sisteme de operare. Chestiuni teoretice i practice

ordinea specificrii lor, pentru c oricum ntregul set de operaii se execut atomic. Codul de mai jos ilustreaz acest mod de implementare:
// Primul procesul int id; struct sembuf op[2]; int pid; id = semget(10000, 0, 0); semctl(id, 0, SETVAL, 1); semctl(id, 1, SETVAL, 1); op[0].sem_num = 0; op[0].sem_op = -1; op[0].sem_flg = 0; op[1].sem_num = 1; op[1].sem_op = -1; op[1].sem_flg = 0; semop(id, op, 2); op[0].sem_num = 1; op[0].sem_op = -1; op[0].sem_flg = 0; op[1].sem_num = 0; op[1].sem_op = -1; op[1].sem_flg = 0; semop(id, op, 2); // Al doilea proces int id; struct sembuf op[2]; int pid; id = semget(10000, 0, 0);

Alte cteva observaii care trebuie fcute referitor la cele dou funcii de efectuare a operaiilor pe semafoare sunt legate de situaiile n care un proces care le apeleaz este blocat. Aa cum am menionat deja, acest lucru se ntmpl deoarece cel puin unul dintre semafoarele indicate n setul de operaii nu poate fi decrementat cu valoarea indicat sau se ateapt atingerea valorii zero a unuia dintre semafoare. Dac mai multe procese apeleaz succesiv una dintre cele dou funcii (pe acelai set de semafoare) i sunt blocate, ele sunt puse ntr-o list de ateptare asociat setului de semafoare, list ce funcioneaz dup principiul FIFO. Acest principiu este aplicat ns doar n cazul n care pentru mai multe procese din cele din list sunt ndeplinite la un moment dat condiiile de deblocare. ntr-o astfel de situaie procesele sunt trezite n ordinea n care ele au fost introduse n lista de ateptare. ns, n cazul n care doar pentru un anumit proces sunt ndeplinite condiiile de deblocare, atunci, indiferent de ordinea n list a acelui proces, el va fi deblocat imediat, procesele din faa lui din list rmnnd n continuare blocate. Cu alte cuvinte, putem spune c efectuarea operaiilor pe semafoare nu se bazeaz pe o strategie de rezervare a semafoarelor. De exemplu, dac un proces P1 vrea la un moment dat s decrementeze un semafor cu valoarea 3, dar valoarea semaforului este doar 2, atunci procesul P1 este blocat, dar cele dou permisiuni ale semaforului nu sunt considerate rezervate procesului, ci dac un alt proces P2 vrea la un moment ulterior s decrementeze valoarea semaforului cu 1 sau cu 2, atunci
179

Sincronizarea prin semafoare n Linux

i se permite s fac acest lucru imediat, fr a se ine cont de intenia procesului P1. Evident, ntr-o astfel de situaie exist riscul ca procesul P1 s rmn definitiv blocat, dac, s zicem, procese de genul lui P2 continu s apar n mod continuu i valoarea semaforului nu ajunge niciodat la 3. Funciile semop i semtimedop returneaz 0 n caz de succes i -1 n caz de eroare, nscriind n variabila de sistem errno codul corespunztor situaiei de eroare. Cteva posibile coduri de eroare sunt: E2BIG (argumentul nrOperatii este mai mare dect limita SEMOPM admis de sistem), EACCES (procesul apelant nu are dreptul s execute cel puin una din operaiile indicate pe setul de semafoare), EAGAIN (cel puin una dintre operaiile specificate ar fi blocat procesul, dar a fost specificat i opiunea IPC_NOWAIT), EFBIG (pentru cel puin o operaie valoarea cmpului sem_num nu se ncadreaz n intervalul corespunztor setului de semafoare 0 primul semafor etc.), EIDRM (setul de semafoare a fost ters, sau nainte de apelul funciei, sau n timp ce procesul atepta dup o anumit valoare a unui semafor din set), EINVAL (identificatorul setului de semafoare este invalid sau parametrul nrOperatii este negativ). Pe lng operaiile descrise mai sus, de modificare a valorii unui semafor prin incrementare sau decrementare, exist i posibilitatea stabilirii n mod direct a unei noi valori pentru semafor. Aceast situaie corespunde momentului iniializrii unui semafor, deci nainte de a fi el folosit de procesele concurente pentru sincronizare. Funcia care permite acest lucru se numete semctl. Ea este folosit i pentru alte operaii de control legate de setul de semafoare, motiv pentru care este descris n seciunea urmtoare.

12.4. Controlul seturilor de semafoarelor


Sistemul de operare asociaz fiecrui set de semafoare o structur de date care conine informaii necesare gestionrii acelui set de semafoare. Aceast structur de date este de tipul struct semid_ds i conine, printre altele, urmtoarele cmpuri:
struct semid_ds { struct ipc_perm sem_perm; time_t sem_otime;

// // // time_t sem_ctime; // // unsigned long int sem_nsems; // };

permisiuni timpul ultimului apel al functiei semop timpul ultimei modif. a structurii semid_ds nr. de sem. din set

180

Sisteme de operare. Chestiuni teoretice i practice


struct ipc_perm { key_t key; ushort uid; ushort gid; ushort cuid; ushort cgid; ushort mode; ushort seq; }; // // // // // // // cheia setului de semafoare uid efectiv proprietar gid efectiv proprietar uid efectiv utilizator creator uid efectiv utilizator creator permisiuni numar de secventa

De asemenea, fiecrui semafor din cadrul unui set de semafoare i este asociat o structur de date care descrie acel semafor. O astfel de structur este de tipul struct sem i conine cmpurile urmtoare:
struct sem { ushort semval; // valoarea semaforului short sempid; // pid ultima operatie ushort semncnt; // nr. de procese ce asteapta ca // val. semaforului sa creasca ushort semzcnt; // nr. de procese ce asteapta ca // val. semaforului sa devina 0 };

Funcia ce permite accesul aplicaiilor utilizator la aceste structuri de date, att pentru citirea lor, ct i pentru modificarea unora dintre cmpurile lor, se numete semctl i are urmtoarea sintax:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> union semun { int val; struct semid_ds *buf; unsigned short int *array; };

// pt. SETVAL // IPC_STAT & IPC_SET // pt. GETALL & SETALL

int semctl(int id, int nrSem, int cmd, union semun parametru);

Parametrul id este identificatorul setului de semafoare, identificator returnat de funcia semget. Parametrul nrSem indic unul dintre semafoarele din set, numrtoarea ncepnd cu 0 pentru primul semafor i aa mai departe pn la ultimul semafor. Acest parametru este folosit n relaie cu valoarea celui de-al treilea parametru al funciei, cmd, care indic operaia ce trebuie efectuat, sau asupra unui semafor din set caz n care valoarea parametrului nrSem este luat n considerare sau asupra ntregului set de semafoare sau a structurii asociate lui caz n care parametrul nrSem este
181

Sincronizarea prin semafoare n Linux

ignorat. n mod similar, interpretarea parametrului parametru este dependent de valoarea lui cmd, aceasta putnd fi una dintre urmtoarele constante predefinite:
IPC_STAT

Indic citirea structurii asociat setului de semafoare i, n mod corespunztor, al patrulea parametru al funciei semctl este interpretat ca indicnd spre o structur de tipul struct semid_ds. Parametrul nrSem este ignorat.
IPC_SET

Indic modificarea unora dintre cmpurile structurii asociate setului de semafoare i, n mod corespunztor, al patrulea parametru al funciei semctl este interpretat ca indicnd spre o structur de tipul struct semid_ds. Cmpurile care pot fi modificate de ctre aplicaiile utilizator sunt: sem_perm.uid, sem_perm.gid i sem_perm.mode. Parametrul nrSem este ignorat.
IPC_RMID

Indic intenia de tergere a setului de semafoare specificat prin parametrul id. Operaia poate fi efectuat doar de ctre procese aparinnd administratorului sau utilizatorului creator sau proprietar al setului de semafoare. Parametrii nrSem i parametru sunt ignorai.
GETVAL

Indic citirea valorii unuia dintre semafoarele din set, i anume, cel indicat prin parametrul nrSem. Al patrulea parametru al funciei este ignorat, iar funcia semctl ntoarce ca rezultat valoarea semaforului sau -1 n caz de eroare.
SETVAL

Indic modificarea valorii unuia dintre semafoarele din set, i anume, cel indicat prin parametrul nrSem. Al patrulea parametru al funciei este interpretat ca un numr ntreg, indicnd noua valoare a semaforului.
GETALL

Indic citirea valorii tuturor semafoarelor din set. Parametrul nrSem este ignorat. Al patrulea parametru al funciei indic spre un ir de ntregi n care se vor depune valorile curente ale semafoarelor.
SETALL

Indic modificarea valorii tuturor semafoarelor din set. Parametrul nrSem este ignorat. Al patrulea parametru al funciei indic spre un ir de ntregi din care se vor lua noile valori ale semafoarelor.
182

Sisteme de operare. Chestiuni teoretice i practice


GETNCNT

Indic obinerea numrului de procese care ateapt ca valoarea semaforului s creasc, numr ntors ca rezultat de ctre funcia semctl. Parametrul nrSem este ignorat.
GETZCNT

Indic obinerea numrului de procese care ateapt ca valoarea semaforului s devin zero, numr ntors ca rezultat de ctre funcia semctl. Parametrul nrSem este ignorat.
GETPID

Indic obinerea identificatorului procesului care a efectuat ultimul apel al funciei semop cmpul sempid al structurii struct sem pe semaforul indicat de parametrul nrSem. Identificatorul procesului respectiv este ntors ca rezultat de ctre funcia semctl. n cazurile n care funcia semctl nu se poate executa cu succes, ea va returna -1, setnd variabila de sistem errno la un anumit cod de eroare. n caz de succes, ea va returna sau o valoare pozitiv, atunci cnd prin parametrul cmd se cere acest lucru conform cazurilor descrise mai sus sau 0, n celelalte situaii.

12.5. Comenzi shell pentru seturile de semafoare


O alt modalitate de a obine informaii despre seturile de semafoare sau de a le terge este oferit de comenzile shell ipcs i ipcrm. Comanda ipcs afieaz informaii despre toate resursele sistemului create pentru comunicarea ntre procese: cozi de mesaje, zone de memorie partajat i seturile de semafoare. Pentru a se afia doar informaiile despre semafoare, comanda trebuie lansat cu opiunea s. Informaiile afiate cuprind cheia sub care a fost creat setul de semafoare, identificatorul asociat setului i returnat de apelul funciei semget, drepturile de acces, numrul de semafoare din set i altele. tergerea unui set de semafoare, n cazul n care exist drepturile necesare, se poate face cu ajutorul comenzii ipcrm, sub una din urmtoarele forme:
ipcrm s identificator ipcrm S cheie

tergerea unui set de semafoare poate fi fcut doar de ctre administratorul sistemului, utilizatorul proprietar sau creator al respectivului set.

183

Sincronizarea prin semafoare n Linux

12.6. Exemple
Exemplul 1. Dou procese printe i fiu copiaz n mod concurent un fiier surs ntr-un fiier destinaie folosind aceleai fiiere deschise, motenite de fiu de la printe. Folosind semafoare, se impune accesul n regim de excludere mutual a celor dou procese la zona de cod unde se realizeaz copierea.
#include #include #include #include #include <sys/types.h> <sys/stat.h> <sys/ipc.h> <sys/sem.h> <fcntl.h>

void P(int semId, int semNr) { struct sembuf op = {semNr, -1, 0}; semop(semId, &op, 1); } void V(int semId, int semNr) { struct sembuf op = {semNr, 1, 0}; semop(semId, &op, 1); } void copy(int fdSursa, int fdDest, int semId) { char c; int nr, term = 0; while (! term) { P(semId, 0);

// cerere unica permisiune

if ((nr=read(fdSursa, &c, 1)) != 1) { perror("Eroare citire"); term = 1; } if (!term && (write(fdDest, &c, nr) != nr)) { perror("Eroare scriere"); term = 1; } } V(semId, 0); // eliberare permisune

int main(int argc, char **argv) { int id, pid, fdSursa, fdDest; if (argc != 3) { printf("Utilizare: %s sursa dest\n", argv[0]); exit(1); } id = semget(30000, 1, IPC_CREAT | 0600); if (id < 0) { perror("Eroare creare semafor"); exit(2); }

184

Sisteme de operare. Chestiuni teoretice i practice


if (semctl(id, 0, SETVAL, 1) < 0) { perror("Eroare setare val. sem."); exit(3); } if ((fdSursa = open(argv[1], O_RDONLY)) < 0) { perror("Eroare deschidere fisier"); exit(4); } if ((fdDest = creat(argv[2], 0644)) < 0) { perror("Eroare creare fisier"); exit(5); } pid = fork(); copy(fdSursa, fdDest, id); if (pid) { waitpid(pid, 0); semctl(id, 0, IPC_RMID, 0); }

Exemplul 2. Problema productori / consumatori. S se sincronizeze execuia proceselor de tip productor i consumator, care sunt generate n mod continuu i acioneaz asupra unui buffer de mesaje circular, astfel nct s se respecte principiul de comunicare FIFO, s nu se suprascrie mesaje nepreluate i s nu se preia acelai mesaj de mai multe ori.
#include <sys/types.h> #include <sys/stat.h> #include <sys/ipc.h> #include <sys/sem.h> #include <fcntl.h> #define N 100 #define MUTEX 0 #define SPATII 1 #define MESAJE 2 int *buffer, *prodMsg, *consMsg; void P(int semId, int semNr) { struct sembuf op = {semNr, -1, 0}; semop(semId, &op, 1); } void V(int semId, int semNr) { struct sembuf op = {semNr, +1, 0}; semop(semId, &op, 1); } void producator(int idProd, int msg, int semId) { P(semId, SPATII); P(semId, MUTEX); buffer[*prodMsg] = msg; *prodMsg = (*prodMsg + 1) % N;

185

Sincronizarea prin semafoare n Linux


printf("Prod.%d msg: %d\n", idProd, msg); V(semId, MUTEX); V(semId, MESAJE);

void consumator(int idCons, int *msg, int semId) { P(semId, MESAJE); P(semId, MUTEX); *msg = buffer[*consMsg]; *consMsg = (*consMsg + 1) % N; printf("Cons.%d msg: %d\n", idCons, *msg); V(semId, MUTEX); V(semId, SPATII); } int main(int argc, char **argv) { int semId, shmId, i, pid, msg; shmId = shmget(IPC_PRIVATE, (N+2) * sizeof(int), IPC_CREAT | 0600); if (shmId < 0) { perror("Eroare creare shm"); exit(2); } buffer = (int*) shmat(shmId, 0, 0); prodMsg = &buffer[N]; consMsg = &buffer[N+1]; semId = semget(IPC_PRIVATE, 3, IPC_CREAT | 0600); if (semId < 0) { perror("Eroare creare sem"); exit(2); } semctl(semId, MUTEX, SETVAL, 1); semctl(semId, SPATII, SETVAL, N); semctl(semId, MESAJE, SETVAL, 0); // lacat // pt. prod. // pt. cons.

if ((pid = fork()) == 0) // fiu creeaza prod. for (i=1; i<=10*N; i++) if (fork() == 0) { producator(i, i, semId); exit(0); } else // parinte creeaza cons. for (i=1; i<=10*N; i++) if (fork() == 0) { consumator(i, &msg, semId); exit(0); }

Exemplul 3. Problema scriitori / cititori. S se sincronizeze execuia a dou tipuri de procese care acceseaz aceeai resurs partajat, n cazul de fa un ir de ntregi: unele procese doar citesc din ir procese cititor, iar altele doar scriu n ir procese scriitor. Regulile de sincronizare sunt: 1. mai muli cititori pot accesa simultan irul, dar nu n acelai timp cu scriitorii;
186

Sisteme de operare. Chestiuni teoretice i practice

2. scriitori acceseaz irul n regim de excludere mutual, adic atunci cnd un scriitor acceseaz irul nici un alt proces, fie el scriitor sau cititor nu poate accesa irul. Soluia din exemplu folosete un semafor cu o valoare iniial mare (mai mare dect numrul posibil de cititori care pot fi activi simultan la un moment dat), semafor pe care scriitorii l decrementeaz cu un numr egal cu valoarea iniial, adic cer toate permisiunile pentru a bloca accesul oricrui alt proces, iar cititorii l decrementeaz cu 1, pentru a bloca doar scriitori, dar fr a bloca ali cititori.
#include #include #include #include #include #define #define #define #define #define #define <sys/types.h> <sys/stat.h> <sys/ipc.h> <sys/sem.h> <fcntl.h>

N 10 MUTEX 0 SEM 1 PERMISIUNI 100 CITITORI 100 SCRIITORI 10

int *buffer; void down(int semId, int semNr, int val) { struct sembuf op = {semNr, val<0?val:-val, 0}; semop(semId, &op, 1); } void up(int semId, int semNr, int val) { struct sembuf op = {semNr, val>0?val:-val, 0}; semop(semId, &op, 1); } void scriitor(int id, int semId) { int i; down(semId, SEM, PERMISIUNI); printf("SCRIITOR: %d\n", id); for (i=0; i<=N; i++) { buffer[i] = id * N + i; usleep(100000); } } up(semId, SEM, PERMISIUNI);

187

Sincronizarea prin semafoare n Linux


void cititor(int id, int semId) { int localBuf[N], i; down(semId, SEM, 1); printf("CITITOR: %d - GET\n", id); for (i=0; i<=N; i++) { localBuf[i] = buffer[i]; usleep(100000); } up(semId, SEM, 1); // scrierea pe ecran in regim de execludere mutuala down(semId, MUTEX, 1); // blocare lacat printf("CITITOR: %d - PRINT\n", id); for (i=0; i<=N; i++) printf("%d ", localBuf[i]); printf("\n"); } up(semId, MUTEX, 1); // eliberare lacat

int main(int argc, char **argv) { int semId, shmId, i, pid, msg; shmId = shmget(IPC_PRIVATE, N * sizeof(int), IPC_CREAT | 0600); if (shmId < 0) {perror("Eroare creare shm"); exit(2); } buffer = (int*) shmat(shmId, 0, 0); semId = semget(IPC_PRIVATE, 2, IPC_CREAT | 0600); if (semId < 0) { perror("Eroare creare sem"); exit(2); } // initialiyare semafoare semctl(semId, MUTEX, SETVAL, 1); semctl(semId, SEM, SETVAL, PERMISIUNI); // creare procese cititori si scriitori if ((pid = fork()) == 0){ // fiul creeaza scriitori for (i=0; i<SCRIITORI; i++) { if (fork() == 0) { scriitor(i, semId); exit(0); } if ((i % (SCRIITORI/3)) == 0) sleep(3);

188

Sisteme de operare. Chestiuni teoretice i practice


for (i=0; i<SCRIITORI; i++) wait(0);

} else { // parintele creeaza scriitori for (i=0; i<CITITORI; i++) { if (fork() == 0) { cititor(i, semId); exit(0); } if ((i % (CITITORI/5)) == 0) sleep(3);

for (i=0; i<CITITORI; i++) wait(0); waitpid(pid, 0); shmctl(shmId, IPC_RMID, 0); semctl(semId, IPC_RMID, 0);

12.7. Probleme
1. S se sincronizeze dou procese folosind semafoare, astfel nct ele s incrementeze strict alternativ de un anumit numr de ori (acelai pentru ambele procese) un contor aflat ntr-o zon de memorie partajat. 2. Pentru Exemplul 1 din cadrul lucrrii s se testeze ce se ntmpl dac unul dintre procese se termin n regiunea sa critic fr s elibereze semaforul. S se modifice modul de efectuare al operaiilor pe semafoare prin folosirea opiunii SEM_UNDO i s se verifice din nou funcionarea aplicaiei n contextul precizat. 3. S se modifice Exemplul 3 din cadrul lucrrii astfel nct s se asigure o prioritate de acces la irul partajat pentru urmtoarele tipuri de procese: a. cititori b. scriitori 4. S se scrie, folosind semafoare Linux, codul C al dou tipuri de procese, care joac rolul unor maini care circul n direcii opuse peste un pod. Presupunnd c podul este n lucru, se impune ca la un moment dat pe pod s poat fi maximum MAX maini, iar circulaia pe pod s se desfoare ntr-o singur direcie. Pentru sincronizarea circulaiei pe pod, se poate presupune existena a cte unui semafor la fiecare capt al podului, semafoare care nu pot indica simultan aceeai culoare (rou sau verde). Schimbarea simultan a culorilor semafoarelor poate fi fcut periodic sau
189

Sincronizarea prin semafoare n Linux

5.

6.

7.

8.

pe baza unor alte criterii, cu acest lucru putndu-se ocupa un alt proces. Trebuie s se in cont de faptul c n momentul schimbrii direciei de circulaie, pe pod pot fi n traversare maini, iar mainile din sensul opus trebuie s atepte eliberarea podului nainte de a putea intra pe pod. Se consider dou strzi care se intersecteaz. Mainile circul pe cele dou strzi ntr-un singur sens. n intersecie exist dou semafoare, cte unul pe fiecare strad, corespunztor direciei de circulare pe strada respectiv. S se scrie, folosind semafoare Linux, codul proceselor care joac rolul mainilor ce circul pe prima i respectiv, pe cea de-a doua strad. De asemenea, s se scrie codul procesului care controleaz semafoarele fizice din intersecie, schimbnd periodic culorile lor. S se scrie un program C, care creeaz n mod continuu procese care incrementeaz un contor aflat ntr-o zon de memorie partajat, ateapt o perioad de 1 secund i apoi se termin. Un alt proces citete periodic ntr-o bucl infinit valoarea contorului din zona de memorie partajat i o afieaz pe ecran. S se scrie codul celor dou tipuri de procese folosind semafoare, astfel nct procesul care citete valoarea contorului s fie prioritar n accesul la zona de memorie partajat fa de procesele care incrementeaz contorul. Se presupune c N filozofi doresc s serveasc cina ntr-o ncpere n care exist o mas cu N farfurii cu spagheti i N furculie. Pentru a putea mnca, un filozof are nevoie de dou furculie: a lui i cea a vecinului din stnga. Fiecare filozof are un numr de identificare unic i este reprezentat printr-un proces, care va apela ntr-o bucl infinit dou funcii: think(int idFilozof) i eat(int idFilozof). S se scrie codul unui proces filozof folosind semafoare, astfel nct servirea mesei s se desfoare fr probleme. Situaiile care trebuie evitate sunt cele de interblocare i de ateptare la infinit a unui filozof pentru a intra la mas. Se presupune c ntr-o frizerie exist M frizeri i N scaune de ateptare. Prin urmare, M clieni pot fi servii la un moment dat i ali maximum N clieni pot atepta n frizerie. Restul trebuie s atepte afar din frizerie. Rolul frizerilor i al clienilor este jucat de procese, cte un proces pentru fiecare persoan. S se scrie codul proceselor frizer i client astfel nct s fie respectate regulile de mai sus i frizerii s nu stea n cazul n care sunt clieni care ateapt. De asemenea, trebuie evitat situaia ca un client care ateapt afar din frizerie s fie servit naintea unuia care ateapt n frizerie. Se poate lua eventual n considerare i situaia respectrii ordinii de sosire n frizerie.

190

Sisteme de operare. Chestiuni teoretice i practice

9. Un proces genereaz aleator dou tipuri de procese: productor i consumator. Un productor trebuie s transmit un mesaj (un numr ntreg) unui consumator. Se presupune c nu exist un loc (buffer) de stocare a mesajelor i, prin urmare, un productor trebuie s atepte sosirea unui consumator cruia s-i transmit mesajul n mod direct. Transmiterea mesajului se va face printr-o coad de mesaje pe care o creeaz fiecare consumator i a crei cheie o cunoate i productorul. Evident, i un consumator trebuie s atepte sosirea unui productor pentru a avea ce mesaj s preia. Se cere s se scrie codul corespunztor celor dou tipuri de procese, execuia lor sincronizndu-se prin semafoare. 10. Se presupune c ntr-o sal de ateptare, pentru a se face economie de curent pe timpul nopii, s-a instalat un senzor optic, astfel nct lumina s fie aprins doar atta timp ct exist persoane n acea sal. Se cere s se implementeze, folosind pentru sincronizare semafoarele Linux, codul C al urmtoarelor tipuri de procese: a. persoana, care joac rolul unei persoane care dorete s intre n sal, iar dup ce o face, st un timp acolo, apoi iese; b. controler, care controleaz senzorul optic i care ntr-o bucl infinit detecteaz apariia primei persoane ce intr n sal, moment n care comand aprinderea luminii, respectiv detecteaz ieirea ultimei persoane din sal, moment n care comand stingerea luminii; Se va ine cont de urmtoarele observaii: nu se va folosi tehnica busy waiting de verificare n mod continuu ntr-o bucl a unei condiii, ci se vor folosi operaiile pe semafoare pentru a pune un proces n ateptare; dup momentul n care sala devine goal i lumina se stinge, prima persoan care apare, poate s intre numai dup ce lumina s-a aprins. 11. S se implementeze funciile atomNa() i atomCl() necesare simulrii unui proces chimic virtual de formare a srii de buctrie (NaCl). Pentru simulare se vor crea un numr aleator de threaduri, unele avnd rol de atomi de Na, executnd funcia atomNa, iar celelalte avnd rol de atomi de Cl, executnd funcia atomCl. Se cere folosirea semafoarelor pentru a se realiza sincronizarea threadurilor, n scopul formrii moleculelor de sare din atomii de Na i Cl. Un thread oarecare trebuie s atepte doar pn n momentul n care este posibil formarea unei molecule. Se cere ca intrarea atomilor n reacie s se produc n ordinea n care ei au aprut. S se rezolve apoi problema similar pentru formarea apei H2O.
191

13. Sincronizarea thread-urilor n Linux


Scopul lucrrii Lucrarea prezint mecanismele de sincronizare ntre thread-uri puse la dispoziie n Linux prin implementarea specificaiei PTHREADS. Sunt descrise funciile care permit accesul la aceste mecanisme, precum i cteva modaliti de utilizare a lor.

13.1. Prezentare general


Mecanismele de sincronizare existente n Linux ca urmare a implementrii specificaiei PTHREADS pot fi utilizate pentru sincronizarea execuiei thread-urilor unui proces, n condiiile n care acestea acceseaz n mod concurent resurse comune. Avnd n vedere faptul c thread-urile unui proces folosesc n comun toate resursele alocate de sistemul de operare acelui proces, sincronizarea este o problem implicit folosirii thread-urilor multiple. Aadar, mecanismele de sincronizare sunt inerente utilizrii thread-urilor. Mecanismele de sincronizare prezentate n cadrul acestei lucrri sunt lactele, variabilele condiionale i mecanismul care asigur execuia o singur dat a unor funcii. Fiecare proces i poate crea, sub forma unor variabile globale, propriile mecanisme de sincronizare de tipul celor precizate mai sus. Fiind declarate ca variabile globale, aceste mecanisme de sincronizare sunt vizibile tuturor thread-urilor acelui proces, ns ele nu sunt accesibile thread-urilor unui alt proces i, prin urmare, nu pot fi folosite pentru sincronizarea execuiei proceselor. Lactele sunt folosite pentru asigurarea accesului n regim de excludere mutual a unor thread-uri la o anumit resurs. Variabilele condiionale reprezint un mecanism specializat de ateptare a unui eveniment n cadrul unei regiuni de excludere mutual. Mecanismul de execuie o singur dat a unor funcii este folosit n cazul operaiilor de iniializare, care trebuie efectuate o singur dat. Prezentm n cele ce urmeaz funciile de creare i de folosire a acestor mecanisme de sincronizare.

192

Sisteme de operare. Chestiuni teoretice i practice

13.2. Lacte
Variabilele de tip lact din specificaia PTHREADS sunt de tipul phtread_mutex_t. Ele sunt folosite pentru asigurarea accesului n regim de excludere mutual a unei resurse. nainte de a putea fi utilizat, un lact trebuie iniializat, fie n mod implicit, fie n mod explicit. Iniializarea i tergerea lactului n momentul iniializrii unui lact, se stabilesc valorile atributelor care descriu modul de funcionare al lactului respectiv. Accesul la atributele unui thread se poate face prin intermediul unei structuri de tipul pthread_mutex_attr_t. Valorile atributelor unui thread pot fi setate automat la valorile lor implicite sau pot primi valori explicite specificate de ctre utilizator. Iniializarea implicit a unui lact se face utiliznd constanta predefinit PTHREAD_MUTEX_INITIALIZER. Efectul acestui tip de iniializare este crearea unui lact cu valorile implicite ale atributelor sale. Acest tip de iniializare se face n urmtorul mod:
#include <pthread.h> pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

Iniializarea explicit necesit folosirea funciei pthread_mutex_init. Sintaxa funciei este urmtoarea:
#include <pthread.h> int pthread_mutex_init ( pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

Primul parametru este setat de ctre sistem i joac rol de identificator al lactului. Cel de-al doilea parametru reprezint adresa unei structuri care conine atributele lactului i care trebuie alocat de ctre utilizator anterior apelului. Modul de creare i iniializare a unei astfel de structuri este descris puin mai jos. Dac valoarea celui de-al doilea parametru este NULL, lactul va fi creat cu valori implicite ale atributelor sale. n momentul n care nu mai este nevoie de un lact el poate fi ters cu ajutorul funciei pthread_mutex_destroy, a crei sintax este urmtoarea:
193

Sincronizarea thread-urilor n Linux


#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex);

Atributele lactului Crearea unei structuri care s conin atributele unui lact se face prin declararea unei variabile de tipul pthread_mutexattr_t. nainte de a putea fi folosit, ea trebuie ns iniializat, lucru care se face cu ajutorul funciei pthread_mutexattr_init, care are urmtoarea sintax:
#include <pthread.h> int pthread_mutexattr_init(pthread_mutexattr_t *attr);

Efectul apelului acestei funcii este iniializarea valorilor atributelor din cadrul structurii de la adresa attr la valorile implicite. Este important de observat faptul c funcia pthread_mutexattr_init nu aloc memorie pentru structura de atribute, ci doar iniializeaz valorile atributelor coninute de acea structur. Distrugerea structurii de atribute ale unui lact se face cu urmtoarea funcie:
#include <pthread.h> int pthread_mutexattr_destroy ( pthread_mutexattr_t *attr);

n implementarea din Linux a specificaiei PTHREADS, exist posibilitatea stabilirii valorii unui singur atribut al unui lact. Acest atribut descrie efectul apelrii repetate a primitivei de blocare a lactului de ctre thread-ul care a blocat deja lactul. Funciile de setare, respectiv obinere a valorii acestui atribut sunt:
#include <pthread.h> int pthread_mutexattr_settype ( pthread_mutexattr_t *attr, int kind); int pthread_mutexattr_gettype ( pthread_mutexattr_t *attr, int *kind);

Valorile posibile ale atributului de tip sunt:


PTHREAD_MUTEX_FAST_NP

Dac thread-ul care a blocat anterior lactul apeleaz funcia de blocare din nou, atunci el este suspendat n mod definitiv.
194

Sisteme de operare. Chestiuni teoretice i practice


PTHREAD_MUTEX_RECURSIVE_NP

Thread-ul care a blocat anterior lactul nu este suspendat n momentul apelrii repetate a funciei de blocare, dar pentru eliberarea lactului, funcia de deblocare trebuie apelat de acelai numr de ori ca i cea de blocare.
PTHREAD_MUTEX_ERRORCHECK_NP

n momentul apelrii funciei de blocare a lactului se verific dac thread-ul care face acest lucru este chiar cel care a blocat anterior lactul i, n caz afirmativ, funcia ntoarce ca rezultat o eroare. Codul de eroare este EDEADLK. Terminaia _NP (Non-Portable) a constantelor de mai sus, indic faptul c acest atribut este specific numai implementrii sub Linux a standardului PTHREADS, fiind posibil ca el s nu fie regsit n alte implementri. Prin urmare, nu este recomandat utilizarea lui n aplicaii portabile. Valoarea implicit a atributului care determin tipul unui thread este PTHREAD_MUTEX_FAST_NP. Stabilirea tipului unui lact se poate face i n momentul declarrii sale prin atribuirea unor valori predefinite, n felul urmtor:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; pthread_mutex_t mutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

Blocarea lactului Operaia prin care se realizeaz accesul n regim de excludere mutual la o resurs partajat utiliznd un lact se numete operaia de blocare a lactului. Aceast operaie se termin cu succes doar pentru un singur thread la un moment dat. Celelalte thread-uri sunt puse n stare de ateptare ntr-o coad asociat lactului, pn n momentul eliberrii lui de ctre thread-ul care l-a blocat. Funcia de blocare a unui lact se numete pthread_mutex_lock i are urmtoarea sintax:
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex);

195

Sincronizarea thread-urilor n Linux

O alt funcie ce poate fi utilizat pentru a ncerca blocarea unui lact este pthread_mutex_trylock. Comportamentul acestei funcii este similar cu cel al funciei pthread_mutex_lock, cu diferena c nu are ca efect suspendarea thread-ului care o apeleaz dac lactul nu poate fi blocat, ci se revine din funcie imediat, dar cu o valoare de eroare. Sintaxa funciei este urmtoarea:
#include <pthread.h> int pthread_mutex_trylock(pthread_mutex_t *mutex);

Utilizarea funciei pthread_mutex_trylock este aparent trivial i poate prea chiar util, ns lucrurile nu stau chiar aa. n primul rnd funcia reprezint o abatere de la regula general de sincronizare, care presupune blocarea thread-urilor n momentul n care nu au acces la resursa partajat. Dac acel thread poate face altceva n timp ce resursa este ocupat, aceasta nseamn c el nu are cu adevrat nevoie de acea resurs. Se pune atunci ntrebarea de ce nu s-a creat un alt thread care s se ocupe de ceea ce thread-ul n discuie poate face n timp ce resursa e ocupat, noul thread nefiind astfel implicat n protocolul de sincronizare. Pe de alt parte, situaia folosirii funciei pthread_mutex_trylock este aceea n care thread-ul verific periodic ndeplinirea anumitor condiii (tehnica de spooling). S-ar putea ns, ca n cazul n care lactul este intens solicitat i de ctre alte thread-uri, s se ajung la situaia ca thread-ul care apeleaz pthread_mutex_trylock s nu reueasc practic niciodat s blocheze lactul. n plus, verificarea periodic are ca rezultat ncrcarea procesorului. Situaiile n care folosirea funciei pthread_mutex_trylock este util sunt cele de programare n timp-real, cnd thread-ul trebuie s poat reaciona rapid la apariia anumitor evenimente i situaiile n care se ncearc detectarea i evitarea interblocrilor prin folosirea de lacte organizate sub forma unei ierarhii. Eliberarea lactului Eliberarea lactului este operaia opus celei de blocare. Aceste dou operaii sunt ntotdeauna folosite n pereche pentru a asigura faptul c unele thread-uri nu vor atepta la nesfrit pentru un anumit lact. Operaia de eliberare se realizeaz prin apelul funciei pthread_mutex_unlock, cu urmtoarea sintax:
#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);

196

Sisteme de operare. Chestiuni teoretice i practice

Exemplu de utilizare Secvena de cod urmtoare ilustreaz modul de creare i respectiv, de utilizare a lactelor pentru asigurarea accesului n regim de excludere mutual la o variabil.
/* Functia de creare a unui lacat */ void createMutex(pthread_mutex_t *mutex) { pthread_mutex_init(mutex, NULL); } /* Functia de modificare a unei variabile */ /* Accesul la variabila e in excludere mutuala */ void increment(pthread *mutex, int *variable) { pthread_mutex_lock(mutex); // blocarea lacatului (*variable)++; } // acces exclusiv pthread_mutex_unlock(mutex); // deblocarea lacatului

/* Functia de distrugere a unui lacat */ void destroyMutex(pthread_mutex_t *mutex) { pthread_mutex_destroy(mutex); }

13.3. Variabile condiionale


Variabila condiional este un mecanism care poate fi utilizat de thread-uri pentru a atepta ndeplinirea unei condiii, condiie care caracterizeaz starea resurselor partajate. n cazul n care respectiva condiie nu este realizat, thread-ul este pus ntr-o coad de ateptare asociat variabilei prin apelul unei funcii speciale, coad din care poate va fi ulterior trezit de ctre un alt thread (prin apelul altei funcii speciale), n momentul n care acesta din urm constat ndeplinirea condiiei. Deoarece condiiile pentru care se ateapt utiliznd variabile condiionale sunt dependente de valorile unor variabile care descriu starea resursele partajate, variabile care la rndul lor sunt partajate i accesate n mod concurent, testarea condiiilor trebuie s se fac n regim de excludere mutual. Acesta este motivul pentru care variabilele condiionale sunt ntotdeauna utilizate n combinaie cu un lact. Se va observa, n acest sens, n cele de mai jos c funciile cu ajutorul crora sunt folosite variabilele condiionale sunt apelate ntotdeauna doar n cadrul unei zone de excludere mutual.

197

Sincronizarea thread-urilor n Linux

Variabilele condiionale sunt de tipul pthread_cond_t i trebuie iniializate nainte de a fi folosite. Similar cu lactele, iniializarea lor poate fi implicit sau explicit. Iniializarea i tergerea variabilelor condiionale Iniializarea implicit presupune declararea variabilei i atribuirea constantei predefinite PTHREAD_COND_INITIALIZER, sub forma urmtoare:
#include <pthread.h> pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

Iniializarea explicit se face cu ajutorul funciei pthread_cond_init, cu urmtoarea sintax:


#include <pthread.h> int pthread_cond_init ( pthread_cond_t *cond, const pthread_condattr_t *attr);

Parametrul cond este setat de ctre sistem i reprezint identificatorul variabilei condiionale, iar parametrul attr reprezint adresa unei structuri care conine atributele variabilei condiionale. Dac se dorete tergerea unei variabile condiionale, acest lucru se poate face cu ajutorul funciei de mai jos:
#include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond);

Atributele variabilelor condiionale O structur care conine atributele unei variabile condiionale este de tipul pthread_condattr_t. Funcia de iniializare a unei astfel de structuri este:
#include <pthread.h> int pthread_condattr_init(pthread_condattr_t *attr);

Distrugerea unei structuri de atribute ale unei variabile condiionale se face cu funcia urmtoare:

198

Sisteme de operare. Chestiuni teoretice i practice


#include <pthread.h> int pthread_condattr_destroy ( pthread_condattr_t *attr);

Implementarea din Linux a specificaiei PTHREADS nu ofer posibilitatea stabilirii valorii nici unui atribut a variabilelor condiionale. Funcia de ateptare Intrarea unui thread n starea de ateptare a ndeplinirii unei anumite condiii se face cu ajutorul uneia dintre funciile pthread_cond_wait i pthread_cond_timedwait. Thread-ul va fi scos din starea de ateptare prin apelul funciei pthread_cond_signal de ctre un alt thread, care constat ndeplinirea condiiei dup care se ateapt. Cele dou funcii de ateptare au urmtoarea sintax:
#include <pthread.h> int pthread_cond_wait ( pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_timedwait ( pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

n ambele cazuri thread-ul este blocat i pus ntr-o coad de ateptare. Diferena ntre cele dou funcii este aceea c prima duce la blocarea threadului pe o durat nelimitat, pn la apelul funciei care indic ndeplinirea condiiei, pe cnd cea de-a doua funcie precizeaz un interval de timp dup care thread-ul este automat scos din coada de ateptare i i reia execuia, chiar dac condiia nu este nc ndeplinit. Se observ c ambele funcii au ca parametru un lact. Aceasta se datoreaz faptului c ntotdeauna variabilele condiionale sunt folosite, aa cum am precizat, n cadrul unei regiuni de cod protejate de un lact. n momentul n care se intr n ateptare este necesar ca lactul s fie eliberat, pentru ca un alt thread s poat intra n propria sa regiune critic, s modifice starea resurselor partajate, s verifice realizarea condiiei i s semnalizeze acest lucru threadurilor blocate. Acesta este motivul transmiterii adresei lactului blocat anterior de ctre thread n momentul apelului uneia dintre funciile de ateptare.

199

Sincronizarea thread-urilor n Linux

Funcia de semnalizare sau trezire Exist dou astfel de primitive, prima funcia pthread_cond_signal, pentru scoaterea unui singur thread din lista celor care ateapt, cea de-a doua funcia pthread_cond_broadcast, pentru scoaterea tuturor thread-urilor din coada de ateptare a variabilei condiionale. Sintaxa acestor funcii este urmtoarea:
#include <pthread.h> int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);

Exemplu de utilizare Codul de mai jos reprezint implementarea funciilor specifice problemei cunoscut sub numele de productor-consumator. Scenariul problemei presupune c mai multe thread-uri comunic prin intermediul unei zone de memorie de tip buffer circular de mesaje. Unele thread-uri, numite productori, adaug mesaje n buffer, iar alte thread-uri, numite consumatori, preiau mesajele din buffer, n ordinea n care acestea au fost adugate. Pentru evitarea situaiilor de aducere a buffer-ului ntr-o stare inconsistent sau de obinere a unor rezultate eronate, se permite accesul la buffer doar n regim de excludere mutual, att a thread-urilor productor, ct i a celor consumator. Pentru rezolvarea eficient a situaiilor n care thread-urile productor trebuie s atepte eliberarea de spaiu n cadrul buffer-ului plin i a celor n care thread-urile consumator trebuie s atepte adugarea de noi mesaje n buffer-ul gol, se folosesc variabile condiionale.
#define DIMBUFFER 100 int int int int buffer[DIMBUFFER]; msgNo = 0; indexProd = 0; indexCons = 0; // nr. de mesaje nepreluate // index de adaugare mesaj // index de preluare mesaj

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t empty = PTHREAD_COND_INITIALIZER; pthread_cond_t full = PTHREAD_COND_INITIALIZER; /* Functia apelata de producatori */ /* Acces in excludere mutuala */ void produce(int item) { pthread_mutex_lock(&mutex); // blocare lacat while (msgNo == DIMBUFFER) // test bufer plin pthread_cond_wait(&full, &mutex);

200

Sisteme de operare. Chestiuni teoretice i practice


buffer[indexProd] = item; // adaugare mesaj

// reactualizare index producatori indexProd = (indexProd + 1) % DIMBUFFER; // incrementare nr. mesaje nepreluate msgNo++; // trezirea unui consumator pthread_cond_signal(&empty); } pthread_mutex_unlock(&mutex); // debloare lacat

/* Functia apelata de consumatori */ /* Acces in excludere mutuala */ void consumer(int *item) { pthread_mutex_lock(&mutex); // blocare lacat while (msgNo == 0) // test bufer gol pthread_cond_wait(&empty, &mutex); *item = buffer[indexCons]; // preluare mesaj

// reactualizare index consumatori indexCons = (indexCons + 1) % DIMBUFFER; // decrementare nr. mesaje nepreluate msgNo--; // trezirea unui producator pthread_cond_signal(&full); } pthread_mutex_unlock(&mutex); // debloare lacat

13.4. Execuia unei funcii o singur dat


De multe ori este nevoie, n aplicaiile care presupun existena mai multor thread-uri, de funcii care sunt apelate n faza de iniializare. O astfel de funcie poate, spre exemplu, s deschid un fiier sau s iniializeze o variabil sau un lact. Aceste proceduri ar trebui n mod normal executate o singur dat. n situaia n care toate thread-urile sunt identice din punct de vedere funcional, apelul unei astfel de funcii de iniializare apare n toate thread-urile, ns ea ar trebui executat doar o singur dat. Standardul PTHREADS specific un mecanism util n situaii ca cea descris mai sus, prin care utilizatorul poate fi sigur c o anumit funcie va fi
201

Sincronizarea thread-urilor n Linux

executat doar o singur dat, indiferent de numrul de apeluri ale ei din diferite thread-uri. Vom numi n cele ce urmeaz acest mecanism pthread_once. Mecanismul pthread_once este un mecanism de sincronizare ntre thread-uri n faza de iniializare a acestora. n situaia n care apelm o anumit funcie doar prin mecanismul pthread_once, putem face urmtoarele presupuneri: 1. Indiferent de numrul de apeluri ale funciei, dintr-unul sau mai multe thread-uri, ea va fi executat doar o singur dat de ctre primul thread care o apeleaz. 2. Nici un thread care apeleaz funcia prin mecanismul pthread_once nu poate trece de ea (nu se revine din funcie) pn cnd primul apel al ei nu se termin cu succes. Acest lucru ne asigur c thread-urile nu pot trece de punctul de iniializare, pn cnd iniializarea nu este finalizat. Pentru a putea apela o funcie prin intermediul mecanismului pthread_once, trebuie mai nti declarat o variabil de tip pthread_once_t, care trebuie apoi iniializat cu valoarea PTHREAD_ONCE_INIT.
#include <pthread.h> pthread_once_t once_block = PTHREAD_ONCE_INIT;

Acestei variabile i se poate asocia o singur funcie, ale crei apeluri se vor face doar prin intermediul mecanismului pthread_once. Apelul funciei asociate variabilei se va face cu ajutorul funciei pthread_once, cu urmtoarea sintax:
#include <pthread.h> int pthread_once ( pthread_once_t *once_block, void (*init_routine) (void));

Primul parametru reprezint variabila de tip pthread_once_t, iar cel deal doilea este funcia asociat ei. De reinut c o funcie apelat prin mecanismul pthread_once, nu ar trebui s mai fie apelat de nicieri dect prin mecanismul respectiv, deoarece n caz contrar nu se mai poate respecta principiul de sincronizare impus.

202

Sisteme de operare. Chestiuni teoretice i practice

13.5. Probleme
1. S se scrie programele C care s testeze efectul urmtoarelor operaii: a. blocarea unui lact neiniializat; b. blocarea unui lact de ctre un thread care deja a blocat lactul; c. eliberarea de ctre un thread a unui lact blocat de un alt thread. S se identifice eventualele tipuri de erori care rezult n cazul folosirii fiecrui tip dintre cele trei tipuri de lact prezentate. 2. S se implementeze problema productor-consumator cu buffer circular folosind funciile descrise mai sus. Se va crea un numr aleator de thread-uri de tip productor i consumator i fiecare va efectua un numr aleator de pai. S se afieze, pe parcursul execuiei aplicaiei numrul de thread-uri productor i consumator care sunt n ateptare. 3. S se implementeze un protocol de traversare a unui pod aflat n reparaii, pe care circulaia mainilor se poate face la un moment dat doar ntr-un singur sens. Se presupune c podul suport greutatea a maximum N maini. n momentul sosirii la pod, un thread asociat unei maini va executa urmtoarea funcie:
void CirculaPePod(int dir) { IntraPePod(dir); TraverseazaPod(dir); IeseDePePod(dir); }

4. 5.

6. 7.

Se cere implementarea funciilor de mai sus, astfel nct circulaia s se desfoare n siguran. S se testeze comportamentul unei variabile de tipul pthread_once_t n cazul asocierii ei cu dou sau mai multe funcii diferite. S se scrie un program C prin care se creeaz mai multe thread-uri, fiecare thread iniializnd variabila global var cu valoarea iniial 1 i incrementnd-o apoi cu 1. Thread-ul principal va afia valoarea final a variabilei. S se testeze efectul execuiei programului att n cazul folosirii mecanismului pthread_once pentru iniializarea variabilei, ct i n cazul nefolosirii lui. S se implementeze, folosind lacte, o clas prin care s se realizeze comportamentul mecanismului pthread_once. Problema filozofilor. Se presupune ca N filozofi doresc s serveasc cina ntr-o ncpere n care exist o mas rotund cu N farfurii cu spagheti i N furculie. Pentru a putea mnca, un filozof are nevoie de dou furculie: a lui i cea a vecinului din stnga. Fiecare filozof are
203

Sincronizarea thread-urilor n Linux

un numr de identificare unic i este reprezentat printr-un thread, care va apela ntr-o bucl infinit dou funcii: think(int idFilozof) i eat(int idFilozof). S se scrie programul C care genereaz threadurile corespunztoare filozofilor i codul celor dou funcii, astfel nct servirea mesei s se desfoare fr probleme. Situaiile care trebuie evitate sunt cele de inter-blocare i de ateptare la infinit a unui filozof pentru a intra la mas. 8. Se presupune c ntr-o frizerie exist un singur frizer i N scaune de ateptare. Un singur client poate fi servit la un moment dat i ali maximum N clieni pot atepta n frizerie. Restul clienilor trebuie s atepte afar din frizerie. Att frizerul, ct i clienii si sunt reprezentai cu ajutorul thread-urilor. S se scrie funciile care vor fi executate de cele dou tipuri de thread-uri, astfel nct s fie respectate regulile de mai sus i frizerul s nu stea, n cazul n care are clieni care ateapt. De asemenea trebuie evitat situaia ca un client care ateapt afar din frizerie s fie servit naintea unuia care ateapt n frizerie. Se poate lua eventual n considerare i situaia respectrii ordinii de sosire n frizerie. 9. Se presupune c la un moment dat, n cadrul unui proces, se execut concurent mai multe thread-uri, fiecare reprezentnd fie un atom de H, fie unul de O. S se scrie codul funciilor executate de cele dou tipuri de thread-uri astfel nct, ntotdeauna cnd e posibil, doi atomi de H i cu unul de O s se cupleze formnd o molecul de ap (H2O). n cazul n care formarea moleculei nu este posibil, atomii prezeni trebuie s atepte sosirea atomilor necesari. Fiecare atom va intra n componena unei singure molecule de ap. Presupunnd c fiecare atom are un identificator unic, s se scrie ntr-un fiier, o singur dat, componena fiecrei molecule de ap. 10. Pe malul unui ru se gsesc misionari i canibali. Ei doresc s traverseze rul avnd la dispoziie o barc. n barc ncap trei persoane i traversarea se face doar cu barca plin. Pentru a nu fi mncai n timpul traversrii, misionarii trebuie s fie n barc mai muli dect canibali. S se scrie codul unor funcii executate de threaduri care joac rol de misionari i canibali, astfel nct aceste threaduri s-i sincronizeze execuia n ncercarea lor de a traversa rul. De asemenea, se cere ca n cazul n care exist numrul de persoane suficient pentru a se face un transport, acestea s nu fie ntrziate.

204

14. Planificarea thread-urilor n Linux


Scopul lucrrii Lucrarea prezint funciile care pot fi utilizate de ctre utilizator pentru a stabili modul de planificare a unui thread n Linux, n contextul execuiei concurente a mai multor thread-uri.

14.1. Planificarea thread-urilor


Planificarea thread-urilor const n strategia folosit de ctre sistemul de operare pentru a decide la un moment dat care thread trebuie executat i pentru ct timp. Componenta sistemului de operare care realizeaz planificarea se numete planificatorul de thread-uri. Sistemul de operare Linux folosete un mecanism de planificare bazat pe prioriti. Fiecrui thread i este asociat o prioritate, fiind ales ntotdeauna pentru execuie thread-ul cu prioritatea cea mai mare. Corespunztor fiecrei prioriti, se menine o list de thread-uri care candideaz pentru obinerea procesorului. Rezultatul deciziei de planificare este controlat prin implementarea a trei strategii diferite de inserare a thread-urilor n listele de prioriti. Dintre cele trei strategii, numite politici de planificare, dou sunt destinate planificrii thread-urilor de prioritate ridicat, care aparin aplicaiilor de timp real, iar cea de-a treia este destinat planificrii threadurilor aplicaiilor obinuite, de prioritate mic. n cele ce urmeaz vom descrie cele trei politici de planificare i vom prezenta setul de funcii puse la dispoziie n Linux pentru stabilirea i modificarea parametrilor care influeneaz planificarea unui thread, mai exact prioritatea i politica de planificare.

14.2. Prioritatea i politica de planificare


Exist dou atribute ale unui thread care fac ca respectivul thread s fie tratat ntr-un mod special de ctre planificatorul de thread-uri. Aceste atribute sunt prioritatea i politica de planificare. Pe baza prioritii se face diferenierea ntre thread-uri n ceea ce privete planificarea lor pentru execuie. Politica de planificare este strategia care
205

Planificarea thread-urilor n Linux

definete modul n care thread-urile cu aceeai prioritate sunt executate pe procesoarele disponibile. n Linux exist posibilitatea stabilirii a trei politici de planificare, una pentru thread-urile aplicaiilor obinuite i celelalte dou pentru aplicaiile de timp real. Fiecrui thread i se asociaz o prioritate static, avnd valoarea cuprins ntre 0 i 99, valoare care poate fi modificat doar cu ajutorul unor anumite funcii. Corespunztor fiecrei prioriti posibile, planificatorul de thread-uri din Linux menine cte o list a thread-urilor active care au acea valoare a prioritii statice. Planificatorul va alege ntotdeauna pentru execuie un thread din lista corespunztoare celei mai mari prioriti pentru care exist thread-uri active. Politica de planificare determin pentru fiecare thread modul n care el este inserat i avanseaz n lista corespunztoare prioritii statice pe care o are asociat. Politica de planificare Acestui atribut al thread-ului i se poate atribui o anumit valoare ntreag, sub forma unor constante predefinite, corespunztor uneia dintre cele trei politici de planificare posibile. Numele constantelor, precum i caracteristicile fiecrei politici de planificare sunt:
SCHED_FIFO

Este o politic disponibil pentru thread-urile aplicaiilor de timp real i funcioneaz pe baza principiului primul sosit, primul servit FIFO. O dat ales un thread pentru execuie, acesta nu poate fi ntrerupt, dect dac devine activ un alt thread cu prioritatea mai mare dect a lui, dac execut o instruciune care l pune n stare de ateptare (de exemplu o instruciune de I/O) sau dac n mod voluntar cedeaz procesorul prin apelul funciei sched_yield. n primul caz, thread-ul este pus la nceputul listei ataate prioritii pe care o are, iar n celelalte dou la sfritul listei.
SCHED_RR

Funcioneaz similar cu politica SCHED_FIFO, dar unui thread avnd aceast politic de planificare i se poate aloca procesorul pentru execuie doar pe durata unei cuante de timp fixate. n momentul expirrii cuantei, thread-ul este ntrerupt i pus la sfritul listei ataate prioritii pe care o are thread-ul.
SCHED_OTHER

Reprezint politica de planificare a thread-urilor obinuite. Threadurile avnd asociat aceast politic de planificare pot avea doar
206

Sisteme de operare. Chestiuni teoretice i practice

valoarea 0 a prioritii statice, fiind pstrate ntr-o singur list, din care sunt alese pentru execuie pe baza principiului de time-sharing i prin calcularea dinamic a unor prioriti specifice doar threadurilor din cadrul listei respective. Valoarea implicit a parametrului ce descrie politica de planificare a unui thread este SCHED_OTHER. Toate politicile de planificare prezentate sunt preemtive, adic dac la un moment dat devine activ (e inserat ntr-una din listele meninute de planificator) un thread mai prioritar dect cel curent, acesta din urm este ntrerupt i procesorul este alocat thread-ului cu prioritatea mai mare. Prioritatea de planificare Valoarea pe care o poate primi prioritatea static a unui thread depinde de politica de planificare stabilit pentru acel thread. Acest atribut este vzut ca un parametru al politicii de planificare. Intervalul n care se situeaz prioritatea unui thread este definit de o valoare minim, respectiv maxim. n general, aceste limite sunt 1 i, respectiv 99, ns valoarea lor efectiv pentru o anumit politic de planificare se poate obine cu ajutorul funciilor de mai jos:
#include <sched.h> int sched_get_priority_max(int policy); int sched_get_priority_min(int policy);

Thread-urile programate pentru o planificare cu politica SCHED_OTHER pot avea doar prioritatea static 0, fiind considerate thread-uri de prioritatea cea mai mic. Pentru celelalte dou politici de planificare, prioritile pot fi cuprinse ntre 1 i 99. n Linux, setarea pentru un thread a unei prioriti mai mari dect 0 i prin urmare a unei politici de planificare alta dect SCHED_OTHER este posibil doar pentru thread-urile cu privilegii de administrator. Funcii de stabilire a prioritii i politicii de planificare Exist dou modaliti de stabilire a politicii de planificare i respectiv, a prioritii unui thread. Prima modalitate este folosit n procesul de creare a thread-ului i presupune folosirea unei structuri de atribute de tipul

207

Planificarea thread-urilor n Linux pthread_attr_t, n cadrul creia trebuie stabilite, anterior momentului

crerii thread-ului, valorile dorite ale celor dou atribute. Cea de-a doua modalitate permite schimbarea valorilor atributelor thread-ului n mod dinamic, de ctre el nsui, pe durata execuiei sale. Pentru ambele modaliti sunt puse la dispoziie funcii specifice, care vor fi prezentate n cele ce urmeaz. n cazul primei modaliti de lucru, funciile care pot fi utilizate pentru stabilirea, respectiv obinerea valorii curente a politicii de planificare i a prioritii unui thread sunt cele prezentate mai jos, cu urmtoarea sintax:
#include <pthread.h> #include <sched.h> int pthread_attr_setschedpolicy( pthread_attr_t *attr, int policy); int pthread_attr_getschedpolicy( pthread_attr_t *attr, int *policy); int pthread_attr_setschedparam( pthread_attr_t *attr, const struct sched_param *param); int pthread_attr_getschedparam( pthread_attr_t *attr, struct sched_param *param);

Semnificaia parametrilor funciei este urmtoarea:


attr

Este adresa structurii de atribute ale unui thread, structur care trebuie iniializat anterior prin apelul funciei pthread_attr_init. Structura va fi apoi transmis funciei pthread_create, avnd ca efect crearea unui thread cu valorile atributelor stabilite n cadrul respectivei structuri de atribute. Reprezint valoarea care se stabilete pentru politica de planificare, n cazul funciei pthread_attr_setschedpolicy, respectiv adresa variabilei n care se obine valoarea curent a politicii de planificare, n cazul funciei pthread_attr_getschedpolicy. Valoarea acestui parametru poate fi una dintre constantele amintite mai sus: SCHED_OTHER, SCHED_FIFO, SCHED_RR.

policy

208

Sisteme de operare. Chestiuni teoretice i practice


param

Este o structur prin care se specific (funcia pthread_attr_setschedparam) sau n care se obin (funcia pthread_attr_getschedparam) parametrii politicii de planificare. n forma actual, structura conine un singur cmp, care este prioritatea thread-ului, aa cum este ilustrat i mai jos:
struct sched_param{ int sched_priority; };

Setarea dinamic a celor dou atribute legate de planificarea thread-urilor se poate face cu ajutorul funciei pthread_setschedparam, iar obinerea valorilor lor cu funcia pthread_getschedparam, cu urmtoarea sintax:
#include <pthread.h> int pthread_setschedparam( pthread_t th, int policy, const struct sched_param *param); int pthread_getschedparam( pthread_t th, int *policy, struct sched_param *param);

n urma apelului funciei pthread_setschedparam, thread-ul pentru care se stabilesc noile valori ale parametrilor de planificare este mutat la nceputul listei asociate noii prioriti a thread-ului i el poate ntrerupe thread-ul curent, n caz c are prioritatea mai mare dect acesta. Thread-ul curent poate ceda la un moment dat procesorul prin apelul funciei pthread_yield, caz n care el este pus la sfritul listei corespunztoare prioritii statice a thread-ului i thread-ul din capul listei respective va fi ales pentru execuie. n cazul n care thread-ul care apeleaz funcia sched_yield este singurul cu acea prioritate, el i va continua execuia. Sintaxa funciei sched_yield este urmtoarea:
#include <sched.h> int sched_yield();

Avnd n vedere c implementarea specificaiei PTHREADS sub Linux se bazeaz pe utilizarea proceselor (lightweight processes), considerm util precizarea ctorva funcii referitoare la planificarea proceselor, caz n care chestiunile descrise mai sus rmn n totalitate valabile, dar raportate la procese. Funciile respective i sintaxa lor sunt descrise mai jos:
209

Planificarea thread-urilor n Linux


#include <sched.h> int sched_setscheduler( pid_t pid, int policy, const struct sched_param *p); int sched_getscheduler(pid_t pid); int sched_setparam( pid_t pid, const struct sched_param *p); int sched_getparam( pid_t pid, struct sched_param *p);

n cazul politicii de planificare SCHED_OTHER, calculul prioritii dinamice a thread-urilor din aceast categorie poate fi influenat prin stabilirea unei prioriti de baz a thread-ului. Stabilirea unei astfel de prioriti i obinerea valorii ei curente se poate face cu ajutorul urmtoarelor funcii:
#include <sys/time.h> #include <sys/resource.h> int getpriority(int which, int who); int setpriority(int which, int who, int prio);

Semnificaia i valorile parametrilor funciilor este:


which Indic pentru cine se dorete setarea sau obinerea valorii prioritii

de baz. Valorile posibile sunt PRIO_PROCESS, PRIO_PGRP sau PRIO_USER pentru cazul unui proces, grup de procese i respectiv, utilizator. dinamice
who

Este interpretat n funcie de valoarea parametrului which i poate fi identificatorul unui proces, al unui grup de procese sau al unui utilizator. Valoarea 0 indic procesul din care se apeleaz funcia. Este valoarea prioritii dinamice de baz i poate avea o valoarea cuprins ntre 20 i +20. Valorile mai mici indic o prioritate mai mare. Stabilirea unei valori mai mici dect cea curent (adic, creterea prioritii) poate fi fcut doar de ctre administratorul de sistem. Valoarea implicit este 0. Funcia getpriority returneaz o valoare cuprins ntre 1 i 40 (reprezentnd rezultatul expresiei 20 - prio), deoarece valorile negative sunt rezervate de obicei cazurilor de eroare.

prio

210

Sisteme de operare. Chestiuni teoretice i practice

Modificarea valorii prioritii dinamice de baz poate fi fcut cu ajutorul funciei nice descris mai jos.
#include <unistd.h> int nice(int inc);

Funcia are ca efect adunarea valorii inc la valoarea curent a prioritii dinamice de baz a procesului. Valori negative, avnd ca efect creterea prioritii procesului (thread-ului) pot fi specificate doar de ctre administratorul de sistem, ceea ce nseamn c un proces obinuit nu poate fi dect politicos (nice) n sensul scderii propriei prioriti n favoarea altor procese. Prin urmare, funcia nu prezint un grad mare de utilitate.

14.3. Domeniul de planificare i domeniul de alocare


Domeniul de planificare a thread-urilor determin mulimea thread-urilor care concureaz la un moment dat pentru obinerea unui procesor din cele disponibile. Se definesc dou posibiliti de specificare a unui asemenea domeniu, i anume: domeniu de proces: cnd un thread concureaz pentru obinerea unui procesor doar cu thread-uri aparinnd aceluiai proces; domeniu sistem: cnd thread-urile tuturor proceselor din sistem sunt luate n considerarea n momentul alocrii unui procesor unui thread. O alt problem care se pune n cazul sistemelor multiprocesor este aceea a determinrii setului de procesoare pe care un thread poate fi executat. Acest set se numete domeniu de alocare. n cazul cel mai simplu, toate procesoarele din sistem pot fi incluse n acelai domeniu de alocare, avnd ca efect executarea tuturor thread-urilor din sistem pe oricare dintre ele. n cazurile mai speciale, din motive de eficien sau atunci cnd anumite threaduri necesit un regim preferenial, se pot defini mai multe domenii de alocare, fiecare domeniu fiind destinat execuiei unui anumit grup de thread-uri. Dezavantajul utilizrii unui domeniu de proces pentru planificarea threadurilor este acela c la un moment dat thread-uri ale unui proces pot s atepte dup eliberarea unui procesor, chiar n situaiile n care thread-uri cu prioritate mai mic ale altor procese sunt n execuie pe unele procesoare. Aceasta deoarece dintre thread-urile respectivului proces doar unul este ales la un moment dat pentru execuie. n cazul domeniului sistem, thread-urile aceluiai proces pot fi simultan n execuie, att datorit disponibilitii unor
211

Planificarea thread-urilor n Linux

procesoare, ct i datorit faptului c au prioritate mai mare fa de threadurile altor procese. Funciile de mai jos pot fi folosite pentru stabilirea i obinerea valorii atributului care determin pentru un thread domeniul de planificare.
#include <pthread.h> int pthread_attr_setscope( pthread_attr_t *attr, int scope); int pthread_attr_getscope( pthread_attr_t *attr, int *scope);

Valorile permise pentru atributul scope sunt: PTHREAD_SCOPE_SYSTEM i PTHREAD_SCOPE_PROCESS.

14.4. Proprietatea de motenire


Exist un atribut care ofer posibilitatea ca un thread s moteneasc atributele de planificare ale thread-ului care l-a creat. Funcia prin care se seteaz acest atribut se numete pthread_attr_setinheritsched, iar cea prin care se obine el este pthread_attr_getinheritsched, cu urmtoarea sintax:
#include <pthread.h> int pthread_attr_setinheritsched( pthread_attr_t *attr, int inherit); int pthread_attr_getinheritsched( pthread_attr_t *attr, int *inherit);

Parametrul inherit poate lua urmtoarele dou valori predefinite:


PTHREAD_EXPLICIT_SCHED

Valorile atributelor care identific politica i prioritatea de planificare nu se motenesc, ci trebuie specificate explicit. Evident, dac nu sunt specificate, ele iau valorile implicite setate de ctre sistem, dar nu le motenesc pe cele ale thread-ului creator.
PTHREAD_INHERIT_SCHED

Politica i prioritatea de planificare se motenesc de la thread-ul creator.

212

Sisteme de operare. Chestiuni teoretice i practice

14.5. Exemplu de utilizare


Codul de mai jos ofer un model de utilizare i testare a principalelor funcii descrise mai sus pentru stabilirea politicii de planificare i a prioritii unui thread.
#include <sched.h> #include <stdlib.h> void* fcTh(void* arg) { int i, policy; struct sched_param schdPar; int id = *(int*)arg; sleep(2); for (i=1; i<10000; i++) { pthread_getschedparam(pthread_self(), &policy, &schdPar); printf("Thread %d has priority %d\n", id, schdPar.sched_priority); printf("Thread %d has policy: ", id); switch (policy){ case SCHED_OTHER: printf("SCHED_OTHER\n"); break; case SCHED_FIFO: printf("SCHED_FIFO\n"); break; case SCHED_RR: printf("SCHED_RR\n"); break; } // end switch } // end for } // end fcTh() main() { pthread_t th1, th2, th3; pthread_attr_t attr1, attr2, attr3; struct sched_param schdPar1, schdPar2, schdPar3; int id1, id2, id3; printf("The SCHED_FIFO min priority is: %d\n", sched_get_priority_min(SCHED_FIFO));

213

Planificarea thread-urilor n Linux


printf("The SCHED_FIFO max priority is: %d\n", sched_get_priority_max(SCHED_FIFO)); printf("The SCHED_RR min priority is: %d\n", sched_get_priority_min(SCHED_RR)); printf("The SCHED_RR max priority is: %d\n", sched_get_priority_max(SCHED_RR)); printf("The SCHED_OTHER min priority is: %d\n", sched_get_priority_min(SCHED_OTHER)); printf("The SCHED_OTHER max priority is: %d\n", sched_get_priority_max(SCHED_OTHER)); id1 = 1; pthread_attr_init(&attr1); pthread_attr_setschedpolicy(&attr1, SCHED_RR); schdPar1.sched_priority = 10; pthread_attr_setschedparam(&attr1, &schdPar1); pthread_create(&th1, &attr1, fcTh, &id1); id2 = 2; pthread_attr_init(&attr2); pthread_attr_setschedpolicy(&attr2, SCHED_RR); schdPar2.sched_priority = 10; pthread_attr_setschedparam(&attr2, &schdPar2); pthread_create(&th2, &attr2, fcTh, &id2); id3 = 3; pthread_attr_init(&attr3); pthread_attr_setschedpolicy(&attr3, SCHED_RR); schdPar3.sched_priority = 12; pthread_attr_setschedparam(&attr3, &schdPar3); pthread_create(&th3, &attr3, fcTh, &id3); pthread_join(th1, NULL); pthread_join(th2, NULL); pthread_join(th3, NULL);

214

Sisteme de operare. Chestiuni teoretice i practice

14.6. Probleme
1. S se testeze funciile descrise n cadrul lucrrii. Se va folosi ca model programul C de mai sus. S se stabileasc alternativ diferite politici de planificare i prioriti ale celor trei thread-uri. 2. n cadrul problemei productor-consumator, se cere introducerea unui nou thread numit garbage-collector, care are rolul de eliberare a spaiilor din buffer coninnd mesaje preluate, dar neeliminate de ctre thread-urile consumator. Se presupune c thread-urile productor i consumator sunt programate pentru planificare utiliznd politica SCHED_RR, iar pentru thread-ul garbage_collector se folosete politica SCHED_OTHER. 3. Se consider un pod pe care se poate circula doar ntr-un singur sens la un moment dat. n plus, pe pod se pot afla simultan doar MAX_MASINI maini. O main este reprezentat de un thread, care va executa procedura Circula, avnd urmtoarea form:
Circula(int directie) { IntraPePod(directie); TraverseazaPod(directie); IeseDePePod(directie); }

(a) Se cere s se implementeze procedurile de mai sus, folosind lacte i variabile condiionale, astfel nct s fie respectate regulile de traversare a podului amintite mai sus. (b) Datorit faptului c respectarea regulilor de mai sus poate duce la apariia cazului cnd mainile dintr-o anumit parte a podului pot s atepte un timp nedeterminat, atunci cnd din sens contrar vin ncontinuu maini, se cere introducerea unui thread cu rol de control a circulaiei (ceea ce n realitate este realizat cu dou semafoare la fiecare intrare pe pod, care indic pe rnd culoarea verde). Acest thread va aloca un interval de traversare pentru fiecare direcie, la expirarea acestui timp, schimbnd sensul de circulaie. Opional se poate introduce un anumit grad de inteligen controlorului de trafic, care s in cont de fluxul de maini din ambele direcii. Acest controlor inteligent va acorda un timp de traversare mai mare pentru direcia din care vin mai multe maini, sau dac dintr-o direcie nu vin maini, atunci nu va schimba sensul de circulaie. (c) S se introduc thread-uri care s joace rolul mainilor de poliie sau salvare, adic vor avea o prioritate mai mare dect a thread-urilor reprezentnd maini obinuite. Acestea nu vor fi afectate de direcia
215

Planificarea thread-urilor n Linux

de circulaie impus de controlorul de trafic, dar evident vor trebui s in cont de regulile enunate iniial, adic s atepte dup mainile din sens contrar care sunt pe pod i s nu se depeasc numrul maxim de maini de pe pod. 4. Inversarea prioritilor. S se testeze funcionarea unui proces cu trei thread-uri T1, T2 i T3, avnd fiecare trei prioriti diferite 0 < p1 < p2 < p3, n urmtorul context: primul thread blocheaz un lact L pentru a modifica o variabil partajat V. ntre timp pornete cel de-al doilea thread, care va executa o bucl infinit, fr a ncerca ns blocarea lactului L. Cel de-al treilea thread va ncerca ulterior i el blocarea lactului. S se urmreasc i s se comenteze rezultatul execuiei celor trei thread-uri.

216

Bibliografie
1. Andrew Tanenbaum, Modern Operating Systems, 2nd Edition, Prentice Hall, 2001. 2. Daniel Bovet, Marco Cesati, Understanding the Linux Kernel, 2nd Edition, OReilly, 2002. 3. Mark Mitchell, Jeffrey Oldham, Alex Samuel, Advanced Linux Programming, CodeSourcery LLC, New Riders Publishing, First Edition, June 2001 (disponibil n format pdf la adresa www.advancedlinuxprogramming.com) 4. Bradford Nichols, Dick Buttlar, Jacqueline Proulx Farrell, PThreads Programming. A POSIX Standard for Better Multiprocessing, first edition, OReilly, 1996. 5. ***, Paginile de manual din Linux, disponibile i la adresa
www.linuxmanpages.com

217

S-ar putea să vă placă și