0% au considerat acest document util (0 voturi)
105 vizualizări

Programare Sub Linux

Documentul prezintă utilitățile pentru dezvoltarea de aplicații în C sub Linux, inclusiv compilarea, legarea, gestionarea dependințelor și versiunilor surselor. De asemenea, este descrisă interfața aplicațiilor cu sistemul de operare, gestionarea fișierelor, proceselor, semnalelor și comunicarea între procese.

Încărcat de

Irina Dobre
Drepturi de autor
© © All Rights Reserved
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd
0% au considerat acest document util (0 voturi)
105 vizualizări

Programare Sub Linux

Documentul prezintă utilitățile pentru dezvoltarea de aplicații în C sub Linux, inclusiv compilarea, legarea, gestionarea dependințelor și versiunilor surselor. De asemenea, este descrisă interfața aplicațiilor cu sistemul de operare, gestionarea fișierelor, proceselor, semnalelor și comunicarea între procese.

Încărcat de

Irina Dobre
Drepturi de autor
© © All Rights Reserved
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd
Sunteți pe pagina 1/ 116

UNIVERSITATEA DIN BUCUREŞTI

FACULTATEA DE MATEMATICĂ ŞI INFORMATICĂ

PROGRAMAREA APLICAŢIILOR
SUB SISTEMUL DE OPERARE LINUX

Conf. dr. Andrei BARANGA

PROGRAMUL DE MASTER IFR: BAZE DE DATE ŞI TEHNOLOGII WEB


Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Cuprins

1. UTILITARE PENTRU DEZVOLTAREA DE APLICATII SCRISE IN LIMBAJUL C ........... 3


1.1 Introducere .......................................................................................................................... 4
1.2 Verificarea sintaxei cu ajutorul comenzii lint ................................................................... 4
1.3 Comanda de compilare cc ................................................................................................. 5
1.4 Editorul de legături ld.......................................................................................................... 8
1.5 Construirea bibliotecilor ................................................................................................... 12
1.6 Comanda nm..................................................................................................................... 13
1.7 Gestiunea dependinţelor. Comanda make .................................................................... 14
1.8 Gestiunea de versiuni a surselor. Sistemul SCCS ........................................................ 17
1.9 Indentarea codului. Comanda cb .................................................................................... 20
1.10 Vizualizarea structurii unui program cu ajutorul comenzii cflow ................................ 21
1.11 Alte comenzi utlizabile pentru dezvoltarea aplicaţiilor ................................................ 21
1.12 Intrebari si teste .............................................................................................................. 22
1.13 Comentarii şi răspunsuri la testele de autoevaluare ................................................... 23
1.14 Lucrare de verificare pentru studenţi ........................................................................... 23
2. INTERFATA APLICATIILOR CU SISTEMUL DE OPERARE ........................................... 24
2.1 Generalităţi despre sistemul de operare UNIX .............................................................. 25
2.2 Bibliotecile sistem ............................................................................................................. 26
2.3 Gestionarea erorilor .......................................................................................................... 27
2.4 Aspecte generale despre pornirea si oprirea proceselor .............................................. 28
2.5 Intrebari si teste ................................................................................................................ 31
2.8 Comentarii şi răspunsuri la testele de autoevaluare ..................................................... 32
2.9 Lucrare de verificare pentru studenţi .............................................................................. 32
3. GESTIUNEA INTRARILOR SI IESIRILOR ........................................................................ 33
3.1. Generalităţi ....................................................................................................................... 33
3.2. Gestionarea atributeleor i-nodului .................................................................................. 35
3.3. Operaţiunile de bază asupra fişierelor: deschidere, închidere, citire, scriere ............ 40
3.4. Duplicarea descriptorilor ................................................................................................. 45
3.5 Controlul intrărilor-ieşirilor prin intermediul apelului fcntl .............................................. 46
3.6 Controlul poziţiei curente prin intermediul apelului lseek .............................................. 49
3.7 Gestiunea legăturilor simbolice ....................................................................................... 49
3.8 Gestiunea directoarelor .................................................................................................... 50
3.9 Utilizarea bibliotecii C standard pentru gestionarea fişierelor ...................................... 51
3.10 Intrebari si teste .............................................................................................................. 58
3.11 Comentarii şi răspunsuri la testele de autoevaluare ................................................... 59
3.13 Lucrare de verificare pentru studenţi ............................................................................ 60
4. GESTIUNEA PROCESELOR ............................................................................................... 61
4.1. Carateristicile generale ale unui proces ........................................................................ 62
4.2 Organizarea memoriei unui proces ................................................................................. 67
4.3 Crearea proceselor ........................................................................................................... 69
4.4. Procese zombi şi sincronizarea tată-fiu......................................................................... 70
4.5 Primitivele din familia exec. ............................................................................................. 72
4.6 Intrebari si teste ................................................................................................................ 74
4.6. Comentarii şi răspunsuri la testele de autoevaluare .................................................... 75
4.6. Lucrare de verificare pentru studenţi ............................................................................. 75
5. GESTIONAREA SEMNALELOR......................................................................................... 76

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 1
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

5.1. Introducere ....................................................................................................................... 77


5.2. Identificarea semnalelor .................................................................................................. 77
5.3 Trimiterea de semnale către un proces .......................................................................... 80
5.4 Comportarea proceselor la primirea unui semnal ......................................................... 81
5. 5 Condiţii de recepţie a unui semnal de către un proces ................................................ 81
5. 6 Blocarea şi deblocarea semnalelor ................................................................................ 82
5. 7 Gestionarea handlerelor de tratare a semnalelor. ........................................................ 84
5.8 Aşteptarea unui semnal de către un proces .................................................................. 85
5.9 Salturi într-o altă funcţie decât cea curentă ................................................................... 86
5.10 Intrebari si teste .............................................................................................................. 88
5.11. Comentarii şi răspunsuri la testele de autoevaluare .................................................. 89
5.12. Lucrare de verificare pentru studenţi ........................................................................... 89
6. COMUNICAREA INTRE PROCESE CU AJUTORUL FISIERELOR DE TIP TUB (PIPE) . 90
6.1 Caracteristicele fişierelor de tip tub ................................................................................. 91
6.2 Tuburile obişnuite ............................................................................................................. 91
6.3 Tuburile cu nume .............................................................................................................. 95
6.4 Intrebari si teste ................................................................................................................ 97
6.5. Comentarii şi răspunsuri la testele de autoevaluare .................................................... 98
6.6. Lucrare de verificare pentru studenţi ............................................................................. 98
7. COMUNICAREA INTRE PROCESE CU AJUTORUL IPC SYSTEM V ............................. 99
7.1 Introducere ...................................................................................................................... 100
7.2 Cozile de mesaje ............................................................................................................ 103
7.3 Vectorii de semafoare .................................................................................................... 107
7. 4 Segmentele de memorie partajată ............................................................................... 110
7.5 Intrebari si teste .............................................................................................................. 113
7.6. Comentarii şi răspunsuri la testele de autoevaluare .................................................. 114
7.7. Lucrare de verificare pentru studenţi ........................................................................... 114
8. BIBLIOGRAFIE.................................................................................................................. 115

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 2
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

1. UTILITARE PENTRU DEZVOLTAREA DE APLICATII SCRISE IN LIMBAJUL C

Obiectivele unităţii de învăţare nr. 1

După ce veţi parcurge această unitate de învăţare, veţi reuşi să:


 Compilati un program scris in C
 Editarea legaturilor
 Sa construiti un fisier de tipul makefile
 Sa utilizati alte comenzi utile pentru programator

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 3
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

1.1 Introducere
Reamintim, cu această ocazie, că în sistemul de operare UNIX, noţiunea de
extensie a unui fişier nu are un rol determinant în stabilirea tipului de fişier, cum este cazul
în alte sisteme de operere cum ar fi MS-DOS sau WINDOWS. Totuşi, pentru buna
funcţionare a utilitarelor ce constituie mediul de dezvoltare al aplicaţiilor, fişerele sursă,
create cu ajutorul unui editor de texte cum ar fi vi sau emacs trebuie sa aibă numele
terminat cu extensia “.c” (prog.c, de exemplu). Odată fişierele sursă editate, acestea pot fi
compilate cu ajutorul comenzii cc pe care o vom descrie mai jos.

Un fişier sursă (text) în limbajul C este compus dintr-o colecţie de obiecte

şi anume variabile globale şi funcţii (subprograme) care primesc la intrare parametrii şi


returnează o valoare având un tip bine precizat, eventual void în cazul în care nu se
returnează nici o valoare. Compilatorul va translata fişierle sursă (text), cu extensia .c, în
fişiere obiect, cu extensia .o, după care va lansa editorul de legături, care va realiza, în
final, după rezovarea referinţelor încrucişate, executabilul.

Întrebare: Care este diferenţa dintre compilare si editare de legaturi?

Răspuns: Compilarea furnizeaza fisere obiect cu referinte nerezolvate, iar editrea de


legaturi furnizeaza executabilul.

1.2 Verificarea sintaxei cu ajutorul comenzii lint


Comanda lint primeşte ca argumente nume de fişiere sursă şi nu generează la
ieşire cod obiect, dar, în schimb furnizează mesaje de eroare rezultate în urma
analizei codului sursă. Evident, erorile de compilare sunt semnalate şi de
compilatorul cc, dar lint semnalează, în plus, eventuale erori de natură semantică,
cum ar fi: instrucţiuni imposibil de atins, utilizarea de variabile neiniţializate, variabile
declarate şi nefolosite etc.

Atragem atenţia asupra faptului că o eroare semnalată de acest utilitar nu este, în


mod obligatoriu şi una reala. De exemplu, pentru secvenţa de mai jos

int j=0,i;

for ( j=0; j<10; j++)

if (j>0)

i++;

else

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 4
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

i = j;

se va semnala faptul că variabila i este utilizată înainte de fi iniţializată, deşi, aşa cum se
observă, nu este cazul. Totuşi, programatorii sunt sfătuiţi să utilizeze înainte de compilare
această comandă. Forma generală a acestei comenzi este:

$lint [-abuv] fisier,…

unde fisier este numele unui fişier sursă iar efectul utilizării opţiunilor este acela de a nu
furniza mesaj pentru anumite tipuri susceptibile de erori, cum ar fi:
-a atribuire de valoare a unei expresii de tip long unei variabile al cărei tip

nu este long;

-b instrucţiuni break inaccesibile;

-u obiecte externe definite, dar neutilizate sau invers

-v parametrii de funcţii neutilizaţi

Întrebare: Care este diferenţa dintre comenzile lint si cc?

Răspuns: cc verifica sintaxa si translateaza textul sursa in cod obiect, pe cand lint
analizeaza doar textul sursa fara furniza cod obiect.

1.3 Comanda de compilare cc


Transformarea fişierelor sursă în executabile se efectuează prin lansarea de către
executabilul cc , rând pe rând, a patru alte comenzi, şi anume:

1. precompilatorul cpp ce realizează precompilarea, adică subsituţii în fişierul sursă


