Documente Academic
Documente Profesional
Documente Cultură
Indrumator So
Indrumator So
IOSIF IGNAT
ZOLTN SOMODI
SISTEME DE OPERARE
CHESTIUNI TEORETICE I PRACTICE
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
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).
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
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
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
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.
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.
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
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
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
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.
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
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
11
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
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
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
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
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
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
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
starea de ieire a ultimei comenzi executate. identificatorul de proces asociat interpretorului. identificatorul ultimului proces lansat n background.
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
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
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.
-n ir_de_caractere -z ir_de_caractere
ir_de_caractere1 = ir_de_caractere2
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
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 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
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
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
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
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
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
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]
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
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
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
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
"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
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
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
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
28
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
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
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
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
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
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
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
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
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
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
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
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
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");
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
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
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
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
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
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).
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
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); }
43
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
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
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
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); }
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
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
Zona de fiiere
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
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
51
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
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
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
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
55
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
Handler ctre fiierul de citit. Handler-ul de fiier trebuie s fi fost creat cu accesul GENERIC_READ la fiier.
lpBuffer
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
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);
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
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
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 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
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
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
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
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
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
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.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
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.
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
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.
68
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
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.
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
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
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.
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
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
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.
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
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
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
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
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
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.
80
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
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
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
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);
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);
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
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.
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
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
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));
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
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
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
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.
95
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
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);
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
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);
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
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.
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
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);
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
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
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
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
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
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
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
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
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
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
} 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); }
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");
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
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.
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
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.
114
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
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
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
118
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);
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
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.
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);
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
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
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
124
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
// 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
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
} return 1;
128
/* 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
#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
// Citeste din pipe fOk = ReadFile( hPipe, chBuf, BUFSIZE*sizeof(TCHAR), &cbRead, NULL); if (!fOk && GetLastError() != ERROR_MORE_DATA) break; _tprintf( TEXT("%s\n"), chBuf );
131
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
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.
Semnal
SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP SIGABRT SIGBUS SIGFPE SIGKILL SIGSEGV SIGPIPE SIGALRM SIGTERM SIGUSR1 SIGUSR2
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
Ignorare
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
135
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
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
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
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
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.
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
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
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.
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
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
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
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
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
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
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
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.
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
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
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
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
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
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
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
157
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
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
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
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
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
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
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
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>
165
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
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
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
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
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.
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
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
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.
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
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
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.
// secunde // microsecunde
int semop(int id, struct sembuf *operatii, unsigned nrOperatii); int semtimedop(int id, struct sembuf *operatii, unsigned nrOperatii, struct timespec *timeout);
175
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
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
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
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
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.
permisiuni timpul ultimului apel al functiei semop timpul ultimei modif. a structurii semid_ds nr. de sem. din set
180
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; };
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
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
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.
tergerea unui set de semafoare poate fi fcut doar de ctre administratorul sistemului, utilizatorul proprietar sau creator al respectivului set.
183
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);
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
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
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
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>
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
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
} 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
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
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
192
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
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);
Dac thread-ul care a blocat anterior lactul apeleaz funcia de blocare din nou, atunci el este suspendat n mod definitiv.
194
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
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
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
197
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;
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
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
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
// 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
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
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
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
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
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);
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
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
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);
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
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.
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 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
212
213
214
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
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