în urma întâlnirii comenzilor de precompilare ce încep cu caracterul # în prima coloana
(#define, #include, etc…). Acest proces furnizează la ieşire un fişier text cu acelaşi nume
ca şi fişierul sursă, dar cu extensia .i.

2. compilatorul ccom, ce realizează transalatarea fişierului precompilat


în limbaj de asamblare. Acest proces furnizează la ieşire un fişier text cu acelaşi nume ca
şi fişierul sursă, dar cu extensia .s.

3. asamblorul as ce realizează translatarea fişierului din limbaj de


asamblare în fişier obiect. Acest proces furnizează la ieşire un fişier care ne este text cu
acelaşi nume ca şi fişierul sursă, dar cu extensia .o. Acest fişier nu are referinţele externe
rezolvate.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 5
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

4. editorul de legaturi ld, care leagă fişierele obiect obţinute şi


furnizează la ieşire fişierul executabil ce poartă numele a.out în mod implicit, dacă acesta
nu este specificat prin opţiunea –o a comenzii cc.

Este evident faptul că utilizatorul poate lansa, rând pe rând, comenzile

cpp, ccom, as şi ld, însă comanda cc, asigură lansarea acestor comenzi cu parametrii
adecvaţi, a căror editare este extrem de laborioasă şi care depind de versiunea de sistem.
Acest fapt poate fi observat lansând comada cc cu opţiunea

–v (verbose, vorbaret) care, în plus, afişează şi comenzile lansate.

Forma generală a comenzii este :

$cc [-optiuni] fisier,…

unde în lista de fişiere se pot menţiona fişiere sursă (cu extensia .c), fişiere precompilate
(cu extensia .i), fişiere text în limbaj de asamblare (cu extensia .s), fişiere obiect (cu
extensia .o) sau biblioteci de fişiere obiect (cu extensiile .a, .so sau .sl). Fişierlor cu
extensia .c li se vor aplica procedurile 1-3 de mai sus, celor cu extensia .i procedurile 2-3,
iar celor cu extensia .s procedura 3. Odată obţinute, pentru toate fişierele obiect se aplică
procedura 4, de editare de legături, în vederea obţinerii executabilului.

Parametrii opţionali se împart în trei categorii:

a. Optiuni care specifică punctul de oprire al procesului. Prin lipsă,


acesta se termină cu etapa numarul 4, adică editarea de legături şi generarea
executabilului. Opţiunile din această categorie sunt:

-E semnifică oprirea procesului după faza 1 şi afişarea rezultatului


precompilării pe ecran.
-I semnifică oprirea procesului după faza 1 şi generarea fişierelor cu

extensia .i.

-S semnifică oprirea procesului după faza 2 şi generarea fişierelor cu

extensia .s.

-c semnifică oprirea procesului dupa faza 3 şi generarea fişierelor cu

extensia .o.

Semnalăm faptul că după executarea comenzii cc, fişierele intermediare

sunt şterse, rămânând doar fişierele de intrare şi destinaţiile finale.

Atenţie. În cazul în care avem erori de compilare a căror cauză nu o

putem sesiza uşor, este foarte util să analizăm rezultatul produs de precompilator.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 6
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

b. Opţiuni cu caracter general:

-v afişare pe ecran a comenzilor generate

-g generare în fişierele obiect şi executabile de cod suplimentar în vederea

depanării cu ajutorul depanatoarelor simbolice.

c.Opţiuni destinate precompilatorului care vor fi şi opţiuni ale comenzii

cpp generate:

-C nu se elimină comentariile

-Dnume_identificator echivalent cu a introduce în prima linie a fişierului

sursă linia

#define nume_identificator

-Dnume_identificator=valoare echivalent cu a introduce în prima linie a

fişierului sursă linia

#define nume_identificator valoare

Ultimele două opţiuni pot servi pentru directive de compilare condiţionată

cum ar fi:

#ifdef UNIX

a+b=c;

c=x;

#endif

Cele două atribuiri de mai sus vor fi luate în consideraţie doar în cazul în

care linia de comandă arată în felul următor:


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 7
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

$cc …. –DUNIX ……

-Idirector serveşte la desemnarea directoarelor de căutare a fişierelor

specificate în directiva de precompilare #include şi care vor fi incluse în textul


programului. În cazul în care numele unui asemenea fişier este încadrat de semnele <>
acesta va fi considerat fişier header sistem şi va fi căutat în directoarele standard ale
sistemului. În cazul în care, însă, numele unui asemenea fişier este încadrat de ghilimele
va fi considerat fişier header scris de utilizator şi va fi căutat întâi în directorul curent şi,
mai apoi, pe rând, în directoarele specificate cu ajutorul opţiunii –I. De exemplu:

$cc -Id1 –Id2 –Id3…….

va declanşa căutarea fişierelor header scrise de utilizator în directorul curent şi, în caz de
insucces, mai apoi, în d1, d2, d3 în această ordine.
4. Opţiuni destinate editorului de legături ld. Vor fi discutate în paragraful

următor.

Întrebare: Care este optiunea care permite indicarea directoarelor unde se afla fisierele ce
urmeaza a fi incluse de percompilatror?

Răspuns: Optiunea -I

1.4 Editorul de legături ld

Editorul de legături va rezolva referinţele încrucişate între fişierele obiect

rezultate în urma compilării sau specificate explicit în linia de comandă. Pentru ca

executabilul generat sa fie valid, trebuie ca pentru tuturor invocărilor de obiecte externe

(variabile globale sau funcţii) să li se găsească un corespondent în alt fişier obiect şi să nu

existe duplicate. În plus, în executabil trebuie să se gasească o funcţie numită main şi

care să returneze int.

În caz că fişierele obiect nu sunt suficiente pentru a rezolva o referinţa


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 8
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

externă, se va recurge la bibliotecile specificate în linia de comandă (cu una din

extensiile .a, .so sau .sl). Bibliotecile sunt colecţii de fişiere obiect. Editorul de legături va

căuta referinţa nerezolvată în aceste biblioteci, iar în cazul în care o găseşte, va extrage

obiectul necesar şi îl va lega la executabil. Legarea cu biblioteca libc.a (.so, .sl după caz),

ce conţine apelurile sistem, se va face implicit, fară a fi nevoie să o desemnăm în mod

expres în linia de comandă.

Atenţie – este deosebit de importantă ordinea în care sunt menţionate

bibliotecile în linia de comandă. Aceasta deoarece, pe multe dintre sisteme, editorul de

legături le analizează doar o singură dată. De exemplu, avem referinţa nerezolvată r1.

Linia de comandă este

$cc ….. b1.a b2.a ….

Referinţa se gaseşte, să zicem, în fişierul, f1.o din b1.a. Legarea cu acest

fişier generează altă referinţă nerezolvată, r2. Se analizează b2.a şi se găseşte în referinţa

în fişierul f2.o. Legarea cu acest fişier generează referinţa nerezolvată r3 care se găseşte

în fişierul f3.o aflat în b1.a. Însă editorul de legături a analizat deja această bibliotecă şi, în

consecinţă, editarea de legături se va solda cu eşec. Este recomandabil ca bibliotecile să

fie concepute în mod ierarhizat, iar modulele din bibliotecile de nivel inferior să nu invoce

obiecte din bibliotecile de nivel superior.

Atenţie. Este facută deseori confuzia între erorile semnalate de procesul

de compilare şi cele de procesul de editare de legături. În general, cele din urmă, se

rezolvă prin adăugarea în linia de comandă a fişierului ce conţine referinţa semnalată ca

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 9
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

nerezolvată. De asemenea, se mai face confuzia între declaraţia de prototip a unei funcţii

în fişiere de tip header şi codul obiect al funcţiei propriu zis. De exemplu, afirmaţia “funcţia

printf se găseşte în stdio.h” este complet falsă. În stdio.h se găseţte o declaraţie de forma

“extern int printf()”, pe când codul obiect al acestei funcţii se găseşte în biblioteca

standard.

Legarea cu bibliotecile se poate face în două moduri:

a. Legare statica. Acest mod se aplică bibliotecilor al căror nume


poartă extensia .a şi sunt numite biblioteci de tip arhivă. Obiectele extrase sunt legate

efectiv la executabil.

b. Legare dinamica. Acest mod de legare se aplică bibliotecilor al căror


nume poartă extensia .so pe versiunile SYSTEM V.4 şi sunt numite obiecte partajate

(shared objects), sau .sl pe HP-UX şi sunt numite biblioteci partajate (shared libraries).

Obiectele necesare nu sunt legate la executabil, ci doar se adaugă la acesta o listă cu

simbolurile externe găsite în biblioteci.

Rezolvarea efectivă a referinţelor se face în timpul execuţiei. Codul obiect

din biblioteci se încarcă atunci efectiv în memorie. Acest lucru se poate face în două

moduri: imediat, la lansarea executabilului sau diferenţiat, doar în momentul în care este

prima data nevoie de a se executa cod din asemenea biblioteci. O dată codul obiect al

bibliotecilor partajate încărcat în memorie, el va fi folosit şi de alte programe lansate

ulterior în execuţie. În acest fel, codul obiect conţinut în aceste biblioteci nu va fi rezident în

fiecare executabil, realizându-se astfel o diminuare considerabilă a dimensiunii

executabilelor.

Forma generală a comenzii de editare de legături este:

$ld [-optiuni] [-L director] [-lnume] [-o executabil] fisier…


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 10
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Dintre opţiuni menţionăm –s care elimină din executabil eventualele

informaţii de depanare ce se găsesc în fişierele obiect.

Opţiunea –L director serveşte la desemnarea directoarelor în care trebuie

căutate bibliotecile ce nu se găsesc în directorul curent.

Optiunea –o executabil serveşte la desemnarea numelui fişierului de

ieşire. În lipsa acestei opţiuni numele executabilului va fi a.out.

Opţiunea –lnume (fara spatiu dupa -l!) serveşte drept abreviere pentru

desemnarea numelor de biblioteci. Bibliotecile invocate cu această opţiune trebuie să aibă

numele prefixat de lib. nume va fi obţinut din eliminarea prefixului şi a extensiei. De

exemplu pentru libm.a folosim –lm, pentru libisam.a folosim –lisam etc. În ultimul exemplu

este posibil să existe şi biblioteca arhivă libisam.a şi versiunea partajată libisam.so.

Editorul de legături va prefera varianta partajată în absenţa altei opţiuni specifice. Opţiunile

de acest tip diferă însa de la o versiune la alta. Spre exemplu, pe SYSTEM V avem:

-dy preferinţă pentru versiunea partajată (implicită)

-dn preferinţă pentru versiunea arhivă

-Bstatic bibliotecile partajate nerecunoscute până la întalnirea opţiunii

-Bdynamic

-Bdynamic reautorizarea folosirii bibliotecilor dinamice.

Pe HP-UX avem:

-a archive utilizare exclusiv a bibliotecilor arhivă pană la întâlnirea unei

opţiuni de forma -a

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 11
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

-a shared utilizare exclusiv a bibliotecilor dinamice până la întâlnirea

unei opţiuni de forma -a

-a default preferinţă pentru versiunea dinamică

-Bimmediate încărcare totală a bibliotecii în memorie

-Bdeffered încărcare numai a modulelor invocate din bibliotecă

Întrebare: Care este diferenta intre legare statica si dinamica?

Răspuns: La legare statica se include codul obiect al bibliotecii in executabil, ia la legare


dinamica biblioteca se incarca o singura data pentru executabilele pe care o folosesc

1.5 Construirea bibliotecilor


Mai întâi de toate, se obţin fişierele obiect (extensie .o), compilând

fişierele sursă cu opţiunea –c:

$cc -c fisier1.c fisier2.c fisier3.c ….

Bibliotecile de tip arhivă se obţin cu ajutorul comenzii ar ce are

următoarea sintaxă:

$ar cheie nume-arhiva fisier-obiect1, fisier-obiect2,…

unde cheie poate avea una din următoarele valori:

a. c rescriere completă a arhivei


b. r înlocuire sau adăugare, după caz
c. q adăugare de fişiere la sfârşitul arhivei fară test de existenţă în
prealabil

d. t afişarea componenţei arhivei


e. x extragerea fişierelor specificate din arhivă
La cheie se poate adăuga, eventual, litera v (verbose) pentru afişarea pe
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 12
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

ecran a detaliilor modului de funcţionare a comenzii. Exemplu:

$ar rv arhiva.a fisier1.o fisier2.o fisier3.o …

Modul de construcţie al bibliotecilor partajate depinde esenţial de

versiunea de sistem cu care lucrăm. Pe SYSTEM V se realizează cu ajutorul opţiunii –G al


comenzii ld :

$ld –G –o arhiva.so fisier1.o fisier2.o fisier3.o …

În plus, pentru a fi recunoscute bibliotecile partajate la lansarea

programelor în execuţie, trebuie specificat directorul în care se găsesc aceste biblioteci în


variabila de mediu $LD_LIBRARY_PATH

Pe HP_UX, la obţinerea fişierelor obiect trebuie folosită, în plus opţiunea

+z. Această opţiune permite obţinerea aşa numitului cod PIC (Position Independent Code),
independent de locaţia de încărcare. De exemplu:

$cc -c +z fisier1.c fisier2.c fisier3.c ….

După aceea obţinem biblioteca partajată utilizând opţiunea –b a editorului

de legături. Reluând exemplul avem:

$ld –b –o arhiva.sl fisier1.o fisier2.o fisier3.o …

1.6 Comanda nm
Deseori avem la dispoziţie textul sursă scris de noi ce trebuie să facă apel

la referinţe externe din fişiere obiect al căror text sursă nu ne este pus la dispoziţie.

Studiu de caz. Editorul de legături ne semnalează o referinţa nerezolvată

care ştim sigur că este într-unul din fişierele a căror sursă nu o deţinem. Sigur am omis să
adaugăm un fişier obiect (.o) în linia de comandă cu care am lansat cc. Ce este de făcut?
Comanda nm ne scoate din impas.

Sintaxa comenzii este:

$nm fisier…

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 13
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

unde fişierele date în argument pot fi fie fişiere obiect, fie biblioteci. Această comandă ne
va furniza lista referinţelor publice oferite de fiecare fişier, precum şi lista referinţelor
externe invocate de fiecare fişier obiect.

1.7 Gestiunea dependinţelor. Comanda make


Studiu de caz Se lucrează la un proiect (aplicaţie) şi s-a ajuns la sute de

fişiere sursă, unele dintre acestea conţinând mii de linii de cod. Compilarea cu ajutorul
unei comenzi de forma:

$cc –o nume_exe f1.c, f2.c, …

conduce la un timp enorm de compilare, posibil de ordinul orelor. S-a modificat o linie într-
unul din fişierele sursă şi suntem obligaţi să aşteptăm câteva ore pentru obţinerea noii
versiuni de executabil. Soluţia este comanda make, cu ajutorul căreia se recompilează
numai fişierele sursă modificate, după care se execută editarea de legături.

Acest principiu a fost preluat de mulţi dintre producătorii de medii

integrate de dezvoltare (IDE) create pentru diferite limbaje de programare (C, C++,
JAVA,etc.).

Principiul este următorul: executabilul depinde de fişierele obiect şi

biblioteci, acestea la rândul lor de fişierele sursă şi aşa mai departe. În momentul în care
se doreşte construirea unui fişier obiect sau a unui executabil se analizeaza ierarhia de
dependinţe şi de îndata ce se constată că data ultimei modificări a unei destinaţii este
anterioară datei ultimei modificări a cel puţin unui fişier de care depinde aceasta, se trage
concluzia că destinaţia nu este actualizată şi se lansează comanda de reconstruire a
acesteia. Altfel nu se reconstruieşte destinaţia. Strategia de bază este aceea de a nu
reconstrui decât ce nu mai este de actualitate şi se bazează pe principiul “fiul este
întotdeauna mai tânăr decât părintele”.

Forma generală a acestei comenzi este :

$make [-f makefile] [-optiuni] [destinatie, …]

unde:
În cazul folosirii opţiunii –f se indică numele fişierului ce descrie dependinţele, numit
fişier de tip makefile. Prin lipsă, acesta se consideră a avea numele makefile sau
Makefile.

După opţiunile ce vor fi descrise ulterior, se dă lista fişierelor destinaţie ce se doresc


a fi reconstruite, în caz că dependinţele au suferit modificări. Prin lipsă, se consideră că se
doreşte reconstruirea primei destinaţii ale cărei dependinţe sunt descrise în makefile.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 14
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Fişierele de tip makefile sunt de tip text, al căror conţinut îl vom prezenta

în continuare.

Pentru fiecare din fişierele destinaţie, sunt prezente în fişierul de tip

makefile două linii. Prima conţine denumirea fişierului destinaţie, urmată de caracterul „:‟ şi
apoi de lista fişierlor dependente, separate prin spaţii. A doua începe obligatoriu cu
caracterul <tab> („\t‟ conform simbolisticii limbajului C) şi conţine comanda ce trebuie
executată pentru reconstruirea dependinţei. Această comandă nu se execută decât dacă
una din dependinţe este “mai tânără” decât destinaţia.

Exemplu. Fie un proiect ale cărui fişiere obiect sunt obţinute prin

compilarea fişierelor sursă p1.c, p2.c şi p3.c, urmată de editarea de legături între modulele
obiect obţinute, obţinându-se, astfel, executabilul numit aplic. Fişierele sursă pot include,
eventual, unul sau mai multe dintre fişierele header p.h, q.h, r.h. Fişierul de tip makefile va
avea următorul conţinut:

aplic:p1.o p2.o p3.o

<tab>cc –o aplic p1.o p2.o p3.o

p1.o:p1.c p.h q.h

<tab> cc –c p1.c

p2.o:p2.c

<tab> cc –c p1.c

p3.o:p1.c p.h q.h r.h

<tab> cc –c p3.c

Modificarea fişierului q.h va duce la recompilarea fişierelor p1.c şi p3.c şi, implicit, la
efectuarea editărilor de legături. Comanda

$make

este echivalentă cu

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 15
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

$make aplic

pe când

$make p1.o p2.o

va produce noi versiuni de fişiere obiect doar dacă au fost modificate, între timp, fişierele
p1.c, p2.c, p.h sau q.h.

Atenţie Mulţi programatori, din comoditate, după ce introduc în proiect

un nou fişier de tip header, nu actualizeză, în totalitate, dependinţele din fişierul de tip
makefile. Acest fapt poate duce la situaţia neplăcută ca, după modificarea headerului, o
parte din fişierele sursă C să fie recompilate cu noua versiune, iar altele, care depind, de
asemenea, de acest fişier sa rămână compilate cu vechea versiune de header, lucru care
poate avea consecinţe nefaste în timpul execuţiei.

Alt fapt demn de menţionat, este acela că acest mecanism poate fi folosit

şi în alte scopuri decât acela de a obţine executabile. Bunăoară, comanda de refacere a


destinaţiei nu trebuie să fie în mod obligatoriu cc, ci orice altă comandă UNIX (cp, mv, rm,
etc.). De asemenea, dependinţele nu trebuie să fie, în mod obligatoriu, header-obiect,
sursă-obiect sau obiect-executabil. Fiecare utilizator al acestei facilităţi işi poate stabili
regulile de dependinţă în concordanţă cu scopul urmărit.

Fară alte opţiuni, decât cele prezentate mai sus, se execută toate

comenzile necesare pentru actualizarea fişierlor destinaţie, cu afişarea în ecou a acestora


pe display şi cu sistarea execuţiei la întâlnirea primei erori (de compilare, de editare de
legături, etc.).

Alte opţiuni sunt:

-i continuarea execuţiei după apariţia unei erori

-s (silent) fară afişarea comenzilor lansate

-n afişarea comenzilor ce urmează a fi lansate, fară execuţie efectivă

-p afişarea listei de dependinţe

-d (debug) furnizare de informaţii despre sursa care a generat comenzile

lansate (data ultimei modificări,etc.)

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 16
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

1.8 Gestiunea de versiuni a surselor. Sistemul SCCS


Studiu de caz. Suntem în aceeaşi situaţie ca la studiul de caz prezentat în debutul
paragrafului anterior. Doi programatori doresc să modifice acelaşi fişier sursă. Cum
preîntâmpinăm situaţia ca modificările unuia să fie distruse de salvările celuilalt? Mai
precis, în momentul când dorim să modificăm un fişier sursă, cum putem fi siguri că nici un
alt coechipier nu îl are deschis în acelaşi timp pentru a-l modifica?

Studiu de caz. S-a constatat că s-a mers într-o direcţie greşită în ceea ce priveşte
dezvoltarea unor facilităţi ale aplicaţiei. În consecinţă, toate modificările făcute în ultima
vreme trebuiesc anulate. Cum se poate realiza recuperarea de versiuni anterioare, fără
reeditarea fişierelor sursă, şi deci, fără a risca producerea de noi erori în programe?

Sistemul SCCS de gestiune a versiumilor ne rezolvă probleme de genul celor menţionate


mai sus. Trebuie menţionat că aceste idei au fost preluate şi de alte medii integrate de
dezvoltare (IDE), un exemplu elocvent fiind Visual Source Safe (VSS) produs de compania
MICROSOFT.

După crearea fişierului text cu numele sursa, acesta va fi supus

administraţiei sistemului SCCS cu ajutorul comenzi:

$admin –isursa s.sursa

unde sursa reprezintă numele fişierului nou creat, iar s.sursa este numele fişierului ce va fi
gestionat de SCCS. Prefixul s. este obligatoriu. Se va crea fişierul s.sursa care este read-
only şi va gestiona toate versiunile ulterioare ale fişierului cu numele sursa.

De exemplu, dacă fişierul nou creat se numeşte prog.c, comanda este:


$admin –iprog.c s.prog.c

În acest moment putem şterge fişierul cu numele prog.c, s.prog.c fiind

suficient pentru recuperarea atât a ultimei versiuni a fişierului cât şi, eventual, a unor
versiuni anterioare.

În exemplul de mai sus, după execuţia comenzii admin, se va obţine,

probabil mesajul de avertisment “No id keywords”, ce va fi explicat ulterior. Menţionăm că


avem la dispoziţie comanda:

$help

ce are ca efect explicarea ultimei erori sau a ultimului avertisment dat de ultima comandă
SCCS lansată.

Acum, fişierul s.prog.c va conţine versiunea 1.1 a fişierului prog.c.

Ultima versiune a fişierului prog.c se va obţine cu ajutorul comenzii:


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 17
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

$get s.prog.c

Fişierul obţinut va fi read-only şi va fi folosit doar pentru compilare. Dacă vrem, în plus, să
edităm fişierul, în vederea obţinerii unei versiuni ulterioare, se va utiliza opţiunea –e a
comenzii get:

$get –e prog.c

În acest caz se va obţine un fişier al cărui proprietar este utilizatorul care a

dat comanda şi asupra căruia numai acesta are drept de scriere. Se consideră fişierul
deschis pentru editare de către proprietar, până când acesta va renunţa la acest privilegiu.
Se crează şi un fişier cu numele p.prog.c ce va fi prezent până când proprietarul va
renunţa asupra drepturilor sale. Acest fişier joacă un rol de lacăt, prezenţa acestuia
împiedicând alţi utilizători să dea o comandă $get –e asupra aceluiaşi fişier. În acest fel se
asigură faptul că, la un moment dat, cel mult o persoană poate edita fişierul în cauză.

După ce a terminat şi, eventual, testat modificările (operaţie ce se poate

întinde pe parcursul a mai multor zile), utilizatorul va introduce modificările în fişierul


SCCS, creând o nouă versiune (de la x.y la x.y+1), şi va permite ca, ulterior, alţi utilizatori
să deschidă fişierul în scriere cu ajutorul comenzii $get –e. Aceasta se va realiza cu
ajutorul comenzii:

$delta s.prog.c

În acest moment, utilizatorul este invitat să introducă un comentariu


despre noua versiune. Mulţi utilizatori, din comoditate, dau <enter> în acest caz. Se
recomandă, totuşi, introducerea unui comentariu de un rând, pertinent, asupra naturii
ultimelor modificări. Fişierele prog.c şi s.prog.c sunt şterse.
În cazul în care utilizatorul renunţă la modificările făcute se poate da
comanda:
$unget s.prog.c

ce eliberează fişierul fără a înregistra o nouă versiune.


Versiunile sunt numerotate în stilul x.y unde x este numărul versiunii

propriu-zise, iar y numărul sub-versiunii. În mod normal, utilizând comenzile $get –e şi


$delta se va incrementa numărul sub-versiunii în cadrul versiunii curente, adică ultima.

Recuperarea unei versiuni anterioare se poate obţine tot cu ajutorul

comenzii get, utilizând opţiunea -r. De exemplu:

$get -r10 ….

va extrage ultima sub-versiune a versiuni cu numărul 10. În schimb

$get –r10, 20
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 18
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

va recupera sub-versiunea cu numărul 20 a versiunii cu numărul 10, dacă aceasta există.

Comanda delta incrementează numărul sub-versiunii în cadrul versiunii

curente. Pentru schimbarea numărului versiunii este suficient să dăm comanda:

$get –e –r10 …

În urma comenzii delta se va obţine versiunea 10.1, dacă aceasta nu

există.

Comanda

$prs s.prog.c

va afişa pe display lista versiunilor şi sub-versiunilor fişierului, însoţite de comentariile


introduse de utilizatori la comenzile delta folosite pentru înregistrarea noilor versiuni şi
sub-versiuni.

În debutul acestui paragraf am semnalat faptul că după utilizarea comenzii admin,


poate surveni mesajul de avertisment “No id keywords”. Acelaşi mesaj poate apărea şi
după închiderea unei sub-versiuni folosind comanda delta. Explicăm acum semnificaţia
acestui mesaj.

Se poate observa uşor că sistemul SCCS poate fi folosit şi pentru

gestiunea versiunilor altor tipuri de fişiere decât cele de tip sursă scrise în C. Pentru
acestea din urmă, programatorul poate insera în textul sursă declaraţia unei variabile
statice globale de forma :

static char v[]=”%Z%….”;

Această variabilă nu va fi folosită deloc ulterior. Valoarea asignată

acesteia trebuie prefixată obligatoriu de şirul “%Z%”. În cazul unei comenzi admin sau
delta, în fişierul SCCS s.prog.c, şirul de caractere asignat acestei variabile se va înlocui
după urmatoarele reguli:

%M% se va înlocui cu numele fişierului salvat.

%H% se va înlocui cu data curentă

%I% se va înlocui cu numărul versiunii

%Z% se va înlocui cu şirul “@(#)”

%E% se va înlocui cu data crearii ultimei versiuni.

În urma deschiderii fişierului pentru editare cu ajutorul unei comenzi de

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 19
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

tipul “get –e” se va înlocui şirul de caractere cu valoarea inţială, urmând ca la închiderea
versiunii cu ajutorul comenzii delta sau unget să se realizeze din nou substituţiile de
rigoare. Prezenţa unei asemenea declaraţii conduce la absenţa mesajului de eroare “No id
keywords”.

Care este, însă, utilitatea declarării unei asemenea variabile ce nu este

folosită nicăieri altundeva în program?

Studiu de caz. S-au livrat la beneficiar executabilele ce compun aplicaţia.

Beneficiarul semnalează o funcţionare incorectă. Identificăm faptul că este vorba de o


eroare corectată anterior. Ne punem întrebarea firească: “Oare în componenţa
executabilului au intrat fişierele obiect obţinute în urma compilării versiunilor corecte ale
surselor C?”

În cazul în care fişierele sursă conţin declaraţia de variabilă descrisă mai


sus, se poate utiliza comanda:
$what nume_executabil

(comanda poate fi aplicată şi fişierelor obiect sau bibliotecilor). Această comandă caută
şirurile de caractere ce încep cu “@(#)‖ şi afişează fişierele în care identifică asemenea
şiruri de caractere, precum şi valoarea acestora.

În acest fel putem identifica ce versiuni de fişiere sursă au fost folosite la

producerea executabilului.

1.9 Indentarea codului. Comanda cb


Comanda cb (C beautyfier) este folosită pentru obţinerea unui cod

indentat. Sintaxa comenzii este următoarea:

$cb [-s] [-j] [-llungime] [fisier…]

Prin lipsă, fişierul este luat din intrarea standard stdin. Opţiunile
semnifică:
-s indentare în stilul Kernigham-Ritchie, adică

if (i==0) {

a=b;

c=d;

altfel codul de mai sus va fi indentat astfel:

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 20
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

if (i==0)

a=b;

c=d;

Recomandare. Indentarea codului este esenţială pentru gestiunea unui

proiect a cărui dezvoltare necesită efortul mai multor programatori pe o perioadă mai lungă
de timp, de ordinul lunilor de zile, pe parcursul cărora se produce o cantitate impresionantă
de linii de cod. Recomandăm ca fiecare programator să indenteze cu stricteţe codul scris,
în conformitate cu regulile de indentare utilizate de echipa din care face parte, fără să
ajungă la situaţia de a utiliza comanda cb.

1.10 Vizualizarea structurii unui program cu ajutorul comenzii cflow

Comanda cflow permite vizualizarea arborescenţei apelurilor de funcţii


dintr-un program C. În acest fel putem vizualiza pentru fiecare funcţie ce funcţii apelează
sau din ce funcţii este apelată. Sintaxa comenzii este:

$cflow [-r] [-ix] fisier…

unde parametrii opţionali au următoarea semnificaţie:


-r are ca efect furnizarea informaţiei în forma apelat-apelant
-ix are ca efect tratarea, în plus, a obiectelor externe şi statice

1.11 Alte comenzi utlizabile pentru dezvoltarea aplicaţiilor


Sistemele de operare de tip UNIX mai conţin şi alte comenzi destinate
dezvoltării aplicaţilor cum ar fi: size, ce afişează informaţii despre dimensiunea
executabilului şi a spaţiului de date folosit de acesta, prof şi time, pentru evaluarea
performanţelor în urma execuţiei, trace şi truss, pentru trasarea de informaţii pe ieşirea
standard în timpul execuţiei în scopul monitorizării acesteia, precum şi depanatoare
simbolice. Aşa cum este bine ştiut, depanatoarele simbolice reprezintă un instrument
indispensabil pentru toţi proiectanţii de aplicaţii. Din păcate, aceste instrumente sunt
implementate în mod diferit pe diferite versiuni ale sistemului de operare UNIX. De
exemplu xdb pe HP_UX (ce utilizează biblioteca grafică pentru terminale alfanumerice
curses), dbx pe SYSTEM V şi gdb pe LINUX, ce funcţionează pe terminale alfanumerice
în mod linie de comandă sau diferite alte versiuni ce posedă interfaţă de tip “fereastră” (X-
WINDOWS) cu utilizatorii. Din aceste considerente, nu vom prezenta aceste instrumente
în prezentul material.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 21
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

1.12 Intrebari si teste

Testul de autoevaluare nr. 1

1. Scrieti comanda de compilare a unui program alcatuit din 3 fisiere


sursa p1.c, p2.c, p3.c, executabilul avand numele myexe

Răspunsurile
la test se vor
da în spaţiul
liber din
chenar, în
continuarea
enunţurilor

2. Scrieti comanda de compilare a unui program alcatuit din 3 fisiere


sursa p1.c, p2.c, p3.c si care foloseste biblioteca bib1.a, executabilul
avand numele myexe

Raspunsurile la acest test se gasesc la pagina 21

Tema de autoinstruire nr. 1

3. Scrieti fisierul de tip makefile pentru programul de mai sus


Consultaţi bibliografia pentru a afla:

1. Avantajele legaturii dinamice.

2. Avantajele
Răspunsurile la acestfolosirii
test sefisierelor
găsesc ladepagina
tip makefile
13 a acestei unităţi de
învăţare.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 22
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

1.13 Comentarii şi răspunsuri la testele de autoevaluare

Testul 1.

1. cc –o myexe p1.c p2.c p3.c

2. cc –o myexe p1.c p2.c p3.c bib1.a

3. myexe: p1.o p2.o p3.o bib1.a

cc –o myexe p1.o p2.o p3.o bib1.a

p1.o: p1.c

cc –c p1.c
1.14 Lucrare de verificare pentru studenţi
p2.o: p2.c

cc –c p2.c

p3.o: p3.c
Descrieţi comenzile sistemul de operare UNIX, aşa cum reiese din cele de
mai suscc –c p3.c

Indicaţii de redactare:

- scrieţi pe scurt ce înseamnă fiecare funcţionalitate


- încercaţi să descrieţi pe scurt legăturile între acestea

Rezolvările, dar şi dificultăţile întâmpinate, vor fi expediate prin mail


tutorelui.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 23
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

2. INTERFATA APLICATIILOR CU SISTEMUL DE OPERARE

Obiectivele unităţii de învăţare nr. 2

După ce veţi parcurge această unitate de învăţare, veţi reuşi să


intelegeti:
 Generalitati despre sistemul de operare
 Bibliotecile sistem
 Gestionarea erorilor
 Pornirea proceselor
 Oprirea proceselor

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 24
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

2.1 Generalităţi despre sistemul de operare UNIX


Sistemul de operare UNIX gestionează, în principal, trei tipuri de obiecte: utilizatori,
fişiere şi procese.

Un utilizator este înregisrat în sistemul de operare, cu nume şi parolă, printre


altele, de către administratorul de sistem (root), caracteristicile acestuia fiind înregistrate
în fişierul /etc/passwd. În momentul înregistrării, sistemul de operare atribuie utilizatorului
nou creat un număr, numit în continuare uid. În momentul deschiderii unei sesiuni,
utilizatorul va furniza numele său, iar sistemul va recupera uid-ul prin intermediul fişierului
/etc/passwd. Un utilizator poate vizualiza uid-ul propriu prin comanda:

$id

În UNIX, noţiunea de fişier are o semnificaţie mai largă decât în celelalte sisteme
de operare. În UNIX un fişier poate reprezenta la fel de bine un fişier de date (numit în
continuare fişier de tip obişnuit), ca în celelalte sisteme de operare, dar şi un periferic sau
un director. O altă particularitate, specifică sistemelor UNIX, este aceea că noţiunea de
“nume” de fişier nu există, deşi, prin abuz de limbaj, este folosită. Fiecare periferic pe
suport magnetic are în componenţă o tabelă de i-noduri ce conţin caracteristicile fiecărui
fişier. Printre acestea se găsesc proprietarul, drepturile de acces, etc… dar şi adresele de
unde se regăsesc datele. Fiecare fişier este determinat, în mod unic, de identificatorul
perifericului şi de numărul sau de i-nod. Ce reprezinta atunci aşa zisul “nume” al fişierului?
Fişierele speciale de tip director sunt alcătuite din înregistrari care conţin, eventual, printre
altele, un nume, numit numele înregistrării din director, şi numărul de disc şi de i-nod
corespunzător. În momentul în care un fişier este invocat prin “nume”, se caută
înregistrarea din director după nume, se identifică numărul de disc şi i-nod, şi prin
intermediul informaţiilor din i-nod se accesează datele. În acest fel un fişier poate fi
înregistrat sub acelaşi nume sau sub nume diferite în diverse directoare. O astfel de
înregistrare se numeşte legatură fizică. Una din caracteristicile unui fişier este numărul
de legături fizice. O nouă legătură fizică a fişierului cu numele fisier1 , din directorul dir1
înregistrată sub numele fisier2 în directorul dir2 se poate realiza cu ajutorul comenzii:

$ln dir1/fisier1 dir2/fisier2

Această comandă încrementează numărul de legături fizice şi creează o nouă


înregistrare în directorul destinaţie. Comanda rm, cunocută, incorect, drept comanda de
“ştergere” a unui fişier are drept efect, în realitate, desfacerea unei legături fizice, şi, prin
urmare ştergerea unei înregistrări din director şi decrementarea numărului de legături
fizice. Un fişier este efectiv şters în urma acestei comenzi atunci când numărul de legături
fizice ajunge la valoarea zero şi nici un alt proces nu are deschis acest fişier pentru
scriere.

O altă particularitate a sistemului UNIX este aceea că directoarele sunt organizate


arborescent şi, mai mult, există un singur arbore de directoare spre deosebire de MS-DOS
şi WINDOWS unde fiecare disc sau partiţie are un arbore de directoare propriu (C:\, D:\,
etc…). Astfel, fişierele rezidente pe discul cu numărul 0 se află în arborescenţa de
directoare corespunzătoare rădăcinii, după care administratorul de sistem (root) poate
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 25
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

declara prin intermediul comenzii mount, unul din directoarele ce se situează pe ultimul
nivel al arborescentei, drept rădăcina pentru arborescenţa corespunzătoare fişierelor de pe
un disc secundar. De asemenea, cu ajutorul aceleiaşi comenzi mount , se poate monta pe
un director situat pe ultimul nivel al arborescenţei, arborele de directoare corespunzător
unui director aflat pe o altă maşina din reţea utilizând sistemul NFS (Network File System).
În acest fel, pentru utilizator, este transparent suportul fizic pe care se află un fişier.
Orice director conţine, în mod obligatoriu, două intrări: una cu numele “.”
(corespunzătoare propriului i-nod) iar alta cu numele “..” (corespunzătoare
i-nodului directorului părinte). Aceste două intrări nu pot fi şterse, iar un director se
consideră “gol” atunci când conţine numai aceste două intrări şi numai în acest caz
ştergerea directorului cu ajutorul comenzii rmdir reuşeste.
Un proces ia naştere prin lansarea în execuţie a unui fişier de tip executabil. O
caracteristică a sistemelor UNIX este faptul că procesele sunt organizate arborescent. Mai
precis, la lansare, sistemul alocă procesului o nouă intrare în tabela de procese active,
atribuiindu-i acestuia un număr de identificare numit în continuare pid. Printre
caracteristicile unui proces se numără pid-ul, identificatorul procesului părinte, numt în
continuare ppid şi directorul curent, moştenit de la procesul părinte, adică directorul ce
serveşte la identificarea fişierelor referite prin referinţă relativă (al căror nume nu începe cu
“/”). Bineînţeles că, după lansare, procesul poate modifica directorul curent moştenit de la
procesul tată.

Altă caracteristică moştenită de la procesul părinte este lista variabilelor de mediu


(environment) definite şi valorile acestora.

La terminare orice proces emite către procesul părinte un cod reprezentând o


valoare întreagă. Rolul acestei valori este acela de a informa procesul părinte despre
modul în care s-a terminat fiul. După execuţia unei comenzi lansate din SHELL, se poate
vizuliza codul de retur primit de procesul executat prin intermediul comenzii:

$echo $?

Uzual, cod de retur 0 înseamna terminare normală a procesului.

Întrebare: Care sunt obiectele principale cu care lucreaza sistemul de operare UNIX?

Răspuns: Utilizatori, fisiere si procese.

2.2 Bibliotecile sistem

Bibliotecile sistem sunt furnizate sub formă de biblioteci arhivă sau partajată. La
compilare se face impilicit editarea de legături cu biblioteca libc.a (.so, .sl, etc). Alte
biblioteci trebuie menţionate explicit în comanda de compilare. De exemplu, biblioteca

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 26
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

matematică libm.a (.so, .sl, etc) trebuie menţionată în comanda de compilare cu ajutorul
opţiunii –lm.

Pe lângă bibliotecile în format obiect, sistemul oferă programatorului fişiere sursă de


tip header (terminate cu extensia .h) ce conţin macro-definiţii, definiţii de tipuri de date şi
prototipuri de apeluri sistem. Incluziunea acestor fişiere în sursele C asigură
compatibilitatea cu apelurile sistem din biblioteci.

Menţionăm că în aceste fişiere de tip header sunt re-definite diverse tipuri de date
de tip întreg (char, short, int, long, etc…), în funcţie de particularităţile platformei. De
exemplu în locul definiţiei apelului

int getpid();

ce returnează pid-ul procesului curent, în fişierul <unistd.h> este definit tipul de date
pid_t, iar apelul este descris sub forma

pid_t getpid();

Facem convenţia ca, pentru tipurile de date care nu sunt descrise explicit, să le
considerăm de tip întreg, adaptate la particularităţile platformei.

Întrebare: Care este diferenta intre fisiere header si biblioteci?

Răspuns: Fisierul header este de tip text si contine definitii care sa permita compilatorului
generarea corecta a codului ce face apel la functiile din biblioteca

2.3 Gestionarea erorilor


Fără alte specificaţii suplimentare, pentru apelurile sistem care returnează int,
codul de retur este 0 în caz de success şi –1 în caz de eroare. Pentru apelurile ce
returnează pointeri de orice tip, codul de retur NULL, reprezintă eşec. Constituie o gravă
eroare de programare faptul de a nu testa dacă un apel sistem s-a soldat cu eşec. În caz
că dorim să aflăm mai multe informaţii despre cauza insuccesului unui asemenea apel
putem utiliza variabila globală:
extern int errno;
a cărei declaraţie se găseşte în fişierul header errno.h. În urma fiecărui apel sistem
valoarea acestei variabile se modifică, şi anume cu 0 dacă apelul a fost încununat de
succes sau cu o valoare specifică tipului de eroare, altfel. În acelaşi fişier header, errno.h,
se găsesc definiţii pentru diverse tipuri de erori, cum ar fi, de exemplu:
#define EACCES 13
pentru eroarea ce survine la accesarea unui fişier asupra căruia procesul nu are drepturile
de acces necesare. Din nou semnalăm că teste de forma
if (errno == 13)
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 27
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

în loc de
if (errno == EACCES)
constituie o greşeală de programare deoarece pe alte versiuni de sistem, EACCES poate
să fie definită cu o altă valoare în errno.h.
În caz că un apel sistem se soldează cu eroare, putem obţine pe ieşirea standard a
programului un mesaj explicativ despre cauza erorii, pornind de la valoarea variabilei
errno, utilizând următorul apel:
void perror(char *mesaj_utilizator);
Parametrul mesaj_utilizator reprezintă un şir de caractere ales de utilizator ce va fi
afişat în urma apelului lui perror, urmat de explicaţia furnizată de sistemul de operare. De
exemplu în urma apelului:
perror(“Nu s-a putut deschide fisierul sursa”);
se va afişa pe ecran textul Nu s-a putut deschide fişierul sursă urmat de explicaţia
sistemului. Dicţionarul ce realizează corespondenţa între valorile posibile ale lui errno şi
mesajele explicative poate fi accesat şi direct prin intermediul altor două variabile globale
declarate, de asemenea, în errno.h, şi anume:
extern char **sys_errlist;
ce reprezintă vectorul mesaje explicative şi
extern int sys_errno;
ce reprezintă numărul de elemente din sys_errlist. De exemplu mesajul corespunzător
valorii EACCESS a lui errno poate fi recuperat cu ajutorul expresiei
sys_errlist[EACCESS].

Întrebare: Care este fisierul header ce trebuie inclus pentru a accesa variabila errno?

Răspuns: errno.h

2.4 Aspecte generale despre pornirea si oprirea proceselor


Folosind opţiunea –v la lansarea comenzii cc pentru obtinerea unui executabil,
observăm ca în linia de comandă pentru editarea de legaturi ld, pe lângă obiectele,
bibliotecile specificate de utilizator şi bibliotecile sistem, mai intră şi alte fişiere obiect ale
sistemului printre care şi crt0.o. Acest modul conţine funcţia start ce reprezintă punctul de
pornire la lansarea procesului în execuţie. Funcţia start face apel la funcţia main, scrisă
de utilizator. main are următorul prototip:
int main(int argc, char **argv, char **arge)
unde parametrii au semnificaţia următoare:
- argc este numărul de argumente
- argv este un vector de şiruri de caractere reprezentând valorile celor
argc argumente. În plus, argv[argc] are valoarea NULL.
- arge este un vector de şiruri de caractere ce reprzintă variabilele de

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 28
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

mediu (environment) şi valorile acestora. Şirurile de caractere din arge au sintaxa


nume_variabila=valoare_variabila, iar numărul de elemente din acest vector este
determinat de primul indice I pentru care arge[I] are valoarea NULL.
De multe ori dacă nu se doreşte folosirea acestor parametrii, fie a lui arge
fie a tuturor trei, aceştia se pot omite din declaraţia funcţiei main.
Să presupunem că executabilul are numele a.out şi îl lansăm în execuţie
cu ajutorul comenzii:
$a.out alpha beta gama
Atunci argc va avea valoarea 4 deoarece argv[0] va fi întotdeauna numele
executabilului. În plus argv[1]=alpha, argv[2]=beta, argv[3]=gama, argv[4]=NULL. arge va
avea drept valori lista variabilelor de mediu şi valorile acestora moştenite de la procesul de
tip shell prin care a fost lansat procesul. Aceste variabile se pot vizualiza cu ajutorul
comenzii
$env
În caz că nu s-a folosit al treilea argument în main, pe parcurs se pot recupera valorile
variabilelor de mediu fie cu ajutorul variabilei globale declarata în stdlib.h
extern char **environ;
ce are aceeaşi structură cu arge. În plus, pentru citirea valorii unei variabile de mediu se
poate folosi apelul:
#include <stdlib.h>
char *getenv(char *nume_variabila);
ce returnează adresa unui şir de caractere corespunzător valorii variabilei de mediu al
cărei nume a fost furnizat în parametru. În caz că variabila cu numele specificat nu se află
în lista variabilelor, codul de retur este NULL.
Un proces poate defini alte varbiabile de mediu sau modifica valoarea unora deja
existente cu ajutorul apelului:
#include <stdlib.h>
int putenv(char *nume_variabila);
unde parametrul nume_variabila trebuie să aiba sintaxa variabila=valoare.
Pentru terminarea unui proces se poate folosi apelul:
#include <stdlib.h>
void exit(int valoare);
unde valoare reprezintă codul de retur ce urmeaza a fi transmis procesului părinte. În
stdlib.h este definită constanta EXIT_SUCCES ce are valoarea 0. Funcţia exit, după apel,
va executa, în această ordine, următoarele activităţi:
- execută toate funcţiile înregistrate de a fi executate înainte de
terminare cu ajutorul apelului:
#include <stdlib.h>
int atexit(void (*functie)());

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 29
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

unde parametrul reprezintă funcţia de executat, ce nu trebuie să aibă parametrii şi să fie


de tip void. La apelul lui exit, funcţiile instalate cu atexit, se vor executa în ordinea inversă
celei în care au fost înregistrate. Exista o limitare a numărului de funcţii înregistrate cu
ajutorul lui atexit, definit în limits.h cu ajutorul macrodefiniţiei constantei ATEXIT_MAX.
- închide toate fişierele deschise cu apel la fopen şi şterge toate
fişierele temporare create cu apel la tmpfile.
- apelează funcţia _exit cu parametrul valoare.
Terminarea unui proces se poate face şi cu ajutorul apelului
#include <unistd.h>
void _exit(int valoare);
Acest apel va executa, în această ordine, următoarele activităţi:
- inchide toate fişierele deschise cu apel la open şi toate directoarele
deschise cu ajutorul aplelui opendir.
- eliberează toate resursele sistem folosite de proces.
- dacă procesul este lider de grup, emite semnalul SIGHUP către toate
procesele din grup.
- toate procesele fiu o să aibă procesul sistem init, având pid-ul 1,
drept proces tată
- codul de retur dat de parametrul valoare este emis către procesul tată
şi se transmite acestuia semnalul SIGCHLD. Procesul rămâne în tabela de procese active
în starea defunct sau zombie până când procesul tată recuperează codul de retur prin
intermediul unuia dintre apelurile wait sau waitpid.
Efectul unei instrucţiuni în main de forma
return valoare;
are acelaşi efect ca şi apelul:
exit (valoare);
în caz că nu s-au făcut apeluri recursive la main, sau, mai precis, suntem în varful stivei
de apeluri.
În caz că procesul se termină fără apel la exit, _exit sau în urma unui return în
main la terminare se execută aceleaşi acţiună ca în urma apelului
_exit(0);.

Întrebare: Care este efectul instructiunii return k in functia main?

Răspuns: Este acelasi cu exit (k) daca main nu este apelata recursiv.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 30
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

2.5 Intrebari si teste

Tema de autoinstruire nr. 2

Consultaţi bibliografia pentru a afla:

1. Descrierea amănunţită a gestiunii erorilor.

2. Pornirea si oprirea proceselot.

Testul de autoevaluare nr. 2

1. Care sunt codurile de eroare ce pot fi returnate de apeluri sistem?

Răspunsurile
la test se vor
da în spaţiul
liber din
chenar, în
continuarea
enunţurilor
2. Cum se lansează un nou program în UNIX?

3. Cum se pot opri procesele UNIX?

Raspunsurile se gasesc la pagina 30

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 31
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

2.8 Comentarii şi răspunsuri la testele de autoevaluare

Testul 2.

1. -1 pentru apelurile care returneaza int si NULL pentru apelurile care


returneaza pointer.

2. Din modulul obiect crt.o se va invoce functia main

3. Terminarea functiei main sau apel explicit la una din functiile exit sau
_exit

2.9 Lucrare de verificare pentru studenţi

Realizaţi un program C sub UNIX care citeşte de la tastatură un şir de


maxim 100 de caractere, si le afiseaza pe ecran

Indicaţii de redactare:

- scrieţi comentarii
- se vor testa toate codurile de eroare posibile

Rezolvările, dar şi dificultăţile întâmpinate, vor fi expediate prin mail


tutorelui.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 32
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

3. GESTIUNEA INTRARILOR SI IESIRILOR

Obiectivele unităţii de învăţare nr. 3

După ce veţi parcurge această unitate de învăţare, veţi reuşi să:


 Înţelegeţi notiunea de fisier UNIX
 Cunoasteti realizarea operatiilor de baza cu fisiere
 Operatii specifice UNIX asupre fisierelor
 Utilizarea bibliotecii sistem si a bibliotecii C standard

3.1. Generalităţi
Aşa cum am arătat în capitolul precedent, noţiunea de fişier are, în UNIX, un înţeles
mai larg decât în alte sisteme de operare. În UNIX prin fişier se înţelege o resursă sistem,
care poate să corespundă unui periferic cum ar fi: disc, cititor de bandă, terminal sau
imprimantă sau unui fişier pe suport magnetic, în sensul obişnuit al cuvântului. Fişierul
este identificat unic de numărul de disc şi de numărul de i-nod, şi poate să fie înregistrat
sub diverse nume, în diverse directoare (cataloage).
O caracteristică a fişierelor UNIX este tipul acestora, care poate fi:

- fişier de tip obişnuit (regular file), care reprezintă fişierele de date


propriu-zise.

- director, care memorează legătura dintre numerele de i-nod şi


numele atribuite fişierelor (numele legăturilor fizice).

- tub, reprezentând obiecte de comunicare unidirecţională între


procese, în sensul că un proces scrie date iar celălalt citeşte date din tub.

- fişier special corespunzător perifericelor fizice. Acestea, la rândul lor,


pot fi de tip bloc (corespunzător perifericelor a căror scriere sau citire se face în mod bloc
prin intermediul cache-urilor sistemului cum ar fi discul sau cititorul de bandă) sau caracter
(corespunzător perifericelor a căror scriere sau citire se face în mod direct fără a utiliza
cache-urile sistemului, cum ar fi terminalul sau imprimanta).

- socket. Resurse de comunicare bi-direcţională între procese care


pot să fie executate, eventual, pe maşini diferite din reţea.

- legături simbolice. Această noţiune o vom explica în continuare.


Aşa cum am arătat în capitolul precedent o legătură fizică se poate crea cu

ajutorul comenzii:
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 33
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

$ln nume_sursa nume_destinatie

Această comandă nu creează un i-nod nou, ci doar o intrare nouă într-un

director. Legătura simbolică se creează cu ajutorul comenzii:

$ln –s nume_sursa nume_destinatie

În acest caz, se creează un i-nod nou, care, însă, nu are propria sa zonă de date.
Adresa corespunzătoare zonei de date din i-nod conţine adresa i-nodului corespunzător
fişierului sursă. La exploatarea unei legături simbolice, se acţionează asupra informaţiilor
fişierului sursă.

Montarea arborescenţei de directore corespunzatoare unui nou disc în structura de


directoare a sistemului se poate face cu ajutorul comenzii:

$mount nume_fisier_special nume_director

Cu ajutorul comenzii :
$mount nume_masina:nume_director_sursa nume_ director_destinatie

se poate monta în sistemul de directoare de pe maşina locală, arborescenţa de directoare


corespunzătoare unui director situat fizic pe altă maşină. Aceste fişiere pot fi exploatate pe
maşina locală utilizând sistemul NFS.
Intern, gestiunea la fişierelor se face prin intermediul a trei tabele, şi anume:

a. Tabela de descriptori. Aceasta este proprie fiecărui proces. Fiecărui


fişier deschis îi corespunde o intrare în această tabela, indicele acesteia fiind considerat
descriptorul fişierului. Caracteristicile memorate de intrările din aceasta tabela pot fi citite
prin intermediul structurii ofile_t descrisă în “sys/user.h”. La lansare, orice proces are, din
start, deschise trei fişiere, corespunzătoare descriptorilor 0, 1 si 2 care sunt : intrarea
standard (standard input-stdin), ieşirea standard (standard output-stdout) şi ieşirea pentru
erori (standard error-stderr). Implicit, aceste fişiere corespund terminalului de lansare.
Aceşti descriptori pot fi referiţi prin constantele simbolice STDIN_FILENO,
STDOUT_FILENO şi STDERR_FILENO.

b. Tabela de fişiere deschise. Această tabelă este partajată de către toate


procesele. Orice intrare din tabela de descriptori a oricărui proces memorează adresa unei
intrări din această tabelă. La deschiderea unui fişier de către un proces, se crează o nouă
intrare în tabela de descriptori a procesului şi o nouă intrare în tabela de fişiere deschise.
Intrările în această tabelă memorează:

- numărul total de descriptori ce pointează către ea.


- modul de deschidere al fişierului
- poziţia curentă (offset)
- un pointer către o intrare în a treia tabelă şi anume:
c. Tabela de i-noduri din memorie. La deschiderea unui fişier, în caz că
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 34
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

datele despre i-nodul corespunzător nu se găsesc deja în această tabelă, se creează o


nouă intrare. Intrările din această tabelă memorează:

- numărul total al intrărilor din tabela de fişiere deschise ce pointează


către aceasta.

- identificatorul discului de origine


- numărul de i-nod
- starea acestuia : daca informaţiile din i-nod au fost modificate ulterior
incărcarii acestuia în memorie sau dacă un proces a impus un blocaj.

Întrebare: Care sunt principalele tabele folosite de sistemul de gestiune a fisierelor?

Răspuns: Tabela de descriptori, tabela de fisiere deschise in memorie, tabela de i-noduri


din memorie

3.2. Gestionarea atributeleor i-nodului


Din SHELL atributele i-nodurilor se pot vizualiza cu ajutorul comenzii

ls –l.

Programatorii au la dispoziţie structura următoare:

#include <sys/types.h>

#include <sys/stat.h>

struct stat {

dev_t st_dev; /* numarul de disc */

ino_t st_ino; /* numarul de i-nod */

mode_t st_mode; /* tip si drepturi de acces */

nlink_t st_nlink; /* numarul de legaturi fizice */

uid_t st_uid; /* uid-ul proprietarului */

gid_t st_gid; /* id-ui grupului proprietar */

off_t st_size; /* dimensiunea in octeti */

time_t st_atime; /* data ultimului acces */

time_t st_mtime; /* data ultimei modificari */

time_t st_ctime; /* data ultimelor modificari ale

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 35
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

caracteristicilor i-nodului */

};

Toate tipurile de date corespunzătoare câmpurilor sunt de tip întreg şi definite în


headere. Cu această ocazie, menţionăm că tipul de date time_t exprimă numărul de
secunde scurse de la 1 Ianuarie 1970, data de referinţă penru sistemele de tip UNIX. Cu
această explicaţie considerăm că este suficient de clar modul de interpretare a valorii
câmpurilor, cu exceptia lui st_mode care memorează pe biţi, atât tipul fişierului, cât şi cele
trei tipuri de drepturi (citire, scriere execuţie) corespunzător celor trei categorii de
utilizatori: proprietar (user), grup proprietar (group) şi ceilalţi (other).

Exploatarea acestui câmp se face cu ajutorul unor măşti definite prin constante
simbolice şi anume:

S_IRUSR drept de citire pentru proprietar

S_IWUSR drept de scriere pentru proprietar

S_IXUSR drept de lansare în execuţie pentru proprietar.

Valorile tuturor celor trei biţi corespunzători drepturilor proprietarului pot fi recuperate cu
ajutorul măştii S_IRWXU.

S_IRGRP drept de citire pentru grupul proprietar

S_IWGRP drept de scriere pentru grupul proprietar

S_IXGRP drept de lansare în execuţie pentru grupul proprietar. Valorile tuturor


celor trei biţi corespunzători drepturilor grupului proprietar pot fi recuperate cu ajutorul
măştii S_IRWXG.

S_IROTH drept de citire pentru ceilalţi utilizatori

S_IWOTH drept de scriere pentru ceilalţi utilizatori

S_IXOTH drept de lansare în execuţie pentru ceilalţi utilizatori Valorile tuturor


celor trei biţi corespunzători drepturilor pentru ceilalţi utilizatori pot fi recuperate cu ajutorul
măştii S_IRWXO.

Mai avem la dispoziţie două măşti pentru recuperarea, din acelaşi câmp a altor
două caracterisitici pe bit : set-uid (masca S_ISUID) şi set-gid (masca S_ISGID).
Semnificaţiile acestor două informaţii le vom explica ulterior.

De exemplu, masca pentru drepturile rwxr-x—x va fi alcatuită cu una din


următoarele două disjuncţii:

S_IRWXU | S_IRGRP | S_IXGRP | S_IXOTH

sau

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 36
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IXOTH.

Testul pentru tipul de fişier se poate face atât cu ajutorul unor măşti dar şi
cu ajutorul unor expresii macro-definite (macro-uri), ce reprezinta expresii ce dau ca
rezultat 1 sau 0, în funcţie de faptul daca fişierul este de tipul respective sau nu.

Tip de fişier Masca Macro__


Obişnuit _S_IFREG S_ISREG

Special bloc _S_IFBLK S_ISBLK

Special caracter _S_IFCHR S_ISCHR

Director _S_IFDIR S_ISDIR

Tub _S_IFIFO S_ISFIFO

Legatură simbolică S_IFLNK S_ISLNK

Socket S_IFSOCK S_ISSOCK

Menţionăm că nu este o eroare faptul că numele simbolic al ultimelor două măşti nu


începe cu caracterul „_‟. Acestea provin din versiuni anterioare normei POSIX.

Exemplu

struct stat st;

…………

if (st.st_mode & _S_IFDIR) {

……… /* test dacă fişierul este de tip director */

if (S_ISDIR(st.st_mode) {

……… /* altă alternativă a aceluiaşi test */

if (st.st_mode & (S_IRGRP | S_IXGRP) ==

S_IRGRP | S_IXGRP) {

……… /* test dacă grupul proprietar are drept de

citire şi scriere */

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 37
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Atenţie. Macro-definiţiile prezentate mai sus sunt valabile pe HP-UX. Pe alte


versiuni avem la dispoziţie maco-definiţii similare, dar este posibil ca numele acestora să
difere, în mică masură, ce-i drept. Sfătuim programatorul să consulte de fiecare dată
manualele de utilizare ale versiuni de system de operare cu care lucrează.

Apelurile cu care se pot încărca datele referitoare la un i-nod într-o structură de tipul
stat sunt

#include <sys/types.h>

#include <sys/stat.h>

int stat (char *nume-fisier, struct stat *ptr_stat);

int fstat (int *desc, struct stat *ptr_stat);

Ambele apeluri încarcă în structura a cărei adresă este dată în al doilea parametru,
caracteristicile i-nodului fişierului desemnat de primul parametru. Diferenţa între cele două
apeluri este că la primul fişierul este referit prin nume, pe când la al doilea fişierul este
referit prin numărul unei intrări valide din tabela de descriptori. Pentru succesul acestor
apeluri, nu sunt necesare drepturi asupra fişierului, ci numai drepturile de rigoare asupra
directoarelor ce intervin în localizarea fişierului invocat prin nume. În caz că aceste drepturi
nu există, variabila errno va lua valoarea EACCESS. O altă cauză posibilă a insuccesului
acestor doua apeluri este inexistenţa fişierului referit. În acest caz, errno va lua valoarea
ENOENT.

Apelul următor testează drepturile de acces ale proprietarului procesului asupra


unui fişier:

#include <unistd.h>

int acces (char *nume_fisier, int tip_drepturi);

Primul parametru indică numele fişierului, iar al doilea este alcătuit


dintr-o disjuncţie formată din elemente ale listei de constante simbolice de mai jos:
R_OK - drept de citire
W_OK - drept de scriere
X_OK - drept de lansare în execuţie
F_OK - simplu test de existenţă
Apelul returnează 0 dacă există drepturile cerute şi –1 altfel.
Crearea unei legături fizice, altfel decât cu ajutorul comenzii ln, se poate realiza cu
ajutorul apelului:
#include <unistd.h>

int link (char *nume_fisier_sursa, char *nume_fisier_destinatie);

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 38
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Limitările acestui apel sunt următoarele:


- fişierul sursă nu poate fi director. Un asemenea apel nu va reuşi decât
pentru procesele având drept proprietar pe root.
- fişierul destinaţie nu trebuie să existe
- ambele fişiere trebuie să se gasească pe acelaşi disc, deşi pe anumite
versiuni apelul funcţionează fără ca această condiţie să fie îndeplinită.
Ştergerea unei legături fizice, altfel decât cu ajutorul comenzii rm, se poate realiza
cu ajutorul apelului:
#include <unistd.h>

int unlink (char *nume_fisier);

Reamintim că un fişier este efectiv şters dacă numărul de legături fizice ajunge 0 şi nici un
proces nu mai are un descriptor deschis corespunzător acestui fişier.

Redenumirea unui fişier, sau ca să fim mai precişi, a numelui unei legături fizice,
altfel decât cu ajutorul comenzii mv, se poate realiza cu ajutorul apelului:
#include <unistd.h>

int rename (char *nume_fisier_sursa, char *nume_fisier_destinatie);

Restricţiile acestui apel sunt următoarele:

- dacă fişierul destinaţie există, acesta trebuie să fie de acelaşi tip cu


fişierul sursă.

- nu se pot redenumi legăturile “.” si “..”


- dacă destinaţia există şi este de tip director, acesta va fi suprimat. În
consecinţă trebuie să fie gol, adică să nu conţină decât intrările cu numele “.” şi “..”.

- ca şi în cazul apelului link, anumite versiuni cer ca ambele fişiere să


se găsească pe acelaşi disc. În cazul versiunilor ce nu impun această restricţie, apelul va
crea un nou i-nod şi va recopia fişierul pe discul de destinaţie.

Doar două dintre caracteristicile i-nodului pot fi schimbate.

Prima este reprezentată de drepturile de acces (comanda SHELL echivalentă


chmod). Modificarea acesteia se realizează cu ajutorul comenzii:

#include <sys/types.h>

int chmod (char *nume-fisier, mode_t drepturi_acces);

int fchmod (int desc, mode_t drepturi_acces);

Ca şi în cazul funcţiei stat, cele două apeluri diferă prin modul de referire al fişierului
specificat de primul argument (nume sau descriptor). Al doilea argument reprezintă noile
drepturi de acces şi este construit prin disjuncţie dintre constantele prezentate la
descrierea câmpului st_mode al structurii stat.
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 39
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

A doua caracteristică modificabilă a unui I-nod este reprezentată de proprietar şi


grupul proprietar (comanda shell echivalentă chown). Modificarea acesteia se realizează
cu ajutorul comenzii:

#include <unistd.h>

int chown (char *nume-fisier, uid_t proprietar, gid_t grup_proprietar);

int chown (char *nume-fisier, uid_t proprietar, gid_t grup_proprietar);

Ca şi în cazul funcţiei stat, cele două apeluri diferă prin modul de referire
al fişierului specificat de primul argument (nume sau descriptor). În caz de apel reuşit,
indicatorii set-uid şi set-gid sunt resetaţi automat.
Menţionăm faptul că apelurile pentru schimbarea atributelor unui i-nod nu reuşesc
decăt dacă proprietarul procesului este acelaşi cu proprietarul fişierului sau dacă
proprietarul procesului este root.
Anumite apeluri sistem pot avea drept efect lateral crearea de noi i-noduri.
Enumerăm unele dintre acestea, în funcţie de tipul noului i-nod creat:
- fişiere de tip obişnuit : open (în anumite circumstanţe) şi creat.
- director : mkdir.
- tuburi: pipe si mkfifo.
- legături simbolice : symlink.
Pentru crearea de noi i-noduri generice mai avem la
dispoziţie apelul:
#include <sys/stat.h>
int mknod(char *nume_fisier, mode_t drepturi_acces);
Al doilea argument descrie tipul şi drepturile de acces şi este este construit prin disjuncţie
dintre constantele prezentate la descrierea câmpului st_mode al structurii stat.

Întrebare: Cum se numeste campul structurii stat care memoreaza tipul fisierului si
drepturile de acces asupra acestuia

Răspuns: st_mode

3.3. Operaţiunile de bază asupra fişierelor: deschidere, închidere, citire, scriere

Primitiva cu ajutorul căreia se deschid fişiere este

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

int open (char *nume_fisier, int mod_deschidere, mode_t drepturi)


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 40
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Menţionăm faptul că al treilea parametru nu este obligatoriu de furnizat decât atunci


când este posibilă crearea unui nou fişier (i-nod) şi, în acest caz, acest argument
reprezintă drepturile de acces şi este construit prin disjuncţie dintre constantele prezentate
la descrierea câmpului st_mode al structurii stat.

Acest apel este recomandabil, în special, pentru fişierele de tip obişnuit. Pentru alte
tipuri de fişiere există apeluri specifice pentru deschidere.

Acest apel poate deveni, în anumite condiţii, blocant. De exemplu, deschiderea


pentru scriere a unui tub fără să existe un descriptor deschis pentru citire. În acest caz
apelul aşteaptă până ce un alt proces face o deschidere pentru citire. Parametrul al doilea
se furnizează ca o disjuncţie între constante simbolice din lista de mai jos:

Obligatoriu una şi numai una dintre O_RDONLY (deschidere

exclusiv pentru citire), O_WRONLY (deschidere exclusiv pentru scriere) şi O_RDWR


(deschidere pentru citire şi scriere simultan).

O_TRUNC dacă fişierul există şi este de tip obişnuit, informaţia

conţinută în acesta se trunchiază, adică i se reduce dimensiunea la zero octeţi.

O_CREAT - Dacă fişierul nu există, se crează un nou i-nod de tip

obişnuit, ce are drept proprietar şi grup proprietar, proprietarul şi, respectiv grupul
proprietar al procesului, şi, ca drepturi de acces cele menţionate în al treilea parametru
filtrate prin masca de drepturi implicite pentru crearea fişierelor. Această mască se poate
vizualiza sau modifica cu ajutorul comenzii SHELL umask .

O_EXCL – Nu are efect decât în prezenţa opţiunii O_CREAT. În

acest caz existenţa prealabilă a fişierului conduce la eşecul apelului (se doreşte exclusiv
creare de fişier nou şi nicidecum deschiderea unui fişier existent). Ca apelul sa fie
încununat de succes trebuie să existe drepturi de scriere în director.

O_NOCTTY – Dacă fişierul deschis corespunde unui terminal,

acesta nu devine terminal de control al procesului.

O_APPEND – Toate scrierile se fac la sfărşitul fişierului. În cazul

unei operaţiuni de scriere, poziţia curentă se mută la sfârşitul fişierului.

O_NONBLOCK – Deschiderile blocante eşuează, adică se iese din

apel fără a se mai aştepta înlăturarea cauzei blocajului, se furnizează cod de retur –1
către apelant, iar errno ia valoarea EAGAIN. Citirile şi scriereile ulterioare se tratează, de
asemenea, în mod neblocant.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 41
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

O_SYNC – Scrierile blochează procesul până la copierea datelor pe

disc.

O_NDELAY - Citirile şi scrierile blocate eşuează, adică se iese din

apel fără a se mai aştepta înlăturarea cauzei blocajului, se furnizează cod de retur –1
către apelant, iar errno ia valoarea EAGAIN.

În caz de eroare se returnează –1, iar în caz de succes se crează o nouă intrare în
tabela de descriptori şi se returnează indexul acesteia. Un cod de retur ne-negativ
înseamnă succes şi reprezintă descriptorul ce va fi folosit la operaţiile următoare. În plus:

poziţia curentă în fişier este 0.

se crează o nouă intrare în tabela de fişiere deschise ce are un

descriptor ce pointează spre aceasta.

în intrarea din tabela de i-noduri din memorie corespunzătoare

fişierului deschis, numărul de intrari din tabela de fişiere deschise ce pointează către
aceasta este incrementat.

descriptorul rămâne deschis până la terminarea procesului, mai puţin

situaţia în care s-a cerut în mod expres închiderea sa prin apel la primitiva close.

Observaţii.

Testul asupra drepturilor de acces se face numai la deschidere. Odată

descriptorul deschis, acesta rămâne valid în ciuda unor modificări ulterioare ale drepturilor
de acces efectuate de alt proces.

Nu este garantat faptul că valoarea de retur reprezintă indexul celui

mai mic descriptor nefolosit, chiar dacă experimental se poate constata acest fapt.

O deschidere în modul O_RDWR nu este acelaşi lucru cu două

deschideri, una în mod O_RDONLY şi alta în mod O_WRONLY, deoarece în acest din
urmă caz cei doi descriptori gestionează fiecare propria sa poziţie curentă.

Apelul urmator :

#include <sys/types.h>

#include <sys/stat.h>

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 42
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <fcntl.h>

int creat (char *nume_fisier, mode_t drepturi);

este echivalent cu

open(nume_fisier, O_WRONLY | O_CREAT | O_TRUNC, drepturi);

Primitiva folosită pentru inchiderea fişierelor este

#include <unistd.h>

int close (int desc);

unde argumentul reprezintă descriptorul recuperat cu open. Efectele acestui apel sunt
următoarele:

- se ridică toate blocajele puse de proces asupra fişierului, chiar dacă


acestea au fost puse prin intermediul altui descriptor.

- se va decrementa numărul de descriptori din intrarea corespunzătoare


din tabela de fişiere deschise

- dacă acest număr devine nul, se eliberează intrarea din tabela de


fişiere deschise şi în intrarea din tabela de i-noduri din memorie se decrementează
numărul de intrări din tabela de fişiere deschise ce pointează spre aceasta

dacă numărul de mai sus devine nul, se eliberează intrarea din tabela

de i-noduri din memorie şi în caz că numărul de legături fizice ale fişierului este, de
asemenea, nul, fişierul este şters.

Citirea fişierelor se face cu ajutorul apelului:

#include <unistd.h>

ssize_t read (int desc, void *ptr, size_t nr_oct);

Acest apel citeşte din fişierul desemnat de descriptorul desc, nr_oct

octeţi, pe care îi va depune în memorie la adresa desemnată de ptr. Pentru fişiere de tip
obişnuit, procedura de citire se desfăşoară astfel:

Se returnează –1 în caz de eşec (descriptor invalid sau care nu este

deschis în citire, de exemplu)

Dacă nu există un blocaj care să împiedice citirea atunci: Fie rest_oct

numărul de octeţi de la poziţia curentă până la sfărşitul fişierului. Se vor citi k = min(nr_oct,
rest_oct). Codul de retur va fi k, adică numărul de octeţi efectiv citiţi. Poziţia curentă se va

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 43
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

incrementa cu k. Remarcăm că dacă poziţia curentă era de la început la sfârşitul fişierului,


valoarea returnată va fi 0.

Dacă există un blocaj care sa împiedice citirea datelor, apelul rămâne

blocat în aşteptarea ridicării blocajului, în situaţia în care indicatorii O_NONBLOCK sau


O_NDELAY nu au fost poziţionaţi la deschidere. În caz contrar, codul de retur este –1, iar
errno ia valoarea EAGAIN.

Exemplu

Presupunem că avem un fişier care are drept înregistrări date de tipul

typedef struct {

char a[20];

long b;

int c;

} MY_STR;

Pentru a citi 40 de asemenea structuri putem folosi secvenţa:

int nr;

MY_STR *ptr;

ptr = (MY_STR *)malloc (40 * sizeof (MY_STR));

while ((nr=read(desc, ptr, 40 * sizeof (MY_STR))>0){

…..

unde nr împarţit la sizeof (MY_STR) reprezintă numărul de structuri efectiv citite. În caz că
acesta este mai mic decât 40, în mod sigur, la următorul apel codul de retur va fi 0,
deoarece s-a ajuns la sfârşitul fişierului.

Scrierea în fişiere se face cu ajutorul apelului:

#include <unistd.h>

ssize_t write (int desc, void *ptr, size_t nr_oct);

Acest apel scrie în fişierul desemnat de descriptorul desc, nr_oct octeţi,

citiţi din memorie de la adresa desemnată de ptr. Pentru fişiere de tip obişnuit, procedura
de scriere se desfăşoară astfel:

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 44
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Se returnează –1 în caz de eşec (descriptor invalid sau care nu este

deschis în citire, de exemplu)

Dacă nu există un blocaj care să împiedice scrierea, atunci octeţii se

vor scrie în fişier, la sfârşitul acestuia sau la poziţia curentă, în funcţie de faptul dacă
indicatorul O_APPEND a fost poziţionat sau nu. Funcţia returnează numărul de octeţi
scrişi. În caz că se returnează mai puţin de nr_oct, aceasta semnifică eroare (disc plin, de
exemplu). Scrierea octeţilor nu se face direct pe disc, ci prin intermediul memoriei cache,
mai puţin situaţia în care s-a folosit la deschidere opţiunea O_SYNC.

Dacă există un blocaj care să împiedice scrierea datelor, apelul

rămâne blocat în aşteptarea ridicării blocajului, în situaţia în care indicatorii


O_NONBLOCK sau O_NDELAY nu au fost poziţionaţi la deschidere. În caz contrar,
codul de retur este –1, iar errno ia valoarea EAGAIN.

Exemplu

Reluăm exemplul precedent. Scrierea a 40 de structuri de tipul MY_STRUCT se


poate realiza astfel:

MY_STRUCT vec[40];

……

/* secvenţa de iniţializare a datelor */

……

if (write(desc, vec, 40*sizeof(MY_STRUCT)) !=

40*sizeof(MY_STRUCT)) {

/* caz de eroare */

Întrebare: Ce optiune este obligatorie in al doilea argument al apelului open?

Răspuns: Una si numai una dintre O_RDONLY, O_WRONLY, O_RDWR.

3.4. Duplicarea descriptorilor


Prin duplicare, un proces poate obţine mai mulţi descriptori care pointează spre
aceeaşi intrare din tabela de fişiere deschise. Un prim apel este:

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 45
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <unistd.h>
int dup(int desc);
Paremetrul reprezintă valoarea unui descriptor valid, iar apelul returnează, în caz de
succes, valoarea noului descriptor ce duplică pe primul. Acest apel garantează duplicarea
în indexul cel mai mic corespunzător unei intrări libere în tabela de descriptori ai procesului.
Apelul:
#include <unistd.h>
int dup2(int desc1, int desc2);
duplică descriptorul valid desc1, în valoarea desc2, dacă aceasta corespunde unui
descriptor liber. În caz că această condiţie nu este realizată, se va închide fişierul
corespunzător lui desc2, în prealabil.
Răspuns: Este garantata duplicarea descriptorului in cel mai mic descriptor liber.

Întrebare: Care este unul din avantajele duplicarii unui descriptor, fata de o deschidere
obisnuita?

3.5 Controlul intrărilor-ieşirilor prin intermediul apelului fcntl

Apelul care este prezentat în acest paragraf are prototipul următor:


#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl (int desc, int comanda, parametru3)
Primul parametru reprezintă o intrare validă din tabela de descriptori, iar al doilea
desemnează comanda ce urmează a fi executată, valoarea acestuia fiind furnizată prin
intermediul unor constante simbolice. Natura acestei comenzi poate impune prezenţa celui
de al treilea parametru, tipul de date al acestuia precum şi modul de interpretare al codului
de retur (-1 înseamnă, ca de obicei, eşec).
O primă categorie de comenzi este aceea care se referă la citirea sau modificarea
atributelor descriptorului. Comanda F_GETFD, determină returnarea atributelor
descriptorului sub forma unui întreg, a cărui valoare este interpretată pe biţi. Comanda
F_SETFD, determină instalarea de noi atribute, caz în care este necesară pasarea celui
de al treilea parametru, de tip int, care conţine valoarea noilor atribute. Exemplificăm, în
continuare, modul de instalare al atributului FD_CLOEXEC, ce are ca efect închiderea
descriptorului imediat după un apel din familia exec. De altfel, acest atribut este singurul
prevăzut expres de norma POSIX ca fiind modificabil.

int desc, atribut;


…….
atribut = fcntl(desc, F_GETFD); /* citire valoare */
atribut |= FD_CLOEXEC; /* noua valoare */
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 46
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

fcntl(desc, F_SETFD, atribut); /* instalare */


……..

Altă categorie de comenzi este aceea pentru citirea sau modificarea modului de
deschidere. Se pot seta sau reseta numai indicatorii O_APPEND, O_NONBLOCK,
O_NDELAY si O_SYNC. Atragem atenţia asupra faptului că aceste caracteristici sunt ale
tabelei de fişiere deschise şi, în consecinţă, orice modificare a acestora afectează toţi
descriptorii ce pointează spre intrarea respectivă din tabela de fişiere deschise. Comanda
F_GETFL, determină returnarea modului de deschidere sub forma unui întreg, a cărui
valoare este interpretată pe biţi. Comanda F_SETFL, determină instalarea noului mod de
deschidere, caz în care este necesară pasarea celui de al treilea parametru, de tip int,
care conţine valoarea de instalat. Exemplificăm, în continuare, modul de setare şi resetare
al atributului O_APPEND.
int desc, atribut;
…….
atribut = fcntl(desc, F_GETFL); /* citire valoare */
atribut |= O_APPEND; /* setare */
fcntl(desc, F_SETFL, atribut); /* instalare */
……..
atribut = fcntl(desc, F_GETFL); /* citire valoare */
atribut &= ~O_APPEND; /* resetare */
fcntl(desc, F_SETFL, atribut); /* instalare */
……..
Comanda F_DUPFD, duplică descriptorul în intrarea liberă cu cel mai mic indice
din tabela de descriptori, care este mai mare sau egal cu valoarea celui de al treilea
parametru, care este de tip int.
Una din funcţionalităţile principale ale apelului fcntl este aceea de a gestiona
blocajele asupra fişierelor. Blocajele se înregistrează la nivelul tabelei de i-noduri din
memorie, şi este proprietatea exclusivă a procesului care l-a pus. În consecinţă, numai
acesta poate ridica blocajul. Dacă nu o face, blocajul va persista până la terminarea
procesului respectiv.
Caracteristicile unui blocaj sunt:
- zona blocată, ce poate fi între două poziţii din fişier sau de la
o poziţie din fişier până la sfârşitul fişierului.
- tipul blocajului: partajat sau exclusiv. Un blocaj partajat poate
coexista cu alte blocaje partajate, pe când existenţa unui blocaj exclusiv, împiedică
instalarea altor blocaje până la deblocare.
- modul de operare : consultativ, fără să conducă efectiv la blocarea
operaţiilor de citire/scriere, dar împiedică punerea de alte blocaje incompatibile, şi
imperativ ce duce la blocarea operaţiilor de citire/scriere în zona blocată astfel: un blocaj
imperativ partajat blochează operaţiile de scriere, iar un blocaj imperativ exclusiv
blochează atât operaţiile de scriere cât şi de citire din zona blocată.
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 47
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Pentru reprezentarea caracteristicilor unui blocaj, programatorul are la dispoziţie


urmatoarea structură definită în fişierele header:
struct flock {
short l type;
short l_whence;
off_t l_start;
off_t l_len;
pid_t pid;
};
unde valorile câmpurilor au următoarea semnificaţie:
- l_type determină tipul blocajului şi poate avea următoarele valori:
F_RDLCK, corespunzătoare tipului partajat, F_WRLCK, corespunzătoare tipului exclusiv
şi F_UNLCK ce corespunde unei cereri de deblocare. Punerea unui blocaj partajat se
poate pune numai pentru descriptorii deschişi pentru citire, iar pentru punerea unui blocaj
exclusiv este necesar ca descriptorul respectiv să fie deschis pentru scriere
- l_whence poate avea următoarele valori: SEEK_SET, ce semnifică
începutul fişierului, SEEK_CUR, ce semnifică poziţia curentă şi SEEK_END care
semnifică sfârşitul fişierului.
- Poziţia de start va fi calculată cu formula l_whence +l_start
- l_len indică lungimea zonei blocate în octeţi. Valoarea 0 a acestui
câmp semnifică faptul că zona blocată se întinde până la sfârşitul fişierului, lungimea
acesteia modificându-se odată cu dimensiunea fişierului.
- pid_t indică procesul proprietar al blocajului.
Gestiunea blocajelor se face cu ajutorul apelului fcntl, pasând drept al
treilea parametru un pointer catre o structura de tip flock, iar drept comandă una din
urmatoarele valori:
- F_SETLCK, ce semnifică o tentativă de punere a unui blocaj cu
caracter consultativ. Parametrul al treilea este de intrare, în acest caz, şi câmpurile
structurii de tip flock, trebuie umplute cu caracteristicile blocajului. Codul de retur este 0 în
caz de succes şi –1 altfel. Cauzele eşecului ar putes fi: descriptor incorect (errno va avea
valoarea EACCESS), existenţa prealabilă a unui blocaj incompatibil (errno va avea
valoarea EAGAIN) şi interblocaj (deadlock), caz în care errno va avea valoarea
EDEADLK. Noţiunea de interblocaj va fi explicată ulterior.
- F_SETLKW, ce are acelaşi efect cu F_SETLCK cu deosebirea că
se doreşte punerea unui blocaj cu caracter imperativ şi că acest apel este blocant în
sensul că se va aştepta, dacă este cazul, dispariţia tuturor blocajelor incompatibile
existente. Acest apel este intreruptibil. În caz de intrerupere codul de retur va fi –1 iar
errno va avea valoarea EINTR.
- F_GETLCK, test de existenţă a unui blocaj incompatibil cu cel
descris de al treilea parametru. Codul de retur este totdeauna 0. Dacă nu există blocaj
incompatibil se va pune valoarea F_UNLCK în campul l_type al structurii de tip flock. În
caz contrar câmpurile structurii de tip flock vor conţine
caracteristicile blocajului incompatibil. Este evident că dacă, în urma acestui apel se

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 48
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

constată inexistenţa unui blocaj incompatibil cu cel desemnat, o tentativă ulterioară de


punere efectivă a acestui blocaj poate eşua, deoarece, între timp, alt proces poate pune
un alt blocaj.
Un proces poate debloca o zonă din cea blocată anterior, spărgând astfel
blocajul în două blocaje pe două zone de dimensiuni mai reduse şi să schimbe tipul
blocajului, dacă este posibil. Schimbarea tipului blocajului din partajat în exclusiv necesită,
pentru reuşită, absenţa oricărui alt blocaj pe zona blocată.
Situaţia de interblocaj, cunoscută şi sub numele de deadlock se produce când
două procese ajung în situaţia de a se aştepta unul pe altul pentru ridicarea unui blocaj.
De exemplu. procesul P1 pune blocajul imperativ B1, iar procesul P2 pune blocajul
imperativ B2. Ulterior P1 va încerca să pună un blocaj imperativ incompatibil cu B2, iar P2
va încerca să pună un blocaj imperativ incompatibil cu B1. În acest caz cele două procese
se vor aştepta unul pe altul. Sistemul de operare sesizează acest interblocaj şi va alege
unul dintre procese drept “victimă”, punând capăt aşteptării procesului în apelul sistem.
Codul de retur va fi pentru apelul efectiat de “victimă” este în acest caz -1, iar errno va lua
valoarea EDEADLK.
Pe multe versiuni de sistem, blocajele imperative nu acţionează efectiv, impiedicând
scrierile sau, eventual, scrierile asupra unui fişier decât dacă acesta are indicatorul set-gid
setat şi fără drept de execuţie pentru grupul proprietar.

Întrebare: Care este valoarea variabilei errno in urma unui apel sistem esuat din cauza
unui deadlock?

Răspuns: EDEADLCK.

3.6 Controlul poziţiei curente prin intermediul apelului lseek

Prototipul apelului menţionat în titlul paragrafului este:


# include <unistd.h>
off_t lseek (int desc, off_t pozitie, int orig);
şi are drept efect schimbarea poziţiei curente în fişier fără a efectua, în prealabil, o
operaţie de citire sau scriere. desc reprezintă, ca de obicei, descriptorul, iar pozitie poate
lua una din valorile SEEK_SET (început de fişier), SEEK_CUR (poziţie curentă) sau
SEEK_END (sfârşit de fişier). Noua poziţie curentă se calculează după formula pozitie +
orig, şi reprezintă codul de retur al apelului, în caz de succes. În caz de insucces se
returnează –1. Putem să ne poziţionăm dincolo de sfârşitul fişierului şi să obţinem, prin
scrieri ulterioare, fişiere ce nu sunt contigue, dar nu ne vom putea poziţiona niciodată
înaintea începutului fişierului.
3.7 Gestiunea legăturilor simbolice
Crearea unei legături simbolice (comanda shell echivalentă: ln –s) se poate realiza
cu ajutorul apelului:

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 49
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <unistd.h>
int symlink (char *sursa, char *destinatie);
ce are acelaşi efect ca şi comanda shell:
$ln –s sursa destinatie
Consultarea unui fişier de tip legatură simbolică cu ajutorul apelului stat va conduce
la furnizarea caracteristicilor i-nodului către care pointează legătura, şi, în consecinţă, tipul
de fişier obţinut nu va fi niciodată legătură simbolică. Pentru a obţine caracteristicile i-
nodului de tip legătură simbolică se poate folosi apelul :
#include <sys/stat.h>
int lstat (char *nume_fisier, struct stat *ptr);
ce are acelaşi efect cu stat, mai puţin cazul unui fişier de tip legătură simbolică, în care
obţinem caracteristicile legăturii simbolice, şi nu ale fişierului spre care pointează aceasta .
În cazul unei legături simbolice putem determina numele legăturii fizice (fişierului)
spre care pointează aceasta, cu ajutorul apelului:
#include <symlink.h>
ssize_t readlink(char *nume_fisier, char *ptr, size_t dim);
unde nume_fisier este numele unei legături simbolice. Apelul încarcă la adresa ptr primele
cel mult dim caracterele din numele fişierului spre care pointează legătura simbolică, şi
returnează numărul de caractere scrise la această adresă. Menţionăm că şirul scris nu
este terminat cu caracterul „\0‟;
3.8 Gestiunea directoarelor
Fişierele speciale de tip director pot fi deschise cu open si citite cu read. Acest mod
de abordare a problemei citirii unui director este, însă, complet contraindicată deoarece
structura acestor fişiere se poate modifica de la o versiune la alta a sistemului de operare.
Singurul lucru pe care îl ştim cu certitudine este acela că directorul realizează
corespondenţa între i-nod şi numele unei legături fizice.
Fişierul header dirent.h, conţine, printre altele, definiţia tipului de date DIR, ce
descrie caracteristicile directorului, precum şi a structurii dirent, ce conţine caracteristicile
unei intrări in director, printre care numele în câmpul char d_name[NAME_MAX];
Atenţie! Este uşor de ghicit faptul că tipul de date DIR este o structură. Cu toate
acestea, programatorului trebuie să-i fie transparent acest lucru. Dimpotrivă, acesta poate
folosi, de exemplu, câmpurile structurilor stat, flock, dirent,… descrise ca atare în
documentaţie.
Deschiderea unui fişier de tip director se poate face prin intermediul apelului:
#include <dirent.h>
DIR *opendir (char *nume_dir);
ce deschide directorul având numele dat in argument. În caz de eroare se returnează
NULL. Codul de retur va fi folosit, în continuare, pentru exploatarea directorului.
Citirea intrării următoare din director se poate face cu apelul:
#include <dirent.h>
struct dirent *readdir(DIR *p);

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 50
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Acest apel va furniza, dupa deschidere, rând pe rând, intrările din director, inclusiv cele
numite “.” şi “..”. Codul de retur NULL, semnifică sfârşitul parcurgerii directorului sau o
eroare.
Apelul:
#include <dirent.h>
void rewidndir (DIR *p);
repoziţionează intrarea într-un director deschis, la începutul acestuia.
Închiderea unui fişier de tip director se va realiza cu ajutorul apelului:
#include <dirent.h>
int closedir (DIR *p);
Nu există apeluri speciale pentru scrierea intr-un director. Această operaţiune este
un efect lateral al apelurilor: creat, open, link, mkfifo (creare de fişier de tip tub), mknod
sau unlink.
Crearea unui nou director se face cu apelul:
#include <sys/types.h>
int mkdir (char *nume_dir, mode_t drepturi);
Se creează astfel un nou director având numele specificat de primul parametru şi
drepturile de acces specificate de cel de al doilea. Acest director va conţine doar două
legături şi anume “.” şi “..”.
Ştergerea unui director se face cu ajutorul apelului:
#include <unistd.h>
int rmdir (char *nume_dir);
Acest apel reuşeşte doar dacă directorul având numele specificat de parametru conţine
doar legăturile “.” şi “..”.

3.9 Utilizarea bibliotecii C standard pentru gestionarea fişierelor


Biblioteca C standard este disponibilă pe toate implementările de limbaj C, nu
neaparat pe cele de pe sistemele de operare UNIX. Apelurile la funcţiile din această
bibliotecă nu antrenează direct intrarea procesului în mod nucleu. Aceste apeluri vor
efectua, în subsidiar, apeluri sistem, ce vor produce intrarea procesului în mod nucleu.
Dată fiind disponibilitatea acestei biblioteci pe toate sistemele de operare, pentru
portabilitate, recomandăm folosirea acesteia în situaţia în care nu avem de făcut operaţii
de un tip special, ce necesită expres serviciile sistemului de operare cum ar fi blocaje,
duplicări de descriptori etc.
Pentru folosirea acestei biblioteci, fişierele sursă trebuie să includă header-ul
<stdio.h>. Acest header conţine, printre altele:
- Macro definiţia constantelor NULL, EOF, (sfârşit de fişier), _NFILE (numărul
maxim de fişiere ce pot fi deschise) şi BUFFSIZ (dimensiunea buffer-ului tampon).
- Tipul de date FILE, transparent pentru utilizator. La deschidere se
obţine un pointer către acest tip de date, care va fi folosit ulterior pentru de efectuarea de
operaţii asupra fişierului. Este, totuşi, lesne de bănuit ca este vorba de o structură, ce

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 51
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

conţine, printre altele, adresa buffer-ului tampon si descriptorul de fişier. Pentru un fişier
deschis cu biblioteca standard, descriptorul se poate recupera cu ajutorul apelului:
#include <stdio.h>
int fileno (FILE *fp);
- Macro definiţiile de tip FILE * ale fişierelor standard: stdin, stdout,
stderr.
- Macro definiţiile _IOFBF, _IOLBF şi _IONBF ce descriu modul de
gestionare al buffer-ului tampon, etc.
Deschiderea fişierelor se face cu ajutorul apelului:
#include <stdio.h>
FILE *fopen (char *nume_fisier, char *mod);
ce deschide fişierul cu numele dat de primul parametru în modul descris de al doilea.
Acesta din urmă poate lua valorile:
“r” – citire (corespunzător lui O_RDONLY la un apel echivalent al lui open), “w” –
scriere, precedată de trunchiere (O_WRONLY | O_CREAT | O_TRUNC), “a” – scriere
exclusiv la sfârsit (O_WRONLY | O_CREAT | O_APPEND), “r+” (O_RDWR), “w+”
(O_RDWR | O_CREAT | O_TRUNC), “a+” (O_RDWR| O_CREAT| O_APPEND). Pe
diferite versiuni, parametrul mod mai poate avea diverse alte valori cum ar fi: “r+b”, “w+b”
sau “a+b” corespunzătoare operaţiilor asupra fişierelor de tip binar.
Un fişier deschis cu open, poate fi exploatat in continuare cu ajutorul bibliotecii
standard în urma apelului:
#include <stdio.h>
FILE *fdopen (int desc, char *mod);
unde primul argument reprezintă descriptorul, iar al doilea modul de deschidere ce poate
lua aceleaşi valori ca şi la fopen , numai că acestea trebuie să fie compatibile cu modul
iniţial de deschidere al fişierului cu open, pentru reuşita apelului.
Apelul următor permite redirectarea unei intrări din tabela de fişiere deschise către
un alt fişier:
#include <stdio.h>
FILE *freopen(char *nume_fisier, char *mod, FILE *fp);
De exemplu, următorul apel redirectează intrarea standard către fişierul cu numele
iii
freopen (―iii”, ―r‖, stdin);
pe când :
freopen (―ooo”, ―a‖, stdout);
redirectează ieşirea standard către fişierul cu numele ooo.
Deschiderea unui fişier temporar se face cu apelul:
#include <stdio.h>
FILE *tmpfile();

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 52
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Fişierul temporar este plasat într-un director special, va fi şters la apelul lui exit, şi va avea
un nume generat aleator de sistem. Un nume de asemenea fişier, fără crearea acestuia,
poate fi obţinut cu apelul:
#include <stdio.h>
char *tmpnam(char *ptr);
unde zona alocată pentru parametru trebuie să fie de cel puţin L_tmpnam caractere.
Numele este furnizat în codul de retur şi scris la adresa dată de parametru în caz că
acesta nu are valoarea NULL.
Închiderea unui fişier se face cu apelul:
#include <stdio.h>
int fclose(FILE *fp);
Funcţia returnează 0 în caz de succes şi EOF în caz de eroare.
Scrierea unui caracter într-un fişier se poate face fie cu ajutorul macro-ului
#include <stdio.h>
int putc (int c, FILE *fp);
sau prin apelul funcţiei:
int fputc (int c, FILE *fp);

putchar(c);
este echivalent cu putc(c, stdout);
Scrierea unui şir de caractere se face cu apelul:
#include <stdio.h>
int fputs(char *sir, FILE *fp);
Subliniem faptul că terminatorul de şir „\0‟ nu este scris în fişier.
Scrierea unui vector de elemente de un tip oarecare se face cu apelul:
#include <stdio.h>
int fwrite(void *p, size_t dim, size_t nr_elem, FILE *fp);
unde primul parametru reprezintă adresa primului element, al doilea dimensiunea fiecărui
element, iar al treilea numărul de elemente. Codul de retur reprezintă numărul de
elemente scrise.
Apelul :
#include <stdio.h>
int fprintf (FILE *fp, char *format, …);
este un apel cu număr variabil de argumente, primele două menţionate fiind obligatorii.
Alcătuirea şirului desemnat de al doilea argument determină numărul şi tipul argumentelor
din zona variabilă. Aceste argumente vor fi convertite din format intern în format text şi
scrise în fişierul desemnat de primul argument. Apelul
printf (fp, format, …); \
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 53
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

este echivalent cu
fprintf (stdout, format, …);
Caracterele din şirul desemnat de al doilea argument vor fi scrise ca atare în fişier, mai
puţin situaţia în care apare caracterul special %. În acest caz, unul sau mai multe
caractere ce-I urmează sunt interpretate drept specificator de conversie care determină,
printre altele, tipul următorului argument din zona variabilă. Se scrie atunci rezultatul
conversiei în fişier, după care se continua parcurgerea şirului format. Caracterul ce
urmează după % determină, de regulă, tipul conversiei şi anume:
%i, %d - întreg în reprezentare zecimală
%u - întreg fără semn în reprezentare zecimală
%o - întreg fără semn în reprezentare octală
%x - întreg fără semn în reprezentare hexazecimală (cu cifre 0..9 şi a..f)
%X - întreg fără semn în reprezentare hexazecimală (cu cifre 0..9 şi A..F)
%f - float, double în reprezentare zecimală cu virgulă fixă.
%e, %E - float, double în reprezentare zecimală cu virgulă mobilă.
%g, %G - float, double în reprezentare zecimală cu virgulă fixă sau mobilă în
funcţie de valoarea exponentului.
%c - unsigned char
%s - char *, şir tipărit până la întâlnirea terminatorului de şir, „\0‟.
%p - pointer de orice tip în format hexazecimal.
%n - int *, parametru de ieşire, unde se scrie numărul de caractere scrise
până în acel moment, fără a scrie informaţie în fişier.
Între caracterul % şi cel ce determină tipul conversiei mai pot fi
intercalate alte caractere speciale ce dau informaţii suplimentare asupra modului în care
trebuie realizată conversia şi anume:
- ―-’’ implică alinierea rezultatului conversiei la stânga zonei atribuite,
altfel alinierea se face la dreapta.
- ―+‖ implică precedarea rezulatului conversiei de semn, chiar dacă
numărul convertit este pozitiv.
- blanc are acelaţi efect ca şi “+” cu deosebirea că rezultatul conversiei
unui număr pozitiv este precedat de blanc şi nu de + ca semn.
- n1, număr ce indică numărul de poziţii rezervate conversiei. Prin
lipsă, numărul de caractere ocupat de rezultatul conversiei este cel rezultat în urma
operaţiunii
- n1.n2, unde n1 are aceeaşi semnificaţie ca mai sus dar în cazul
conversiei unui număr real, n2 specifică numărul de cifre de după punctul zecimal.
- unul din caracterele h (pentru short), l (long), sau L (long double) ce
determină un tip special al argumentului de convertit.
Funcţia returnează numărul de caractere scrise în caz de succes şi –1 în
caz de eroare.
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 54
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Citirea unui caracter din fişier se poate efectua fie cu ajutorul macro-ului:
#include <stdio.h>
int getc (FILE *fp);
sau prin apelul funcţiei:
int fgetc (FILE *fp);

getchar();
este echivalent cu getc(stdin);
Se obţine astfel, drept cod de retur, următorul caracter din fişier. Valoarea de retur
EOF înseamnă, fie eroare, fie faptul că s-a ajuns la sfârşitul fişierului.
Apelul:
#include <stdio.h>
int ungetc (int car, FILE *fp);
are drept efect diminuarea poziţiei curente cu o unitate si depunerea caracterului specificat
de primul argument in zona tampon pentru a fi citit la următoarea operaţiune de citire. Nu
se produc modificări la nivelul fişierului propriu-zis, ci numai în memorie. Se returnează
valoarea caracaterului in caz de succes şi EOF în caz contrar. Se garantează, însă,
succesul unui singur apel înaintea unei operaţii de citire.
Citirea unui şir de caractere se face cu ajutorul apelului:
#include <stdio.h>
char *fgets(char *ptr, int dim, FILE *fp);
ce realizează depunerea la adresa desemnată de primul parametru a maxim dim-1
caractere citite din fişier. Spunem maxim deoarece citirea se poate opri înainte de a citi
dim caractere, daca se întâlneşte sfârşitul fişierului sau un caracter sfârşit de linie, „\n‟, caz
în care acest caracter este depus şi el în zona rezultat. Se returnează valoarea ptr în caz
de succes şi NULL, în caz contrar.
Apelul:
#include <stdio.h>
char *gets(char *ptr);
citeşte un şir de caractere de la intrarea standard pâna la întâlnirea unui sfârşit de linie,
depunând rezultatul citirii la adresa desemnată de parametru. Acest apel este puţin fiabil,
deoarece nu se specifică nici o dimensiune limită pentru rezultatul citirii.
Citirea unui vector de elemente se face cu ajutorul apelului:
#include <stdio.h>
int fread(void *p, size_t dim, size_t nr_elem, FILE *fp);
unde primul argument este adresa primului element al vectorului unde se vor depune
rezultatele, al doilea dimensiunea fiecărui element, iar al treilea numărul de elemente din
vector. Codul de retur reprezintă numărul de elemente citite. Nu se garantează rezultatul
în care nu se citeşte un numar întreg de elemente.
Apelul :
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 55
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <stdio.h>
int fscanf (FILE *fp, char *format, …);
este un apel cu număr variabil de argumente, primele două menţionate fiind obligatorii.
Alcătuirea şirului desemnat de al doilea argument determină numărul şi tipul argumentelor
din zona variabilă. Aceste argumente reprezintă adrese unde se vor depune rezultatul
conversiilor din format text in format intern. Numărul, tipul conversiilor şi, implicit, numărul
şi tipul pointerilor din zona variabilă este dat de al doilea argument. Apelul
scanf (format, …);
este echivalent cu
fscanf (stdin, format, …);
Interpretarea celui de al doilea argument este asemănătoare, până la un punct, cu
cea de la apelul fscanf. Numai elementele ce determină tipul conversiei au o semnificaţie
aici. Aceste elemente sunt începute de caracterul special % urmat de alt caracter ce
determină tipul conversiei. Aceste caractere sunt:
%c - un caracter.
%s - şir de caractere, citirea terminându-se la
intâlnirea unui blanc sau a unui sfârşit de linie. Caracterul de
sfârşit de şir, „\0‟, este depus la sfârşitul şirului citit.
%p - pointer în format hexazecimal fără semn.
%d - întreg reprezentat zecimal
%u - întreg fară semn reprezentat zecimal
%o - întreg reprezentat octal
%x - întreg reprezentat hexazecimal
%e, %f, %g - float, double
%n - întreg reprezentând numarul de caractere citite până în acel
moment
%[…] - se omit caracterele din mulţimea delimitată de [ şi ].
%[^…] - se omit caracterele din complementara mulţimii delimitată [ şi ].
Între caracterul procent şi cel ce determină tipul conversiei, pot fi
intercalate diferite alte caractere ce furnizează informaţii suplimentare asupra modului de
citire. În lipsa acestora, citirea datelor se face până la întâlnirea primului caracter
incompatibil cu tipul de conversie specificat. Aceste caractere sunt:
- * indică faptul că valoarea citită nu se atribuie unei variabile şi, in
consecinţă, argumentul corespondent din zona variabilă va lipsi.
- Un întreg n, ce indică lungimea maximă pentru citirea câmpului
- unul din caracterele h (pentru short), l (long), sau L (long double) ce
determină un tip special al argumentului de convertit.
Modificarea poziţiei curente din fişier, fără a efectua operaţii de citire sau
scriere, se poate realiza cu ajutorul apelului:
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 56
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <stdio.h>
int fseek (FILE *fp, long offset, int orig);
unde al doilea parametru are ca valoare una din constantele simbolice SEEK_SET,
SEEK_CUR sau SEEK_END, a căror semnificaţie a fost explicată cu ocazia apelului lseek.
Noua poziţie curentă va fi calculată cu ajutorul formulei offset+orig.
Testarea faptului dacă poziţia curentă a ajuns la sfârşitul fişierului, se face cu
apelul:
#include <stdio.h>
int feof (FILE *fp);
ce returnează 0 dacă nu s-a atins sfârşitul fişierului şi 1 altfel.
Aşa cum am mai spus, dupa deschidere, avem un buffer tampon de dimensiune
BUFSIZ. Se poate schimba, ori adresa acestui buffer, ori dimensiunea acestuia sau modul
de utilizare prin intermediul apelului:
#include <stdio.h>
int setvbuf (FILE *fp, char *buff, int mode, size_t dim);
Parametrul al doilea reprezintă adresa noului buffer, iar al patrulea noua sa
dimensiune. Parametrul al treilea reprezinta modul de golire al bufferului pe disc şi este
specificat cu ajutorul unor constante simbolice, şi anume:
_IOFBF - golire la umplere
_IOLBF - golire la umplere sau la scrierea completă a unei linii
_IONBF - golire dupa fiecare caracter scris.
Implicit, pe fişierele de tip obişnuit, bufferul se goleşte în mod _IOFBF, adică la
umplere.
Există şi o formă simplificată a acestui apel, şi anume:
#include <stdio.h>
int setbuf (FILE *fp, char *buff);
Apelul
setbuf (fp, buff);
este echivalent cu
setvbuf (fp, buff, mode, BUFSIZ);
unde modul de golire al buffer-ului este cel dinainte, cu exceptia cazului când buff are
valoarea NULL, fapt ce reprezintă păstrarea vechiului buffer, cu aceeaşi dimensiune, dar
cu modificarea modului de golire la _IONBF, adică golire imediată.
Se poate cere golirea imediată a bufferului, chiar dacă nu sunt îndeplinite condiţiile
pentru aceasta, cu ajutorul apelului:
#include <stdio.h>
int fflush (FILE *fp);
Valoarea NULL a argumentului semnifică golirea imediată a bufferelor pentru toate
fişierele deschise.
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 57
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Pentru ieşirea standard (stdout), gestiunea buffer-ului se face în mod linie


(_IOLBF) dacă aceasta este asociată terminalului de control. În plus, în caz că şi intrarea
standard (stdin) corespunde, de asemenea, terminalului de control, o citire de la aceasta
determină golirea imediata a buffer-ului ieşirii tampon. Dacă ieşirea standard a fost
redirijată către un fişier de tip obişnuit, buffer-ul este gestionat în mod _IOFBF.
Pentru ieşirea standard de eroare (stderr), corespunzătoare unui terminal, golirea
buffer-ului se face imediat (_IONBF), dacă nu a fost redirectată, şi în mod linie (_IOLBF),
dacă a fost redirectată către un fişier de tip obişnuit, cu ajutorul apelului freopen. Dacă
această ieşire a fost redirectată către un fişier de tip obişnuit, buffer-ul este gestionat în
mod _IOFBF.
Biblioteca C standard mai pune la dispoziţie apeluri pentru conversia dintre formatul
intern al datelor si şirurile de caractere:
#include <stdio.h>
int sscanf (char *ch, char *format,…);
int sprintf (char *ch, char *format,…);
ce au acelaşi efect ca şi scanf şi, respectiv, printf, cu deosebirea că în loc de fişiere se
lucreaza cu şirurile de caractere din memorie desemnate de primul argument al acestor
apeluri.
3.10 Intrebari si teste

Tema de autoinstruire nr. 3

Consultaţi bibliografia pentru a afla:

3. Functionalitatea completa a apelului fcntl.

3. Utilizarea duplicarii descriptorilor

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 58
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Testul de autoevaluare nr. 3

1. Care este utilitatea blocarii portiunilor din fisiere?

Răspunsurile
la test se vor
da în spaţiul
liber din
chenar, în
continuarea
enunţurilor
2. Care este informatia principala continuta de o intrare din tabela
de fisiere deschise in memorie ?

3. Cum se realizeaza redirectionare fisierlor stand

Raspunsurile se gasesc la pagina 58

3.11 Comentarii şi răspunsuri la testele de autoevaluare


Răspunsurile la acest test se găsesc la pagina 26 a acestei unităţi de
învăţare.
Testul 3.

1. Prevenirea coruperii continutului unui fisier in urma acceselor


concurente

2. Pozitia curenta in fisier.

3. Se deschide fisierul catre care se doreste redirectarea, se


inchide fisierul standard si se va duplica descriptorul fisierului
deschis.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 59
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

3.13 Lucrare de verificare pentru studenţi

Realizaţi un program C sub UNIX care inlocuieste intr-un fisier un caracter


cu altul. Numele fisierului si cele doua caractere vor fi date ca argumente in
linia de comanda.

Indicaţii de redactare:

- scrieţi comentarii
- scrieti o varianta ce foloseste biblioteca sistem si alta care foloseste
biblioteca C standard

Rezolvările, dar şi dificultăţile întâmpinate, vor fi expediate prin mail


tutorelui.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 60
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

4. GESTIUNEA PROCESELOR

Obiectivele unităţii de învăţare nr. 4

După ce veţi parcurge această unitate de învăţare, veţi reuşi să:


 Creati procese fiu
 Sa sincronizati procesele tata si fiu
 Sa lansati in executie fisiere executabile

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 61
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

4.1. Carateristicile generale ale unui proces


O primă caracteristică a unui proces este spaţiul său de adresare, mai precis
adresele unde sunt situate codul executabil sau datele.
O a doua caracteristică este starea execuţiei procesului. În timpul execuţiei, un
proces se poate afla în stare utilizator (user mode) când execută instrucţiunile din codul
obiect al executabilului propriu-zis, şi în mod nucleu (kernel mode) când execută
instrucţiuni din sistemul de operare fie în urma unui apel sistem, fie în urma primirii unui
semnal. La primirea unui semnal, procesul se întrerupe, intră în mod nucleu, de unde se
va apela o funcţie de tratare a semnalului, moment în care se intră iar în mod utilizator.
Dacă funcţia de tratare a semnalului se termină cu return, se reintră în mod nucleu, de
unde sistemul de operare va relua execuţia procesului din punctul în care a fost întrerupt,
reintrându-se astfel în mod utilizator.

Enumerăm acum alte caracteristice ale proceselor:

a. Idenificatorul (pid) şi identificatorul procesului părinte (ppid). Oricărui proces


activ îi corespunde o intrare în tabela sistem de procese, identificată unic printr-un număr
numit pid. Această caracteristică, pentru procesul curent, poate fi citită cu ajutorul apelului:

#include <unistd.h>

pid_t getpid();

Cu excepţia procesului având pid 0, orice proces are un proces părinte al cărui pid
poate fi citit, pentru procesul curent, cu apelul:
#include <unistd.h>

pid_t getppid();

b. Raporturile cu utilizatorii.

Din punct de vedere al legăturii cu utilizatorii, orice proces posedă următoarele


caracteristici:

- proprietarul real. Este acelaţi cu proprietarul real al


procesului părinte. Pentru procesul curent poate fi identificat cu apelul:

#include <unistd.h>

pid_t getuid();

- proprietarul efectiv. În mod uzual, este acelaşi cu proprietarul real, cu

excepţia cazului când fişierul executabil corespunzător procesului are indicatorul set-uid
setat. În acest caz, proprietarul efectiv este proprietarul fişierului executabil. Drepturile de
acces la diferite obiecte ale sistemului (fişiere, etc.) sunt cele ale proprietarului efectiv, nu
cele ale proprietarului real. De exemplu, comanda SHELL passwd, modifică fişierul
/etc/passwd, asupra căruia nici un utilizator, cu excepţia lui root, nu are drept de scriere.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 62
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

În mod normal, deschiderea acestui fişier în scriere ar trebui sa eşueze. În cazul comenzii
passwd, acest lucru nu se intâmplă deoarece fişierul executabil passwd, are setat
indicatorul set-uid şi pe root ca proprietar. Prin urmare, root este proprietarul efectiv al
procesului. Deci orice utilizator poate modifica fişierul /etc/passwd, prin intermediul
acestei comenzi, dar numai linia aferentă lui. Pentru procesul curent, această
caracteristică poate fi citită cu apelul:

#include <unistd.h>

pid_t geteuid();

Un proces având drept proprietar pe root, poate modifica, în acelaşi timp, proprietarul real
şi efectiv prin apelul:

#include <unistd.h>

pid_t setuid(uid_t uid);

unde parametrul indentifică noul proprietar.


- grupul proprietar real. Este acelaşi cu grupul proprietar al procesului
rinte. Pentru procesul curent, poate fi citit cu apelul:

#include <unistd.h>

pid_t getgid();

- grupul proprietar efectiv. În mod uzual, este acelaşi cu grupul


proprietar real, cu excepţia cazului când fişierul executabil corespunzător procesului are
indicatorul set-gid setat. În acest caz, grupul proprietar efectiv este grupul proprietar al
fişierului executabil. Pentru procesul curent, poate fi citit cu apelul:

#include <unistd.h>

pid_t getegid();

De asemenea, un proces având drept proprietar pe root, poate modifica, în acelaşi timp,
grupul proprietar real şi efectiv prin apelul:

#include <unistd.h>

pid_t setgid(gid_t gid);

unde parametrul indentifică noul grup proprietar.

c. Directorul curent
O altă caracteristică a unui proces este directorul său curent. Prin raport la acesta
se face adresarea fişierelor în mod relativ. Procesul moşteneşte, de asemenea, această
caracteristică de la procesul părinte, dar poate să o modifice prin apelul:

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 63
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <unistd.h>

int chdir(char *dir);

unde argumentul desemnează numele noului director curent. Procesul curent poate citi
această caracteristică prin apelul:
#include <unistd.h>

char *getcwd(char *buffer, int dim);

unde primul argument desemnează bufferul unde se va depune numele directorului curent,
care va fi furnizat şi drept valoare returnată, iar al doilea argument dimensiunea bufferului.
Se poate pasa primul argument cu valoarea NULL, caz în care se returnează o adresă
dintr-o zonă statică. În caz că numele directorului are mai multe caractere decât s-a
menţionat în al doilea parametru, apelul eşuează, iar errno va lua valoarea ERANGE.

d. Grupul şi sesiunea din care face parte.

e. Data de creare.

Este memorată sub forma numărului de secunde scurse de la 1 Ianuarie 1970.

f. Timpul procesor consumat de proces.

Acestă caracteristică poate fi citită cu ajutorul apelului:


#include <sys/times.h>

clock_t times(struct tms *p);

care va depune la adresa desemnată în argument informaţiile cerute. Structura de tip tms
conţine, printre altele, următoarele câmpuri:

- clock_t tms_utime ce va conţine numărul de tick-uri de ceas


consumate în mod utilizator.

- clock_t tms_stime ce va conţine numărul de tick-uri de ceas


consumate în mod nucleu.

- clock_t tms_cutime ce va conţine numărul de tick-uri de ceas


consumate în mod utilizator de procesele copil.

- clock_t tms_cstime ce va conţine numărul de tick-uri de ceas


consumate în mod nucleu de procesele copil.

Pentru a exprima timpii în secunde este suficient să împărţim valorile obţinute la


constanta CLK_TCK definită în limits.h.

g. Masca pentru drepturi de acces asupra fişierelor nou create.

Această caracteristică se poate citi şi modifica prin intermediul apelului:

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 64
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <sys/stat.h>

mode_t umask(mode_t masca);

unde argumentul reprezintă noua mască, vechea mască fiind obţinută în codul de retur.
Argumentul şi valoarea de retur sunt interpretate ca disjuncţie bit cu bit a constantelor
prezentate cu ocazia apelului stat. Prin acest apel nu pot fi specificate decât cele 9
drepturi reprezentând accesul în citire, scriere şi execuţie pentru proprietar, grup proprietar
şi ceilalţi. Nu se pot specifica drepturile speciale de set-uid şi set-gid. La un apel open
sau creat, drepturile de acces la noul fişier creat se vor obţine făcând o conjuncţie bit cu
bit între această mască şi argumentul care reprezintă drepturile de acces.

h. Tabela de descriptori asupra fişierelor deschise

i. Starea procesului

Stările unui proces pot fi:

1. proces nou, stare tranzitorie existentă în momentul lansării


2. ready, adică gata de a fi lansat în execuţie
3. activ în mod nucleu (kernel mode)
4. activ în mod utilizator (user mode)
5. adormit (sleeping) în aşteptarea unui eveniment
6. suspendat (stopped)
7. zombi, adică proces terminat, dar părintele nu a recuperat codul de retur
transmis prin apel la exit.
Tranziţiile posibile între aceste stări sunt următoarele:

- 1-2 Se produce când sistemul de operare a alocat toate resursele


necesare pentru lansarea procesului în execuţie.

- 2-3 Sistemul de operare lansează procesul în execuţie, recuperând


contextul său la momentul în care s-a întrerupt ultima lansare în execuţie.

- 3-4 Se iese dintr-un apel sistem sau s-a terminat tratarea unui semnal
sau s-a apelat funcţia (handler) ce tratează un semnal.

- 4-3 S-a facut un apel sistem sau s-a primit un semnal sau s-a ieşit din funcţia
(handler) ce tratează un semnal.

- 3-5 S-a facut un apel sistem ce implică aşteptarea producerii unui


eveniment (de exemplu citire de la un periferic).

- 5-2 Evenimentul aşteptat s-a produs, deci sistemul este pregătit


pentru reluarea execuţiei.

- 5-6 S-a primit semnalul SIGSTOP sau SIGTSTP.


- 3-6 S-a primit semnalul SIGSTOP sau SIGTSTP.
- 6-2 S-a primit semnalul de continuare SIGCONT,
care “trezeşte” procesul.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 65
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

- 3-7 Execuţia procesului s-a terminat, dar intrarea aferentă din tabela
de procese active persistă până când procesul tată ia cunoştiinţă de acest fapt..

j. Evenimentul aşteptat de proces în starea adormit (sleeping).

k. Informaţii despre memoria utilizată.

l. Starea regiştrilor procesorului.

m. Prioritatea.

Prioritatea unui proces se calculează pornind de la prioritatea de bază, dată de


utilizator, şi timpul procesor consumat recent. Stabilirea priorităţii de către utilizator se
poate face fie cu ajutorul comenzii SHELL nice, fie cu ajutorul apelului:

#include <unistd.h>

int nice (int incr);

care adaugă valoarea argumentului la prioritatea implicită, micşorând astfel proritatea


procesului curent. Creşterea priorităţii unui proces se poate face transmiţând apelului un
argument negativ, însă acest lucru nu va reuşi decât pentru procesele al căror proprietar
este root.

n. Stiva procesului.

o. Valori limită.

Aceste valori se pot citi sau mdifica cu ajutorul apelului:

#include <ulimit.h>

long ulimit (int comanda, …);

unde primul argument specifică, prin intermediul constantelor simbolice valoarea limită, iar
al doilea, dacă este cazul să existe, noua valoare. Primul argument poate lua valorile:

- UL_GETFSIZE dimensiunea maximă pentru un fişier deschis


pentru scriere exprimată în număr de blocuri de câte 512 octeţi.

- UL_SETFSIZE modifică dimensiunea maximă pentru un fişier


deschis pentru scriere. Al doilea parametru este de tip long. Reuşeste doar pentru
procesele al căror proprietar este root.

- UL_GETMAXBRK, valoarea punctului de ruptură (breakpoint) în


memoria procesului, noţiune ce va fi explicată mai jos.

p. Modul de tratare a semnalelor ce constituie obiectul unui capitol

următor.
Întrebare: Care este apelul prin care putem gasi pid-ul procesului curent?

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 66
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Răspuns: getpid.

4.2 Organizarea memoriei unui proces


Memoria unui proces se compune din zona de cod (ce corespunde codului
executabil) şi zona de date. Prima este accesibilă doar în citire, iar a doua şi în citire şi în
scriere. Zona de date este alcatuită din trei segmente:

- datele statice. Aici sunt plasate toate variabilele globale şi cele locale statice.
Aceste variabile au o adresă fixă pe toată durata execuţiei procesului.

- zona de date dinamice (heap). Aici sunt situate adresele tuturor

variabilelor create dinamic pe parcursul execuţiei procesului prin apeluri la funcţiile malloc,
calloc sau realloc.

- stiva, unde sunt plasate variabilele automatice, valorile de retur ale


funcţiilor, etc.

De exemplu pe HP_UX codul executabil este situat în primul cadran

(adrese cuprinse între 0x00000000 şi 0x3fffffff), zona de date în al doilea cadran (adrese
cuprinse între 0x40000000 şi 0x7fffffff), zona de cod pentru bibliotecile partajate în al
treilea cadran (adrese cuprinse între 0x80000000 şi 0xbfffffff) şi zona rezervată
segmentelor de memorie partajată între procese în al patrulea cadran (adrese cuprinse
între 0xc0000000 şi 0xffffffff).

În zona de date, adresele din stivă scad de la valori mari către valori mici. Punctul
care delimitează stiva de heap se numeşte punct de ruptură sau breakpoint. Utilizatorul
poate modifica, între anumite limite, această valoare cu ajutorul apelului:

#include <unistd.h>

int brk (void *ptr);

Valoarea transmisă ca argument trebuie să fie, totuşi, compatibilă cu restricţiile sistemului.


Din acest motiv, folosirea acestui apel nu este recomandată decăt în cazul în care
utilizatorul are cunoştiinţe precise despre valorile fizice ale adreselor din sistem. În schimb,
avem la dispoziţie un apel care incrementează sau decrementează valoarea punctului de
ruptură:
#include <unistd.h>

int sbrk (int incr);

unde valoarea argumentului reprezintă incrementul, iar codul de retur valoarea noului
punct de ruptura.
Adresele sunt alocate în zona dinamică (heap) prin intermediul unor apeluri care
returnează pointeri. Trebuie remarcat faptul că pointerii returnaţi sunt aliniaţi perfect, în
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 67
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

sensul că la adresele obţinute pot fi depuse orice fel de date indifferent de tipul acestora.
Un prim apel de acest tip este:

#include <stdlib.h>

void *malloc (size_t dim);

ce alocă o zonă de memorie a cărei dimensiune în octeţi este exprimată de parametru.


Atenţie Pentru acest apel, precum şi pentru cele ce urmează a fi descrise, pentru
specificarea dimensiunilor zonelor de memorie ce urmează a fi alocate, se recomandă
folosirea operatorului sizeof din motive de portabilitate.

Alt apel este

#include <stdlib.h>

void *calloc (size_t nr, size_t dim);

ce alocă spaţiu unui vector de nr elemente de dimensiune dim. Un apel calloc(x, z) este
echivalent cu malloc (x*z) cu singura deosebire că apelul calloc face iniţializarea zonei de
memorie alocate cu toţi biţii având valoarea zero, pe când malloc nu face nici un fel de
iniţializare a zonei alocate.

Următorul apel ne permite să modificăm dimensiunea unei zone de memorie


alocată în prealabil:

#include <stdlib.h>

void *realloc (void *ptr, size_t nr);

Primul argument este un pointer obţinut în prealabil printr-un apel la malloc, calloc sau
realloc. Al doilea argument reprezintă dimensiunea nouă a zonei. Se garantează, în caz
de modificare a adresei zonei de memorie, copierea datelor iniţiale în noul spaţiu alocat.
Apelul cu primul argument având valoarea NULL este echivalent cu un apel la malloc.

Atenţie! Primul argument trebuie să fie un pointer obţinut ca rezultat al unui apel
malloc, calloc, realloc sau NULL.

Secventa urmatoare :
int x;

……

realloc (&x, 300);

va fi compilată, executată, însă va strica datele statice privind gestiunea de memorie ale
procesului, efectul acestei erori riscând a se manifesta la alte apeluri ulterioare ce privesc
alocarea sau eliberarea spaţiilor de memorie.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 68
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Eliberarea unei zone de memorie alocată în prealabil cu malloc, calloc sau realloc
se face cu apelul

#include <stdlib.h>

void *free (void *ptr);

Atenţie! Argumentul trebuie să fie un pointer obţinut ca rezultat al unui apel malloc,
calloc, realloc sau să aibe valoarea NULL. Dacă argumentul este NULL, apelul este fără
efect. Efectul apelului cu o altă valoare a argumentului poate duce la erori în urma unui
apel ulterior la una din funcţiile de gestiune a memoriei.

Este recomandat să fie eliberate zonele de memorie cât mai repede, în ciuda
faptului ca acestă operaţie se face automat la terminarea procesului.

Întrebare: Care sunt apelurile ce permit alocarea dinamica a memoriei?

Răspuns: malloc, calloc, realloc

4.3 Crearea proceselor


Orice proces, cu excepţia celui având pid-ul egal cu 0, este creat prin apel la
următoarea primitivă, efectuat de către procesul tată:

#include <unistd.h>

pid_t fork ();

În urma acestui apel ia naştere procesul fiu care execută acelaşi cod obiect ca şi procesul
tată, din acelasi punct şi primind drept date o copie a datelor procesului tată. Singurul lucru
care deosebeşte cele două procese este valoarea codului de retur al apelului fork.
Procesul tată va primi un cod de retur strict pozitiv ce reprezintă pid-ul procesului fiu nou
creat, iar procesul fiu va primi valoarea 0 drept cod de retur al apelului fork. O valoare
egală cu -1 a codului de retur indică eşecul apelului, o cauză posibilă fiind umplerea
tabelei de procese a sistemului. Un exemplu ar fi:
pid_t pid;
switch (pid = fork()) {
case -1:
………
/* eroare */
………

break;

case 0:

………
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 69
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

/* procesul fiu */
………

break;

default:

………
/* procesul tata, pid are valoarea pid-ului procesului fiu */
………

Procesul fiu va moşteni toate caracteristicile procesului tată cu următoarele excepţii:

- Identificatorul pid;
- Identificatorul procesului părinte
- Timpii de execuţie care sunt initializaţi cu valoarea nulă.
- Tabela de descriptori pentru fişierele deschise este moştenita de la
procesul tată. Trebuie menţionat că descriptorii ambelor procese pointează către aceeaşi
intrare în tabela de fişiere deschise, deci orice modificare a poziţiei curente de către unul
din procese în urma unei operaţii de scriere sau citire se va repercuta şi asupra celuilalt
proces.

- Nu se moştenesc semnalele primite.


- Nu se moşteneste prioritatea, procesul fiu având prioritatea implicită.
- Nu se moştenesc blocajele asupra fişierelor.

Întrebare: Care sun argumentele apelului fork?

Răspuns: Apelul fork nu are argumente..

4.4. Procese zombi şi sincronizarea tată-fiu


La terminarea sa, orice proces emite către procesul tată o valoare întreagă numită
cod de retur. De asemeneaţ se transmite semnalul SIGCHLD către procesul tată. Dupa
aceasta procesul intră in starea zombi sau defunct până când procesul tată ia cunostiinţă
despre terminarea fiului şi modul în care s-a produs acest lucru. Un proces zombi nu mai
are decât următoarele informaţii:

- codul de retur
- timpii de execuţie în mod nucleu şi utilizator
- pid-ul său şi pid-ul procesului tată
Procesul părinte poate executa următorul apel pentru a lua cunostiinţă

despre terminarea unui fiu şi modul în care s-a produs acest lucru:

#include <sys/types.h>

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 70
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <sys/wait.h>

pid_t wait (int *stare);

În situaţia în care procesul nu are nici un proces fiu, codul de retur este –1, iar errno
ia valoarea ECHILD. Dacă procesul are cel puţin un fiu zombi, codul de retur este pid-ul
fiului zombi, care va dispare din tabela de procese a sistemului, iar la adresa pasată drept
parametru, daca aceasta nu are valoarea NULL, se vor depune informaţii despre modul in
care s-a terminat procesul fiu. Modul de interpretare a acestei valori va fi descris mai jos.

În caz că procesul are fii, dar nici unul nu este zombi, se va aştepta până când unul
dintre fii ajunge zombi sau până când apelul wait va fi întrerupt de primirea unui semnal,
caz în care codul de retur este –1, iar errno ia valoarea EINTR.

Alt apel care realizează aproape acelaşi lucru ca şi wait, dar care permite
utilizatorului, printre altele, să specifice pid-ul procesului aşteptat să se termine este:

#include <sys/types.h>

#include <sys/wait.h>

pid_t waitpid (pid_t pid, int *stare, int optiuni);

Primul argument indică procesul, sau mai bine zis mulţimea de procese aşteptate pentru a
se termina, şi anume:

- Valoare strict mai mică decât –1 înseamnă toate procesele din grupul
având drept identificator valoarea absolută a lui pid.

- Valoare egală cu –1 înseamnă orice proces fiu.


- Valoare egală cu 0 înseamnă orice proces din acelaşi grup cu
procesul apelant.

- Valoare strict mai mare decât 0 înseamnă procesul desemnat de pid.


Al doilea argument reprezintă acelaşi lucru ca şi argumentul lui wait. Al treilea argument
se construieşte prin disjuncţia bit cu bit a următoarelor constante simbolice:

- WNOHANG, apel neblocant în cazul în care există fii, dar nici unul
zombi

- WUNTRACED, se urmăresc şi procesele fiu care intră in starea


sopat, în afară de cele care ajung în stare zombi.

Codul de retur poate fi –1 în caz de eroare, 0 în caz ca există procesele, dar nici
unul zombi (sau stopat, după caz) în mod neblocant. Un cod de retur strict pozitiv
reprezintă pid-ul unui fiu intrat în stare zombi sau stopat, după caz.
Interpretarea valorii depuse la adresa stare se va face cu ajutorul unor
macrodefiniţii, ce primesc drept argument o expresie întreagă şi calculează valorile cerute.
Aceste macro-uri sunt:
- WIFEXITED – rezultat 1 daca procesul s-a terminat normal, 0 altfel.
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 71
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

- WEXITSTATUS – rezultat codul de retur. Nu are semnificaţie decât


dacă WIFEXITED are valoarea 1.
- WIFSIGNALED – rezultat 1 dacă procesul s-a terminat din cauza
primirii unui semnal, 0 altfel.
- WTERMSIG – rezultat identificatorul semnalului ce a dus la
terminarea procesului. Nu are semnificaţie decât dacă WIFSIGNALED are valoarea 1.
- WIFSTOPPED – rezultat 1 dacă procesul a intrat în stare stopat, 0
altfel.
- WSTOPSIG – rezultat identificatorul semnalului ce a dus la intrarea
procesului în stare stopat. Nu are semnificaţie decât dacă WIFSTOPPED are valoarea 1.
Iată un exemplu ce calculează codul de retur al unui proces fiu:
int stare, cod;
……..
wait(&stare);
if (WIFEXITED(stare))
cod = WEXITSTATUS(stare);
……..

Întrebare: Care sunt ajantajele folosirii apelului waitpid in locul lui wait?

Răspuns: waitpid permite sa specificam procesul pe care il asteptam sa se termine, si, in


plus, cu ajutorul optiunilor putem sa specificam o asteptare in mod ne-blocant si sa asteptam
si dupa procese care intra in starea stopat

4.5 Primitivele din familia exec.


Apelul uneia din primitivele din familia exec duce la încetarea execuţiei codului
obiect şi la execuţia de la început a unui nou cod executabil, specificat prin parametru, în
cadrul aceluiaşi proces. Ca parametrii se pasează numele fişierului ce conţine noul cod
executabil, argumentele şi, eventual, noile variabile de mediu şi valorile acestora. Apelurile
din familia exec se diferenţiază prin modul de transmitere a argumentelor şi prin modul de
localizare a fişierului executabil. Toate apelurile returnează –1, ceea ce reprezintă eşec
datorat negăsirii fişierului executabil invocat prin nume. Prin urmare, nu există ieşire din
aceste apeluri decât în caz de eroare.
Apelul
int execl (char *executabil, char *arg, …, NULL);
este cu număr variabil de argumente: primul, obligatoriu, este numele fişierului executabil
exprimat prin referinţă absolută, al doilea reprezintă primul argument care este numele
executabilului (a se vedea descrierea funcţiei main din al doilea capitol), după care
urmează argumentele, iar ultimul parametru are obligatoriu valoarea NULL. De exemplu,
dacă dorim ca procesul să listeze continutul directorului rădăcina (echivalentul comenzii
SHELL $ls –l /) apelul va avea următoarea formă:
execl (“/bin/ls”, “ls”, “-l”, “/”, NULL);
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 72
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Următorul apel este foarte asemănător cu cel descris mai sus, cu deosebirea că
fişierele executabile nu mai trebuie, în mod obligatoriu, specificate prin cale absolută,
deoarece căutarea acestora se face prin intermediul variabilei de mediu PATH:
int execlp (char *executabil, char *arg, …, NULL);
Exemplul anterior se va putea realiza astfel:
execlp (“ls”, “ls”, “-l”, “/”, NULL);
Apelul următor:
int execle (char *executabil, char *arg, …, NULL, char **arge);
este similar cu execl, cu deosebirea că are un argument în plus, ultimul, ce reprezintă
lista noilor variabile de mediu şi valorile acestora sub forma unei liste de adrese de şiruri
de caractere ce au sintaxa nume=valoare, terminat cu valoarea NULL. Acest ultim
argument va fi al treilea argument al functiei main pentru noul executabil.
Urmeaza trei apeluri cu numar fix de argumente. Primul este:
int execv (char *executabil, char **arg);
ce are acelaşi efect cu execl, cu deosebirea că argumentele se pasează sub forma unei
liste de adrese de şiruri de caractere terminate cu valoarea NULL. Exemplul de mai sus se
va realiza astfel:
char *argv[4];
argv[0]=”ls”;
argv[1]=”-l”;
argv[2]=”/”;
argv[3]=NULL;
execv (“/bin/ls”, argv);
Apelul următor este similar:
int execvp (char *executabil, char **arg);
cu execv, cu deosebirea că se caută executabilul prin intermediul variabilei de mediu
PATH.
Ultimul apel transmite, în plus, lista noilor variabile de mediu şi valorile lor, ca în
cazul lui excle:
int execve (char *executabil, char **arg, char **arge);
Numărul maxim de argumente ce poate fi transmis este limitat de constanta
ARG_MAX definită în headerul limits.h.
În urma apelului uneia din primitivele din familia exec se pot schimba următoarele
caracteristici ale procesului:
- proprietarul efectiv, în funcţie de faptul dacă fişierul executabil are
poziţionat sau nu indicatorul set-uid.
- grupul proprietar efectiv în funcţie de faptul dacă fişierul executabil
are poziţionat sau nu indicatorul set-gid.
- funcţiile de tratare a semnalelor devin cele implicite.
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 73
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

- descriptorii de fişiere ce au poziţionat indicatorul FD_CLOEXEC în


urma unui apel la fcntl vor fi închise.
În finalul acestui paragraf vom explica modul de funcţionare a proceselor
de tip SHELL.
a. Se citeşte linia de comandă.
b. Se lansează un proces fiu prin apel la fork.
c. Procesul fiu va identifica din linia de comandă noul executabil ce va
fi lansat şi argumentele acestuia, şi va face un apel din familia exec (unul cu număr fix de
argumente) lansând astfel procesul.
d. Procesul tată va face apel la wait în caz că procesul lansat este în
prim plan (foreground).
e. Se merge la a, adică la citirea unei noi linii de comandă.

4.6 Intrebari si teste

Testul de autoevaluare nr. 4

1. Care sunt starile principale ale unui proces?

2. Care este apelul sistem UNIX prin care se poate lansa un nou
Răspunsurile proces ?
la test se vor
da în spaţiul
liber din
chenar, în 3. Care este familia de apeluri sistem UNIX prin care se poate lansa in
continuarea executie un nou fisier executabil in cadrul aceluiasi proces ?
enunţurilor

Răspunsurile la acest test se găsesc la pagina 74 a acestei unităţi de


învăţare.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 74
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Tema de autoinstruire nr. 4

Consultaţi bibliografia pentru a afla:

1. Ce carateristici ale procesului tata sunt mostenite de procesul fiu.

2. Diferitele ajantaje ale apelurilor din familia exec.

4.6. Comentarii şi răspunsuri la testele de autoevaluare

Testul 4.

1. Ready, running, sleeping, stopped, zombi.

2. fork

3. Apelurile din familia exec

4.6. Lucrare de verificare pentru studenţi

Realizati un mini process de tip shell.

Indicaţii de redactare:

- scrieţi un parser de comenzi


- scrieti un modul de lansare a comenzilor

Rezolvările, dar şi dificultăţile întâmpinate, vor fi expediate prin mail


tutorelui.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 75
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

5. GESTIONAREA SEMNALELOR

Obiectivele unităţii de învăţare nr. 5

După ce veţi parcurge această unitate de învăţare, veţi reuşi să:


 Transmiteti un semnal catre alt proces
 Sa blocati si deblocti semnale
 Sa instalati propriile rutine de tratare a semnalelor

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 76
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

5.1. Introducere
Un proces poate primi un semnal, ceea ce este echivalent cu întreruperea sa,
urmată de trecerea în mod nucleu, de unde se apelează o funcţie ce tratează semnalul,
numită handler de semnal, iar dacă aceasta se termină cu return, se va reveni in mod
nucleu, de unde se va reveni, dacă este cazul, în mod utilizator, continuându-se, astfel,
programul din punctul în care a fost intrerupt.

Un semnal poate fi primit, fie prin apăsarea unor taste speciale de la terminalul de
control (intr, quit, stop), fie prin transmiterea de către sistemul de operare în cazul unei
erori (tentativă de a scrie la adrese ilegale, de exemplu), fie transmise de alte procese.

În urma transmiterii, semnalul intră în starea pendinte, adică în aşteptarea faptului


ca evenimentul să fie luat in consideraţie de către proces. Odată luat in consideraţie, se
va trece in mod nucleu, la executarea apleului handler, şi se revine cu execuţia în punctul
în care a fost intreruptă. Orice semnal, cu anumite excepţii pe care le vom enumera mai
jos, poate fi blocat de către proces, ceea ce semnifică faptul că luarea sa in evidenţă va fi
amânată până la deblocare. În caz că în perioada blocării, au venit mai multe semnale de
acelaşi tip cu cel blocat, după deblocare doar ultimul va fi luat in consideraţie, celelalte
fiind pierdute.

În ceea ce priveşte gestionarea semnalelor, procesele au următoarele caracteristici:

- mulţimea semnalelor pendinte


- mulţimea semnalelor blocate
- funcţiile de tratare (handler) pentru fiecare semnal în parte.
La lansare, fiecare semnal posedă o funcţie de tratare implicită,

programatorul având posibilitatea să instaleze alte funcţii de tratare a semnalelor.

5.2. Identificarea semnalelor


Numărul de semnale ce pot fi primite de un proces diferă de la o versiune la alta a
sistemului de operare. Aceste număr este definit sub forma unei constante simbolice,
NSIG, în fişierul header signal.h. Tot în acest header, fiecărui semnal i se atribuie o
valoare întreagă sub forma unei macrodefiniţii, valoarea fiind cuprinsă între 0 şi NSIG.
Denumirea acestei macrodefiniţii începe cu prefixul SIG şi este urmată de identificarea
semnalului sufix. De exemplu macrodefiniţia:

#define SIGKILL 9

defineşte semnalul cu sufixul KILL având valoarea 9. Din SHELL unui proces, având pid-ul
pid, i se poate transmite un semnal fie cu ajutorul comenzii:

$kill –nr pid

(unde primul argument reprezintă numărul semnalului, care însă poate să fie diferit de la
un sistem la altul) sau
$kill –sufix pid

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 77
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

De exemplu semnalul SIGKILL se poate transmite procesului cu pid 12134 cu ajutorul


uneia din comenzile:
$kill –9 12314

sau

$kill –KILL 12314.

Orice semnal este primit, fie în urma unei comenzi kill din SHELL, fie a apelului
sistem cu acelaşi nume efectuat de alt proces, fie în urma producerii unui eveniment
specific, extern procesului, cum ar fi tastarea unor caractere speciale de la terminalul de
control, erori de program, etc. Un proces nu poate transmite, cu succes, un semnal către
alt proces, decât dacă acesta are acelaşi proprietar real cu acesta sau dacă procesul ce
transmite semnalul are drept proprietar pe root.

Implicit, efecul recepţiei unui semnal poate fi:

1. terminare
2. terminare cu producerea unui fişier cu numele core ce conţine
informaţii utile pentru o depanare ulterioară

3. nici un efect
4. suspendarea procesului
5. trezirea procesului suspendat
În continuare furnizăm lista semnalelor existente pe marea majoritate a

implementărilor sistemului de operare, împreună cu evenimentele ce conduc la primirea


acestora şi cu consecinţele primirii acestora, numerotate de la 1 la 5, conform enumerării
de mai sus.

Semnal Evenimentul care îl Efect implicit


produce

SIGHUP Terminarea procesului Terminare


lider de sesiune

SIGINT Tastarea tastei intr de la Terminare


terminalul de control

SIGQUIT Tastarea tastei quit de la Terminare cu creare de


terminalul de control fişier core

SIGILL Instrucţiune ilegală (apel Terminare cu creare de


via un pointer catre o fişier core
funcţie invalid)

SIGABRT Apel la funcţia abort Terminare

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 78
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

SIGFPE Eroare aritmetică Terminare


(impărţire la zero, etc.)

SIGKILL Terminare, nemodificabil

SIGSEGV Scriere la o adresă Terminare cu creare de


ilegală fişier core

SIGPIPE Scriere într-un fişier tub Terminare


fără cel puţin un
descriptor in citire

SIGALARM Apel la alarm Terminare

SIGTERM Comanda SHELL kill Terminare


fără argumente

SIGUSR1 Terminare

SIGUSR2 Terminare

SIGCHLD Terminarea unui proces Ignorat


fiu

SIGSTOP Suspendare,
nemodificabil

SIGTSTP Tastarea tastei susp de Suspendare


la terminalul de control

Continuarea unui proces


SIGCONT suspendat, nemodificabil

SIGTTIN Citire de la terminal Suspendare


efectuată de un proces in
background

SIGTTOU Scriere la terminal Suspendare


efectuată de un proces in
background în cazul în
care terminalul lucrează
in modul tostop

Mai există şi alte semnale legate de X-WINDOWS pe care, însă, nu le expunem


aici.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 79
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

O menţiune specială pentru condţiile în care un proces primeşte semnalul


SIGALARM, şi anume în urma apelului:

#include <unistd.h>

unsigned int alarm (unsigned int sec);

unde argumentul reprezintă numărul de secunde după care procesul curent va primi

semnalul SIGALARM. Aşadar, este vorba de un semnal care este primit de la acelaşi

proces. Valoarea zero a argumentului reprezintă o comandă de anulare a tuturor

comenzilor anterioare efectuate cu ajutorul acestui apel. Valoarea maximă a argumentului

este dată de constanta MAX_ALARM, definită în headerul sys/param.h.

5.3 Trimiterea de semnale către un proces


În afară de evenimentul ce duce la primirea unui semnal specific de către un
proces, un alt proces poate trimite un semnal către procesul în cauză fie cu ajutorul
comenzii SHELL kill, fie cu ajutorul apelului:

int kill (pid_t pid, int sig);

unde primul argument reprezintă procesele către care se transmite semnalul, iar al doilea
identificatorul semnalului trimis.

In caz că primul argument este strict pozitiv, acesta reprezintă identificatorul


procesului către care se trimite semnalul. În situaţia în care acest argument este nul,
înseamnă că semnalul este transmis către toate procesele din acelasi grup cu procesul
curent. Valoarea –1 a argumentului este ilegală, în schimb orice altă valoare negativă
semnifică transmiterea semnalului către toate procesele din grupul al cărui lider este
procesul având drept identificator valoarea absolută a acestui argument.

Al doilea argument trebuie să fie nenegativ şi să nu depăşească valoarea NSIG.


Valoarea nulă a acestui argument semnifică faptul că nu se doreşte transmiterea unui
semnal anume, ci pur şi simplu se doreşte testarea existenţei proceselor desemnate de
primul argument.

Apelul se încheie cu succes (cod de retur 0) doar dacă procesele către care se
emite semnalul au acelaşi proprietar cu procesul curent, excepţie situaţia în care procesul
curent are drept proprietar pe root.

Cu ajutorul următorului apel, un proces poate trimite către el însuşi un semnal,


specificat de argument:

int raise (int sig);

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 80
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

5.4 Comportarea proceselor la primirea unui semnal.

La primirea unui semnal, un proces trece la execuţie în mod nucleu. Sistemul de

operare va apela o funcţie specifică de tratare a semnalului respectiv, denumită in

continuare handler. Aceasta funcţie are prototipul următor:

void handler (int sig);

unde argumentul reprezintă identificatorul semnalului de tratat. Dupa execuţia acestei

funcţii, în caz ca aceasta se termină cu return, procesul reintră in mod nucleu, iar sistemul

de operare va relua execuţia procesului din punctul în care a fost întrerupt.

La lansarea procesului, fiecare semnal posedă un handler implicit, desemnat prin

constanta generică SIG_DFL. Utilizatorul poate instala alt handler pentru anumite semnale,

cu excepţia lui SIGKILL, SIGCONT şi SIGSTOP, cu ajutorul unor apeluri pe care le vom

descrie mai jos. O altă constantă generică pentru identificarea handlerelor este SIG_IGN

care semnifică o funcţie ce execută doar return, echivalentă cu ignorarea semnalului.

5. 5 Condiţii de recepţie a unui semnal de către un proces


În momentul în care un proces primeşte un semnal, acesta intră in stare de

aşteptare (pending) până la recepţia sa de către proces. Semnalele blocate rămân în stare

pending până la deblocare. În caz că se execută un apel sistem, semnalul rămâne în stare

pending până la terminarea apelului, adică apelurile sistem sunt neintreruptibile. Excepţie

fac, în anumite condiţii, apelurile: pause, sigsuspend, wait, waitpid, fcntl, read, send,

sendto, write, recv, recvfrom, msgsnd, msgrcv şi semop.

Un apel sistem întrerupt returnează codul de eroare –1, iar variabila errno ia

valoarea EINTR.

Un proces intrat în starea stopat, în urma primirii unuia din semnalele SIGSTOP sau

SIGTSTP va fi trezit de semnalele SIGKILL, SIGTERM, ce vor duce la terminarea

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 81
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

procesului (mai puţin situaţia în care SIGTERM are specificat un handler care se termină

cu return), şi SIGCONT, ce va duce la continuarea procesului.

Trimiterea unui semnal către un proces aflat în stare zombi nu are nici un efect.

5. 6 Blocarea şi deblocarea semnalelor


Orice proces poate bloca sau debloca anumite semnale cu excepţia lui SIGKILL,

SIGSTOP si SIGCONT.

O mulţime de semnale este specificată prin tipul de date sigset_t, definit în

headerul signal.h. Pentru mânuirea variabilelor de acest tip pot fi folosite apelurile:

int sigemptyset (sigset_t *p);

ce atribuie variabilei de ieşire desemnată de parametru, valoarea corespunzătoare mulţimii

vide.

int sigfillset (sigset_t *p);

ce atribuie variabilei de ieşire desemnată de parametru, valoarea corespunzătoare mulţimii

totale adică {1, … , NSIG}.

int sigaddset (sigset_t *p, int sig);

ce atribuie variabilei de ieşire desemnată de primul parametru, valoarea obţinută prin

adăugarea la mulţimea de semnale indicată de acest parametru, a semnalului desemnat

de al doilea parametru.

int sigdelset (sigset_t *p, int sig);

ce atribuie variabilei de ieşire desemnată de primul parametru, valoarea obţinută prin

scoaterea din mulţimea de semnale indicată de acest parametru, a semnalului desemnat

de al doilea parametru.

int sigismember (sigset_t *p, int sig);

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 82
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

returnează 1 dacă semnalul desemnat de al doilea argument face parte din mulţimea de

semnale desemnată de primul argument, şi 0 în caz contrar.

Modificarea mulţimii de semnale blocate se poate face cu ajutorul apelului:

#include <signal.h>

int sigprocmask (int op, sigset_t *p_nou, sigset_t *p_vechi);

unde ultimul parametru este de ieşire şi va conţine, dacă este diferit de NULL, mulţimea

semnalelor blocate înaintea apelului. Primul argument poate lua una din următoarele valori,

definite prin constante simbolice:

- SIG_SETMASK noua mulţime de semnale blocate este cea desemnată de al

doilea argument.

- SIG_BLOCK noua mulţime de semnale blocate este cea obţinută prin

reuniunea mulţimii de semnale desemnate de al doilea argument la mulţimea de semnale

deja blocate. Cu alte cuvinte se adaugă, eventual, elemente noi la mulţimea de semnale

blocate.

- SIG_UNBLOCK noua mulţime de semnale blocate este cea obţinută


prin diferenţa dintre mulţimea de semnale deja blocate şi mulţimea de semnale desemnate

de al doilea argument. Cu alte cuvinte se vor debloca, eventual, anumite semnale.

Mulţimea de semnale blocate ale procesului curent se poate obţine cu ajutorul

următorului apel:

#include <signal.h>

int sigpending(sigset_t *p);

unde argumentul este de ieşire şi va conţine lista semnalelor blocate.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 83
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

5. 7 Gestionarea handlerelor de tratare a semnalelor.

Utilizatorul are la dispoziţie următoarea structură definită în signal.h:

struct sigaction {

void (*sa_handler)();

sigset_t sa_mask;

int sa_flags;

};

Primul câmp semnifică un handler pentru tratarea unui semnal. Poate avea drept valoare

şi una din constantele simbolice SIG_DFL sau SIG_IGN.

Al doilea câmp indică o mulţime de semnale care se va adăuga la mulţimea de

semnale deja blocate pe durata execuţiei handlerului. Semnalul în curs de tratare este, în

toate circumstanţele, blocat pe durata tratării.

Al treilea câmp este construit dintr-o disjuncţie bit cu bit a următoarelor constante

simbolice:

- SA_NOCLDSTOP nu se va primi semnalul SIGCHLD în cazul


când unul din fii intră în starea stopat.

- SA_RESETHAND handlerul nou instalat nu va fi valabil decât


pentru prima recepţie a semnalului, după care se va instala handlerul implicit.

- SA_RESTART un apel sistem întrerupt va fi reluat după tratarea


semnalului în loc să întoarcă codul de eroare –1.

- SA_NOCLDWAIT are efect pentru semnalul


SIGCHLD, caz în care procesele fiu terminate dispar din tabela de procese fără a trece în

starea zombi în aşteptarea unui apel wait sau waitpid efectuat de procesul tată. Primirea

acestui semnal va înştiinţa procesul tată de terminarea fiului.


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 84
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Instalarea unui nou handler de tratare a unui semnal se face cu ajutorul apelului:

#include <signal.h>

int sigaction(int sig, struct sigaction *p_nou, struct sigaction *p_vechi);

unde primul argument reprezintă semnalul căruia vrem să-l modificăm comportamentul, al

doilea argument descrie noul comportament, pe când al treilea argument este de ieşire şi

va conţine, dacă este diferit de NULL, detalii despre modul de tratare a semnalului înainte

de apel. Al doilea argument poate avea valoare NULL, ce va conduce la citirea

informaţiilor despre tratarea semnalului cu ajutorul celui de al treilea argument, fără a

efectua o modificare de comportament.

Instalarea unui nou handler pentru tratarea unui semnal poate fi făcută şi cu ajutorul

apelului următor, moştenit de la versiunile mai vechi ale sistemului, şi care este mai puţin

flexibil:

#include <signal.h>

void (*signal(int sig, void (*handler)())();

unde primul argument desemnează semnalul căruia vrem să-i modificăm comportamentul,

iar al doilea un pointer către noul handler (poate lua şi valorile SIG_DFL şi SIG_IGN).

Valoarea de retur este un pointer către vechiul handler de tratare a semnalului. Instalarea

noului handler are efect decât pentru prima apariţie a semnalului, după care va fi reinstalat

handlerul implicit. Dacă vrem ca instalarea handlerului să aibă caracter permanent putem

să inserăm în funcţia handler un apel la signal care să instaleze acelaşi handler.

După efectuarea cu succes a unui apel din familia exec se vor reinstala automat

handlerele implicite pentru toate semnalele.

5.8 Aşteptarea unui semnal de către un proces


În urma apelului

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 85
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

#include <unistd.h>

int pause();

un proces este trecut în starea adormit (sleeping) până la recepţionarea unui semnal. Este

un apel intreruptibil care returnează totdeauna –1 si errno ia valoarea EINTR.

Primitiva următoare:

#include <signal.h>

int sigsuspend(sigset_t *p);

realizează într-o manieră neintreruptibilă instalarea temporară a măştii de semnale blocate

desemnate de primul argument şi trecerea procesului în stare adormit până la primirea

unui semnal ce nu este blocat. Ieşirea din acest apel se va face, deci, doar în urma primirii

unui semnal. În urma acestui eveniment, mulţimea de semnale blocate va fi cea de

dinaintea apelului.

5.9 Salturi într-o altă funcţie decât cea curentă


Studiu de caz. O eroare de programare conduce la primirea semnalului SIGSEGV,
în urma unei tentative de scriere la o adresa ilegală. Comportarea implicită la primirea
acestui semnal este terminarea procesului. Cum vom proceda atunci pentru a evita acest
lucru, şi ca execuţia programului să se reia dintr-un anume punct prestabilit? A instala un
handler de tratare a semnalului care să se termine cu return ar înrăutăţi situaţia,
deoarece, în acest caz, la reintrarea în mod utilizator, se va reedita aceeaşi încercare de
scriere la adresa ilegală, prin revenirea execuţiei în punctul în care a fost întreruptă. Se va
primi din nou semnalul SIGSEGV şi astfel se va intra într-un cerc vicios din care nu mai
putem ieşi.

Din fericire, sistemul ne pune la dispoziţie apeluri prin care putem rezolva această
problemă.

În headerul setjmp.h este definit tipul de date sigjmp_buf, care memorează


contextul unui proces la un moment dat, pentru ca acesta să fie reluat din acest punct.
Contextul curent al procesului se va reţine cu ajutorul apelului:

#include <setjmp.h>

int sigsetjmp (sigjmp_buf context, int optiune);

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 86
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

unde primul parametru este de ieşire şi va memora contextul procesului din momentul
apelului. Este evident că această variabilă, trebuie să fie, în principiu, globală. Al doilea
argument, dacă este diferit de zero, indică faptul că se salvează şi mulţimea de semnale
blocate. Codul de retur al acestui apel va fi explicat mai târziu. Reţinem, totuşi, faptul că un
apel normal, efectuat în scopul memorării contextului procesului, va returna totdeauna
valoarea 0.
După aceea, dacă, dintr-un punct ulterior al procesului, dorim să revenim în punctul
în care am salvat contextul, putem folosi apelul:
#include <setjmp.h>

int siglongjmp (sigjmp_buf context, int valoare);

unde primul parametru este cel salvat cu ajutorul apelului sigsetjmp. Efectul acestui apel
este un salt imediat după apelul lui sigsetjmp corespunzător. Al doilea argument al lui
siglongjmp este codul de retur al lui sigsetjmp, care, de obicei, se transmite cu o valoare
diferită de zero. În felul acesta, după un apel la sigsetjmp, se poate face diferenţierea
dacă s-a ieşit dintr-un apel normal, sau în urma unui apel la siglongjmp.
Atragem atenţia asupra faptului că în urma apelului siglongjmp se poate face salt
doar într-o funcţie apelantă, în mod direct sau indirect, nicidecum într-una apelată, în
mod direct sau indirect.
Un mod de tratare moştenit de la versiunile mai vechi foloseşte tipul de date
jmp_buf, folosind apelurile :
#include <setjmp.h>

int setjmp (jmp_buf context);

echivalent cu sigsetjmp(context, 0); şi


#include <setjmp.h>

int longjmp (sigjmp_buf context, int valoare);

echivalent cu siglongjmp(context, valoare);

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 87
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

5.10 Intrebari si teste

Testul de autoevaluare nr. 5

1. Enumerati trei semnale primite de la sistemul de operare in urna


unor erori grave de programare

Răspunsurile 2. Care sunt apelurile sistem UNIX prin care se pot instala rutine de
la test se vor tratare a semnalelor?
da în spaţiul
liber din
chenar, în
continuarea 3. Care este numele comenzii SHELL si a apelului sistem UNIX
enunţurilor prin care se pot emite semnale catre un proces ?

Răspunsurile la acest test se găsesc la pagina 88 a acestei unităţi de


învăţare.

Tema de autoinstruire nr. 5

Consultaţi bibliografia pentru a afla:

1. Comportamentul la primirea semnalelor clasice UNIX.

2. Avantajele folosirii apelului sigaction fata de signal.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 88
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

5.11. Comentarii şi răspunsuri la testele de autoevaluare

Testul 5.

1. SIGSEGV, SIGILL, SIGBUS.

2. signal, sigaction

3. kill

5.12. Lucrare de verificare pentru studenţi

Realizati o aplicatie care mascheaza toate semnalel, mai putin cele


nemodificabile.

Indicaţii de redactare:

- Mascati cate un semnal odata


- Comentati codul

Rezolvările, dar şi dificultăţile întâmpinate, vor fi expediate prin mail


tutorelui.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 89
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

6. COMUNICAREA INTRE PROCESE CU AJUTORUL FISIERELOR DE TIP TUB


(PIPE)

Obiectivele unităţii de învăţare nr. 6

După ce veţi parcurge această unitate de învăţare, veţi reuşi să:


 Creati si exploatati fisiere de tip tub cu nume
 Creati si exploatati fisiere de tip tub fara nume
 Sa programati comenzi SHELL inlantuite

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 90
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

6.1 Caracteristicele fişierelor de tip tub


Fişierele de tip tub servesc la comunicarea unidirecţionala între procese, în sensul

că descriptorii deschişi corespunzători acestor fişiere sunt fie în mod citire, fie în mod

scriere. În consecinţă nu se pot deschide aceste fişiere în mod citire şi scriere simultan.

În plus operaţia de citire din astfel de fişiere este distructivă, în sensul că, informaţia
odată citită nu poate fi recuperată a doua oară din fişier. Informaţia este citită în mod fifo
(primul intrat, primul ieşit), în sensul că octeţii sunt citiţi în ordinea în care au fost scrişi.

Orice tub are o capacitate finită. Mai precis dacă se scrie informaţie fără a fi citită,
se poate ajunge la umplerea tubului, caz în care procesul care scrie va fi blocat la un apel
write, până cand un alt proces va efectua un apel de citire, golind astfel tubul.

O caracteristică esenţială a acestui tip de fişiere este numărul de descriptori


deschişi pentru citire, precum şi numărul de descriptori deschişi pentru scriere.

Fişierele de tip tub se împart în două categorii:

- tuburi obişnuite (fără nume) ale căror i-noduri nu se


regasesc în tabela de i-noduri de pe disc, ci numai în tabela de i-noduri din memorie.
Aceste tuburi nu sunt disponibile decât procesului care le-a creat precum şi descendenţilor
acestora. Numărul de legături fizice ale acestora este totdeauna nul şi, în consecinţă, nu le
corespunde intrare n nici un director. Aceste fişiere dispar odata cu terminarea oricarui
proces ce are deschis un descriptor asupra acestora.

- tuburi numite, ce au cel puţin o legatură fizică într-un director şi a căror


existenţă nu depinde de existenţa unor procese particulare. Aceste fişiere pot fi vizualizate
cu ajutorul comenzii ls –l, în dreptul caracterului ce desemnează tipul de fişier putând fi
observata litera p.

6.2 Tuburile obişnuite


Aşa cu am spus mai sus, descriptori asupra unui tub obişnuit nu poate avea decât
procesul care l-a creat precum şi descendenţii acestuia ce obţine descriptori prin
moştenire sau prin apel la primitiva dup.

Crearea unui tub obişnuit se face cu ajutorul primitivei:

#include <unistd.h>

int pipe (int *p);

unde argumentul este parametru de ieşire şi corespunde unui vector de minim doi întregi,
alocat în prealabil. În caz că apelul a fost încununat de succes, p[0] va conţine un
descriptor în mod citire, iar p[1] va conţine un descriptor în scriere aferent noului fişier
creat. Spre deosebire de descriptori deschişi pentru fişiere de tip obişnuit, anumite operaţii
sunt ilegale asupra acestor descriptori cum ar fi lseek sau ioctl. În schimb, cu ajutorul
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 91
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

apelului fcntl, se poate comanda ca operaţiunile de scriere sau citire să devină


neblocante. Implicit, un urma creării, descriptorii au proprietatea ca operaţiunile de citire
sau scriere să fie blocante

Citirea din astfel de fişiere se face cu ajutorul primitivei read. Descriem în


continuare comportarea acestei primitive la citirea dintr-un tub obişnuit. Presupunem că
tubul conţine dim caractere. Fie secvenţa de cod :

#define DIM_BUF 128

………..

char buf[DIM_BUF];

int nr;

nr = read (p[0], buf, DIM_BUF* sizeof(char));

Efectul acestui apel este următorul:

- dacă tubul nu este gol (dim > 0) atunci se citesc


min(dim, DIM_BUF) caractere, iar acest număr reprezintă numărul de caractere citite.

dacă tubul nu este gol (dim = 0) atunci:


-
a. dacă numărul de descriptori deschişi în scriere este 0, atunci
codul de retur este 0, ceea ce este echivalent cu sfârşitul fişierului

b. dacă numărul de descriptori deschişi în scriere este diferit de 0,


atunci:

i. Dacă operaţiunea de citire este blocantă atunci procesul


aşteaptă ca să se efectueze o operaţiune de scriere via un descriptor deschis în scriere

ii. Dacă operaţiunea de citire este neblocantă


- codul de retur este –1 iar ERRNO ia valoarea EAGAIN,
dacă este poziţionat indicatorul O_NONBLOCK

- codul de retur este 0, dacă este poziţionat indicatorul


O_NDELAY

Remarcăm faptul că mânuirea defectuoasă a descriptorilor ce corespund unui tub pot să


conducă la autoblocarea unui proces în care acesta face o tentativă de citire blocantă
dintr-un tub gol, ca în exemplul următor:

int p[2];

char ch;

pipe (p);

read (p[0], &ch, sizeof(char));


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 92
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Apelul este blocant deorece există descriptorul deschis în scriere. Un alt exemplu este

acela în care două procese se blochează unul pe celalalt:

int p1[2], p2[2];

char ch;

pipe (p1);

pipe (p2);

if (fork() == 0){

read (p1, &ch, sizeof(char));

…………………….

else {

read (p2, &ch, sizeof(char));

…………………….

Pentru a se evita situaţii de genul celor descrise mai sus, drept o masură de igiena
programării, se recomandă închiderea tuturor descriptorilor inutili.

Scrierea în tuburile obişnuite se face cu ajutorul primitivei write. Fie secvenţa de


cod:

#define DIM_BUF 128

………..

char buf[DIM_BUF];

…………………..

int nr;

nr = write (p[0], buf, DIM_BUF* sizeof(char));

Efectul ultimului apel este urmatorul:

Înainte de toate scoatem în evidenţa faptul că, pentru un tub obişnuit, dacă s-au
închis toţi descriptorii de un fel, citire sau scriere, nu avem nici o şansă să recuperăm
descriptorul.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 93
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Dacă numărul de descriptori deschişi în citire este nul, procesul va primi semnalul
SIG_PIPE. Altfel:

- dacă scrierea este blocantă, se aşteaptă, eventual, golirea tubului


până când toţi octeţii au fost scrişi.

-dacă scrierea este neblocantă atunci:


a. dacă este loc în tub pentru a scrie toate caracterele, atunci
operaţiunea se efectuează cu succes şi codul de retur este DIM_BUF.

b. nu este loc în tub pentru a scrie toate caracterele atunci nu se


scrie nici un caracter şi

- codul de retur este –1 iar ERRNO ia valoarea EAGAIN,


dacă este poziţionat indicatorul O_NONBLOCK

- codul de retur este 0, dacă este poziţionat


indicatorul O_NDELAY

În incheierea acestui paragraf, exemplificăm un model de implementare posibilă, de


către un program de tip SHELL a unei comenzi de tipul:

$ls –l | wc -l

Reamintim că apelul dup duplică descriptorul dat drept argument în cel mai mic descriptor
liber.

int p[2];

pipe (p);

if (fork() == 0) {

close (1); /* închidere stdout */

dup (p[1]); /* stdout este intrarea în tub */

close (p[0]); /* măsură de “igienă” */

close (p[1]); /* măsură de “igienă” */

execlp (“ls”, “ls”, “-l”, NULL);

exit (2); /* eroare la exec */

else {

close (0); /* închidere stdin */

dup (p[0]); /* stdin este ieşirea în tub */

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 94
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

close (p[0]); /* masură de “igienă” */

close (p[1]); /* masură de “igienă” */

execlp (“wc”, “wc”, “-l”, NULL);

exit (2); /* eroare la exec */

6.3 Tuburile cu nume


Un fişier tub este “cu nume” dacă are cel puţin o legatură fizică (un nume) într-un

director.

Crearea unui asemenea fişier se poate face cu ajutorul comenzii SHELL

$mkfifo [-p] [-n mod] nume….

unde parametrul opţional –p cere crearea tuturor directoarelor care intervin, dacă este

necesar, iar argumentul care urmează parametrului opţional –n indică drepturile de acces

asupra fişierului nou creat, exceptând pe cele implicite ( a se vedea comanda umask). Din

program, un asemenea fişier se poate crea cu ajutorul apelului:

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo (char *nume, mode_t mod);

unde primul argument reprezintă numele fişierului de tip tub nou creat, iar al doilea

argument drepturile de acces exprimate prin disjuncţie bit cu bit între constantele

enumerate la prezentarea apelului stat.

O altă modalitate de a construi un asemenea fişier este primitiva mknod. Apelul

mkfifo (nume, mod);

este echivalent cu

mknod (nume, mod | S_IFIFO, 0);

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 95
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

Deschiderea unui fişier de tip tub “cu nume” se face cu ajutorul primitivei open. În

lipsa opţiunilor O_NONBLOCK sau O_NDELAY, aceste apeluri sunt blocante în sensul ca

o deschidere în citire aşteaptă ca numărul descriptorilor deschişi în scriere să fie nenul şi

viceversa. Acest lucru reprezintă o modalitate de sincronizare a două procese. În cazul

poziţionării unuia din indicatorii O_NONBLOCK sau O_NDELAY care fac apelul de

deschidere neblocant, atunci:

- deschiderea în citire reuşeşte totdeauna


- deschiderea în citire eşuează (cod de retur –1) dacă nu există nici un
descriptor deschis pentru scriere.

Ca la orice fişier, tuburile “cu nume” dispar dacă numărul de legături fizice devine

nul şi nici un proces nu posedă un descriptor asupra lui. Facem observaţia că, atunci când

numărul de legături fizice devine nul, dar există încă descriptori deschişi asupra lui, fişierul

devine “tub obişnuit”.

Operaţiile de citire şi, respectiv, scriere se realizează cu ajutorul primitivelor read şi,

respectiv, write. Atragem atenţia asupra faptului că atunci când un fişier de tip tub dispare

(are zero legături fizice şi nici un proces nu posedă un descriptor deschis asupra lui)

informaţia necitită se pierde.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 96
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

6.4 Intrebari si teste

Testul de autoevaluare nr. 6

1. Care este apelul sistem prin care se creeaza un tub cu nume?

2. Care este apelul sistem prin care se creeaza un tub fara nume?
Răspunsurile
la test se vor
da în spaţiul
liber din 3. Ce optiune a apelului sistem open este invalida la deschiderea
chenar, în unui tub cu nume ?
continuarea
enunţurilor

Răspunsurile la acest test se găsesc la pagina 98 a acestei unităţi de


învăţare.

Tema de autoinstruire nr. 6

Consultaţi bibliografia pentru a afla:

1. Modul exact de operare a apelurilor open, read, write,


close asupra fisierelor de tip tub.

2. Pro si contra utilizarii tuburilor cu nume fata de cele fara


nume.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 97
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

6.5. Comentarii şi răspunsuri la testele de autoevaluare

Testul 6.

1. mkfifo, creat.

2. pipe

3. O_RDWR

6.6. Lucrare de verificare pentru studenţi

Realizati comanda Shell care inlantuie mai multe comenzi elementare


odata.

Indicaţii de redactare:

- Deschideti n procese fiu, unde n este numarul de comenzi


elemetare
- Comentati codul

Rezolvările, dar şi dificultăţile întâmpinate, vor fi expediate prin mail


tutorelui.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 98
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

7. COMUNICAREA INTRE PROCESE CU AJUTORUL IPC SYSTEM V

Obiectivele unităţii de învăţare nr. 7

După ce veţi parcurge această unitate de învăţare, veţi reuşi să:


 Creati si exploatati cele trei tipuri de IPC SYSTEM V
 Arbitrati concurenta proceselor cu ajutorul IPC SYSTEM V

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 99
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

7.1 Introducere
Noţiunea de IPC (Inter Process Communication) reprezintă obiecte cu ajutorul

cărora procesele comunică între ele. Aceste obiecte nu sunt tratate ca fişiere. Ele sunt

împărţite în trei categorii : cozile de mesaje, vectorii de semafoare şi segmentele de

memorie partajată. Fiecare din aceste obiecte sunt tratate în mod specific în funcţie de

tipul acestora. Totuşi, aceste obiecte posedă şi caracteristici comune pe care le vom

expune în cadrul acestui paragraf.

Toate aceste obiecte sunt identificate la nivelul sistemului de operare prin tip şi un
identificator numeric intern. Utilizatorul le poate invoca prin intermediul unui identificator
extern. În program, identificatorii externi sunt de tipul key_t, tip de date definit în headerul
<sys/types.h>.

Fişierul header <sys/ipc.h> conţine o seamă de macrodefiniţii pe care le


enumerăm mai jos, explicitând în acelaşi timp şi semnificaţia acestora

- IPC_PRIVATE obiect a cărui utilizare este exclusiv pentru procesul


creator şi descendenţii acestuia.

- IPC_CREAT creare obiect invocat, dacă acesta nu există deja.


- IPC_EXCL exclusiv creare de obiect invocat şi eroare în care
obiectul invocat există deja

- IPC_NOWAIT apeluri neblocante în timpul exploatarii


- IPC_RMID suprimare obiect
- IPC_STAT citire caracteristici obiect
- IPC_SET modificare caracteristici obiect.
Pentru memorarea caracteristicilor generale ale unui IPC, indiferent de

tipul acestuia, programatorul are la dispoziţie următoarea structură:

struct ipc_perm {

uid_t uid; /* proprietar */

gid_t gid; /* grup proprietar*/

uid_t cuid; /* utilizatorul creator */

gid_t cgid; /* grupul creator */

unsigned short mode; /* drepturi de acces*/

unsigned short seq; /* număr de utilizări */

key_t cheie; /* cheia externă */


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 100
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

};

Drepturile de acces sunt construite la fel ca la fişiere, cu ajutorul aceloraşi constante


simbolice.

O cheie externă este unic determinată de un fişier existent (mai precis de numărul
de disc şi de i-nod al acestuia) şi de un număr ales de utilizator. Cheia se obţine cu
ajutorul primitivei:

#include <sys/ipc.h>

key_t ftok(char *nume, int nr);

Observăm că pentru apelul prezentat mai sus nu are importanţă conţinutul fişierului ci
numai numărul de disc şi de i-nod. Este garantat faptul că acelaşi apel, cu aceleaşi
argumente, dă acelaşi rezultat, mai puţin situaţia în care fişierul este şters şi, apoi, creat
din nou cu acelaşi nume, deoarece nu ar mai avea acelaşi număr de i-nod. Apelul eşuează
dacă fişierul desemnat de primul argument nu există.

Din SHELL se pot vizualiza IPC-urile existente cu ajutorul comenzii:

$ipcs

Fără nici o altă opţiune, se afişează IPC-urile, indiferent de tipul acestora sub forma unui
tabel ale cărui rubrici sunt după cum urmează:

- T reprezintă tipul IPC-ului şi anume: q pentru cozi de mesaje, s pentru vectori de


semafoare şi m pentru segmente de memorie partajată.

- ID reprezintă indicatorul intern al IPC-ului (numeric)

- KEY reprezintă cheia externă a IPC-ului, sub forma 0x<număr

hexazecimal>. Valoarea particulară 0x0000000000 reprezintă un IPC creat cu opţiunea


IPC_PRIVATE şi care nu poate fi utilizat decât de către procesul creator şi descendenţii
acestuia.

- MODE reprezintă drepturile de acces în acelaşi format ca la comanda ls

–l precedată de două caractere ale căror interpretare se face în funcţie de tipul IPC-ului:

a. Cozi de mesaje :
i. S există cel puţin un proces blocat în timpul
tentativei de a scrie un mesaj (coadă plină)

ii. S există cel puţin un proces blocat în timpul


tentativei de a citi un mesaj (coadă goală)

b. Segmente de memorie partajată :


i. S-a facut o cerere de ştergere. IPC-ul devine privat,

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 101
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

putând fi utilizat doar de procesele ce îl au deja în folosinţă. La terminarea tuturor acestor


procese IPC-ul dispare definitiv din sistem

ii. C segmentul se va iniţializa cu 0 la prima ataşare


efectuată de un proces

- OWNER - proprietarul IPC-ului


- GROUP - grupul proprietar al IPC-ului
Opţiunile –q, -s si, respectiv, -m permit filtarea IPC-

urilor afişate în funcţie de tipul acestora: cozi de mesaje, vectori de semafoare şi, respectiv
zone de memorie partajată.

Opţiunea –c permite afişarea, în plus, a rubricilor CREATOR, ce reprezintă


utilizatorul ce a creat IPC-ul, şi CGROUP, ce reprezintă grupul de utilizatori ce a creat IPC-
ul.

Alte opţiuni conduc la afişarea unor informaţii suplimentare care sunt dependente
de tipul IPC-ului. Acestea sunt:

-b. Informaţiile suplimentare sunt:

i. Pentru cozi de mesaje:


- CBYTES dimensiunea cozii de mesaje exprimată în octeţi
ii. Pentru segmente de memorie partajată:
- NATTACH numărul de procese ce utilizează IPC-ul (ataşate)
-p. Informaţiile suplimentare sunt:

i. Pentru cozi de mesaje:


- LSPID ultimul proces ce a emis un mesaj.
- LRPID ultimul proces ce a extras un mesaj.
ii. Pentru segmente de memorie partajată:
- LSPID ultimul proces care s-a ataşat.
- LRPID ultimul proces care s-a detaşat.
-t. Informaţiile suplimentare sunt:

i. Pentru cozi de mesaje:


- STIME data ultimei emiteri de mesaj.
- RTIME data ultimei extrageri de mesaj.
- CTIME data ultimei modificări ale caracteristicilor IPC-ului cu ajutorul
primitivei msgctl.
ii. Pentru vectori de semafoare:
- OTIME data ultimei operaţiuni.
- CTIME data ultimei modificări ale caracteristicilor IPC-ului cu ajutorul
primitivei semctl.
iii. Pentru segmente de memorie partajată:
- ATIME data ultimei ataşări.
- DTIME data ultimei detaşări.
- CTIME data ultimei modificări ale caracteristicilor IPC-ului cu ajutorul
primitivei shmctl.
Ştergerea unui IPC se poate face cu ajutorul comenzii SHELL $ipcrm
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 102
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

urmată de opţiuni şi argumente ce indică IPC-ul şters. Opţiunile –q, -s şi, respectiv, -m
permit ştergerea IPC-urilor desemnate prin identificatorul intern, în funcţie de tipul
acestora: cozi de mesaje, vectori de semafoare şi, respectiv zone de memorie partajată.
Opţiunile –Q, -S şi, respectiv, -M permit ştergerea IPC-urilor desemnate prin cheia externă
în funcţie de tipul acestora: cozi de mesaje, vectori de semafoare şi, respectiv zone de
memorie partajată. De exemplu, comanda

$ipcrm –m 100 –Q 0x123e35f3

şterge segmentul de memorie partajată având indicatorul intern 100 şi coada de mesaje

ce are indicatorul extern 0x123e35f3.

Prezentăm, în continuare, apelurile specifice fiecărui IPC în parte, în funcţie de tipul

acestuia.

7.2 Cozile de mesaje


În fişierul header <sys/msg.h> sunt definite următoarele constante simbolice:

- MSG_NOERROR, ce semnifică faptul că un mesaj citit a cărui


lungime este mai mare decât cea specificată nu conduce la eroare, ci pur şi simplu la

trunchierea mesajului recepţionat

- MSG_R drept de citire din coada de mesaje


- MSG_W drept de scriere în coada de mesaje
- MSG_RWAIT blocaj în cursul unei operaţiuni de citire
- MSG_WWAIT blocaj în cursul unei operaţiuni de scriere
Descrierea unei cozi de mesaje se face cu ajutorul structurii următoare:

struct msqid_ds {

struct ipc_perm msg_perm;

/* caracteristici generale ale IPC-ului */

struct __msg *msg_first;

/* primul mesaj de extras */

struct __msg *msg_last;

/* ultimul mesaj de scris */

unsigned short msg_qnum;

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 103
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

/* numărul de mesaje existent în coadă */

unsigned short msg_qbytes;

/* numărul maxim de octeţi din coadă */

pid_t msg_lspid;

/* ultimul proces ce a emis un mesaj */

pid_t msg_lrpid;

/* ultimul proces ce a citit un mesaj */

time_t msg_stime;

/* data ultimei scrieri de mesaj */

time_t msg_rtime;

/* data ultimei citiri de mesaj */

time_t msg_ctime;

/* data ultimei modificări a IPC-ului prin intermediul apelului msgctl */

unsigned short msg_qbytes;

/* numărul de octeţi ocupaţi efectiv */

};

Mesajele se transmit prin intermediul unor structuri definite de utilizator al căror prim
câmp este, în mod obligatoriu, de tip long , ce desemnează tipul mesajului. Acest câmp
trebuie să fie, obligatoriu, strict pozitiv. Celelalte câmpuri din structură reprezintă
conţinutul mesajului. Asemenea tip de structuri le vom numi în continuare de tip mesaj.

Deschiderea unei cozi de mesaje se poate face cu ajutorul apelului:

#include <sys/msg.h>

int msgget (key_t cheie, int optiuni);

ce returnează, în caz de succes, identificatorul intern al cozii de mesaje. Primul argument


reprezintă identificatorul extern al IPC-ului, ce poate avea, eventual, valoarea
IPC_PRIVATE, iar al doilea este construit prin disjuncţie bit cu bit între constantele
IPC_CREAT, IPC_EXCL, şi constantele simbolice ce reprezintă drepturile de acces.
Apelul funcţionează astfel:

1. În caz că primul argument are valoarea


IPC_PRIVATE, se creează o nouă coadă de mesaje, utilizabilă doar de procesul creator şi
de descendenţii acestuia.
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 104
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

2. Altfel :
2.1 Dacă IPC-ul invocat de primul argument nu există, atunci

2.1.1 În lipsa indicatorului IPC_CREAT, se face acces la un IPC


existent. Dacă IPC-ul invocat nu există codul de retur este -1, iar variabila globală errno ia
valoarea ENOENT

2.1.2 În prezenţa indicatorului IPC_CREAT se creează o nou coadă


de mesaje.

3.1 Dacă IPC-ul invocat de primul argument există, apelul reuşeşte, mai

puţin situaţia în care ambii indicatori IPC_CREAT şi IPC_EXCL sunt poziţionaţi, caz în
care codul de retur este -1, iar variabila globală errno ia valoarea EEXIST.

Trimiterea unui mesaj se face cu ajutorul apelului:

#include <sys/msg.h>

int msgsnd (int id, void *p, int lungime, int optiuni);

unde primul argument reprezintă identificatorul intern al cozii de mesaje, al doilea


argument adresa unei structuri de tip mesaj, al treilea argument lungimea informaţiei
mesajului (în consecinţă fără lungimea câmpului ce desemnează tipul mesajului). Al
patrulea argument poate avea valoarea 0, caz în care apelul este blocant în situaţia în
care coada de mesaje este plină, sau IPC_NOWAIT, caz în care, în situaţia în care coada
este plină codul de retur este cel de eroare, -1, şi errno ia valoarea EAGAIN.

Alte motive de eşec al acestui apel sunt:

- IPC invocat inexistent, caz în care errno ia valoarea


EINVAL.

- IPC invocat existent, dar nu există permisiunea de


scriere, caz în care errno ia valoarea EPERM.

- tipul mesajului incorect (valoare mai mică sau


egală cu 0), caz în care errno ia valoarea EINVAL.

- apel intrerupt, caz în care errno ia valoarea


EINTR.

Extragerea unui mesaj dintr-o coadă de mesaje se face cu ajutorul apelului:

#include <sys/msg.h>

int msgrcv (int id, void *p, int lungime, long typ, int optiuni);

unde primul argument reprezintă identificatorul intern al cozii de mesaje, al doilea


argument reprezintă adresa unei structuri de tip mesaj, unde se va depune rezultatul
citirii, al treilea argument lungimea informaţiei mesajului (în consecinţă fara lungimea
--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 105
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

campului ce desemneaza tipul mesajului), iar al patrulea argument reprezintă tipul


mesajului ce se doreşte a fi citit. Argumentul de pe poziţia numărul cinci este construit prin
disjuncţie bit cu bit între constantele IPC_NOWAIT, caz în care, în situaţia în care coada
nu conţine nici un mesaj de tipul celui cerut, codul de retur este cel de eroare, -1, şi errno
ia valoarea EAGAIN, şi MSG_NOERROR, caz în care, dacă mesajul ce urmează a fi citit
are lungimea mai mare decât cea desemnată de al treilea argument, nu se furnizează cod
de eroare, ci mesajul citit este trunchiat.

Al patrulea argument, ce indică tipul de mesaj ce urmează a fi citit, se va interpreta


astfel:

- strict pozitiv: indică exact tipul mesajului ce urmează a fi citit


- egal cu 0: mesaj de orice tip
- strict negativ: orice mesaj de tipul inferior modulului celui de al
patrulea argument

Alte motive de eşec al acestui apel sunt:

- IPC invocat inexistent, caz în care errno ia valoarea EINVAL.


- IPC invocat existent, dar nu există permisiunea de citire, caz în care
errno ia valoarea EPERM.

- lungimea mesajului recepţionat este mai mare decât cea desemnată de al treilea
argument şi indicatorul MSG_NOERROR nu este poziţionat, caz în care errno ia valoarea
E2BIG.

- tipul mesajului incorect (valoare mai mică sau egală cu 0), caz în care
errno ia valoarea EINVAL.

- apel intrerupt, caz în care errno ia valoarea EINTR.


Gestionarea atributelor unei cozi de mesaje se poate face cu ajutorul apelului:

#include <sys/msg.h>

int msgctl (int id, int oper, struct msqid_ds *p);

unde primul argument reprezintă identificatorul intern al IPC-ului, iar al doilea este dat prin

una din următoarele constante simbolice:

- IPC_STAT ce indică faptul că se doreşte citirea caracteristicilor IPC-


ului, caz în care al treilea parametru este de ieşire.

- IPC_SET ce indică faptul că se doreşte modificarea caracteristicilor


IPC-ului, conform datelor furnizate de al treilea parametru. Singurele caracteristici
modificabile sunt proprietarul, grupul proprietar şi drepturile de acces. Această operaţie nu
poate fi efectuată cu succes decât de către proprietar şi root.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 106
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

- IPC_RMID ce indică faptul că se doreşte ştergerea IPC-ului, caz în care al treilea


argument nu mai este luat în seamă, putând chiar fi şi omis. Această operaţie nu poate fi
efectuată cu succes decât de către proprietar şi root.

7.3 Vectorii de semafoare


Noţiunea de semafor a fost o soluţie propusă de Dijkstra pentru arbitrarea
proceselor concurente. Din punct de vedere teoretic, un semafor este interpretat drept o
valoare intreagă ne-negativă, asupra căreia se pot efectua următoarele operaţii:

- high, ce reprezintă incrementarea valorii semaforului


- low, ce reprezintă decrementarea valorii semaforului în caz că aceasta
este strict pozitivă şi punerea procesului în aşteptarea efectuării unei operaţii de tip high
de către un alt proces în cazul în care valoarea semaforului este nulă (caz considerat drept
semafor având culoarea roşie).

În sistemul de operare UNIX, se admite o operaţie suplimentară asupra unui


semafor şi anume cererea de a pune procesul curent în aşteptarea evenimentului ca
semaforul să posede valoarea zero, în urma uneia sau mai multor operaţii de tip low
efectuate de alte procese.

În fişierul de tip header <sys/sem.h> este definită structura următoare, ce defineşte


caracteristicile unui semafor:

struct __sem {

unsigned short int semval; /* valoarea semaforului */

unsigned short int sempid; /* ultimul proces utilizator */

unsigned short int semncnt; /* numărul de procese ce aşteaptă efectuarea


unei operaţii de tip high asupra semaforului */

unsigned short int semzcnt; /* numărul de procese ce aşteaptă

ca valoarea semaforului să ajungă zero */};

Caracteristicile IPC-ului de tip vector de semafoare sunt memorate în structuri de


tipul următor

struct semid_ds {

struct ipc_perm sem_perm;

/* caracteristici generale ale IPC-ului */

struct __sem *_base;

/* vector conţinând caracteristicile semafoarelor individuale */

time_t sem_otime;

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 107
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

/* data ultimei operaţii */

time_t sem_ctime;

/* data ultimei modificări cu apelul semctl */

unsigned short int sem_nsems;

/* numărul de semafoare individuale */

};

O operaţie asupra unui semafor este reprezentată de structura:

struct sembuf {

unsigned short int sem_num;

/* indicile în vectorul de semafoare */

short sem_op;

/* tipul operaţiei */

short sem_flg;

/* opţiuni construite prin disjuncţie bit cu bit */

};

Semnul câmpului sem_op determină tipul operaţiei:

- strict pozitiv – incrementare cu valoarea specificată

- strict negativ – decrementare cu valoarea specificată


- egal cu zero – aşteptare ca valoarea semaforului să ajungă zero
Opţiunile din care poate fi construit câmpul sem_flg pot avea valorile

- IPC_NOWAIT, ce semnifică faptul că operaţia este ne-blocantă


- SEM_UNDO, ce semnifică faptul că operaţia va fi anulată la
terminarea procesului ce a executat-o.

Deschiderea unui vector de semafoare se face cu ajutorul apelului:

#include <sys/sem.h>

int semget (key_t cheie, int nr_sem, int optiuni);

care întoarce, în caz de succes, indicatorul intern al IPC-ului. Primul şi al treilea argument
sunt interpretate la fel ca primul, şi respectiv, al doilea argument al primitivei msgget. Al
doilea argument reprezintă numărul de semafoare atomice folosite, ce nu trebuie sa
depaseasca numărul de semafoare atomice declarat la creare.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 108
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

O mulţime de operaţii asupra unui vector de semafoare se poate face prin


intermediul apelului:

#include <sys/sem.h>

int semop (int semid, struct sembuf *tab_op, int optiuni);

unde primul argument reprezintă indicatorul intern al IPC-ului, al doilea o lista de operaţiuni
asupra diverselor semafoare individuale din vector, iar al treilea numărul de elemente din
lista de operaţiuni.

Facem următoarea observaţie esenţială: în caz că apelul reuşeşte, este garantat


faptul că toate operaţiile au reuşit, iar în caz de insucces că nici una din operaţii nu şi-a
produs efectul.

Cazurile frecvente de eşec ale apelului sunt următoarele:

- identificator ilegal de IPC, errno ia valoarea EINVAL


- lipsa drepturi de acces, errno ia valoarea EPERM
- număr de operaţiuni comandat prea mare, errno ia valoarea E2BIG
- număr de semafor specificat incorect, errno ia valoarea EFBIG
- eşec al apelului în mod neblocant, errno ia valoarea EAGAIN
- apel întrerupt, errno ia valoarea EINTR
Schimbarea caracteristicilor unui vector de semafoare se poate face cu

ajutorul apelului:

#include <sys/msg.h>

int semctl (int id, int semnum, int oper, ….);

unde primul argument reprezintă identificatorul intern al IPC-ului, al doilea numărul unui

semafor, al treilea tipul operaţiunii, iar prezenta şi tipul celui de al patrulea argument este

impus de tipul operaţiunii. Prezentăm, în continuare modul de execuţie în funcţie de

valoarea celui de al treilea argument, dat cu ajutorul unor constante simbolice:

- GETNCNT, al doilea argument reprezintă numărul unui semafor,


codul de retur reprezintă numărul de procese ce aşteaptă incrementarea semaforului.

- GETZCNT, al doilea argument reprezintă numărul unui semafor,


codul de retur reprezintă numărul de procese ce aşteaptă ca valoarea semaforului să
ajungă egală cu zero.

- GETVAL, al doilea argument reprezintă numărul unui semafor,


--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 109
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

codul de retur reprezintă valoarea semaforului.

- GETALL, al doilea argument reprezintă numărul total de semafoare,


al patrulea argument este un vector de tip unsigned short având ca dimensiune numărul
total de semafoare, codul de retur este 0 sau -1. Al patrulea argument este de ieşire şi
conţine valorile tuturor semafoarelor.

- GETPID, al doilea argument reprezintă numărul unui semafor,


codul de retur reprezintă identificatorul ultimului proces ce a efectuat o operaţie asupra
vectorului de semafoare.

- SETVAL, al doilea argument reprezintă numărul unui semafor, al


patrulea argument este de tip int, codul de retur este 0 sau -1. Se modifică valoarea
semaforului desemnat de al doilea argument cu valoarea desemnată de al patrulea
argument.

- SETALL, al doilea argument reprezintă numărul total de semafoare,


al patrulea argument este un vector de tip unsigned short având ca dimensiune numărul
total de semafoare, codul de retur este 0 sau -1. Se modifică valorile tuturor semafoarelor
cu valorile desemnate de al patrulea argument.

- IPC_RMID, se cere ştergerea vectorului de semafoare


- IPC_STAT, al patrulea argument este de tip pointer către o structură
de tip semid_ds, codul de retur este 0 sau -1. Al patrulea argument este de ieşire şi
conţine caracteristicile IPC-ului.

- IPC_SET, al patrulea argument este de tip pointer către o structură


de tip semid_ds, codul de retur este 0 sau -1. Se schimbă caracteristicile IPC-ului cu cele
desemnate de cel de al patrulea argument. Sunt modificabile doar proprietarul, grupul
proprietar şi drepturile de acces.

7. 4 Segmentele de memorie partajată


Elementele specifice acestui tip de IPC sunt descrise în fişierul header
<sys/shm.h>. Pe lângă alte constante simbolice, este definită structura:

struct shmid_ds {

struct ipc_perm shm_perm;

/* caracteristici generale ale IPC-ului */

int shm_segsz;

/* dimensiune exprimată în octeţi */

pid_t shm_lpid;

/* procesul ce a efectuat ultima operaţiune */

pid_t shm_cpid;

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 110
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

/* procesul creator al IPC-ului */

unsigned short int shm_nattch;

/* numărul de ataşări */

time_t shm_atime;

/* data ultimei ataşări */

time_t shm_dtime;

/* data ultimei detaşări */

time_t shm_ctime;

/* data ultimei modificări cu shmctl */

};

Identificatorul intern al unui asemenea IPC se obţine cu ajutorul apelului:

# include <sys/shm.h>

int shmget (key_t cheie, int dim, int opt);

ce intoarce, în caz de succes identificatorul intern al IPC-ului. Primul şi al treilea argument


se interpretează ca în cazul primitivelor msgget şi semget. Al doilea argument reprezintă
dimensiunea zonei de memorie. În cazul deschiderii unui IPC existent, valoarea acestui
argument trebuie să nu depăşească dimensiunea zone de memorie declarată la crearea
IPC-ului.

Dupa obţinerea identificatorului intern al IPC-ului, pentru a putea folosi zona de


memorie, se va face operaţiunea de ataşare cu ajutorul apelului:

# include <sys/shm.h>

void *shmat (int shmid, void *adr, int opt);

care întoarce adresa de start a zonei de memorie partajată. Primul argument este
identificatorul intern al IPC-ului, iar al doilea adresa la care dorim să plasăm zona de
memorie. În caz că acest argument are valoarea NULL, adresa este aleasă de sistem. Din
aceste considerente, este recomandabil folosirea valorii NULL pentru acest argument. În
caz că valoarea acestui argument este diferită de NULL, utilizatorul trebuie să fie sigur că
nu intră în conflict cu alte adrese. Prezenţa opţiunii SHM_RND în al treilea argument duce
la ajustarea valorii precizate de al doilea argument cu constanta SHMLBA.

Al treilea argument este alcătuit din disjuncţie bit cu bit între diverse constante
simbolice. Pe lângă SHMLBA, menţionăm valoarea SHM_RDONLY, ce reprezintă
opţiunea de folosire a zonei de memorie doar pentru citire. În această situaţie, orice

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 111
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

încercare de scriere în zona de memorie, se va solda cu primirea semnalului SIGSEV de


către proces.

Dupa ataşare, prin intermediul pointerilor, se poate utiliza zona de memorie


partajată, până la operaţia de detaşare ce se realizează cu ajutorul apelului :

# include <sys/shm.h>

int shmdt (void *adr);

unde argumentul reprezintă adresa obţinută, în prealabil, prin apel la shmat.

Gestionarea atributelor unei zone de memorie partajată se poate face cu ajutorul


apelului:

#include <sys/shm.h>

int shmctl (int id, int oper, struct shmid_ds *p);

unde primul argument reprezintă identificatorul intern al IPC-ului, iar al doilea este dat prin

una din următoarele constante simbolice:

- IPC_STAT ce indică faptul că se doreşte citirea caracteristicilor IPC-ului, caz în


care al treilea parametru este de ieşire.

- IPC_SET ce indică faptul că se doreşte modificarea caracteristicilor


IPC-ului, conform datelor furnizate de al treilea parametru. Singurele caracteristici
modificabile sunt proprietarul, grupul proprietar şi drepturile de acces. Această operaţie nu
poate fi efectuată cu succes decât de către proprietar şi root.

- IPC_RMID ce indică faptul că se doreşte ştergerea IPC-ului, caz în care la treilea


argument nu mai este luat în seamă, putând chiar fi şi omis. Această operaţie nu poate fi
efectuată cu succes decât de către proprietar şi root.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 112
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

7.5 Intrebari si teste

Testul de autoevaluare nr. 7

1. Care este apelul prin care se obtine cheia externa?

2. Care este apelul sistem cu ajutorul caruia se fac operatii


Răspunsurile concurente asupra semafoarelor?
la test se vor
da în spaţiul
3. Care este apelul sistem prin care se pot gestiona atributele unui
liber din obiect de tip IPC SYSTEM V?
chenar, în
continuarea
enunţurilor

Răspunsurile la acest test se găsesc la pagina 114 a acestei unităţi de


învăţare.

Tema de autoinstruire nr. 7

Consultaţi bibliografia pentru a afla:

1. Diferntele de comportament ale celor trei tipuri de IPC


SYSTEM V.

2. Diferenta dintre obiectele IPC SYSTEM V si fisiere

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 113
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

7.6. Comentarii şi răspunsuri la testele de autoevaluare

Testul 7.

1. ftok.

2. Semop.

3. msgctl, semctl, shmctl

7.7. Lucrare de verificare pentru studenţi

Implementati algoritmul producator-consumator.

Indicaţii de redactare:

- Realizati o versiune folosind semafoare si memorii partajate


- Realizati o versiune folosind cozi de mesaje

Rezolvările, dar şi dificultăţile întâmpinate, vor fi expediate prin mail


tutorelui.

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 114
Programarea aplicatiilor sub sistemul de operare LINUX
-----------------------------------------------------------------------------------------------------------------------

8. BIBLIOGRAFIE

[1]. Tanenbaum A. S. Modern Operating Systems, Prentice Hall, 2002


[2]. Doina Logofătu: Bazele programarii in C. Aplicații, Ed. 1, Editura
Polirom, Iași, 2006, ISBN 973-46-0219-5.
[3]. Baranga A, Programarea aplicatiilor C/C++ sub sistemul de operare
UNIX, Editura Albastră, 2004
[4]. Baranga A, Introducere în sistemul de operare UNIX, Editura Albastră,
2007

Bibliografie:
[1]. Tanenbaum A. S. Modern Operating Systems, Prentice Hall, 2002
[2]. Tanenbaum A. S. Operating Systems: design an implementation,
Prentice Hall, 1987
[3]. Baranga A, Programarea aplicatiilor C/C++ sub sistemul de operare
UNIX, Editura Albastra, 2004

--------------------------------------------------------------------------------------------------------------------
Andrei Baranga 115

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