ManualID.dvi Manual SO

Manual-SO

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 230 [warning: Documents this large are best viewed by clicking the View PDF Link!]

Universitatea “Alexandru Ioan Cuza” Ia¸si
Facultatea de Informatia
Departamentul de ˆ
Inat¸˘amˆant la Distant¸˘a
Cristian VIDRAS¸CU
SISTEME DE OPERARE
2006
Cuprins
Prefat¸˘a 1
I Sistemul de operare Linux – Ghid de utilizare 5
1 Introducere ˆın UNIX 7
1.1 Prezentare de ansamblu a sistemelor de operare din familia UNIX ...... 7
1.1.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.2 Scurt istoric al evolut¸iei UNIX-ului . . . . . . . . . . . . . . . . . . . 9
1.1.3 Vedere general˘a asupra sistemului UNIX . . . . . . . . . . . . . . . . 12
1.1.4 Structura unui sistem UNIX . . . . . . . . . . . . . . . . . . . . . . . 13
1.1.5 Caracteristici generale ale unui sistem UNIX . . . . . . . . . . . . . . 15
1.1.6 UNIX ¸si utilizatorii . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.1.7 Conectarea la un sistem UNIX . . . . . . . . . . . . . . . . . . . . . . 19
1.2 Distribut¸ii de Linux. Instalare . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.2.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.2.2 Instalarea unei distribut¸ii de Linux . . . . . . . . . . . . . . . . . . . 24
1.3 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2UNIX. Ghid de utilizare 37
2.1 Comenzi UNIX. Prezentare a principalelor categorii de comenzi . . . . . . . 37
2.1.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.1.2 Comenzi de help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.1.3 Editoare de texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.1.4 Compilatoare, depanatoare, ¸s.a. . . . . . . . . . . . . . . . . . . . . . 42
2.1.5 Comenzi pentru lucrul cu fi¸siere ¸si directoare . . . . . . . . . . . . . 43
2.1.6 Comenzi ce ofer˘a diverse informat¸ii . . . . . . . . . . . . . . . . . . . 43
2.1.7 Alte categorii de comenzi . . . . . . . . . . . . . . . . . . . . . . . . 45
2.1.8 Troubleshooting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.2 Sisteme de fi¸siere UNIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.2.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.2.2 Structura arborescena a sistemului de fi¸siere . . . . . . . . . . . . . 53
2.2.3 Montarea volumelor ˆın structura arborescena . . . . . . . . . . . . . 54
2.2.4 Protect¸ia fi¸sierelor prin drepturi de acces . . . . . . . . . . . . . . . 55
2.2.5 Comenzi de baz˘a ˆın lucrul cu fi¸siere ¸si directoare . . . . . . . . . . . 58
2.3 Interpretoare de comenzi UNIX, partea I-a: Prezentare general˘a . . . . . . . 64
i
2.3.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
2.3.2 Comenzi shell. Lansarea ˆın execut¸ie . . . . . . . . . . . . . . . . . . 65
2.3.3 Execut¸ia secvent¸ial˘a, condit¸ional˘a, ¸si paralel˘a a comenzilor . . . . . 67
2.3.4 Specificarea numelor de fi¸siere . . . . . . . . . . . . . . . . . . . . . . 68
2.3.5 Redirect˘ari I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
2.3.6 ˆ
Inl˘ant¸uiri de comenzi (prin pipe) . . . . . . . . . . . . . . . . . . . . 72
2.3.7 Fi¸sierele de configurare . . . . . . . . . . . . . . . . . . . . . . . . . 73
2.3.8 Istoricul comenzilor tastate . . . . . . . . . . . . . . . . . . . . . . . 74
2.4 Interpretoare de comenzi UNIX, partea a II-a: Programare bash . . . . . . . 76
2.4.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
2.4.2 Proceduri shell (script-uri) . . . . . . . . . . . . . . . . . . . . . . . 76
2.4.3 Variabile de shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
2.4.4 Structuri de control pentru script-uri . . . . . . . . . . . . . . . . . . 86
2.4.5 Alte comenzi shell utile pentru script-uri . . . . . . . . . . . . . . . 91
2.5 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
II Programare concurena ˆın Linux 104
Dezvoltarea aplicat¸iilor C sub Linux 106
3 Gestiunea fi¸sierelor 112
3.1 Primitivele I/O pentru lucrul cu fi¸siere . . . . . . . . . . . . . . . . . . . . . 112
3.1.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
3.1.2 Principalele primitive I/O . . . . . . . . . . . . . . . . . . . . . . . . 113
3.1.3 Funct¸iile I/O din biblioteca standard de C . . . . . . . . . . . . . . . 119
3.2 Accesul concurent/exclusiv la fi¸siere ˆın UNIX: blocaje pe fi¸siere . . . . . . . 120
3.2.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
3.2.2 Blocaje pe ¸siere. Primitivele folosite . . . . . . . . . . . . . . . . . 121
3.2.3 Fenomenul de interblocaj. Tehnici de eliminare a interblocajului . . 130
3.3 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
4 Gestiunea proceselor 135
4.1 Procese UNIX. Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
4.1.1 Not¸iuni generale despre procese . . . . . . . . . . . . . . . . . . . . . 135
4.1.2 Primitive referitoare la procese . . . . . . . . . . . . . . . . . . . . . 138
4.2 Crearea proceselor: primitiva fork . . . . . . . . . . . . . . . . . . . . . . . 140
4.2.1 Primitiva fork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
4.2.2 Terminarea proceselor . . . . . . . . . . . . . . . . . . . . . . . . . . 143
4.3 Sincronizarea proceselor: primitiva wait . . . . . . . . . . . . . . . . . . . . 144
4.3.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
4.3.2 Primitiva wait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
4.4 Reacoperirea proceselor: primitivele exec . . . . . . . . . . . . . . . . . . . 147
4.4.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
4.4.2 Primitivele din familia exec . . . . . . . . . . . . . . . . . . . . . . . 148
4.5 Semnale UNIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
ii
4.5.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
4.5.2 Categorii de semnale . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
4.5.3 Tipurile de semnale predefinite ale UNIX-ului . . . . . . . . . . . . . 155
4.5.4 Cererea explicit˘a de generare a unui semnal primitiva kill . . . . 160
4.5.5 Coruperea semnalelor – primitiva signal . . . . . . . . . . . . . . . 161
4.5.6 Definirea propriilor handler -ere de semnal . . . . . . . . . . . . . . . 164
4.5.7 Blocarea semnalelor . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
4.5.8 A¸steptarea unui semnal . . . . . . . . . . . . . . . . . . . . . . . . . 165
4.6 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
5 Comunicat¸ia inter-procese 171
5.1 Introducere. Tipuri de comunicat¸ie ˆıntre procese . . . . . . . . . . . . . . . 171
5.2 Comunicat¸ia prin canale interne . . . . . . . . . . . . . . . . . . . . . . . . . 172
5.2.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
5.2.2 Canale interne. Primitiva pipe . . . . . . . . . . . . . . . . . . . . . 173
5.3 Comunicat¸ia prin canale externe . . . . . . . . . . . . . . . . . . . . . . . . 180
5.3.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
5.3.2 Canale externe (fi¸siere fifo) . . . . . . . . . . . . . . . . . . . . . . . 180
5.3.3 Aplicat¸ie: implementarea unui semafor . . . . . . . . . . . . . . . . . 184
5.3.4 Aplicat¸ie: programe de tip client-server . . . . . . . . . . . . . . . . 188
5.4 Alte mecanisme pentru comunicat¸ia inter-procese . . . . . . . . . . . . . . . 190
5.5 S¸abloane de comunicat¸ie ˆıntre procese . . . . . . . . . . . . . . . . . . . . . 190
5.6 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Bibliografie 199
Anexe 200
A Rezolvare exercit¸ii din partea I 200
B Rezolvare exercit¸ii din partea II 204
iii
iv
Prefat¸˘a
Manualul de fat¸˘a, utilizat pentru disciplina “Sisteme de operare” la Anul 1, Semestrul
II, Sect¸ia IDD, are drept scop prezentarea unui sistem de operare concret, venind ˆın
completarea disciplinei “Arhitectura calculatoarelor ¸si sisteme de operare” din Anul 1
Semestrul I, care a prezentat conceptele teoretice ce stau la baza sistemelor de calcul ¸si a
sistemelor de operare pentru operarea acestora, folosite ˆın trecut ¸si ˆın zilele noastre.
ˆ
Inainte de a ˆıncepe studiul acestui manual, v˘a recomand s˘a recitit¸i manualul disciplinei
“Arhitectura calculatoarelor ¸si sisteme de operare” pentru a v˘a reˆımprosp˘ata cuno¸stint¸ele
referitoare la arhitectura sistemelor de calcul ¸si not¸iunile de baz˘a despre sisteme de operare.
Un sistem de operare are un rol foarte important ˆıntr-un sistem de calcul, el este
software-ul care asigur˘a interact¸iunea dintre utilizatorul uman ¸si partea de hardware (i.e.,
componentele zice) a calculatorului exploatat de acesta, precum ¸si buna funct¸ionare a
programelor de aplicat¸ii rulate de utilizator.
Sistemul de operare concret ales pentru prezentare ˆın cadrul acestei discipline este un
sistem de operare ce caat˘a ˆın ultima vreme un tot mai pronunt¸at rol, ¸si anume Linux-ul.
Motivele acestei alegeri sunt multiple. ˆ
In primul rˆınd, Linux-ul este unul dintre cele
mai recente ¸si totodat˘a venerabile sisteme de operare din lumea informaticii. Este recent,
deoarece s-a ascut ˆın anul 1991 ca proiect student¸esc al celebrului de-acum Linus Tor-
valds. Este un sistem “venerabil”, deoarece Linux mo¸stene¸ste caracteristicile sistemului
de operare UNIX, ap˘arut la sfˆır¸situl anilor ’60, ¸si care a reprezentat un salt tehnologic
spectaculos ˆın lumea sistemelor de operare ¸si a informaticii ˆın general. UNIX-ul a fost un
catalizator pentru aparit¸ia unor “minuni” tehnologice precum limbajul de programare C
ori ret¸elele de calculatoare ¸si INTERNET-ul, a c˘aror aparit¸ie ¸si dezvoltare a fost strˆıns
ˆımpletit˘a cu evolut¸ia UNIX-ului.
ˆ
In al doilea rˆınd, cel˘alalt exemplu concret pe care l-a¸s fi putut alege, pe baza r˘aspˆındirii
largi a acestuia, ar fi fost sistemul MS-Windows. Am preferat s˘a nu fac lucrul acesta, da-
torit˘a faptului c˘a Windows-ul este deja un sistem binecunoscut ¸si utilizat de marea ma-
joritate a utilizatorilor de calculatoare, inclusiv majoritatea disciplinelor practice de la
Facultatea de Informatic˘a folosesc ca suport sistemul Windows (ment¸ionez ˆın acest context
faptul c˘a ˆın anii superiori vet¸i studia o disciplin˘a dedicat˘a “Program˘arii ˆın Windows”, pre-
cum ¸si cursuri opt¸ionale dedicate framework-ului Dot NET ¸si altor tehnologii Microsoft).
Un alt motiv serios pentru alegerea Linux-ului, ¸si care a fost ¸si un factor determinant
ˆın aspˆındirea acestuia, este faptul a Linux-ul este disponibil gratuit; mai mult decˆıt atˆıt,
este un software open source, adic˘a sunt disponibile ¸si sursele programului, tot gratuit, un
lucru important pentru programatori, care au astfel posibilitatea de a studia codul sura,
de a ˆınat¸a din el, ¸si de a-l adapta propriilor necesit˘at¸i.
Obiectivele generale ale cursului sunt reprezentate de o prezentare general˘a a
Linux-ului (partea I a acestui manual) pe de o parte, ¸si de programarea concurent˘a ˆın
limbajul C sub Linux (partea II a acestui manual), pe de alt˘a parte.
Prima parte a manualului este dedicat˘a prezenarii sistemului de operare Linux, ¸si este
valabil˘a ˆın general pentru toate sistemele de operare din familia UNIX. Aceast˘a parte se vrea
a fi un ghid de utilizare a sistemului Linux, f˘ar˘a a avea pretent¸ia de a fi un ghid complet.
Am preferat s˘a insist ˆın prezentare pe conceptele fundamentale pe care se bazeaz˘a acest
sistem de operare, ar˘a a obosesc cititorul cu prea multe detalii ¸si numeroasele opt¸iuni
ale unor comenzi (mai mult, am preferat s˘a nu prezint deloc opt¸iunile numeroase ale
1
meniurilor de aplicat¸ii grafice, din lips˘a de spat¸iu, ¸si deoarece se asesc destule art¸i care
trateaz˘a acest aspect, cum ar cele publicate ˆın editura Teora).
Ultima parte a manualului este dedicat˘a program˘arii concurente. a vedem mai ˆıntˆıi
ce ˆınseamn˘a not¸iunea de programare concurent˘a?
ˆ
In primul semestru, la disciplina “Algoritmic˘a ¸si programare” at¸i ˆınat¸at programare
secvent¸ial˘a: programele scrise de dumneavoastr˘a erau caracterizate prin faptul c˘a aveau un
singur fir de execut¸ie (adic˘a un singur flux de instruct¸iuni ce erau executate de procesor) ¸si
rulau de sine-st˘at˘ator, f˘ar˘a s˘a coopereze cu alte programe (pentru realizarea unui obiectiv
comun).
Acest tip de programare corespunde perioadei init¸iale din istoria sistemelor de calcul,
cˆınd sistemele folosite erau seriale: la un moment dat se executa un singur program,
unitatea central˘a fiind acaparat˘a de acesta din momentul ˆınceperii execut¸iei lui ¸si pˆın˘a ˆın
momentul termin˘arii acestuia (neputˆınd astfel fi folosit˘a pentru execut¸ia altor programe
pe toat˘a durata de execut¸ie a acelui program).
Apoi sistemele de calcul au evoluat prin introducerea tehnicii de multi-programare,
ce permitea utilizarea concomitena a unit˘at¸ii centrale pentru execut¸ia mai multor pro-
grame; aceast˘a utilizare “simultan˘a” a CPU -ului de atre mai multe programe a fost
posibil˘a datorit˘a faptului c˘a hardware-ul permitea realizarea operat¸iilor de intrare/ie¸sire
(i.e., transferul de date ˆıntre periferic ¸si memoria intern˘a) independent de operat¸iile CPU ,
¸si atunci, ˆın timp ce un program stepta realizarea unei operat¸ii de intrare/ie¸sire (viteza
de transfer cu perifericul fiind mult mai mic˘a decˆıt viteza de lucru a CPU -ului), procesorul
era alocat (de c˘atre sistemul de operare) altui program pentru a executa o port¸iune din
instruct¸iunile acestuia.
Astfel a ap˘arut un nou tip de programare, numit˘a uneori programare paralel˘a, denumire
ce provine de la faptul c˘a avem mai multe programe executate “ˆın paralel” (i.e., simultan,
ˆın acela¸si timp), sau programare concurent˘a, denumire mai potrivit˘a pentru c˘a surprinde
aspectul concurent¸ial al execut¸iei mai multor programe prin tehnica multi-program˘arii:
programele ruleaz˘a concomitent ¸si concureaz˘a unele cu altele pentru resursele sistemului
de calcul (procesor, memorie, periferice de intrare/ie¸sire), puse la dispozit¸ia programelor
¸si gestionate de c˘atre sistemul de operare.
Ca atare, scopul principal al p˘art¸ii a doua a manualului este acela de a v˘a ˆınat¸a con-
ceptele fundamentale ale program˘arii concurente, utilizˆınd pentru aceasta sistemul Linux
ca suport, ¸si C-ul ca limbaj de programare. Acesta este cel mai bun cadru de predare al
program˘arii concurente, pentru a permite concentrarea atent¸iei asupra aspectelor referi-
toare la execut¸ia mai multor programe ˆın regim concurent¸ial de folosire a resurselor cal-
culatorului, f˘ar˘a s˘a ne distrag˘a atent¸ia aspectele referitoare la interfat¸a cu utilizatorul a
programelor respective.
Dup˘a cum cunoa¸stet¸i deja, ˆın trecut sistemele de calcul erau exploatate printr-o
interfat¸˘a cu utilizatorul alfanumeric˘a (i.e., ˆın mod text, nu grafic), care este foarte simpl˘a
de utilizat (ˆın programele C aceasta se face prin intermediul funct¸iilor din biblioteca stan-
dard de intr˘ari/ie¸siri stdio.h). La sfˆır¸situl anilor ’80, s-a introdus un nou concept,
interfat¸a grafic˘a cu utilizatorul, de c˘atre firma Apple, idee preluat˘a ¸si de Microsoft
o dat˘a cu lansarea sistemului de operare Windows, care a contribuit la larga r˘aspˆındire a
utiliz˘arii calculatoarelor ˆın aproape toate domeniile de activitate. Interfat¸a grafic˘a a fost
introdus˘a ¸si ˆın lumea sistemelor de tip UNIX, prin proiectul X Window.
Aceast˘a larg˘a r˘aspˆındire s-a datorat faptului c˘a interfat¸a grafic˘a este mult mai prie-
2
tenoas˘a pentru utilizatorul neprofesionist decˆıt cea clasic˘a, ˆın mod text. Totu¸si, pentru
programatori, reversul medaliei este dificultatea de programare a aplicat¸iilor ce folosesc
o interfat¸˘a grafic˘a cu utilizatorul (GUI =Graphical User Interface), dificultate ce provine
din faptul a trebuie ˆınv˘at¸ate ¸si utilizate mai multe tehnici noi:
i) sistemul de ferestre ¸si alte componente grafice utilizate de GUI se bazeaz˘a pe o ierarhie
de clase ce descriu aceste obiecte grafice, ierarhie care are de obicei sute de clase ¸si mii de
metode;
ii) programarea dirijat˘a de evenimente (event-driven programming) – o nou˘a tehnic˘a de
programare folosit˘a pentru aplicat¸iile GUI , ce const˘a ˆın executarea anumitor operat¸ii la
aparit¸ia anumitor evenimente (ca, de exemplu, deplasarea sau click-ul mouse-ului, sau
ap˘asarea unei taste pe tastatur˘a), ˆın funct¸ie de contextul aparit¸iei (i.e., de obiectul grafic
ce este activ ¸si prime¸ste acel eveniment);
iii) “¸stiint¸a” proiect˘arii componentelor grafice ce vor alc˘atui GUI -ul unei aplicat¸ii, care
se refer˘a la aspectele estetice ¸si ergonomia de utilizare a acesteia (ˆın anii superiori vet¸i
avea un curs opt¸ional intitulat “Interfat¸a grafic˘a cu utilizatorul”, ˆın care vet¸i studia aceste
aspecte).
De¸si pe parcursul anilor urm˘atori a vet¸i mai ˆıntˆılni cu discipline care abordeaz˘a prob-
lema program˘arii concurente (cum ar fi, de exemplu, disciplina “Programare Windows,
sau “Programare ˆın limbajul Java”, sau “Programare Dot NET”), deoarece aspectele refe-
ritoare la programarea concurena, prezentate la aceste discipline, sunt strˆıns ˆımpletite cu
cele legate de programarea interfet¸ei grafice cu utilizatorul, ¸si datorit˘a dificult˘at¸ii acesteia
din urm˘a, cadrul ales (i.e., sistemul Linux, limbajul de programare C, ¸si interfat¸a clasic˘a,
ˆın mod text, cu utilizatorul) pentru predarea program˘arii concurente consider c˘a este cel
mai adecvat pentru acest scop.
Acesta a fost de altfel un alt motiv serios pentru alegerea Linux-ului, ca exemplu
de sistem de operare concret ce urma a fie prezentat ˆın acest manual. (Not˘a: pentru
sistemul de operare Windows este foarte dificil, dac˘a nu chiar imposibil, de realizat o
tratare exclusiv˘a a conceptelor legate de programarea concurena, deoarece mecanismele
prin care sunt acestea implementate nu pot fi disociate de cele referitoare la programarea
interfet¸ei grafice cu utilizatorul.)
Observat¸ie: faptul c˘a am ales folosirea ˆın programe a interfet¸ei clasice (ˆın mod text)
cu utilizatorul, nu ˆınseamn˘a a ˆın Linux nu se poate face programare GUI , ci dimpotriv˘a,
exist˘a medii grafice pentru Linux (despre care voi reveni cu am˘anunte ˆın primul capitol din
partea I a manualului) ˆınsot¸ite de medii de dezvoltare de aplicat¸ii grafice pentru acestea, ce
folosesc conceptele referitoare la GUI ¸si la programarea dirijat˘a de evenimente despre care
am amintit mai sus, ¸si care implic˘a o aceea¸si dificultate de programare ˆın cazul Linux-ului
ca ¸si ˆın cazul Windows-ului.
ˆ
In ˆıncheiere, a¸s dori s˘a mai ment¸ionez faptul c˘a aceast˘a disciplin˘a, predat˘a la Sect¸ia
la zi, cuprinde ¸si o parte teoretic˘a, ce aprofundeaz˘a unele concepte prezentate ˆın cadrul
disciplinei “Arhitectura calculatoarelor ¸si sisteme de operare” din Anul 1 Semestrul I.
Am preferat s˘a renunt¸ la aceast˘a parte teoretia ¸si s˘a p˘astrez partea practic˘a, mult mai
important˘a, dedicat˘a prezenarii sistemului de operare Linux ¸si a program˘arii concurente
ˆın Linux, pentru a nu ˆınc˘arca prea tare materia de ˆınv˘at¸at t¸inˆınd cont de caracterul sect¸iei
dumneavoastr˘a ˆınat¸˘amˆınt deschis la distant¸˘a. Totu¸si, dac˘a dorit¸i s˘a v˘a aprofundat¸i ¸si
cuno¸stint¸ele teoretice, putet¸i consulta varianta ˆın format electronic a prelegerilor sust¸inute
de autorul acestui manual la cursurile de “Sisteme de operare” de la Sect¸ia la zi, accesibil˘a
3
din pagina de web a acestuia (http://www.infoiasi.ro/vidrascu); de asemenea, a recomand
¸si urm˘atoarele dou˘a art¸i de specialitate pentru studiu individual: [9] ¸si [11].
Autorul dore¸ste pe aceast˘a cale s˘a adreseze cititorilor rug˘amintea de a-i semnala prin
email, pe adresa vidrascu@infoiasi.ro, eventualele erori depistate ˆın timpul parcurgerii
acestui manual.
Convent¸ii utilizate ˆın acest manual
Textul tip˘arit cu fontul typewriter este folosit pentru nume de comenzi, nume de vari-
abile, constante, cuvinte-cheie, etc. Este, de asemenea, utilizatˆın cadrul exemplelor pentru
a desemna cont¸inutul unui fi¸sier de comenzi sau program C, ori rezultatul afi¸sat de unele
comenzi.
Textul UNIX>va fi folosit pentru a indica prompterul interpretorului de comenzi din Linux
la care utilizatorul poate tasta comenzi (ˆın interfat¸a clasic˘a, ˆın mod text). Iar comanda cu
parametrii ei va indica comanda ce trebuie tastat˘a la prompter de c˘atre utilizator. ˆ
In
cazul ˆın care, printre parametrii unei comenzi, apare ¸si text italic, el va trebui ˆın general
a fie ˆınlocuit de c˘atre utilizator cu o valoare concret˘a.
Termenii ¸si denumirile p˘astrate ˆın original (netraduse ˆın romˆan˘a) se vor indica prin fontul
termen ˆın englez˘a.
Entit˘at¸ile ce urmeaz˘a se vor indica ˆın maniera descris˘a: nume de fi¸siere,nume de
utilizatori,nume de grupuri,nume (sau adrese IP) de calculatoare,adrese de
pagini web.
Construct¸ia [ text opt¸ional ] folosit˘a ˆın cadrul sintaxei unei comenzi sau instruct¸iuni
specific˘a un text opt¸ional, deci care nu este obligatoriu de folosit.
Caracterele . . . semnific˘a o port¸iune de text care a fost omis˘a pentru a nu ˆıngreuna liz-
ibilitatea sau pentru a reduce din spat¸iul utilizat.
Textul italic va mai fi folosit uneori pentru a scoate ˆın evident¸˘a un concept important
despre care se discut˘a ˆın acel moment.
4
Partea I
Sistemul de operare Linux
Ghid de utilizare
5
Aceast˘a prim˘a parte a manualului cuprinde o prezentare general˘a a sistemelor de operare
din familia UNIX, prezentare ce este valabil˘a ˆın particular pentru sistemul de operare Linux.
Aceast˘a parte se vrea a fi un ghid de utilizare a sistemului Linux, f˘ar˘a a avea pretent¸ia de
a fi un ghid complet. Am preferat s˘a insist ˆın prezentare pe conceptele fundamentale pe
care se bazeaz˘a acest sistem de operare, f˘ar˘a s˘a obosesc cititorul cu prea multe detalii ¸si
numeroasele opt¸iuni ale unor comenzi (mai mult, am preferat s˘a nu prezint deloc opt¸iunile
numeroase ale meniurilor de aplicat¸ii grafice, din lips˘a de spat¸iu, ¸si deoarece se g˘asesc
destule c˘art¸i care trateaz˘a acest aspect).
Primul capitol al acestei p˘art¸i a manualului cont¸ine o vedere de ansamblu asupra sistemelor
de operare din familia UNIX, poveste¸ste istoricul evolut¸iei UNIX-ului, descrie arhitectura
(structura) unui sistem UNIX ¸si caracteristicile sale generale, modul de utilizare ¸si de
conectare la un sistem UNIX, ¸si cont¸ine o introducere despre Linux ca membru al acestei
familii ¸si o descriere general˘a a procedurii de instalare a unei distribut¸ii de Linux.
Al doilea capitol se vrea a fi un ghid de utilizare, cont¸inˆınd o trecere ˆın revist˘a a princi-
palelor categorii de comenzi UNIX, o prezentare a sistemului de fi¸siere UNIX ¸si a interpre-
toarelor de comenzi UNIX, ce au rolul de a asigura interact¸iunea sistemului cu utilizatorul,
pentru a rula programele de aplicat¸ii dorite de acesta, precum ¸si de a-i pune la dispozit¸ie
un puternic limbaj de scripting (pentru fi¸siere de comenzi).
Bibliografie util˘a pentru studiu individual suplimentar: [3], [4], [8].
6
Capitolul 1
Introducere ˆın UNIX
1.1 Prezentare de ansamblu a sistemelor de operare din fa-
milia UNIX
1. Introducere
2. Scurt istoric al evolut¸iei UNIX-ului
3. Vedere general˘a asupra sistemului UNIX
4. Structura unui sistem UNIX
5. Caracteristici generale ale unui sistem UNIX
6. UNIX ¸si utilizatorii
7. Conectarea la un sistem UNIX
1.1.1 Introducere
UNIX este denumirea generica a unei largi familii de sisteme de operare orientate pe
comenzi,multi-user si multi-tasking, dezvoltat pentru prima data in anii ’70 la compania
AT&T si Universitatea Berkeley. In timp, a devenit sistemul de operare cel mai raspin-
dit in lume, atit in mediile de cercetare si de invatamint (universitare), cit si in mediile
industriale si comerciale.
Ce inseamna sistem de operare orientat pe comenzi?
Sistemul poseda un interpretor de comenzi, ce are aceeasi sarcina ca si programul
7
command.com din MS-DOS, si anume aceea de a prelua comenzile introduse de utilizator,
de a le executa si de a afisa rezultatele executiei acestora.
Ce inseamna sistem de operare multi-user?
Un astfel de sistem este caracterizat prin faptul ca exista conturi utilizator, ce au anumite
drepturi si restrictii de acces la fisiere si la celelalte resurse ale sistemului (din acest motiv,
se utilizeaza mecanisme de protectie, cum ar fi parolele pentru conturile utilizator). In
plus, un astfel de sistem permite conectarea la sistem si lucrul simultan a mai multor
utilizatori.
Ce inseamna sistem de operare multi-tasking?
Intr-un astfel de sistem se executa simultan (i.e., in acelasi timp) mai multe programe.
Programele aflate in executie sunt denumite procese). O asemenea executie simultana a
mai multor programe mai este denumita si executie concurenta, pentru a sublinia faptul ca
programele aflate in executie concureaza pentru utilizarea resurselor sistemului de calcul
respectiv.
Observatie:
De fapt, cind UNIX-ul este utilizat pe calculatoare uni-procesor, in asemenea situatie exe-
cutia simultana (concurenta) a proceselor nu este true-parallelism (i.e., multi-procesare),
ci se face tot secvential, prin multi-programare, si anume prin mecanismul de interleaving
(ˆıntret¸esere) cu time-sharing: timpul procesor este impartit in cuante de timp, si fiecare
proces existent in sistem primeste periodic cite o cuanta de timp in care i se aloca proce-
sorul si deci este executat efectiv, apoi este intrerupt si procesorul este alocat altui proces
care se va executa pentru o cuanta de timp din punctul in care ramasese, apoi va fi intre-
rupt si un alt proces va primi controlul procesorului, ¸s.a.m.d.
Dupa cum am discutat la teoria sistemelor de operare din prima parte a acestui manual,
cunoasteti deja faptul ca acest mecanism de stabilire a modului de alocare a procesorului
proceselor existente in sistem, se bazeaza pe una din strategiile: round-robin, prioritati
statice, prioritati dinamice, ¸s.a., uneori intilnindu-se si combinatii ale acestora. In cazul
UNIX-ului, se utilizeaza strategia round-robin combinat cu prioritati dinamice.
Mai exista si alt tip de paralelism (concurenta), si anume multi-procesarea, ce este bazata
pe arhitecturile multi-procesor sau cele distribuite. In asemenea arhitecturi avem mai
multe procesoare, pe care se executa mai multe procese efectiv in paralel, si acestea pot
comunica intre ele fie prin memorie comuna, fie prin canale de comunicatie.
Tabelul 1.1: Exemple de sisteme de operare.
Criteriul de numar users
clasificare mono-user multi-user
numar mono-task MS-DOS,CP/M Nu exist˘a!
tasks multi-task OS/2,Windows 3.x & 9x UNIX family
8
Observatie: sistemele de operare de retea (exemple: Novell,Windows NT/2000/2003
Server) pot fi privite ca sisteme multi-user, multi-tasking. Versiunile personale (desktop)
de Windows NT/2000/XP sunt mono-user, deoarece la un moment dat un singur utilizator
poate fi conectat la sistem.
Asa cum am mai spus, UNIX-ul este un sistem de operare multi-user si multi-tasking. Exista
multe variante de UNIX (System V, BSD, XENIX, AT&T, SCO, AIX, Linux, ¸s.a.) deoarece
multe companii si universitati si-au dezvoltat propria varianta de UNIX, nereusindu-se im-
punerea unui standard unic. Pentru aceste variante exista anumite diferente de imple-
mentare si exploatare, dar principiile utilizate sunt aceleasi. Mai mult, pentru utilizatorul
obisnuit accesul si exploatarea sunt aproape similare (astfel, de exemplu, aceleasi comenzi
sunt disponibile pe toate variantele de UNIX, dar unele comenzi pot avea unele dintre
optiunile lor diferite de la varianta la varianta).
Important: din acest motiv, cele prezentate in manualul de fata (mai exact, in partea I
si partea II a lui) sunt valabile pentru toate sistemele de tip UNIX, si in particular pentru
Linux.
Linux-ul este o variana de UNIX distribuibil˘a gratuit (open-source), pentru sisteme de
calcul bazate pe procesoare Intel (80386, 80486, Pentium, etc.), procesoare Dec Alpha,
¸si, mai nou, ¸si pentru alte tipuri de procesoare (cum ar fi de exemplu cele pentru em-
bedded systems). El a fost creat ˆın 1991 de Linus Torvalds, ind ˆın prezent dezvoltat
ˆın permanent¸˘a de o echip˘a format˘a din mii de entuzia¸sti Linux din lumea ˆıntreaa, sub
ˆındrumarea unui colectiv condus de Linus Torvalds.
Calculatorul UNIX pe care veti lucra la laboratoare este serverul studentilor, fiind un IBM
PC cu 2 procesoare, avind instalat ca sistem de operare Linux-ul. Numele acestui cal-
culator este fenrir, si are adresa IP 193.231.30.197, iar numele DNS complet, sub
care este recunoscut in INTERNET, este: fenrir.info.uaic.ro, sau un nume echiva-
lent, fenrir.infoiasi.ro. Vom reveni mai tirziu cu informatii referitoare la modul de
conectare la el.
1.1.2 Scurt istoric al evolut¸iei UNIX-ului
UNIX-ul este un sistem de operare relativ vechi, fiind creat la Bell Laboratories in 1969,
unde a fost conceput si dezvoltat de Ken Thompson pentru uzul intern al unui colectiv de
cercetatori condus de acesta.
Ei si-au dezvoltat sistemul de operare pornind de la citeva concepte de baza: sistem de
fisiere, multi-user, multi-tasking, gestiunea perifericelor sa fie transparenta pentru utiliza-
tor, ¸s.a. Initial a fost implementat pe minicalculatoarele firmei DEC, seria PDP-7, fiind
scris in limbaj de asamblare si Fortran.
Aparitia in 1972 a limbajului C, al carui autor principal este Dennis Ritchie de la firma Bell
9
Laboratories, a avut in timp un impact deosebit asupra muncii programatorilor, trecindu-
se de la programarea in limbaj de asamblare la cea in C. Astfel in 1971 UNIX-ul este
rescris impreuna cu Dennis Ritchie in C, devenind multi-tasking. In 1973, dupa o noua
rescriere, devine portabil. Aceasta este versiunea 6, prima care iese in afara laboratorului
Bell al firmei AT&T si care este distribuita gratuit universitatilor americane. In 1977 este
implementat pe un calculator INTERDATA 8/32, primul diferit de un PDP.
Sistemul de operare UNIX, compilatorul C si in esenta toate aplicatiile sub UNIX sunt scrise
in C intr-o proportie mare. Astfel, din cele 13000 linii sursa ale sistemului UNIX, numai
800 linii au fost scrise in limbaj de asamblare, restul fiind scrise in C. De asemenea, insasi
compilatorul C este scris in C in proportie de 80%. In felul acesta limbajul C asigura o
portabilitate buna pentru programele scrise in el. (Un program este portabil daca poate fi
transferat usor de la un tip de calculator la altul.)
Portabilitatea mare a programelor scrise in C a condus la o raspindire destul de rapida a
limbajului C si a sistemului de operare UNIX: se scria in asamblare doar un mic nucleu de
legatura cu hardware-ul unui anumit tip de calculator, iar restul sistemului UNIX era scris
in C, fiind acelasi pentru toate tipurile de calculatoare; mai era nevoie de un compilator
de C pentru acel calculator si astfel se putea compila si instala UNIX-ul pe acel calculator
(practic si pentru scrierea compilatorului se folosea aceeasi tehnica: era nevoie sa se scrie
in limbaj de asamblare doar un nucleu, cu rol de meta-compiler, restul compilatorului
fiind deja scris in C).
Prima versiune de referinta, UNIX versiunea 7 (1978), implementata pe un DEC PDP-11,
are nucleul independent de hardware. Este prima versiune comercializata. In 1982 este
elaborat UNIX System III pentru calculatoarele VAX 11/780, iar in 1983 UNIX System V. In
1980-1981 apar primele licente: ULTRIX (firma DEC), XENIX (Microsoft), UTS (Amdahl),
etc.
Versiunea 7 a servit drept punct de plecare pentru toate dezvoltarile ulterioare ale sis-
temului. Plecind de la aceasta versiune, s-au nascut doua mari directii de dezvoltare:
1. dezvoltarile realizate la compania AT&T si Bell Laboratories au condus la versiunile
succesive de System V UNIX;
2. munca realizata la Universitatea Berkeley s-a concretizat in versiunile succesive de
BSD UNIX (acronimul BSD provine de la Berkeley Software Distribution).
Versiunile BSD au introdus noi concepte, cum ar fi: memoria virtuala (BSD 4.1), facilitati
de retea (sc BSD 4.2), fast file system, schimb de informatii intre procese centralizat sau
distribuit, etc.
Iar vesiunile System V au introdus drept concepte noi: semafoare, blocaje, cozi de mesaje,
memorie virtuala, memorie pe 8 biti, etc.
Pe linga aceste variante majore, au fost dezvoltate si alte variante de UNIX, si anume:
XENIX de catre firma Microsoft, VENIX de catre firma Venturecom, UNIX SCO de catre firma
10
SCO Corp., AIX de catre firma IBM, etc. Pe linga aceste variante, ce au fost dezvoltate
plecind de la nucleul (kernel-ul) UNIX al firmei AT&T (ceea ce a necesitat cumpararea unei
licente corespunzatoare), au fost dezvoltate si sisteme ne-AT&T, si anume: MINIX de catre
Andrew Tanenbaum, Linux de catre Linus Torvald, XINU de catre Douglas Comer, GNU
de catre FSF (acronimul FSF inseamna Free Software Fundation). Obiectivul fundatiei
FSF este dezvoltarea unui sistem in intregime compatibil (cu cel de la AT&T) si care sa
nu necesite nici o licenta de utilizare (si deci sa fie gratuit).
Aceasta multiplicare a versiunilor de UNIX, devenite incompatibile si facind dificila portarea
aplicatiilor, a determinat utilizatorii de UNIX sa se regrupeze si sa propuna definirea de
interfete standard: X/OPEN si POSIX (= Standard IEEE 1003.1-1988-Portable Oper-
ating System Interface for Computer Environments).
Aceste interfete au preluat in mare parte propunerile facute in definitia de interfata SVID
(= System V Interface Definition) propusa de AT&T, dar influentele din celelalte variante
nu sunt neglijabile.
Aceasta normalizare (standardizare) a sistemului este doar la nivel utilizator, nefiind vorba
de o unicitate a nucleului: cele doua blocuri formate, Unix International si OSF (OSF =
Open Software Foundation), continua sa-si dezvolte separat propriul nucleu, dar totusi
diferentele de implementare sunt transparente pentru utilizatori.
O alta frina pentru raspindirea sistemului UNIX, pe linga aceasta lipsa de normalizare, a
constituit-o aspectul neprietenos al interfetei utilizator, care a ramas pentru multa vreme
orientata spre utilizarea de terminale alfanumerice (adica in mod text, nu grafic). Dar si
pe acest plan situatia s-a imbunatatit considerabil, prin adoptarea sistemului de ferestre
grafice X Window si dezvoltarea de medii grafice bazate pe acest sistem.
Sistemul X Window a fost dezvoltat in cadrul proiectului Athena de la MIT (Mas-
sachusetts Institute of Technology) din anii ’80. Majoritatea statiilor de lucru aflate actual
pe piata poseda acest sistem. Protocolul X, folosit de acest sistem, a fost conceput pe
ideea distribuirii calculelor intre diferitele unitati centrale, statii de lucru ale utilizatorilor,
si celelalte masini din retea pe care se executa procesele utilizatorilor. Astfel, sistemul X
Window are o arhitectur˘a client-server, ce utilizeaz˘a protocolul Xpentru comunicat¸ia
prin ret¸ea ˆıntre diferitele unitati centrale ¸si statii de lucru. Protocolul Xa fost adoptat ca
standard si s-au dezvoltat o serie de biblioteci grafice, toate avind ca substrat sistemul X
Window, precum ar fi: MOTIF,OPEN LOOK, etc.
ˆ
In prezent, dezvoltarea sistemului X Window este administrat˘a de organizat¸ia Consort¸iul
X(http://www.X.org), ce ofer˘a ¸si o implementare de referint¸˘a a sistemului X Window pe
site-ul organizat¸iei. Astfel, ultima versiune lansat˘a este X11R6.7, la data scrierii acestor
rˆınduri (vara anului 2004). Not˘a: X11 este numele generic al unei variante majore a pro-
tocolului, ce a fost standardizat˘a, ¸si din acest motiv de multe ori apare referirea X11 ˆın
denumirea tehnologiilor ce folosesc sistemul X Window ˆın aceast˘a versiune standardizat˘a,
iar R6.7 este num˘arul ultimului release).
De asemenea, exist˘a ¸si o implementare open-source a sistemului X Window, numit˘a
XFree86 (http://www.xfree86.org). Ea furnizeaz˘a o interfat¸˘a client-server ˆıntre hard-
ware-ul de I/O (tastatur˘a, mouse, placa video/monitor) ¸si mediul desktop, precum ¸si
infrastructura de ferestre ¸si un API (Application Programming Interface) standardizat
11
pentru dezvoltarea de aplicat¸ii grafice X11. Pe scurt, XFree86 este o infrastrutur˘a desktop
bazat˘a pe X11, disponibil˘a gratuit (open-source), ultima versiune lansat˘a fiind versiunea
4.4.0, la data scrierii acestor rˆınduri.
Pe parcursul anilor, punctul de vedere al cercetatorilor si dezvoltatorilor din domeniul
evolutiei sistemelor, si a UNIX-ului in particular, referitor la dezvoltarea unui sistem dis-
tribuit fizic pe mai multe calculatoare, a evoluat de la imaginea unui sistem format din
unitati separate si independente, avind fiecare propriul s˘au sistem de exploatare si care
pot comunica cu sistemele de exploatare de pe celelalte masini din sistem (acesta este
cazul actual al statiilor UNIX dintr-o retea), la imaginea unui ansamblu de resurse a caror
localizare devine transparenta pentru utilizator.
In acest sens, protocolul NFS (NFS = Network File System), propus de firma SUN Mi-
crosystems, a fost, cu toate incovenientele si imperfectiunile sale, prima incercare de re-
alizare a unui astfel de sistem care a fost integrata in sistemele UNIX comercializate.
In ultimii ani din deceniul trecut, cercetarea s-a focalizat pe tehnologia micro-nucleu.
Nota: in revista PC Report nr. 60 (din sept. 1997) puteti citi un articol ce face o comparatie
intre tehnologia traditionala (i.e.,nucleu monolitic) si cea micro-nucleu.
1.1.3 Vedere general˘a asupra sistemului UNIX
UNIX-ul este un sistem de operare multi-user si multi-tasking ce ofera utilizatorilor nu-
meroase utilitare interactive. Pe linga rolul de sistem de exploatare, scopul lui princi-
pal este de a asigura diferitelor task-uri (procese) si diferitilor utilizatori o repartizare
echitabila a resurselor calculatorului (memorie, procesor/procesoare, spatiu disc, impri-
manta, programe utilitare, accesul la retea, etc.) si aceasta fara a necesita interventia
utilizatorilor.
UNIX-ul este inainte de toate un mediu de dezvoltare si utilizatorii au la dispozitie un numar
foarte mare de utilitare pentru munca lor: editoare de text, limbaje de comanda (shell-
uri), compilatoare, depanatoare (debugger-e), sisteme de prelucrare a textelor, programe
pentru posta electronica si alte protocoale de acces INTERNET, si multe alte tipuri de
utilitare.
Pe scurt, un sistem UNIX este compus din:
1. un nucleu (kernel), ce are rolul de a gestiona memoria si operatiile I/O de nivel
scazut, precum si planificarea si controlul executiei diferitelor task-uri (procese).
Este intrucitva similar BIOS-ului din MS-DOS.
2. un ansamblu de utilitare de baza, cum ar fi:
diferite shell-uri (= interpretoare de limbaje de comanda);
12
comenzi de manipulare a fisierelor;
comenzi de gestiune a activitatii sistemului (a proceselor);
comenzi de comunicatie intre utilizatori sau intre sisteme diferite;
editoare de text;
compilatoare de limbaje (C, C++, Fortran, ¸s.a.) si un link-editor;
utilitare generale de dezvoltare de programe: debugger-e, arhivatoare, ges-
tionare de surse, generatoare de analizoare lexicale si sintactice, etc.
diferite utilitare filtru (= programe ce primesc un fisier la intrare, opereaza o
anumita transformare asupra lui si scriu rezultatul ei intr-un fisier de iesire),
spre exemplu: filtru sursa Pascalsursa C, filtru fisier text DOSfisier text
UNIX si invers, etc.
Nota: fisierele text sub MS-DOS se deosebesc de fisierele text sub UNIX prin
faptul ca sfirsitul de linie se reprezinta sub MS-DOS prin 2 caractere CR+LF
(cu codul ASCII: 13+10), pe cind sub UNIX se reprezinta doar prin caracterul
LF.
1.1.4 Structura unui sistem UNIX
Un sistem UNIX are o structura ierarhizata pe mai multe nivele. Mai precis exista 3 nivele,
ilustrate in figura 1.1, ce urmeaza mai jos.
User Applications
level:Shells (Bourne, C, Korn, etc.)
System TCP/UDP File-System Process
level:IP Manager Manager
.
.
..
.
.
Hardware
level:Network .
.
.Harddisk .
.
.Memory CPU
.
.
..
.
.
.
.
.|{z }
NFS
.
.
.|{z }
VM
.
.
.
Figura 1.1: Structura unui sistem UNIX.
Explicatii suplimentare la figura 1.1:
13
1. Nivelul hardware : este nivelul format din componentele hardware ale sistemului
de calcul: CPU + Memory + Harddisk + Network
2. Nivelul system : este reprezentat de nucleul (kernel-ul) sistemului UNIX, si are
rolul de a oferi o interfata intre aplicatii (nivelul 3) si partea de hardware (nivelul
1), astfel ˆıncˆıt aplicatiile sa fie portabile (independente de masina hardware pe care
sunt rulate).
Nucleul UNIX contine trei componente principale: sistemul de gestionare a fisierelor
(File-System Manager), sistemul de gestionare a proceselor (Process Manager), si
componenta de comunicatie in retea (comunicatia se realizeaza pe baza protocoalelor
de comunicatie IP si TCP/UDP).
3. Nivelul user : contine limbajele de comanda (shell-urile) si diversele programe
utilitare si aplicatii utilizator.
Nucleul (kernel-ul) este scris in cea mai mare parte (cca. 90%) in limbajul C. Ca urmare,
functiile sistem respecta conventiile din limbajul C. Ele pot fi apelate din programele
utilizator, e direct din limbaj de asamblare, fie prin intermediul bibliotecilor din limbajul
C. Aceste functii sistem, oferite de kernel, sunt numite in termeni UNIX apeluri sistem
(system calls). Prin ele, functiile kernel-ului sunt puse la dispozitia utilizatorilor, la fel
cum se face in sistemul de operare RSX-11M prin directive sistem, in sistemul MS-DOS prin
intreruperile software, etc.
Fiecare nivel se bazeaza pe serviciile/resursele oferite de nivelul imediat inferior. Pe figura
de mai sus, serviciile/resursele folosite de fiecare componenta sunt cele oferite de compo-
nenta sau componentele aflate imediat sub aceasta. Astfel,
componenta de gestiune a proceselor utilizeaza, ca resurse oferite de nivelul hard-
ware, CPU si memoria interna, plus o parte din hard-disc, sub forma discului de
swap, pentru mecanismul de memorie virtuala (VM = virtual memory), mecanism
ce utilizeaza memoria interna fizica si discul de swap pentru a crea o memorie interna
virtuala;
sistemul de fisiere utilizeaza restul hard-discului, plus o parte din componenta de
retea, prin intermediul NFS-ului (NFS = Network File System);
IP si TCP/UDP sunt protocoalele de baza pentru realizarea comunicatiei in retea,
pe baza carora sunt construite toate celelalte protocoale: posta electronica, transfer
de fisiere (FTP), World Wide Web (HTTP), etc.;
interpretoarele de comenzi (shell-urile) utilizeaza serviciile puse la dispozitie de Pro-
cess Manager si File-System Manager, iar restul aplicatiilor utilizeaza serviciile
oferite de intreg nivelul system.
14
1.1.5 Caracteristici generale ale unui sistem UNIX
Principalele concepte pe care se sprijina UNIX-ul sunt conceptul de fisier si cel de
proces.
Prin fisier se intelege o colectie de date, fara o interpretare anumita, adica o simpla
secventa de octeti (modul de interpretare al lor cade in sarcina aplicatiilor care le
folosesc).
Prin proces, sau task, se intelege un program (i.e., un fisier executabil) incarcat in
memorie si aflat in curs de executie.
Un sistem de fisiere ierarhizat (i.e., arborescent), i.e. este ca un arbore (la fel ca in
MS-DOS: directoare ce contin subdirectoare si fisiere propriu-zise), dar un arbore ce
are o singura radacina, referita prin “/” (nu avem, ca in MS-DOS, mai multe unitati
de disc logice C:,D:, etc.), iar ca separator pentru caile de subdirectoare se utilizeaza
caracterul ’/’, in locul caracterului \’ folosit in MS-DOS.
Numele fisierelor pot avea pina la 255 de caractere si pot contine oricite caractere ’.’
(nu sunt impartite sub forma 8.3, nume.extensie, ca in MS-DOS). Mai mult, numele
fisierelor sunt case-sensitive, adica se face distinctie intre literele majuscule si cele
minuscule.
(Vom vedea mai multe amanunte cind vom discuta despre sistemul de fisiere in
sectiunea 2.2 din capitolul 2).
Un sistem de procese ierarhizat (i.e., arborescent) si un mecanism de “mostenire
genetica”:
Fiecare proces din sistem are un proces care l-a creat, numit proces parinte, (sau
tata) si de la care “mosteneste” un anumit ansamblu de caracteristici (cum ar
proprietarul, drepturile de acces, ¸s.a.), si poate crea, la rindul lui, unul sau mai
multe procese fii.
Fiecare proces are asignat un PID (denumire ce provine de la Process IDentification),
ce este un numar intreg pozitiv si care este unic pe durata vietii acelui proces (in
orice moment, nu exista in sistem doua procese cu acelasi PID.
Exista un proces special, cel cu PID =0, care este creat atunci cind este initializat
(boot-at) sistemul UNIX pe calculatorul respectiv. Acesta nu are proces parinte, fiind
radacina arborelui de procese ce se vor crea pe parcursul timpului (pina la oprirea
calculatorului).
(Vom vedea mai multe amanunte despre sistemul de procese cind vom discuta despre
gestiunea proceselor in capitolul 4 din partea II).
Un ansamblu de puncte de acces, din aplicatiile scrise in limbaje de nivel inalt (pre-
cum C-ul), la serviciile oferite de kernel, puncte de acces ce se numesc apeluri
sistem (system calls).
De exemplu, prin apelul dintr-un program C al functiei fork() putem crea noi pro-
cese.
Este un sistem de operare multi-user: mai multi utilizatori pot avea acces simultan
la sistem in orice moment, de la diferite terminale conectate la sistemul respectiv,
terminale plasate local sau la distanta.
15
Este un sistem de operare multi-tasking: se pot executa simultan mai multe pro-
grame, si aceasta chiar si relativ la un utilizator:
Fiecare utilizator, in cadrul unei sesiuni de lucru, poate lansa in executie paralela
mai multe procese; dintre acestea, numai un proces se va executa in foreground
(planul din fata, ce are controlul asupra tastaturii si ecranului), iar restul proceselor
sunt executate in background (planul din spate, ce nu are control asupra tastaturii
si ecranului).
In plus, fiecare utilizator poate deschide mai multe sesiuni de lucru.
Observatie: pe calculatorul fenrir numarul maxim de sesiuni ce pot fi simultan
deschise de un utilizator s-ar putea sa e limitat, din considerente de supraincarcare
a sistemului.
Este un sistem de operare orientat pe comenzi: exista un interpretor de comenzi
(numit uneori si shell) ce are aceeasi destinatie ca si in MS-DOS, si anume aceea de
a prelua comenzile introduse de utilizator, de a le executa si de a afisa rezultatele
executiei acestora.
Daca in MS-DOS este utilizat practic un singur interpretor de comenzi, si anume pro-
gramul command.com (desi teoretic acesta poate fi inlocuit de alte programe similare,
cum ar fi ndos.com-ul), in UNIX exista in mod traditional mai multe interpretoare
de comenzi: sh (Bourne SHell), bash (Bourne Again SHell), csh (C SHell), ksh
(Korn SHell), ash,zsh, ¸s.a., utilizatorul avind posibilitatea sa aleaga pe oricare
dintre acestea.
Shell-urile din UNIX sunt mai puternice decit analogul (command.com) lor din MS-DOS,
fiind asemanatoare cu limbajele de programare de nivel inalt: au structuri de control
alternative si repetitive de genul if,case,for,while, etc., ceea ce permite scrierea
de programe complexe ca simple script-uri. Un script este un fisier de comenzi UNIX
(analogul fisierelor batch *.bat din MS-DOS).
La fel ca in MS-DOS, fiecare user isi poate scrie un script care sa fie executat la
fiecare inceput de sesiune de lucru (analogul fisierului autoexec.bat din MS-DOS),
script numit .profile sau .bash profile in cazul in care se utilizeaza bash-ul ca
shell implicit (pentru alte shell-uri este denumit altfel).
In plus, fiecare user poate avea un script care sa fie rulat atunci cind se deconecteaza
de la sistem (adica la logout); acest script se numeste .bash logout in cazul shell-
ului bash.
La fel ca in MS-DOS, exista doua categorii de comenzi: comenzi interne (care se
gasesc in fisierul executabil al shell-ului respectiv) si comenzi externe (care se gasesc
separat, fiecare intr-un fisier executabil, avind acelasi nume cu comanda respectiva).
Forma generala de lansare a unei comenzi UNIX este urmatoarea:
UNIX>nume comanda optiuni argumente ,
unde optiunile si argumentele pot lipsi, dupa caz. Prin conventie, optiunile sunt
precedate de caracterul ’-’ (in MS-DOS este folosit caracterul ’/’). Argumentele sunt
cel mai adesea nume de fisiere.
(Vom vedea mai multe amanunte cind vom discuta despre shell-uri in sectiunea 2.3
din capitolul 2).
16
Alta caracteristica: o viziune unitara (prin intermediul unei aceleasi interfete) asupra
diferitelor tipuri de operatii de intrare/iesire.
Astfel, de exemplu, terminalul (tastatura + ecranul) are asociat un fisier special prin
intermediul caruia operatiile de intrare (citirea de la tastatura) si de iesire (scrierea
pe ecran) se fac similar ca pentru orice fisier obisnuit.
Alta caracteristica: redirectarea operatiilor I/O ale proceselor, ce se bazeaza
pe caracteristica anterioara, si a carei principala utilizare este unul dintre conceptele
fundamentale ale UNIX-ului, si anume acela de filtrare.
Ideea de baza consta in a asocia fiecarui proces din sistem un anumit numar de
fisiere logice predefinite, numite intrari-iesiri standard ale procesului. Mai exact,
este vorba despre stdin (intrarea standard), stdout (iesirea standard), si stderr
(iesirea de eroare standard).
Sistemul furnizeaza un mecanism de redirectare (realizat intern prin apeluri sistem
specifice), care permite ca unui fisier logic a unui proces sa i se asocieze un fisier
fizic particular. Astfel, stdin are asociata implicit tastatura, iar stdout si stderr
au asociat implicit ecranul, dar li se pot asocia si alte fisiere fizice particulare de pe
disc.
Acest mecanism este repercutat la nivel extern in diversele limbaje de comanda
(shell-uri) prin posibilitatea de a cere, la executia unei comenzi, asocierea I/O stan-
dard a procesului ce executa comanda la anumite fisiere fizice de pe disc.
Dintre toate comenzile UNIX, acelea ce au proprietatea de a face o anumita prelucrare
asupra datelor citite pe intrarea standard (fara a modifica fisierul fizic asociat ei) si
care scriu rezultatele prelucrarii pe iesirea standard, sunt denumite traditional filtre.
1.1.6 UNIX ¸si utilizatorii
Fiecare utilizator, pentru a putea lucra, trebuie sa posede un cont pe sistemul UNIX
respectiv, cont caracterizat printr-un nume (username) si o parola (password), ce
trebuie furnizate in momentul conectarii la sistem (operatie denumita login).
Fiecare utilizator are asignat un UID (denumire ce provine de la User IDentification),
ce este un numar intreg pozitiv si este unic (nu exista doi utilizatori cu acelasi UID).
Exista un utilizator special, numit root (sau superuser), cu UID =0, care se ocupa
cu administrarea sistemului si are drepturi depline asupra intregului sistem.
Exista grupuri de utilizatori, cu ajutorul carora se gestioneaza mai usor drepturile
si restrictiile de acces a utilizatorilor la resursele sistemului. Fiecare utilizator face
parte dintr-un grup (si poate fi optional afiliat la alte grupuri suplimentare).
Fiecare grup are asignat un GID (Group IDentification), ce este un numar intreg
pozitiv si este unic (nu exista doua grupuri cu acelasi GID).
Pentru a avea acces la sistemul UNIX, un nou utilizator va trebui sa obtina un cont
nou (i.e.,username +password) de la administratorul sistemului. La crearea con-
tului, acesta ii va asigna anumite drepturi si restrictii de acces la fisiere si la celelalte
17
resurse ale sistemului, un grup de utilizatori la care este afiliat, un director de lucru
(numit home directory), un shell implicit, ¸s.a.
Directorul home este directorul curent in care este plasat utilizatorul cind se
conecteaza la sistem pentru a lucra, si este, de asemenea, directorul in care isi va
pastra propriile fisiere si subdirectoare.
Shell-ul implicit este interpretorul de comenzi lansat automat atunci cind utiliza-
torul se conecteaza la sistem.
Informatiile despre fiecare cont (username-ul, UID-ul, parola criptografiata, GID-ul
grupului din care face parte, directorul home,shell-ul implicit, si alte informatii)
sunt pastrate in sierul de sistem /etc/passwd.
Un alt fisier de sistem este /etc/group, in care se pastreaza informatii despre
grupurile de utilizatori.
Observatie: in versiunile mai noi, din motive de securitate, parolele criptografi-
ate au fost inlaturate din fisierul /etc/passwd, fiind pastrate in fisierul de sistem
/etc/shaddow, care este accesibil numai root-ului.
Atent¸ie: fiecare cont trebuie utilizat doar de proprietarul lui, acesta fiind obligat
sa nu-si divulge parola altor persoane, si nici sa nu dea acces prin contul lui altor
persoane. Aceasta din motive de securitate a sistemului: se pot depista incercarile de
“spargere” a sistemului si in acest caz va fi tras la raspundere proprietarul contului
din care s-a facut “spargerea”, indiferent daca acesta este vinovatul real sau altul
s-a folosit de contul lui, cu sau fara stirea proprietarului! Modul acesta de utilizare
a resurselor de calcul este stipulat si prin regulamentul facultatii/universitatii.
Parola poate fi schimbata direct de utilizator cu ajutorul comenzii passwd. Din mo-
tivele expuse mai sus, va trebui sa va alegeti parole cit mai greu de “ghicit”: sa fie
cuvinte de minimum 7-8 litere, sa nu reprezinte numele/prenumele dumneavoastra,
data nasterii, numarul de telefon sau alte date personale usor de aflat, sau combinatii
ale acestora, nici alte cuvinte ce sunt oarecum simple, ca de exemplu palindroamele
(= cuvinte “in oglinda”, cum ar : ab121ba ), ¸s.a.
Parolele sunt criptate cu un program de criptare intr-un singur sens (nu exista
metode de decriptare efective, adica in timp/spatiu rezonabile). Totusi exista pro-
grame care incearca “ghicirea” parolei prin generarea combinatiilor de litere cu prob-
abilitate mare de a folosite, pe baza unui dictionar (initial existau doar pentru limba
engleza, dar acum exista si pentru alte limbi). Din acest motiv programul de criptare
(comanda passwd) nu va accepta cuvinte ce sunt usor de “ghicit” in sensul de mai
sus, dar bineinteles ca nu poate sa-si dea seama daca parola tastata reprezinta vreo
data personala a dumneavoastra, deci din punctul de vedere al datelor personale
trebuie sa aveti singuri grija sa furnizati o parola cit mai “sigura”.
Atent¸ie: daca va uitati parola, nu mai puteti intra in contul dumneavoastra, si nici
administratorul nu va poate afla parola, dar in schimb v-o poate sterge si astfel veti
putea sa va puneti o noua parola.
Pe anumite sisteme, utilizatorul este obligat sa-si schimbe parola periodic din motive
de securitate. Astfel, pentru calculatorul fenrir, termenul de schimbare a parolei
este setat la 2 luni, dupa care vi se blocheaza contul in caz ca nu ati schimbat-o
(doar administratorul vi-l poate debloca in aceasta situatie).
18
1.1.7 Conectarea la un sistem UNIX
a) Sesiunea de lucru
Conectarea la sistem se realizeaza fie direct (de la consola sistemului sau alte terminale
legate direct la sistem), fie de la distanta. In primul caz conectarea se face cu comanda
login, iar pentru legarea de la distanta se utilizeaza comanda telnet.
Explicatie:telnet-ul este o aplicatie care transforma calculatorul PC pe care lucrati (sub
sistemul MS-Windows, de obicei), in terminal conectat, prin retea, la calculatorul
UNIX pe care doriti sa lucrati – in cazul de fata, calculatorul fenrir (i.e., serverul studen-
tilor). Comunicatia prin retea intre cele doua calculatoare se desfasoara prin protocolul
TELNET. Mai nou, se foloseste si protocolul SSH, care este un TELNET criptografiat
(mai exact, informatiile circula criptografiate prin retea intre cele doua calculatoare).
Sub sistemul MS-DOS, comanda de conectare era:
DOS>telnet nume-calculator
Exemple:
DOS>telnet fenrir.info.uaic.ro
DOS>telnet 193.231.30.197
DOS>telnet dragon.uaic.ro
Sub sistemul MS-Windows, exista mai multe aplicatii client de TELNET/SSH, unele com-
erciale si altele freeware, inclusiv comanda telnet implicita a Windows-ului, care se exe-
cuta intr-o fereastra MS-DOS prompt. Va recomand sa utilizati clientul putty, care este o
aplicatie open-source, disponibila gratuit pe INTERNET, inclusiv cu codul sursa.
Atent¸ie: aplicatia putty permite conectarea folosind ambele protocoale pentru sesiuni de
lucru, si cel necriptat (TELNET), si cel criptat (SSH), dar, din motive de siguranta, va
sfatuiesc sa va conectati intotdeauna la serverul fenrir folosind protocolul SSH, indiferent
de unde va conectati dumneavoastra, e de pe un calculator dintr-un laborator al facultatii,
fie de pe calculatorul personal de acasa.
Aceste motive de siguranta se refera la faptul ca informatiile de autentificare (username-ul
si parola) circula prin retea necriptate (i.e., ca si text clar) in cazul folosirii protocolului
TELNET, putind fi aflate astfel de persoane rau-intentionate (cu ajutorul unor programe
care “asculta” traficul prin retea, numite sniffer-e).
Comanda telnet poate fi folosita si de pe un calculator UNIX pentru a te conecta la un
alt calculator UNIX. Daca cele doua calculatoare sunt de acelasi tip de sistem UNIX, atunci
se poate folosi si comanda rlogin in loc de telnet.
In toate situatiile, indiferent de clientul de TELNET sau SSH folosit, conectarea la
calculatorul UNIX incepe cu faza de login (i.e., autentificarea ˆın sistem): utilizatorului i se
19
cere sa furnizeze un nume de cont (usename-ul de care am vorbit mai inainte) si o parola.
Conectarea reuseste doar daca numele si parola introduse sunt corecte (adica daca exista
intr-adevar pe acel sistem un utilizator cu numele si parola specificate), si in aceasta
situatie se incepe apoi o sesiune de lucru, adica se lanseaza interpretorul de comenzi
implicit pentru acel utilizator: se afiseaza un prompter si se asteapta introducerea de
comenzi de catre utilizator.
Prompterul afisat este in mod obisnuit caracterul ’$’ pentru utilizatorii obisnuiti, respectiv
’#’ pentru utilizatorul root, dar poate fi schimbat dupa dorinta cu comanda prompt, sau cu
ajutorul fisierului de initializare a sesiunii de lucru (fisierul .profile sau .bash profile
in cazul shell-ului bash).
La sfirsitul sesiunii de lucru, deconectarea de la sistem se face cu comanda logout (se
poate si cu comanda exit, dar numai in anumite circumstante).
b) Transferul de fi¸siere
In afara comenzii telnet care permite conectarea la un sistem UNIX pentru deschiderea
unei sesiuni de lucru, o alta comanda utila este comanda ftp, care permite conectarea la
un alt sistem pentru a transfera fisiere intre calculatorul pe care este executata comanda
(numit calculatorul local) si sistemul la care se face conectarea (numit calculatorul de la
distanta). Protocolul utilizat de aceasta comanda este protocolul FTP (abrevierea provine
de la File Transfer Protocol).
Sub sistemul MS-DOS, comanda de conectare pentru transfer de fisiere era:
DOS>ftp nume-calculator
Exemple:
DOS>ftp fenrir.info.uaic.ro
DOS>ftp 193.231.30.197
Sub sistemul MS-Windows, exista numeroase aplicatii client de FTP, unele comerciale
si altele freeware, inclusiv comanda ftp implicita a Windows-ului, care se executa intr-
o fereastra MS-DOS prompt. Spre exemplu, managerul de fisiere Windows Commander
are implementat si un client de FTP, operatie ce este disponibila din meniurile acestei
aplicatii.
Comanda ftp incepe si ea cu o faza de login similara cu cea de la comanda telnet, dupa
care urmeaza sesiunea propriu-zisa de transfer de fisiere, in care se afiseaza un prompter si
se asteapta introducerea de comenzi de catre utilizator, comenzi ce sunt de tipul urmator:
1. FTP> ls director
afi¸seaz˘a cont¸inutul directorului specificat de pe calculatorul de la distanta;
20
2. FTP> lls director
afi¸seaz˘a cont¸inutul directorului specificat de pe calculatorul local;
3. FTP> cd director
schima directorul curent ˆın cel specificat pe calculatorul de la distanta;
4. FTP> lcd director
schima directorul curent ˆın cel specificat pe calculatorul local;
5. FTP> get fisier
transfer˘a fi¸sierul specificat de pe calculatorul de la distanta pe cel local;
6. FTP> put fisier
transfer˘a fi¸sierul specificat de pe calculatorul local pe cel de la distanta.
Atˆıt programul client (i.e., comanda ftp), cˆıt ¸si programul server de FTP (i.e., programul
de pe calculatorul de la distanta care va raspunde la cererea de conectare adresata de client)
ˆı¸si p˘astreaz˘a cˆıte un director curent de lucru propriu pe calculatorul respectiv, ˆın raport
cu care se vor considera numele de fi¸siere sau directoare specificate prin cale relativ˘a ˆın
comenzile enumerate de mai sus. Operat¸iile locale lls ¸si lcd se vor executa direct de c˘atre
client, f˘ar˘a ajutorul serverului, ˆın schimb pentru toate celelalte patru operat¸ii clientul va
schimba informatii cu serverul pentru a putea realiza operat¸ia respectiv˘a.
Observat¸ie: evident, sintaxa acestor comenzi difera de la un client la altul (spre exemplu,
in Windows Commander operatiile sunt disponibile prin interfata grafica, utilizind direct
mouse-ul), dar toate aplicatiile de acest tip ofera operatiile amintite mai sus, si altele
suplimentare.
Pe linga protocolul FTP, care este necriptat, mai exista si un alt protocol, criptat, ce
permite transferul de fisiere, protocol numit SCP (abrevierea provine de la Secure Copy
Protocol), si care este practic un FTP realizat printr-un “tunel” SSH.
Atent¸ie: din aceleasi motive de siguranta ca la sesiunile de lucru TELNET/SSH, va
sfatuiesc sa va conectati intotdeauna la serverul fenrir pentru transfer de fisiere folosind
protocolul criptat SCP, indiferent de unde va conectati dumneavoastra, e de pe un
calculator dintr-un laborator al facultatii, fie de pe calculatorul personal de acasa.
In acest sens, va recomand sa utilizati clientul WinSCP, care este o aplicatie open-source,
disponibila gratuit pe INTERNET, inclusiv cu codul sursa, pentru Windows, cu o interfata
grafica asemanatoare celei din Windows Commander.
1.2 Distribut¸ii de Linux. Instalare
1. Introducere
21
2. Instalarea unei distribut¸ii de Linux
1.2.1 Introducere
Dup˘a cum am specificat deja la ˆınceputul acestui capitol, Linux-ul este o variana de
UNIX distribuibil˘a gratuit (open-source), pentru sisteme de calcul bazate pe procesoare
Intel, procesoare Dec Alpha, ¸si, mai nou, ¸si pentru alte tipuri de procesoare (cum ar fi de
exemplu cele pentru embedded systems). El a fost creat ˆın 1991 de Linus Torvalds, fiind
ˆın prezent dezvoltat ˆın permanent¸˘a de o echip˘a format˘a din mii de entuzia¸sti Linux din
lumea ˆıntreag˘a, sub ˆındrumarea unui colectiv condus de Linus Torvalds.
Mai precis, aceast˘a echip˘a mondial˘a se ocup˘a cu dezvoltarea nucleului sistemului de ope-
rare, care ˆın prezent se afl˘a la versiunea 2.6.x (x-ul este num˘arul minor de versiune,
incrementat la lansarea fiec˘arei noi versiuni a kernel-ului de Linux, iar 2.6 este nuarul
major de versiune, ce indic˘a o familie generic˘a de versiuni, ce se deosebste de precedenta
prin caracteristici ¸si funct¸ionalit˘at¸i importante, introduse ˆın nucleu o dat˘a cu trecerea la
o nou˘a familie de versiuni).
Ca orice sistem din familia UNIX-ului, ¸si Linux-ul este compus, pe lˆıng˘a nucleul sistemului
de operare, dintr-o colect¸ie de utilitare de baz˘a ¸si programe de aplicat¸ii, cum ar fi, de ex-
emplu, diverse shell-uri, editoare de texte, compilatoare ¸si medii de dezvoltare de aplicat¸ii
ˆın diverse limbaje de programare, diverse utilitare filtru, programe de pst˘a electronic˘a,
¸s.a. (le-am enumerat deja ˆın subsect¸iunea 1.1.3 de mai sus). Majoritatea sunt programe
open-source, dar exist˘a ¸si aplicat¸ii comerciale pentru Linux.
Observat¸ie: Dup˘a cum am amintit deja la ˆınceputul acestui capitol, fundat¸ia FSF (Free
Software Fundation) ˆı¸si propusese a dezvolte o versiune de UNIX care a fie ˆın ˆıntregime
compatibil˘a cu varianta de UNIX de la AT&T, versiune numit˘a GNU (acronim recursiv ce
ˆınseamn˘a GNU’s Not Unix), ¸si care trebuia s˘a fie free-software, deci s˘a nu necesite nici o
licent¸˘a de utilizare (¸si prin urmare s˘a fie gratuit˘a). ˆ
Inainte de anii ’90, fundat¸ia reu¸sise s˘a
realizeze deja medii de dezvoltare de aplicat¸ii (i.e., compilator de C ¸si C++, depanator,
link-editor, ¸s.a.) ¸si utilitarele de baz˘a, dar ˆıi lipsea tocmai nucleul sistemului de operare
(¸si nici pˆın˘a ˆın prezent situat¸ia nu s-a schimbat, datorit˘a motivului expus ˆın continuare).
Astfel, cˆınd ˆın 1991 Linus Torvalds scria primele versiuni ale unui nou nucleu de tip UNIX,
pe care l-a numit Linux, a luat decizia de a combina nucleul s˘au cu mediile de dezvoltare de
aplicat¸ii ¸si utilitarele de baz˘a din familia GNU dezvoltate de atre FSF, ¸si cu sistemul grafic
X Window dezvoltat la MIT, pentru a forma un sistem de operare complet. Se n˘stea
astfel un nou sistem de operare, numit Linux, primul sistem de operare care era disponibil
ˆın mod gratuit. De fapt, init¸ial se numea GNU/Linux, dar s-a ˆınr˘ad˘acinat folosirea numelui
mai scurt Linux.
Datorit˘a faptului c˘a atˆıt nucleul, cˆıt ¸si uneltele GNU erau disponibile gratuit, diverse com-
panii ¸si organizat¸ii, ba chiar ¸si unii indivizi pasionat¸i de fenomenul open-source si Linux,
au ˆınceput s˘a lanseze diverse variante de Linux, care difereau printre ele prin versiunea
22
nucleului ce o includeau ¸si prin programele (cu propriile lor versiuni) ce alc˘atuiau colect¸ia
de utilitare de baz˘a ¸si programe de aplicat¸ii inclus˘a ˆın respectiva variant˘a de Linux. Ast-
fel, toate includeau compilatorul GNU C/C++ pentru limbajele C ¸si C++, ˆıntr-o anumit˘a
versiune a acestuia. ˆ
In plus, erau ˆınsot¸ite ¸si de un program de instalare a sistemului, care
¸si acesta diferea de la o variana la alta de Linux. Diferent¸ele dintre aceste programe
de instalare sunt mai pregnante ˆın ceea ce privste modul de gestiune al pachetelor ¸si de
select¸ie al lor ˆın vederea instal˘arii, precum ¸si al script-urilor folosite pentru configurarea
sistemului. ˆ
In terminologia UNIX, prin pachet se ˆınt¸elege un grup de programe, uneori de-
pendente unele de altele, ce realizeaz˘a o anumit˘a sarcin˘a (sau mai multe sarcini ˆınrudite),
ˆımpreun˘a cu fi¸sierele de init¸ializare ¸si configurare aferente acestor programe.
Aceste variante de Linux au fost denumite distribut¸ii de Linux. Init¸ial au fost cˆıteva
distribut¸ii, ele fiind cele mai r˘aspˆındite ¸si ˆın ziua de azi, cum ar fi, spre exemplu, distribut¸ia
Red Hat (http://www.redhat.com), distribut¸ia Slackware (http://www.slackware.org),
distribut¸ia Mandrake (http://www.mandrake.com), distribut¸ia SuSE (http://www.suse.de),
distribut¸ia Debian (http://www.debian.org), ¸s.a. ˆ
In prezent exist˘a peste o sut˘a de distribut¸ii
de Linux, adaptate pentru diverse arhitecturi, diverse scopuri de folosire a sistemului, etc.
(spre exemplu, exist˘a distribut¸ii care pot fi rulate direct de sub MS-Windows, ar˘a a fi
necesar˘a instalarea lor ˆın partit¸ii UNIX separate, sau distribut¸ii care pot fi boot-ate direct
de pe CD, f˘ar˘a a necesita instalarea sistemului pe harddisc, numite distribut¸ii live”). Pe
portalul oficial dedicat Linux-ului, accesibil la adresa web http://www.linux.org, se g˘asesc
informat¸ii despre distribut¸iile de Linux disponibile ˆın prezent, grupate dup˘a criteriile de
clasificare amintite mai sus.
Majoritatea distribut¸iilor sunt disponibile pentru download gratuit (adresele de la care pot
fi desc˘arcate sunt indicate pe portalul amintit mai sus). Alternativ, ele pot fi comandate
pe site-ul produc˘atorului spre a fi trimise pe CD-uri prin pst˘a, sau pot fi cump˘arate
de la magazin sub form˘a de pachet software (ce cont¸ine CD-urile plus manuale tip˘arite
de instalare ¸si utilizare), ˆın ambele situat¸ii la pret¸uri modice (care s˘a acopere diversele
cheltuieli implicate de multiplicare, suportul fizic folosit, transport, ¸s.a.).
ˆ
In concluzie, o distribut¸ie de Linux const˘a, ˆın principal, dintr-o anumit˘a versiune a kernel-
ului de Linux ¸si dintr-o anumit˘a select¸ie (specific˘a produc˘atorului distribut¸iei respective)
a programelor, cu diverse versiuni ale lor, ce formeaz˘a colect¸ia de utilitare de baz˘a ¸si
programe de aplicat¸ii proprie acelei distribut¸ii. Plus un anumit program de instalare a
acelei distribut¸ii ¸si de management al pachetelor de programe ce alc˘atuiesc acea distribut¸ie.
Diferent¸ele ˆıntre distribut¸iile provenite de la produatori diferit¸i constau, ˆın principal, ˆın
ce programe au fost selectate pentru a fi incluse, distribut¸iile fiind adaptate pentru diverse
scopuri, precum ¸si, uneori, ˆın programul de instalare ¸si modul de gestiune al pachetelor
de c˘atre acesta. Iar ˆın cadrul unei distribut¸ii provenit˘a de la un produc˘ator oarecare,
diferent¸ele ˆıntre diferitele versiuni ale ei constau, ˆın principal, ˆın versiunea nucleului ¸si
versiunile programelor incluse ˆın respectiva versiune a distribut¸iei.
23
1.2.2 Instalarea unei distribut¸ii de Linux
Fiecare produc˘ator al unei distribut¸ii de Linux ˆınsot¸e¸ste acea distribut¸ie de manuale care
descriu modul de instalare ¸si de utilizare a acelei distribut¸ii. Aceste manuale sunt disponi-
bile ˆın format electronic pe CD-urile cu acea distribut¸ie, ¸si eventual ¸si ˆın format tip˘arit
(ˆın cazul ˆın care at¸i cump˘arat acea distribut¸ie de la magazin).
Atent¸ie:ˆ
Inainte de a v˘a apuca de instalarea unei distribut¸ii este recomandabil˘a citirea
manualului de instalare (mai ales ˆın cazul utilizatorilor ˆıncep˘atori, este chiar necesar˘a
citirea ˆın prealabil a manualului de instalare).
ˆ
In continuare vom prezenta pa¸sii generali ce trebuie urmat¸i ˆın vederea instal˘arii unei
distribut¸ii de Linux “clasice” (i.e., care trebuie instalaa pe harddisc ˆın propria partit¸ie
de tip Linux).
Am optat pentru o prezentare general˘a, ar˘a a intra prea mult ˆın detalii, din mai multe
motive: lips˘a de spat¸iu, diferent¸ele dintre diversele distribut¸ii ˆın ceea ce prive¸ste aspectele
de am˘anunt ale procedurii de instalare, faptul c˘a ecare distribut¸ie are un manual de
instalare bine documentat, ¸si ˆın plus exist˘a numeroase c˘art¸i de specialitate dedicate UNIX-
ului ¸si, ˆın particular, Linux-ului, multe dintre acestea descriind ¸si procedura de instalare
pentru una sau mai multe dintre distribut¸iile Linux cele mai r˘aspˆındite (spre exemplu, se
pot consulta art¸ile [2] ¸si [3]).
ˆ
In concluzie, pa¸sii care urmeaz˘a sunt doar un ghid general, pentru instalarea unei
distribut¸ii fiind necesar˘a studierea documentat¸iei acelei distribut¸ii ¸si/sau a unei art¸i de
specialitate. Aceasta cel put¸in la ˆınceput, ˆın cazul utilizatorilor ˆıncep˘atori ˆıntr-ale Linux-
ului, aci apoi se va ˆıntˆımpla exact ca ˆın cazul Windows-ului: dup˘a efectuarea unui num˘ar
mare de instal˘ari ¸si reinstal˘ari ale sistemului, se cap˘at˘a experient¸˘a, ajungˆındu-se la o
simpl˘a instalare “cu ochii ˆınchi¸si”.
1) Preg˘atirea instal˘arii
Prima etap˘a const˘a ˆın preg˘atirea pentru instalarea sistemului Linux, fiind constituit˘a din
urm˘atorii pa¸si:
1. Preatirea spat¸iului liber pentru stocarea sistemului de fi¸siere al Linux-ului
Aceast˘a etap˘a va crea spat¸iu liber pe harddisc pentru partit¸iile de Linux ce vor fi
create ulterior. Dac˘a este un sistem nou, atunci harddiscul este gol, deci nu avem
nici o problem˘a d.p.d.v. acesta.
Cel mai adesea ˆıns˘a, pe calculator avem deja instalat sistemul MS-Windows (fie 9x,
fie NT/2000/XP, nu conteaz˘a ce fel este). ˆ
In aceast˘a situat¸ie, dac˘a totu¸si mai avem
spat¸iu nepartit¸ionat pe harddisc, atunci iar˘si nu avem nici o problem˘a. De regul˘a
ˆıns˘a, fie nu mai avem deloc spat¸iu liber, nepartit¸ionat, fie avem, dar ˆın cantitate
insuficiena. ˆ
In acest caz, va trebui s˘a eliber˘am spat¸iu prin redimensionarea (i.e.,
mic¸sorarea) partit¸iilor existente.
24
Aceasta se poate face ˆın felul urm˘ator: mai ˆıntii se defragmenteaz˘a partit¸ia ce
urmeaz˘a a fi mic¸sorat˘a, folosind fie utilitarul de defragmentare din MS-Windows, fie
un program de defragmentare separat (cum ar , spre exemplu, cel din suita Norton
Utilities). ˆ
In felul acesta blocurile ocupate de date vor fi mutate laˆınceputul partit¸iei,
iar cele libere la sfˆır¸situl ei. Urmeaz˘a apoi mic¸sorarea efectiv˘a a partit¸iei, care se
poate realiza folosind utilitarul FIPS.EXE (ce se g˘ase¸ste pe CD-urile distribut¸iei re-
spective, de obicei ˆın directorul cd:\DOSUTILS), sau o aplicat¸ie de partit¸ionare com-
ercial˘a (cum ar fi, spre exemplu, programul Partition Magic).
Atent¸ie: ˆınainte de partit¸ionare, realizat¸i copii de sigurant¸˘a ale datelor existente pe
partit¸ia respectiv˘a (deoarece utilizarea programelor de partit¸ionare comport˘a anu-
mite riscuri: ˆın cazul aparit¸iei unor erori de exemplu, dac˘a se ˆıntrerupe alimenta-
rea cu curent electric – ˆın timpul desf˘suarii operat¸iei de (re)partit¸ionare, se pot
pierde datele, recuperarea lor ulterioar˘a fiind, dac˘a nu imposibil˘a, cel put¸in foarte
anevoioas˘a).
2. Alegerea metodei de instalare
De obicei, sunt disponibile trei metode de instalare, dua locat¸ia programului de
instalare:
(a) CD-ROM. Instalarea se va face direct de pe CD-urile ce cont¸in distribut¸ia res-
pectiv˘a. Pentru pornirea sistemului se poate opta fie pentru boot-area de pe
CD (de obicei primul CD al distribut¸iei este boot-abil), dac˘a BIOS-ul calcula-
torului are opt¸iunea de boot-are de pe CD-uri, fie pentru boot-area cu ajutorul
unei dischete de boot, despre a arei mod de obt¸inere vom vorbi mai jos.
(b) Harddisk. Instalarea se va face de pe disc, dup˘a ce ˆın prealabil cont¸inutul
CD-urilor din care este format˘a distribut¸ia au fost copiate pe o partit¸ie Linux
sau Windows existena. ˆ
In acest caz este necesar˘a discheta de boot amintit˘a
adineaori.
(c) Ret¸ea. Instalarea se va face prin ret¸ea, de pe un alt calculator ce cont¸ine
distribut¸ia de Linux, ¸si pe care o export˘a ˆın ret¸ea prin protocolul NFS,FTP,
sau HTTP. S¸i ˆın acest caz este necesar˘a o dischet˘a de boot, care trebuie s˘a aib˘a
¸si suport pentru ret¸ea.
Pe lˆına aceste metode de instalare, care toate presupun pornirea calculatorului
prin boot-area unui sistem Linux minimal, e de pe o dischet˘a de boot, e de pe
CD, mai exist˘a o posibilitate de instalare direct de pe CD-ROM, ˆın situat¸ia ˆın care
pe calculator exist˘a deja instalat sistemul MS-DOS/Windows. S¸i anume, se porne¸ste
acest sistem ¸si se apeleaz˘a un program special dedicat acestui scop (numit, de obicei,
AUTOBOOT.EXE sau AUTORUN.EXE, ¸si care se g˘ase¸ste pe CD-urile distribut¸iei respec-
tive, de obicei ˆın directorul cd:\DOSUTILS); pentru mai multe detalii despre aceast˘a
posibilitate, consultat¸i documentat¸ia distribut¸iei.
3. Crearea dischetei de boot
Dup˘a cum am amintit mai sus, este nevoie de crearea unei dischete de boot pentru
Linux, care va fi folosit˘a pentru boot-area unui sistem Linux minimal, cu ajutorul
aruia se va face instalarea propriu-zis˘a a distribut¸iei de Linux.
25
Crearea dischetei de boot pentru Linux se poate face din cadrul sistemului
MS-DOS/Windows (folosind eventual un alt calculator ce are instalat acest sistem,
ˆın situat¸ia ˆın care sistemul nostru este nou, ar˘a nici un sistem de operare instalat pe
el; sau, putem folosi o dischet˘a de boot pentru MS-DOS, cu suport pentru CD-ROM,
pentru a porni sistemul), procedˆındu-se ˆın felul urm˘ator:
se copie pe o dischet˘a goal˘a imaginea dischetei de boot aflat˘a pe CD-urile
distribut¸iei, folosind un utilitar dedicat acestui scop (cum ar fi programul
RAWRITE.EXE, ce se ase¸ste pe CD-urile distribut¸iei respective, de obicei ˆın direc-
torul cd:\DOSUTILS); pentru mai multe detalii despre aceast˘a operat¸ie, consultat¸i
documentat¸ia distribut¸iei.
Observat¸ie: discheta de boot pentru Linux nu este necesar˘a la instalare ˆın situat¸ia ˆın
care instalarea se va face prin boot-area de pe CD-ul ditribut¸iei, sau cˆınd instalarea
va fi pornit˘a din MS-DOS/Windows.
Totu¸si, este recomandabil s˘a creat¸i o dischet˘a de boot, fie ˆın aceast˘a etap˘a
preg˘atitoare, fie dup˘a instalare, pe care s-o avet¸i la ˆındemˆına dac˘a vreodat˘a vet¸i
ˆın situat¸ia ˆın care boot-manager-ul instalat de Linux va fi corupt (situat¸ie care se
poate ˆıntˆımpla la o reinstalare ulterioar˘a a sistemului MS-Windows, deoarece progra-
mul acestuia de instalare rescrie MBR-ul (=Master Boot Record), iar acesta poate
cont¸ine boot-manager-ul de Linux); ˆın aceast˘a situat¸ie, dua terminarea reinstal˘arii
MS-Windows-ului, vet¸i putea s˘a pornit¸i sistemul cu ajutorul dischetei de boot, ¸si a
refacet¸i boot-manager-ul de Linux ˆın MBR.
4. Planificarea partit¸iilor de Linux
Este recomandabil˘a crearea cel put¸in a urm˘atoarelor partit¸ii:
(a) o partit¸ie de swap, ce va fi folosit˘a pentru memoria virtual˘a. Dimensiunea
acestei partit¸ii trebuie s˘a fie de minim 32 MB ¸si maxim 2 GB, dar recomandabil
este a fie cam dublul memoriei RAM instalate in calculator (mai precis, pentru
a fi optim˘a pentru majoritatea aplicat¸iilor din ziua de azi, capacitatea memoriei
virtuale se recomand˘a a fi aleas˘a astfel: de cca. 500 MB pentru o memorie RAM
de 128 MB, de 256 MB pentru o memorie RAM de 256 MB, ¸si poate lipsi ˆın
cazul unei memorii RAM de 512 MB).
(b) o partit¸ie pentru directorul /boot, ce va cont¸ine nucleul Linux ¸si celelalte fi¸siere
utilizate ˆın timpul bootarii. Ca dimensiune poate fi aleas˘a valoarea 32 MB.
Observat¸ie important˘a: aceasta fiind partit¸ia de pe care se boot-eaz˘a sistemul,
exist˘a o restrict¸ie asupra plaarii sale pe harddisc. S¸i anume, ea trebuie plasat˘a
la ˆınceputul harddiscului, sub limita de 1 GB (mai precis, aceast˘a partit¸ie
trebuie s˘a aib˘a cilindrul de start ˆınaintea cilindrului 1024). Aceast˘a limitare se
datoreaz˘a modului restrictiv de acces la harddisc al programului LILO (i.e.,
programul boot-manager responsabil cu pornirea sistemului). Pentru celelalte
partit¸ii nu exist˘a nici o restrict¸ie, ele putˆınd fi plasate la orice distant¸˘a de
ˆınceputul harddiscului, fie ca partit¸ii primare, fie ca partit¸ii logice ˆın cadrul
partit¸iei extinse definit˘a pe respectivul harddisc.
(c) o partit¸ie de root, acolo unde se va afla /(i.e., r˘ad˘acina structurii arborescente
a sistemului de fi¸siere), ¸si care va cont¸ine toate fi¸sierele din sistem. Dimensiunea
acestei partit¸ii trebuie aleas˘a astfel ˆıncˆıt a ˆıncap˘a toate pachetele de aplicat¸ii
26
ce vor fi alese pentru instalare (ˆın timpul instal˘arii propriu-zise vet¸i avea posi-
bilitatea de a alege ce aplicat¸ii dorit¸i s˘a instalat¸i dintre toate cele disponibile ˆın
distribut¸ia respectiv˘a, ¸si vi se va comunica ¸si spat¸iul necesar). T¸ inˆınd cont a
harddiscurile actuale au dimensiuni de zeci sau chiar sute de GB, putet¸i alege
ar˘a probleme o dimensiune de cˆıt¸iva GB sau chiar mai mult pentru aceast˘a
partit¸ie.
Observat¸ie: ˆın cazul ˆın care calculatorul nu va fi folosit doar ca stat¸ie de lucru, ci ca
server Linux, se recomand˘a crearea unor partit¸ii suplimentare:
(a) o partit¸ie pentru directorul /home, ce va cont¸ine fi¸sierele utilizatorilor cu conturi
pe acel server;
(b) o partit¸ie pentru directorul /var, ce va cont¸ine fi¸sierele cu cont¸inut variabil ale
sistemului;
(c) o partit¸ie pentru directorul /usr, ce va cont¸ine fi¸sierele sistemului de operare
¸si aplicat¸iile instalate ulterior.
ˆ
In final, a amintim ¸si tipul sistemului de fi¸siere ce trebuie utilizat pentru fiecare
dintre partit¸iile amintite mai sus; acest tip trebuie specificat atunci cˆınd se real-
izeaz˘a efectiv operat¸ia de partit¸ionare, ce este ˆınsot¸it˘a de operat¸ia de creare a sis-
temului de fi¸siere pe partit¸ia respectiv˘a (analogul operat¸iei de formatare utilizate ˆın
MS-DOS/Windows).
Pentru partit¸ia de swap, ce va avea codul numeric 82, trebuie ales tipul de sistem
de fi¸siere swap. Celelalte partit¸ii amintite mai sus vor fi partit¸ii native de Linux,
cu codul numeric 83, pentru care se va alege ca sistem de fi¸siere unul din tipurile
urm˘atoare: ext2 (sistemul clasic de fi¸siere Linux, compatibil cu standardele UNIX),
ext3 (noul sistem de fi¸siere Linux, bazat pe ext2, ce are ad˘augat suport pentru
jurnalizare), sau reiserfs (un sistem nou de fi¸siere, cu suport pentru jurnalizare, ce are
performant¸e superioare ˆın multe situat¸ii sistemelor ext2 ¸si ext3, datorit˘a arhitecturii
interne mai eficiente).
Realizarea efectiv˘a a partit¸ioarii se va face ˆıntr-o prim˘a etap˘a a programului de
instalare a distribut¸iei respective. Ea se poate face ¸si ˆın avans, folosind discheta de
boot, creat˘a la un pas amintit mai sus, pentru a porni sistemul, ¸si apoi se poate apela
utilitarul ˆın mod text fdisk pentru a crea partit¸iile de Linux.
2) ˆ
Inceperea instal˘arii propriu-zise
A doua etap˘a const˘a ˆın instalarea propriu-zis˘a a sistemului Linux, care se ˆıncepe prin
pornirea (boot-area) sistemului cu ajutorul dischetei de boot amintite mai sus, sau direct
folosind CD-ul boot-abil al distribut¸iei. La sfˆır¸situl etapei de pornire a sistemului, se va
afi¸sa un ecran cu informat¸ii despre modurile de startare a instal˘arii ¸si un prompter de
forma
boot :
la care se a¸steapt˘a alegerea unei opt¸iuni de startare a instal˘arii.
27
Exist˘a dou˘a interfet¸e ale programului de instalare, ce pot fi folosite la alegere ˆın timpul
instal˘arii: o interfat¸˘a ˆın mod text (care se selecteaz˘a de obicei tastˆınd comanda text sau
linux text la prompterul “boot : amintit anterior; comanda exact˘a ce trebuie tastat˘a
depinde de distribut¸ie, dar de obicei se ofer˘a informat¸ii ˆın acest sens chiar pe ecranul afi¸sat
ˆın acest punct al instal˘arii) ¸si o interfat¸˘a ˆın mod grafic, care este aleaa implicit (dup˘a
scurgerea unui interval de cˆıteva secunde f˘ar˘a react¸ie din partea utilizatorului, sau imediat
la ap˘asarea tastei ENTER dua aparit¸ia prompterului amintit anterior). Sigur, interfat¸a ˆın
mod grafic este mai “prietenoas˘a” pentru utilizator, dar totu¸si este recomandabil˘a folosirea
interfat¸ei ˆın mod text, de exemplu ˆın situat¸ia ˆın care placa video instalat˘a ˆın sistem are
performant¸e slabe, sau dac˘a se dore¸ste un timp mai scurt de instalare, deoarece interfat¸a
ˆın mod text este mai rapid˘a.
Dup˘a alegerea interfet¸ei text sau grafic˘a, programul de instalare parcurge urm˘atoarele
etape de instalare (not˘a: reamintesc faptul c˘a este doar o prezentare general˘a, pentru o
anumit˘a distribut¸ie concret˘a s-ar putea s˘a apar˘a unele mici diferent¸e – etape suplimentare
sau ˆın minus, sau ordinea ˆın care apar acestea poate fi u¸sor schimbat˘a) :
1. Selectarea limbii.
Limba selectat˘a va fi utilizat˘a pe parcursul instal˘arii ¸si, implicit, ¸si dua instalare.
2. Selectarea tipului de instalare.
Se poate alege o instalare complet˘a (“pe curat”) sau o actualizare a unei instal˘ari
mai vechi (upgrade). ˆ
In cazul instal˘arii complete, se poate alege ˆıntre o instalare rec-
ommended, ˆın care opt¸iunile de instalare ¸si pachetele ce vor fi instalate sunt selectate
automat de atre programul de instalare, ˆın conformitate cu un scenariu ales de uti-
lizare a sistemului: personal desktop (sistem personal), workstation (stat¸ie de lucru),
sau server, ¸si o instalare custom (sau expert), care permite modificarea opt¸iunilor
de instalare ¸si select¸ia pachetelor dorite, oferind astfel cea mai mare flexibilitate
posibil˘a.
3. Configurarea tastaturii ¸si a mouse-ului.
4. Partit¸ionarea discului.
ˆ
In cadrul acestei etape se definesc ¸si se formateaz˘a partit¸iile necesare Linux-ului, ˆın
conformitate cu cele discutate la etapa preg˘atitoare a instal˘arii.
Exist˘a de obicei dou˘a opt¸iuni de partit¸ionare: automat˘a ¸si manual˘a. Pentru cea din
urm˘a se folose¸ste programul de partit¸ionare Disk Druid, ˆın cazul interfet¸ei grafice,
respectiv cu programul clasic fdisk, disponibil doar pentru interfat¸a ˆın mod text.
5. Instalarea ˆınc˘aratorului de boot.
Pentru a putea porni sistemul Linux este nevoie de un ˆınc˘arc˘ator de boot (boot
loader), care poate porni de asemenea ¸si alte sisteme de operare ce sunt instalate pe
disc.
ˆ
Inc˘arc˘atorul clasic ce se folo¸seste este programul LILO (acronim ce provine de la
LInux LOader), dar mai avem ¸si alte dou˘a alternative: putem folosi programul
28
GRUB (GRand Unified Boot loader), sau nici un ˆınc˘arc˘ator de boot, situat¸ie ˆın
care va trebui s˘a pornim de fiecare dat˘a sistemul Linux ˆıntr-o alt˘a manier˘a (fie cu
o dischet˘a de boot pentru Linux, e cu un program ce starteaz˘a Linux-ul de sub
MS-DOS/Windows).
Tot ˆın aceast˘a etap˘a se mai stabilesc modul de instalare a ˆınc˘arc˘atorului de boot ¸si
celelalte sisteme de operare ce vor fi pornite de c˘atre ˆınc˘arc˘atorul de boot. Acesta
poate instalat fie ˆın MBR (=Master Boot Record), adic˘a sectorul de boot de la
ˆınceputul discului ce este ˆınc˘arcat automat de BIOS-ul calculatorului (se recomand˘a
folosirea acestei opt¸iuni), fie ˆın primul sector al partit¸iei de root (ˆın aceast˘a situat¸ie
trebuie configurat ˆınarc˘atorul sistemului de operare instalat anterior pe disc pentru
a ¸sti a apeleze ˆınc˘arc˘atorul de Linux plasat ˆın primul sector al partit¸iei de root al
acestuia).
Observat¸ii:
(a) ˆ
In cazul folosirii ˆınc˘arc˘atorului clasic LILO, configurarea sistemelor de ope-
rare ce vor putea fi pornite prin intermediul lui, se face cu ajutorul fi¸sierului
/etc/lilo.conf, care este un fi¸sier text ce poate fi editat direct pentru a speci-
fica sistemele de operare, partit¸iile de pe care vor fi pornite, ¸si alt¸i parametri
opt¸ionali de transmis kernel-ului Linux la ˆınc˘arcarea acestuia. Dup˘a editare,
activarea modific˘arilor efectuate se face cu comanda lilo (/sbin/lilo).
Spre exemplu, se poate pune o parol˘a ˆın /etc/lilo.conf pentru pornirea re-
strictiv˘a a Linux-ului – pentru orice parametru opt¸ional transmis kernel-ului,
se va cere parola; ˆın acest caz, trebuie protejat fi¸sierul astfel ˆıncˆıt s˘a nu fie
accesibil decˆıt superuser-ului (lucru ce se poate realiza cu comanda chmod
600 /etc/lilo.conf , efectuat˘a de c˘atre utilizatorul root). Aceast˘a parol˘a de
pornire ofer˘a protect¸ie fat¸˘a de atacurile de la consol˘a.
Cu comanda man lilo.conf putet¸i consulta documentat¸ia referitoare la acest
fi¸sier de configurare a ˆınc˘arc˘atorului de boot.
(b) La prompterul “lilo : afi¸sat de ˆınc˘arc˘atorul LILO, pe lˆıng˘a comenzile im-
plicite ce pot fi tastate, ¸si anume cele de selectare a sistemului de operare ce
urmeaz˘a a fi ˆınc˘arcat, se mai pot tasta o serie de alte opt¸iuni utile pentru uti-
lizatorii avansat¸i, ca de exemplu cu comanda append="..." se pot specifica o
serie de parametri ce vor fi transmi¸si kernel-ului la ˆınarcarea acestuia, sau cu
comanda linux single se va porni sistemul ˆın mod single user ¸si se va intra
ˆın sistem ca root (i.e.,superuser-ul), f˘ar˘a faza de autentificare (i.e., nu se mai
cere parola). Protect¸ia ˆımpotriva acestui tip de acces se poate realiza folosind
o parol˘a ˆın fi¸sierul /etc/lilo.conf, conform celor discutate mai sus.
6. Configurarea leaturii de ret¸ea.
Se configureaz˘a placa (sau pl˘acile) de ret¸ea aflat˘a ˆın calculator, ˆımpreun˘a cu toate
datele necesare pentru buna funct¸ionare ˆın cazul leg˘arii ˆıntr-o ret¸ea de calculatoare:
adresa IP, adresa de ret¸ea, masca de ret¸ea, numele ma¸sinii, adresa gateway-ului,
adresa DNS-ului, ¸s.a.
7. Configurarea nivelului de securitate.
29
Se configureaz˘a firewall-ul pe baza nivelului de securitate ales, dintre mai multe
opt¸iuni posibile: nivel ˆınalt, nivel mediu, nivel jos (f˘ar˘a firewall), sau opt¸iunea cus-
tom, ce permite configurarea manual˘a a firewall-ului. Firewall-ul este o aplicat¸ie
foarte importana d.p.d.v. al securit˘at¸ii sistemului, ea avˆınd ca sarcin˘a filtrarea trafi-
cului prin leg˘atura de ret¸ea, ˆın funct¸ie de adresele IP ¸si porturile din pachetele de
date ce o tranziteaa.
8. Configurarea utilizatorilor.
Se alege parola pentru superuser (i.e., utilizatorul cu numele root), care posed˘a
drepturi totale asupra sistemului. Acest utilizator trebuie folosit ˆın mod normal doar
pentru instalarea/dezinstalarea de programe ¸si pentru administrarea sistemului. ˆ
In
rest, pentru utilizarea calculatorului, se recomand˘a crearea unuia sau mai multor
utilizatori obi¸snuit¸i (adic˘a, ar˘a drepturi depline ˆın sistem) care s˘a fie folosit¸i pentru
lucrul cu calculatorul, chiar dac˘a acesta este folosit doar ca sistem personal (i.e.,
acas˘a), deoarece o comand˘a gre¸sit˘a tastat˘a ca root (i.e., utilizatorul cu drepturi
depline) poate cauza deteriorarea sistemului sau chiar pierderea datelor ¸si aplicat¸iilor
stocate pe disc.
9. Configurarea autentific˘arii ˆın sistem.
Dac˘a calculatorul este legat ˆın ret¸ea, din motive de securitate este foarte important
ca accesul la sistem de la distant¸˘a (de pe un alt calculator legat la ret¸ea, folosind
protocoalele TELNET sau SSH – revedet¸i discut¸ia despre “Conectarea la un sistem
UNIX” din prima sect¸iune a acestui capitol, ¸si amintit¸i-v˘a recomandarea de a folosi
SSH ˆın loc de TELNET), a e posibil pe baza unui sistem de autentificare sigur.
ˆ
In acest sens, sunt disponibile mai multe opt¸iuni utile:
activarea/dezactivarea sistemului MD5, ce permite folosirea de parole mai sigure
(cu lungimea de maxim 256 de caractere, ˆın loc de lungimea maxim˘a standard de 8
caractere).
activarea/dezactivarea sistemului shadow (ce permite stocarea sigur˘a a parolelor ˆın
fi¸sierul /etc/shadow, ˆın locul variantei nesigure de astrare ˆın fi¸sierul /etc/passwd).
activarea sistemului de autentificare NIS (Network Information Service) sau a
LDAP (Lightweight Directory Access Protocol) – ambele mecanisme folosesc concep-
tul de interogare prin ret¸ea a unui server ce cont¸ine o baz˘a de date de autentificare
(asem˘anat¸or cu mecanismul Active Directory din lumea Windows).
activarea Kerberos sau a SMB (Samba), alte dou˘a sisteme ce ofer˘a servicii de
autentificare ˆın ret¸ea.
10. Selectarea ¸si instalarea pachetelor.
ˆ
In aceast˘a etap˘a, ˆın funct¸ie ¸si de tipul de instalare selectat la al doilea pas, se pot
selecta pachetele (i.e., aplicat¸iile) ce se doresc a fi instalate, dintre cele disponibile
ˆın distribut¸ia respectiv˘a – acestea de obicei sunt de ordinul sutelor, aranjate ˆın
grupuri de pachete pe baza rolului acestora: de exemplu, aplicat¸ii (editoare, de
calcul ingineresc ¸si ¸stiit¸ific, suite de productivitate office, etc.) programe pentru
development, programe pentru INTERNET (po¸st˘a electronic˘a, navigatoare de web,
etc.), programe pentru diverse servere de servicii (server de web, server Samba,
server DNS, etc.), programe de sistem (pentru administrare, configurare, ¸s.a.), medii
30
grafice, ¸s.a.De asemenea, se ofer˘a informat¸ii despre spat¸iul necesar pentru instalarea
pachetelor selectate.
Dup˘a selectarea pachetelor, programul de instalare verific˘a dependent¸ele dintre pa-
chete (anumite aplicat¸ii se bazeaz˘a pe altele pentru a funct¸iona corect) ¸si rezolv˘a
lipsurile constatate pe baza interact¸iunii cu utilizatorul, iar apoi are loc instalarea
propriu-zis˘a a pachetelor.
Observat¸ie: Mediile grafice ce pot fi selectate folosesc sistemul de ferestre grafice X
Window, ce are o arhitectur˘a de tip client-server bazat˘a pe protocolul X11 (acest
sistem a fost dezvoltat init¸tial la MIT , dup˘a cum am amintit ˆın istoricul evolut¸iei
UNIX-ului, de la ˆınceputul acestui capitol). Ca medii grafice, dou˘a sunt cele mai
aspˆındite: GNOME ¸si KDE , ¸si se poate selecta instalarea amˆındorura, numai a
unuia dintre ele, sau a niciunuia (caz ˆın care nu vom putea exploata sistemul folosind
o interfat¸˘a grafic˘a, ci doar ˆın interfat¸a clasic˘a ˆın mod text). Cˆıteva detalii despre
aceste medii:
Mediul grafic GNOME (GNU Network Object Modal Environment) este dez-
voltat de organizat¸ia cu acela¸si nume (http://www.gnome.org), ¸si este o parte
a proiectului GNU. Este free software ¸si ˆın prezent a ajuns la versiunea 2.6,
disponibil˘a pentru Linux ¸si pentru alte variante de UNIX (Solaris,BSD, ¸s.a.).
Pe lˆıng˘a faptul c˘a este un mediu grafic, GNOME este ¸si o platform˘a de
dezvoltare de aplicat¸ii grafice. Pentru programarea aplicat¸iilor GNOME
se folose¸ste GTK+ (http://www.gtk.org), un toolkit multi-platform˘a pentru
crearea de interfet¸e grafice utilizator (GUI s). GTK+ face parte ¸si el din cadrul
proiectului GNU ¸si este free software, fiind dezvoltat dintr-un proiect mai vechi
de manipulare a imaginilor, GIMP (GNU Image Manipulation Program).
Mediul grafic KDE (K Desktop Environment) este dezvoltat de organizat¸ia cu
acela¸si nume (http://www.kde.org). Este open source ¸si ˆın prezent a ajuns la
versiunea 3.2.3, disponibil˘a pentru Linux ¸si pentru alte variante de UNIX.
Pe lˆıng˘a faptul c˘a este un mediu grafic, KDE este ˆınsot¸it ¸si de o suit˘a de
aplicat¸ii de birou, numit˘a KOffice, precum ¸si de un framework de dezvoltare
de aplicat¸ii grafice. Pentru programarea aplicat¸iilor KDE se folose¸ste Qt, care
este un framework general de dezvoltare de aplicat¸ii C++. Qt este free, fiind
disponibil sub o licent¸˘a stil BSD.
Voi mai ment¸iona faptul c˘a mai exist˘a ¸si IceWM (Ice Window Manager), un man-
ager de ferestre pentru sistemul X11 Window.IceWM are avantajul a necesit˘a
mai put¸ine resurse decˆıt mediile GNOME ¸si KDE , fiind deci util pentru cei cu cal-
culatoare mai put¸in performante.
11. Selectarea timpului ¸si datelor regionale.
Se selecteaz˘a limba, tastatura, data ¸si timpul curent, t¸ara ¸si fusul orar (time zone).
12. Configurarea pl˘acii video.
ˆ
In majoritatea cazurilor programul de instalare poate determina singur tipul pl˘acii
video din sistem. ˆ
In situat¸ia ˆın care avet¸i ˆıns˘a o plac˘a video mai neobi¸snuit˘a, s-ar
putea s˘a fit¸i nevoit s˘a-i indicat¸i programului de instalare care este tipul pl˘acii (prin
31
select¸ia dintr-o list˘a de tipuri cunoscute) ¸si chiar s˘a-i c˘autat¸i un driver potrivit (la
distribuitor sau pe INTERNET), dac˘a distribut¸ia nu cont¸ine driver pentru acel tip
de plac˘a.
13. Configurarea monitorului ¸si personalizarea sistemului X Window.
ˆ
In majoritatea cazurilor programul de instalare poate determina singur tipul moni-
torului, altfel poate fi ajutat de utilizator prin select¸ia dintr-o list˘a de tipuri cunos-
cute.
Pentru interfat¸a grafic˘a, se selecteaz˘a rezolut¸ia ¸si adˆıncimea culorii dorite, mediul
desktop (GNOME sau KDE) dorit, ¸si dac˘a sistemul va porni direct ˆın mod grafic,
sau ˆın mod consol˘a (i.e., interfat¸a text clasic˘a).
Observat¸ii:
(a) ˆ
In cazul pornirii sistemului ˆın mod text, avem la dispozit¸ie 6 terminale la care
putem deschide cˆıte o sesiune de lucru de la consola sistemului (i.e., de la
tastatura ¸si monitorul conectate direct la calculatorul respectiv). Bineˆınt¸eles,
ele nu pot fi folosite ˆın acela¸si timp – la orice moment de timp exist˘a doar un
terminal activ, care gestioneaz˘a input-ul de la tastatur˘a ¸si output-ul pe ecranul
monitorului – dar avem posibilitatea de a comuta ˆıntre ele pentru a le folosi
alternativ, aceast˘a comutare realizˆındu-se prin ap˘asarea combinat¸iilor de taste
ALT+F1,ALT+F2, . . . , ALT+F6 ce selecteaz˘a terminalul corespunz˘ator.
(b) ˆ
In cazul conect˘arii la sistem de la distant¸˘a pentru o sesiune de lucru, input-ul de
la tastatur˘a calculatorului de la distant¸˘a ¸si output-ul pe ecranul monitorului de
la distant¸˘a preluate ˆın cadrul aplicat¸iei ce ruleaz˘a la distant¸˘a (clientul de SSH,
spre exemplu aplicat¸ia putty, despre care am discutat ˆın prima parte a acestui
capitol), sunt gestionate local (pe sistemul Linux) prin a¸sa-numitele pseudo-
terminale, care au un rol asem˘an˘ator cu terminalele folosite pentru conectarea
de la consola sistemului.
(c) Se cuvine ment¸ionat faptul c˘a se poate folosi interfat¸a grafic˘a ¸si ˆın cazul
conect˘arii de la distant¸˘a, cu observat¸ia c˘a necesarul de resurse ¸si traficul prin
ret¸ea este mai mare ˆın acest caz (not˘a: din acest motiv accesul student¸ilor pe
serverul fenrir al acestora, din laboratoare sau de acas˘a, este permis numai ˆın
mod text, nu ¸si ˆın mod grafic).
14. Configurarea celorlalte dispozitive hardware.
Configurarea celorlalte dispozitive periferice existente eventual ˆın calculator, ¸si
anume: placa de sunet, placa de ret¸ea, imprimanta, scanner-ul, tunner-ul TV, ¸s.a.,
decurge asem˘an˘ator ca pentru placa video: ˆın majoritatea cazurilor programul de
instalare poate determina automat tipul dispozitivului respectiv, sau poate fi ajutat
de utilizator (prin select¸ia dintr-o list˘a de tipuri cunoscute). ˆ
In situat¸ia ˆın care avet¸i
ˆıns˘a un dispozitiv periferic mai neobi¸snuit, s-ar putea s˘a fit¸i nevoit a-i c˘autat¸i un
driver potrivit (la distribuitor sau pe INTERNET), dac˘a distribut¸ia nu cont¸ine un
driver pentru acel tip de periferic.
15. Crearea unei dischete de boot.
32
Se ofer˘a posibilitatea de a crea o dischet˘a de boot, ce poate fi folosit˘a pentru a
porni sistemul Linux. Despre aceast˘a dischet˘a am amintit deja ˆın prima faz˘a, cea
a preg˘atirii instal˘arii, ¸si am explicat atunci ¸si de ce este recomandabil s˘a fie creat˘a
aceast˘a dischet˘a.
16. Instalarea de versiuni actualizate ale pachetelor.
Unele distribut¸ii ofer˘a opt¸iunea de conectare automat˘a prin INTERNET la site-ul
oficial al distribut¸iei pentru desc˘arcarea eventualelor versiuni mai recente ale pa-
chetelor de programe deja instalate.
17. Terminarea instal˘arii.
La ˆıncheierea tuturor etapelor descrise mai sus, programul de instalare va cere per-
misiunea pentru repornirea sistemului. Se scoate eventuala dischet˘a de boot din
unitatea floppy, sau CD-ul din unitatea CD-ROM, ¸si se restarteaz˘a sistemul. Ca
urmare, se va ˆınc˘arca boot loader-ul configurat ˆın timpul instal˘arii, care va ˆınc˘arca
sistemul Linux, ¸si poate ˆıncepe exploatarea acestuia.
ˆ
In concluzie, cam ace¸stia ar fi pa¸sii generali ce trebuie urmat¸i ˆın vederea instal˘arii unei
distribut¸ii de Linux. Dup˘a cum se poate observa, sunt foarte asem˘an˘atori cu pa¸sii proce-
durii de instalare a unui sistem Windows. Chiar dac˘a la ˆınceput avet¸i impresia a procedura
de instalare a Linux-ului este foarte anevoioas˘a, cu timpul (executˆınd multe instal˘ari ¸si
reinstal˘ari) vet¸i acumula experient¸˘a ¸si vi se va p˘area tot mai u¸soar˘a, la fel ca ˆın cazul
Windows-ului.
3) Actualizarea sistemului
Dup˘a cum am discutat mai sus la tipul instal˘arii, pe lˆıng˘a opt¸iunea de instalare complet˘a
a sistemului, distribut¸iile de Linux mai ofer˘a ¸si opt¸iunea de upgrade al sistemului, adic˘a
o actualizare a acestuia (i.e., instalarea unei versiuni mai recente a distribut¸iei respective
peste una mai veche deja instalat˘a).
De obicei, se ofer˘a dou˘a moduri de upgrade: actualizarea numai a pachetelor (i.e.,
aplicat¸iilor) instalate, ¸si actualizarea complet˘a, a pachetelor instalate, a kernel-ului Linux,
¸si al ˆınc˘arc˘atorului de boot.
ˆ
In continuare, cˆıteva cuvinte despre:
Instalarea de programe
Pe parcursul exploat˘arii sistemului, se poate ivi nevoia de a utiliza programe noi, ne-
instalate/inexistente ˆın cadrul distribut¸iei, obt¸inute din diverse surse, de obicei de pe
INTERNET.
33
Instalarea unui program se poate face ¸si de c˘atre un utilizator obi¸snuit, ˆın propriul di-
rector home, ¸si ˆın acest caz programul respectiv ˆıi va fi accesibil doar acestuia, dar cel
mai recomandabil este s˘a se fac˘a de c˘atre utilizatorul root, pentru a fi accesibil tuturor
utilizatorilor din sistem (unele programe nu pot fi instalate decˆıt numai de root, deoarece
au nevoie de privilegii extinse pentru a putea fi instalate).
ˆ
In principal, se folosesc dou˘a formate pentru distribut¸ia programelor prin INTERNET:
1. formatul arhiv˘a .tar.gz, i.e. nume program.tar.gz (eventual poate cont¸ine ¸si versi-
unea pe lˆınga numele programului);
2. formatul RPM, i.e. nume program -nr versiune.rpm, care este un format intro-
dus de firma Red Hat, pentru distribuirea programelor executabile ¸si gestiunea
pachetelor din cadrul distribut¸iilor de Linux(pentru detalii a se consulta adresa
http://www.rpm.org).
Indiferent de formatul folosit, programele sunt distribuite ˆımpreun˘a cu manuale de in-
stalare ¸si utilizare, pe care v˘a recomand a le citit¸i ˆınainte de a v˘a apuca de instalarea
propriu-zis˘a. De asemenea, de multe ori sunt ˆınsot¸ite ¸si de arhive ce cont¸in sursele pro-
gramului (ca ˆın cazul programelor open-source).
Se cuvine a mai ment¸ion˘am modul propriu-zis de instalare pentru fiecare din cele dou˘a
formate de distribut¸ie (nota: vom descrie doar pa¸sii generali ce trebuie urm˘ati, pentru
detalii r˘amine ˆın sarcina cititorului s˘a consulte documentat¸ia de instalare specific˘a fiec˘arui
program ˆın parte).
I) Formatul .tar.gz
Mai ˆıntii se configureaz˘a aplicat¸ia respectiv˘a, cu comanda:
UNIX>configure [optiuni]
prin care se pot specifica diverite opt¸iuni de configurare (ˆın lipsa specific˘arii, se vor folosi
valorile implicite pentru acestea). Spre exemplu, cu opt¸iunea --prefix=director-de-instalare se
poate specifica un director ˆın care se va instala aplicat¸ia respectiv˘a. Lista tuturor
opt¸iunilor disponibile pentru configurare se poate obt¸ine cu comanda
UNIX>configure --help
Apoi, cu secvent¸a de comenzi
UNIX>make
UNIX>make install
34
are loc instalarea propriu-zis˘a. Iar dezinstalarea se face foarte simplu, prin ¸stergerea
directorului (inclusiv cu subdirectoarele sale) ˆın care a fost instalat˘a acea aplicat¸ie, lucru
realizat prin comanda
UNIX>rm -r director-de-instalare
(ˆın Linux nu exist˘a echivalentul registry-ului din MS-Windows, ˆın care sunt salvate nu-
meroase informat¸ii despre aplicat¸iile instalate).
II) Formatul RPM
Aplicat¸iile ˆın acest format sunt gestionate cu comanda (programul) rpm. Spre exemplu,
prin apelul
UNIX>rpm -qa
se pot afla informat¸ii despre pachetele instalate.
Instalarea unui program nume program -nr versiune.rpm se face prin apelul
UNIX>rpm -iv nume program -nr versiune.rpm
(opt¸iunea -i ˆınseamn˘a instalare, iar opt¸iunea -v ˆınseamn˘a modul verbose, adic˘a o afi¸sare
detaliat˘a de informat¸ii pe parcursul instal˘arii).
Se poate face ¸si upgrade-ul la o versiune mai recena a unui program deja instalat, prin
apelul
UNIX>rpm -u nume program -nr versiune.rpm
(opt¸iunea -u ˆınseamn˘a upgrade).
Dezinstalarea se face cu opt¸iunea -e urmat˘a doar de numele programului respectiv:
UNIX>rpm -e nume program
Se cuvine ment¸ionat faptul c˘a cele dou˘a moduri de instalare de mai sus se folosesc ˆın
modul text. Exist˘a ˆıns˘a ¸si programe de instalare ˆın mod grafic astfel, spre exemplu,
pentru mediul GNOME avem disponibil˘a aplicat¸ia ˆın mod grafic gnorpm, ce ofer˘a acelea¸si
funct¸ionalit˘at¸i ca ¸si programul ˆın linie de comand˘a rpm descris mai sus.
ˆ
In ˆıncheierea acestei sect¸iuni, voi ment¸iona cˆıteva motive pentru a v˘a convinge s˘a v˘a
instalat¸i acas˘a o distribut¸ie de Linux.
De¸si este suficiena folosirea contului pe care-l avet¸i pe serverul student¸ilor fenrir pentru
lucrul individual implicat de aceast˘a disciplin˘a (pentru a experimenta comenzile ¸si a lucra
cu programele C la care le vom referi pe parcursul acestui manual, ¸si pentru a studia
documentat¸ia man), totu¸si acest cont este un cont de utilizator obi¸snuit, ce are numeroase
35
limit˘ari.
Ca atare, instalˆındu-v˘a acas˘a o distribut¸ie de Linux, a vet¸i bucura de o flexibilitate
mult sporit˘a ˆın folosirea sistemului, vet¸i putea lucra cu puteri depline, nefiind ˆıngr˘adit¸i de
limit˘arile impuse pe serverul student¸ilor.
Una dintre aceste limit˘ari este cea referitoare la folosirea interfet¸ei grafice; acas˘a vet¸i putea
instala ¸si folosi nestingherit¸i interfat¸a grafic˘a, adic˘a un mediu grafic (GNOME sau KDE )
bazat pe sistemul de ferestre X Window.
ˆ
In sfˆır¸sit, un aspect deloc de neglijat este ¸si acela al vitezei leg˘aturii de ret¸ea dintre
calculatorul dumneavoastr˘a de acas˘a ¸si serverul student¸ilor de la Facultatea de Informatic˘a
(mai ales dac˘a suntet¸i conectat la ISP-ul dumneavoastr˘a prin modem ¸si nu prin cablu),
precum ¸si cel al pret¸ului implicat de accesul ˆın ret¸ea; d.p.d.v. acesta, este evident mai
convenabil s˘a v˘a instalat¸i Linux pe calculatorul de acas˘a ¸si s˘a lucrat¸i pe el, decˆıt s˘a lucrat¸i
pe serverul student¸ilor conectat de acas˘a.
1.3 Exercit¸ii
Exercit¸iul 1. Ce ˆınseamn˘a UNIX?
Exercit¸iul 2. Care sunt cele trei caracteristici majore ale UNIX-ului? Descriet¸i-le pe scurt.
Exercit¸iul 3. Din ce este compus un sistem UNIX?
Exercit¸iul 4. Care sunt nivelele ˆın care este structurat un sistem UNIX? Descriet¸i-le pe
scurt.
Exercit¸iul 5. Descriet¸i sistemul de fi¸siere ¸si cel de procese din UNIX.
Exercit¸iul 6. Cum gestioneaz˘a UNIX-ul utilizatorii?
Exercit¸iul 7. Care este serverul student¸ilor ¸si cum v˘a putet¸i conecta la el pentru o sesiune
de lucru? Dar pentru una de transfer de fi¸siere?
Exercit¸iul 8. Ce este Linux-ul?
Exercit¸iul 9. Ce ˆınseamn˘a o distribut¸ie de Linux?
Exercit¸iul 10. Descriet¸i pa¸sii generali ai procesului de instalare a unei distribut¸ii de Linux.
ˆ
Incercat¸i s˘a v˘a instalat¸i pe calculatorul de acas˘a o distribut¸ie de Linux.
36
Capitolul 2
UNIX. Ghid de utilizare
2.1 Comenzi UNIX. Prezentare a principalelor categorii de
comenzi
1. Introducere
2. Comenzi de help
3. Editoare de texte
4. Compilatoare, depanatoare, ¸s.a.
5. Comenzi pentru lucrul cu fi¸siere ¸si directoare
6. Comenzi ce ofer˘a diverse informat¸ii
7. Alte categorii de comenzi
8. Troubleshooting (Cum a procedat¸i dac˘a se “blocheaz˘a” o comand˘a)
2.1.1 Introducere
La fel ca ˆın MS-DOS, ¸si ˆın UNIX exist˘a dou˘a categorii de comenzi (numite ˆın acest caz ¸si
comenzi UNIX, sau comenzi shell):
comenzi interne :
comenzi care se gasesc in fisierul executabil al shell-ului (i.e., interpretorului de
comenzi) respectiv, ca de exemplu: cd,echo,alias,exec,exit, ¸s.a.
37
comenzi externe :
comenzi care se gasesc separat, fiecare intr-un fisier avind acelasi nume cu comanda
respectiva.
Acestea pot fi de doua feluri:
1. fisiere executabile (adica, programe executabile obtinute prin compilare din pro-
grame sursa scrise in C sau alte limbaje), ca de exemplu: ls,chmod,passwd,
bash, ¸s.a.;
2. fisiere de comenzi, numite si script-uri (adica, fisiere text ce contin secvente de
comenzi, analog fisierelor *.bat din MS-DOS), ca de exemplu: .bash profile,
.bashrc, ¸s.a.
Forma generala a unei comenzi UNIX este:
UNIX>nume comanda optiuni argumente ,
unde optiunile si argumentele pot lipsi, dupa caz.
Prin conventie, optiunile sunt precedate de caracterul ’-’ (in MS-DOS este folosit caracterul
’/’). Argumentele sunt cel mai adesea nume de fisiere.
Daca este vorba de o comanda externa, aceasta poate fi specificata si prin calea ei (absoluta
sau relativa).
Separatorul intre numele comenzii si ceilalti parametri ai acesteia, precum si intre fiecare
dintre parametri este caracterul SPACE sau TAB (unul sau mai multe spatii sau tab-uri).
O comanda poate fi scrisa pe mai multe linii, caz in care fiecare linie trebuie terminata cu
caracterul ’\, cu exceptia ultimei linii.
Pentru a putea executa fisierul asociat unei comenzi, utilizatorul care a lansat acea co-
manda trebuie sa aiba drept de executie (i.e., atributul xcorespunzator sa fie setat) pentru
acel fisier (vom reveni in sectiunea urmatoare cu amanunte legate de drepturile de acces
la fisiere).
In sectiunea 2.3 vom discuta mai in amanunt despre interpretoarele de comenzi din UNIX
si modul in care executa acestea comenzile.
In continuarea acestei sectiuni vom trece in revista principalele categorii de comenzi
disponibile.
2.1.2 Comenzi de help
Lista tuturor comenzilor interne disponibile intr-un anumit shell (i.e., interpretor de
comenzi UNIX) se poate obtine executind in acel shell comanda urmatoare:
38
UNIX>help
iar pagina de help pentru o anumita comanda interna se obtine cu comanda:
UNIX>help nume comanda interna
Pentru a obtine help despre comenzile externe sunt disponibile urmatoarele comenzi: man,
whatis,apropos,info.
Comanda man se foloseste cu sintaxa:
UNIX>man [nr sectiune]cuvint
si are ca efect afisarea paginii de manual pentru cuvintul specificat.
Cuvintul specificat reprezinta numele unei comenzi externe sau numele unei functii de
biblioteca C/C++ pentru care se doreste obtinerea paginii de manual.
Pagina afisata cuprinde: sintaxa comenzii/functiei, optiunile si argumentele ei cu de-
scrierea lor, efectul acelei comenzi/functii, exemple, etc.
Argumentul optional nr sectiune se utilizeaza atunci cind exista mai multe comenzi sau
functii de biblioteca cu acelasi nume, pentru a afisa pagina de manual (corespunzatoare
uneia dintre acele comenzi sau functii) din sectiunea de manual specificata.
Exemple:
UNIX>man man
Efect: afiseaza pagina de manual referitoare la comanda man.
UNIX>man write
Efect: afiseaza pagina de manual referitoare la comanda write, care scrie un mesaj
pe terminalul utilizatorului specificat.
UNIX>man 2 write
Efect: afiseaza pagina de manual referitoare la functia de biblioteca write, care este
apelata in programe C pentru a executa o scriere in fisierul specificat prin intermediul
descriptorului de fisier deschis.
Comanda whatis se foloseste cu sintaxa:
UNIX>whatis cuvint
si are ca efect cautarea cuvintului specificat in baza de date whatis ce contine scurte
descrieri despre comenzile UNIX si functiile de biblioteca C, si afisarea rezultatului cautarii
(numai potrivirile exacte ale cuvintului cautat sunt afisate).
Exemple:
39
UNIX>man whatis
Efect: afiseaza pagina de manual referitoare la comanda whatis.
UNIX>whatis write
Efect: afiseaza urmatoarele informatii despre cele doua pagini de manual referitoare
la write:
write (1) - send a message to another user
write (2) - write to a file descriptor
Comanda apropos se foloseste cu sintaxa:
UNIX>apropos cuvint
si are ca efect cautarea cuvintului specificat in baza de date whatis ce contine scurte
descrieri despre comenzile UNIX si functiile de biblioteca C, si afisarea rezultatului cautarii
(sunt afisate toate potrivirile, nu doar cele exacte, ale cuvintului cautat).
Exemple:
UNIX>man apropos
Efect: afiseaza pagina de manual referitoare la comanda apropos.
UNIX>apropos write
Efect: afiseaza o lista (destul de mare) cu toate paginile de manual ce contin cuvintul
write, e ca nume de comanda sau functie de biblioteca, e deoarece apare in
descrierea vreunei comenzi sau functii.
Comanda info se foloseste cu sintaxa:
UNIX>info [optiuni] [cuvint ...]
si are ca efect afisarea documentatiei in format Info pentru cuvintul sau cuvintele spec-
ificate.
Documentatia in format Info este o alternativa la paginile de manual furnizate de comanda
man. Formatul Info este o ierarhie arborescenta de documente, ce contin hiper-legaturi
(i.e.,cross-referinte) intre ele, asemanator cu documentele HTML.
Exemple:
UNIX>man info
Efect: afiseaza pagina de manual referitoare la comanda info.
40
UNIX>info
Efect: afiseaza radacina documentatiei in format Info, din care se poate naviga in
toate documentele.
UNIX>info write
Efect: afiseaza documentatia despre comanda write.
Atent¸ie: o recomandare importana, pe care va sfatuiesc s-o urmati:
Folositi help-ul furnizat de comanda man pentru a afla in mod amanuntit pentru ce se
folosesc si cum se folosesc toate comenzile pe care le veti intilni pe parcursul acestui
manual si la laboratoarele de UNIX.
De asemenea, exersati diverse exemple pentru fiecare comanda pentru a intelege bine
ce face si cum functioneaza acea comanda.
2.1.3 Editoare de texte
Exista mai multe editoare de fisiere text sub UNIX, printre care: jo e,pico,vi,vim,ed,
mcedit,emacs,tex/latex, ¸s.a.
Editorul joe :
Apel - se apeleaza cu comanda:
UNIX>jo e [fisier]
Comenzi ale editorului joe (sunt asemanatoare cu cele din editorul Borland Turbo
Pascal):
Ctrl+K,H = help cu comenzile lui
Ctrl+K,B = inceput selectie bloc de text
Ctrl+K,K = sfirsit selectie bloc de text
Ctrl+K,C = copie blocul de text anterior selectat
Ctrl+K,M = muta blocul de text anterior selectat
Ctrl+K,Y = sterge blocul de text anterior selectat
Ctrl+K,W = scrie, in fisierul specificat, blocul de text anterior selectat
Ctrl+K,R = adauga, pe pozitia cursorului, continutul fisierului specificat
Ctrl+C = iesire fara a salva modificarile facute in fisierul editat
Ctrl+K,X = iesire cu salvarea modificarilor facute in fisierul editat
41
... ¸s.a.
Editorul pico :
Apel - se apeleaza cu comanda:
UNIX>pico [fisier]
Comenzi ale editorului pico: consultati pagina de manual despre el (cu comanda
man pico).
Editorul mcedit (este editorul intern din file-manager-ul mc) :
Apel - se apeleaza cu comanda:
UNIX>mcedit [fisier]
Comenzi ale editorului mcedit: consultati pagina de manual despre el (cu comanda
man mcedit).
Editorul emacs : este un editor puternic, ce face parte din proiectul GNU, cu facilitati
de mediu integrat de programare (poate apela compilatorul GNU C si depanatorul GNU).
Editorul tex/latex : este un editor (de fapt, un mediu de lucru) ce permite
tehnoredactarea de documente stiintifice in limbajul T
E
X/L
A
T
EX.
2.1.4 Compilatoare, depanatoare, ¸s.a.
Sub UNIX exista compilatoare si interpretoare pentru majoritatea limbajelor de programare
existente, mai noi sau mai vechi: C, C++, Pascal, Fortran, Java, si multe altele. Dintre
acestea, istoria evolutiei C-ului s-a impletit strins cu cea a UNIX-ului, dupa cum am discutat
in primul capitol.
Compilatorul GNU C pentru limbajul C poate fi apelat cu comanda:
UNIX>gcc sursa.c [-o executabil]
care realizeaza compilarea (inclusiv link -editarea codului obiect) fisierului sursa specificat
(atent¸ie: este obligatorie extensia .c) in fisierul executabil cu numele specificat prin op-
tiunea -o (in lipsa ei, executabilul se numeste a.out). Pentru programe C++, compilarea
se face cu comanda:
42
UNIX>g++ sursa.cpp [-o executabil]
Ca mediu integrat de dezvoltare se poate folosi editorul emacs, de care am amintit mai
devreme, iar pentru depanarea programelor se poate utiliza depanatorul GNU DeBugger,
ce se apeleaza cu comanda gdb.
Vom reveni cu mai multe amanunte cind vom trata programarea concurenta in limbajul
C pentru UNIX, in partea II a acestui manual.
2.1.5 Comenzi pentru lucrul cu fi¸siere ¸si directoare
Sunt numeroase comenzile UNIX care lucreaz˘a cu fi¸siere ¸si directoare. Pe acestea le vom
prezenta ˆın sect¸iunea urm˘atoare, dedicat˘a sistemului de ¸siere UNIX, dup˘a ce vom trata
generalit˘at¸ile legate de acesta.
2.1.6 Comenzi ce ofer˘a diverse informat¸ii
1. Comenzi ce ofera informatii despre utilizatori:
informatii despre utilizatorii conectati la sistem, in diverse formate:
users = afiseaza numele utilizatorilor conectati la sistem;
who = afiseaza utilizatorii conectati la sistem si numele terminalelor;
rwho = afiseaza utilizatorii si terminalele conectate la sistem de la distanta;
w= afiseaza utilizatorii conectati la sistem, numele terminalelor, procesul
curent executat in foreground pentru fiecare terminal, ¸s.a.;
whoami = afiseaza numele utilizatorului curent;
who am i = afiseaza numele calculatorului, numele utilizatorului curent, nu-
mele statiei, data si ora logarii, ¸s.a.
informatii personale despre un utilizator (nume real, adresa, last login, ¸s.a.):
finger
informatii de identificare despre un utilizator (UID-ul, GID-ul, alte grupuri de care
apartine, ¸s.a.):
id
2. Comenzi ce ofera informatii despre terminale:
43
pentru aflarea terminalului la care sunteti conectat:
tty
Observat¸ie: fiecare sesiune de lucru (i.e., conexiune la sistemul UNIX respectiv)
are asociat un terminal de control, care este responsabil cu preluarea datelor
de intrare generate de tastatur˘a ¸si cu afi¸sarea datelor de ie¸sire pe ecranul mon-
itorului.
pentru aflarea/schimbarea diferitelor caracteristici (ca de exemplu: viteza de
transfer, secventele escape, tastele de intrerupere, ¸s.a.) ale terminalului aso-
ciat sesiunii de lucru:
stty
3. Comenzi ce ofera informatii despre data, timp, ¸s.a.:
informatii despre data, timp, etc., in diverse formate:
date = afiseaza data si ora curente;
cal = afiseaza calendarul lunii curente, sau a anului specificat.
informatii despre momentul cind a fost ultima oara pornit (boot-at) un calculator:
uptime = timpul ultimei porniri a calculatorului local;
ruptime = timpul ultimei porniri a calculatoarelor din retea.
4. Comenzi ce ofera informatii despre procesele din sistem (in diverse formate):
comanda process status:
ps = afiseaza informatii despre procesele existente in sistem (PID-ul, starea
procesului, proprietarul procesului, cita memorie ocupa, cit timp procesor con-
suma, ¸s.a.);
Dintre optiunile mai importante amintim:
-a : informatiile se refer˘a la procesele tuturor utilizatorilor;
-g : informatiile se refer˘a la toate procese unui grup;
-l : format lung (mai multe cˆımpuri la afi¸sare);
-tx : informat¸iile se refer˘a la procesele terminalului specificat.
Cˆımpurile afi¸sate se refer˘a la:
PID : identificatorul procesului;
TT : terminalul de control al procesului;
TIME : durata de executie a procesului;
STAT : starea procesului;
CMD : linia de comanda prin care a fost lansat acel proces;
¸s.a. (in functie de optiunile specificate).
Exemplu. Iata citeva exemple de optiuni ale comenzii process status:
UNIX>ps
Efect: afiseaza informatiile doar despre procesele utilizatorului curent, ce sunt
rulate in foreground.
44
UNIX>ps -ux
Efect: afiseaza informatiile despre toate procesele utilizatorului curent, inclusiv
cele ce sunt rulate in background sau fara terminal de control.
UNIX>ps -aux
Efect: afiseaza informatiile despre toate procesele din sistem, ale tuturor uti-
lizatorilor.
comanda pentru afisarea job-urilor aflate in lucru:
jobs
comenzi pentru planificarea lansarii unor job-uri la anumite ore:
at = planifica lansarea unei comenzi la o anumita ora (rezultatele se
primesc in mail);
atq = afiseaza lista comenzilor planificate (aflate in coada de asteptare);
atrm = stergerea din coada de asteptare a unor comenzi planificate anterior.
alte comenzi referitoare la procese:
times = afiseaza timpii de executie ai proceselor din sistem;
nice = permite stabilirea prioritatii de executie a unui proces;
nohup =no hang-up: permite continuarea executiei proceselor ce folosesc
intrarea/iesirea standard, si dupa incheierea sesiunii de lucru (prin deconectare
de la sistem cu logout).
2.1.7 Alte categorii de comenzi
1. Comenzi pentru conectarea la/deconectarea de la un calculator UNIX:
pentru login (i.e., operatia de conectare):
login (login numai de la terminale locale);
rlogin (login de pe alte calculatoare, cu acelasi tip de UNIX);
telnet (login de pe alte calculatoare, conexiunea fiind prin protocolul necrip-
tat TELNET);
ssh (login de pe alte calculatoare, conexiunea fiind prin protocolul criptat
SSH).
pentru logout (i.e., operatia de deconectare):
logout;
exit (posibil numai din shell-ul de login).
2. Comenzi pentru schimbarea datelor unui cont de utilizator:
pentru schimbarea parolei:
passwd
pentru schimbarea shell-ului implicit (i.e.,shell-ul de login):
chsh
45
pentru schimbarea “vˆırstei” contului (i.e., a perioadei dup˘a care expir˘a parola):
chage
3. Comenzi pentru scrierea de mesaje:
pentru afisarea unui mesaj pe ecran (la fel ca in MS-DOS):
echo
pentru trimiterea unui mesaj altui utilizator conectat la sistem:
write
pentru activarea/dezactivarea primirii mesajelor trimise cu comanda write de alti
utilizatori:
mesg [y|n]
pentru stabilirea unei ferestre de comunicare de mesaje intre diferiti utilizatori
conectati la sistem:
talk (pentru comunicare intre doi utilizatori)
ytalk (pentru comunicare intre mai multi utilizatori)
Exemplu. Iata citeva exemple de folosire a acestor comenzi:
UNIX>echo mesaj
Efect: afiseaza pe ecran mesajul specificat.
UNIX>write user [terminal] ENTER
Prima linie a mesajului ENTER
A doua linie a mesajului ENTER
.....................................
Ultima linie a mesajului ENTER
^D (tastele CTRL + D, ce seminifica caracterul EOF)
Efect: afiseaza mesajul specificat pe ecranul utilizatorului specificat, eventual
in sesiunea de lucru specificata prin terminal (lucru util daca acel utilizator are
deschise mai multe sesiuni de lucru in momentul respectiv).
UNIX>mesg n
Efect: dezactiveaza afisarea pe ecran a mesajelor trimise ulterior cu comanda
write de catre alti utilizatori (dezactivarea este utila atunci cind afisarea acelor
mesaje deranjeaza utilizatorul respectiv).
4. Comenzi pentru arhivare/comprimare/codificare de fisiere:
pentru arhivarea fisierelor:
tar
pentru comprimarea/decomprimarea unui fisier:
gzip / gunzip
compress / uncompress
pentru codificarea/decodificarea unui fisier:
encode / decode
46
Exemplu. Iata citeva exemple de utilizare a comenzilor de arhivare:
UNIX>tar cf arhiva.tar /work/proiect
Efect: arhiveaza in arhiva arhiva.tar continutul (recursiv al) directorului
/work/proiect.
UNIX>tar xf arhiva.tar
Efect: dezarhiveaza arhiva arhiva.tar.
UNIX>gzip arhiva.tar
Efect: comprima fisierul arhiva.tar, producind fisierul arhiva.tar.gz.
UNIX>gunzip arhiva.tar.gz
Efect: decomprima fisierul arhiva.tar.gz, producind fisierul arhiva.tar.
UNIX>tar zcf arhiva.tar.gz /work/proiect
Efect: arhiveaza si comprima cu gzip in arhiva arhiva.tar.gz continutul (re-
cursiv al) directorului /work/proiect.
UNIX>tar zxf arhiva.tar.gz
Efect: dezarhiveaza si decomprima arhiva arhiva.tar.gz.
5. Comenzi (programe) pentru diferite protocoale INTERNET:
pentru posta electronica:
mail (utilitar in linie de comanda)
pine (utilitar in mod text)
pentru transferul de fisiere prin retea folosind protocolul FTP:
ftp = un program client de FTP, in linie de comanda
Observatie:FTP-ul este un protocol pentru transferul de fisiere intre doua
calculatoare legate in retea, ca de exemplu oricare doua calculatoare legate la
INTERNET.
scp = un program client de SCP (= Secure Copy Protocol, este un FTP
criptat), in linie de comanda. Sub MS-Windows este disponibila si o varianta
grafica – programul WinSCP.
Observatie: este recomandabil de folosit SCP-ul in locul clientilor clasici de
FTP, deoarece FTP-ul nu este un protocol criptat.
pentru transferul de pagini web din WWW folosind protocolul HTTP:
lynx = un browser WWW (i.e., un program client de WWW), in mod text
netscape = un browser WWW, in mod grafic (necesita X Windows)
Observatie:WWW-ul (abreviere ce provine de la World Wide Web) este un
protocol pentru gestionarea informatiilor in INTERNET, avind la baza pro-
tocolul HTTP si limbajul HTML (= Hyper-Text Markup Language).
pentru regasirea informatiilor personale, folosind protocolul FINGER:
finger = un program client de FINGER, in linie de comanda
Observatie:FINGER este un protocol pentru regasirea informatiilor personale
(nume real, adresa, last login,last mail read, ¸s.a.) despre un utilizator de pe
un calculator din reteaua INTERNET.
47
pentru rezolvarea numelor simbolice in adrese IP sau invers, folosind serviciul
DNS:
nslookup = un program in linie de comanda
Observatie: Fiecare calculator conectat la INTERNET are asociata o adresa
IP unica, care este un sir de 4 numere (4 octeti). Deoarece astfel de adrese
sunt greu de retinut, s-au asociat si nume simbolice. Transformarea adresei
IP a unui calculator in numele simbolic asociat acelui calculator se face prin
protocolul ARP, iar transformarea inversa se face prin protocolul RARP, in
ambele situatii utilizindu-se serviciul DNS (abreviere ce provine de la Domain
Name Service).
Exemplu. Iata citeva exemple de folosire a acestor comenzi:
UNIX>finger so
Efect: afiseaza informatiile personale despre contul utilizator so de pe calcula-
torul local, cel pe care lucrati, adica fenrir.
UNIX>finger vidrascu@thor.infoiasi.ro
Efect: afiseaza informatiile personale despre contul utilizator vidrascu de pe
calculatorul thor.
UNIX>nslookup fenrir.infoiasi.ro
193.231.30.197
Efect: afiseaza adresa IP a calculatorului fenrir.
UNIX>nslookup 193.231.30.197
fenrir.info.uaic.ro
fenrir.infoiasi.ro
Efect: afiseaza numele simbolice asociate calculatorului cu adresa IP 193.231.30.197.
6. Comenzi pentru cautarea de pattern-uri (sabloane) in fisiere:
pentru cautarea unui pattern (i.e., o expresie regulata) intr-un fisier sau grup de
fisiere si afisarea tuturor liniilor de text ce contin acel pattern:
grep
Exemplu. Comanda
UNIX>grep vidrascu /etc/passwd
are ca efect afisarea liniei, din fisierul /etc/passwd, care contine informatiile
despre contul vidrascu.
comenzi (mai exact, limbaje interpretate) pentru procesarea de pattern-uri si con-
structia de comenzi UNIX:
awk
sed
perl
comanda filtru de selectare a anumitor portiuni din fiecare linie de text a fisierului
specificat:
cut
48
comanda filtru de selectare a liniilor de text unice din fisierul specificat (mai exact,
efectul ei consta in pastrarea unui singur exemplar de linie de text din fiecare
grup de linii consecutive ce sunt identice ca si continut):
uniq
7. Alte comenzi:
comanda interna prin care se definesc/sterg alias-uri (i.e., pseudo-comenzi), prin
redenumirea vechilor comenzi sau inlantuirea mai multor comenzi:
alias / unalias
Exemplu. Efectul comenzilor urmatoare
UNIX>alias ll=’ls -Al
UNIX>ll tmpdir
consta in: prima comanda defineste alias-ul cu numele ll, iar a doua executa
alias-ul cu numele ll si cu parametrii specificati, adica, in acest caz, executa
comanda: ls -Al tmpdir.
comanda ce copie intrarea standard (stdin) in iesirea standard (stdout) si in
fisierul specificat ca parametru:
tee
Exemplu. Efectul comenzii
UNIX>my program | tee results.txt
consta in: mesajele scrise pe stdout in timpul executiei programului speci-
ficat sunt tiparite si in fisierul results.txt, putind fi deci consultate dupa
terminarea executiei programului.
(Aceasta facilitate este utila pentru depanarea programelor.)
comanda pentru executia iterativa a unei comenzi specificate pentru un set de
diferite linii de apel (i.e., parametri de apel pentru acea comanda):
xargs
comanda pentru sortarea liniilor de text dintr-un fi¸sier:
sort
comanda pentru operat¸ia de join pe un cˆımp comun a liniilor din dou˘a fi¸siere (este
similar˘a operat¸iei de join a dou˘a tabele relat¸ionale pe care o cunoa¸stet¸i de la
disciplina Baze de date):
join
Observat¸ie: mai sunt multe comenzi, ce nu au fost amintite mai sus; o parte dintre ele le
veti intilni pe parcursul sectiunilor urmatoare.
49
2.1.8 Troubleshooting (Cum s˘a procedat¸i dac˘a se “blocheaz˘a” o comand˘a)
Sa presupunem ca in timpul executiei unei comenzi (un program de sistem sau un program
scris de dumneavoastra), aveti impresia ca acesta s-a blocat (nu mai apare nici un mesaj
pe ecran, desi ar fi trebuit, etc.).
Intr-o asemenea situatie nu trebuie sa intrati in panica, ci, pentru a opri acest program,
deci pentru a obtine din nou controlul asupra prompterului shell-ului, urmati urmatorii
pasi in ordinea in care sunt prezentati, oprindu-va la pasul la care ati reusit sa deblocati
programul (adica sa apara prompterul):
1. Mai intii, asteptati un timp rezonabil, poate totusi programul nu este blocat, ci doar
ocupat cu calcule laborioase.
Daca totusi nu apare prompterul, atunci:
2. Apasati (simultan) tastele CTRL +C. Aceasta determina trimiterea semnalului de
intrerupere SIGINT programului respectiv.
Daca totusi nu apare prompterul, atunci:
3. Apasati (simultan) tastele CTRL +\. Aceasta determina trimiterea semnalului de
terminare SIGQUIT programului respectiv.
Daca totusi nu apare prompterul, atunci:
4. Apasati (simultan) tastele CTRL +Z. Aceasta determina suspendarea programu-
lui respectiv (i.e., acel program este trecut in starea SUSPENDED) si afisarea
prompterului.
Mai departe, pentru a opri acel program (el este doar suspendat, nu si terminat),
procedati in felul urmator. Cu comanda
UNIX>ps
aflati PID-ul acelui program, iar apoi dati comanda
UNIX>kill -9 pid
unde pid este PID-ul aflat anterior. Ca urmare a acestei comenzi procesul in cauza
(i.e., programul blocat) este omorit (i.e., terminat fortat).
In acest moment sigur ati scapat de acel program ce se blocase si aveti din nou controlul
asupra prompterului shell-ului.
50
2.2 Sisteme de fi¸siere UNIX
1. Introducere
2. Structura arborescena a sistemului de fi¸siere
3. Montarea volumelor ˆın structura arborescena
4. Protect¸ia fi¸sierelor prin drepturi de acces
5. Comenzi de baz˘a ˆın lucrul cu ¸siere ¸si directoare
2.2.1 Introducere
In sistemul de operare UNIX, datele si programele sunt pastrate in fisiere identificate prin
nume. Numele fisierelor pot avea pina la 255 caractere si pot contine oricite caractere
’.’ (nu sunt impartite sub forma 8.3, nume.extensie, ca in sistemul de fisiere FAT din
MS-DOS sau MS-Windows), singurele restrictii fiind nefolosirea caracterelor neprintabile,
sau a caracterelor NULL, ’/’, si a spatiilor albe TAB si SPACE.
Important de retinut: spre deosebire de MS-Windows (adica de sistemele de fisiere FAT
si FAT32), numele fisierelor in UNIX sunt case-sensitive, adica se face distinctie intre
majuscule si minuscule.
UNIX nu impune nici o conventie privitoare la numirea fisierelor, dar exista sufixe utilizate
in mod standard, cum ar fi spre exemplu:
.c si .h pentru fisiere sursa in limbajul C;
.cpp si .h pentru fisiere sursa in limbajul C++;
.tar pentru arhive tar;
.gz pentru arhive gzip;
.tar.gz sau .tgz pentru arhive tar gzip;
¸s.a.
Programele executabile si script-urile nu au in general extensie.
Sistemul de fisiere se pastreaza pe suporturi magnetice: HDD (hard-disk), FDD (floppy-
disk), benzi magnetice, ¸s.a., sau pe suporturi optice: CD-uri (in orice format: CD-ROM,
CD-R, CD-RW), DVD-uri, discuri magneto-optice, ¸s.a., sau poate emulat ˆın memoria
intern˘a (RAM-disk), etc. Sistemul de fisiere poate fi p˘astrat doar local (i.e., pe hard-disk-
ul propriu) sau ¸si distribuit (de exemplu prin NFS). Orice hard-disk poate contine mai
multe partitii, dintre care unele pot partitii de UNIX, eventual coabitˆınd cu partitii de
MS-DOS,Novell,Windows, sau alte sisteme.
Fisierele in UNIX pot fi de urmatoarele tipuri:
51
normale (ordinare);
directoare (cataloage);
link-uri (legaturi simbolice): sunt un fel de alias-uri pentru alte fisiere;
fisiere speciale, in mod bloc sau in mod caracter: sunt drivere de periferice, ¸s.a.;
fisiere de tip fifo: sunt folosite pentru comunicatia intre procese, rulate pe acelasi
sistem;
fisiere de tip socket: sunt folosite pentru comunicatia prin retea intre procese, rulate
pe sisteme diferite.
Tipul fisierelor dintr-un director se poate afla cu comanda ls -al, care afiseaza toate
fisierele din directorul specificat, cu diverse informatii despre ele (tipul, drepturile de acces,
proprietarul, grupul proprietar, lungimea, data ultimei modificari, etc.). Tipul fisierelor
este indicat de primul caracter din prima coloana a listingului afisat de comanda ls -al,
acest caracter putind fi:
’-’ pentru fisier normal;
’d’ pentru director;
’l’ pentru legatura simbolica;
’b’ sau ’c’ pentru fisier special in mod bloc sau, respectiv, in mod caracter;
’p’ pentru fisier de tip fifo;
’s’ pentru fisier de tip socket.
Exemplu. Comanda
UNIX>ls -al so
are ca efect: se afiseaza listingul fisierelor din directorul so, rezultatul aratind cam in
felul urmator:
total 70
drwx------ 6 so users 1024 May 10 11:19 ./
drwxr-xr-x 56 root root 1024 May 7 10:25 ../
-rw-r--r-- 1 so users 579 Apr 29 11:28 .addressbook
-rw-r--r-- 1 so users 1821 Apr 29 11:30 .addressbook.lu
-rw-r--r-- 1 so users 3597 May 10 11:19 .bash history
drwxr-xr-x 2 so users 1024 Apr 29 11:42 html/
drwx------ 2 so users 1024 Apr 29 11:42 mail/
-rw-r--r-- 1 so users 32640 Apr 29 11:27 fis.txt
-rw-rw-r-- 1 so users 13594 Apr 29 11:14 fis.dat
52
lrwxrwxrwx 1 so users 579 Apr 29 11:28 labs ->/home/so/html/labs -rw-r--r--
1 so users 65 Apr 29 11:13 prg1.c
prw------- 1 so users 0 Dec 12 11:13 fifo1
2.2.2 Structura arborescena a sistemului de fi¸siere
Sistemul de fisiere ˆın UNIX este ierarhizat (arborescent), adic˘a este ca un arbore, la fel
ca in MS-DOS sau MS-Windows: directoare ce contin subdirectoare si fisiere propriu-zise.
Dar cu deosebirea ca ˆın UNIX avem un arbore ce are o singura radacina, referita prin “/
(nu avem mai multe unitati de discuri logice C:,D:, . . . ), iar ca separator pentru caile
de subdirectoare se utilizeaza caracterul ’/’, in locul caracterului \’ folosit in MS-DOS sau
MS-Windows.
Fisierele pot fi accesate (specificate) fie relativ la radacina “/” sistemului de fisiere (i.e.,
specificare prin cale absoluta), fie relativ la directorul curent de lucru (i.e., specificare
prin cale relativa la directorul curent).
La fel ca in MS-DOS si MS-Windows, in fiecare director exista doua nume predefinite: .”,
care reprezinta directorul curent, si “..”, care reprezinta directorul parinte al directorului
curent.
Dupa cum s-a mentionat, in UNIX fisierele sunt organizate intr-o structura ierarhica ar-
borescenta. O astfel de structura permite o organizare eficienta si o grupare logica a
fisierelor. O structura tipica de sistem de fisiere in UNIX este prezentata mai jos:
/bin : cd, cat, ls, ... (fisiere executabile - comenzi de sistem)
/dev : lp, hdd, fdd, ... (fisiere speciale asociate perifericelor)
root: /etc : mount, passwd, ... (fisiere de configurare si de initializare)
/home: studs, staff, ... (directoarele home ale utilizatorilor)
/usr : bin, lib, ... (aplicatii)
/lib : ... (biblioteci dinamice)
/tmp : ... (director temporar)
... si altele
Navigarea in structura arborescenta se poate face cu comanda pentru schimbarea direc-
torului curent, care este comanda interna cd (la fel ca in MS-DOS), iar aflarea directorului
curent se poate face cu comanda pwd.
Exemplu.
UNIX>pwd
/home/vidrascu
UNIX>cd /home/vidrascu/mail
UNIX>pwd
/home/vidrascu/mail
53
Fiecare utilizator are un director special numit director propriu (sau director home), in
care se face intrarea dupa operatia de login. Adica imediat dupa login directorul curent de
lucru va fi acest director home. De obicei, numele complet (i.e., calea absoluta) a acestui
director este /home/username , unde username este numele de cont UNIX al utilizatorului
respectiv.
In afara de referirea la fisiere prin cale relativa la directorul curent sau prin cale ab-
soluta, mai exista si referirea prin cale relativa la directorul home. Spre exem-
plu: username /dir1/dir2/.../file , ceea ce este echivalent ˆın cazul de fat¸˘a cu:
/home/username /dir1/dir2/.../file.
Atunci cind utilizatorul se refera la propriul director home, in locul formei lungi de
referire username /.../file poate folosi forma prescurtata /.../file. De exemplu,
comanda:
UNIX>cd /mail
va schimba directorul curent in subdirectorul mail al directorului home al utilizatorului
care tasteaza aceasta comanda.
Revenirea in directorul home propriu din directorul curent se poate face simplu introducind
comanda cd fara argumente.
2.2.3 Montarea volumelor ˆın structura arborescena
Sistemele de fisiere UNIX se pot afla pe mai multe dispozitive fizice sau in mai multe partitii
ale aceluiasi disc fizic. Fiecare dintre ele are un director root /” si poate fi navigat prin
incarcarea sistemului de operare de pe dispozitivul respectiv.
Daca totusi sistemul de fisiere de pe un dispozitiv trebuie folosit fara incarcarea sistemului
de operare de pe acel dispozitiv, exista solutia de a monta structura arborescenta de
fisiere de pe acel dispozitiv in structura dispozitivului de pe care s-a incarcat sistemul de
operare. Astfel, spre exemplu, fisierele de pe o dischet˘a sau de pe un CD-ROM trebuie
montate pentru a putea fi accesibile.
Spre deosebire de sistemele MS-DOS sau MS-Windows, unde fisierele de pe diverse dispozitive
se puteau accesa prefixate de numele discurilor logice (A:,C:,D:, etc.), in UNIX ele pot
fi folosite prin montarea ˆın prealabil a structurilor respective in structura arborescenta a
sistemului de pe care s-a incarcat sistemul de operare, sistem al carui radacina este /”-ul
(echivalentul lui C:\din MS-DOS sau MS-Windows). Mai exact, montarea se face intr-un
anumit subdirector al sistemului de fisiere “/”, ca si cum acel subdirector ar fi identificat
cu radacina structurii ce se monteaza.
54
Montarea se face cu comanda mount (ce poate fi executata doar de utilizatorul root), iar
apoi accesarea se face cu ajutorul directorului in care s-a facut montarea.
Observatie: prin operatia de montare se inhiba accesul la eventualele fisiere ce ar exista
in directorul care este punctul de montare, deci acest director trebuie sa fie de preferinta
gol.
Exemplu. Comanda urmatoare monteaza discheta din prima unitate de dischet˘a (echiva-
lentul discului logic A: din MS-DOS):
UNIX>mount /dev/fd0 /mnt/floppy
iar ˆın urma execut¸iei sale fisierele de pe discheta vor fi “vizibile” in subdirectorul
/mnt/floppy. Ca urmare, putem, de exemplu, naviga prin subdirectoarele de pe discheta:
UNIX>cd /mnt/floppy/progs
Efect: noul director curent va subdirectorul progs de pe discheta.
Prin optiunea -r a comenzii mount se poate proteja la scriere noua structura atasata (adica
aceasta va fi montata in modul read-only). Optiunea -t este folosita pentru a specifica
tipul de sistem de fisiere ce se monteaza.
Operatia inversa montarii, numita demontare, se face cu comanda umount (ce poate fi
executata, de asemenea, doar de utilizatorul root).
Exemplu. Comanda
UNIX>umount /mnt/floppy
are ca efect: se demonteaza discheta montata anterior in punctul /mnt/floppy. In con-
tinuare, vor fi din nou accesibile fisierele ce existau eventual in acest director (daca nu era
gol inainte de montare).
2.2.4 Protect¸ia fi¸sierelor prin drepturi de acces
Dupa cum am discutat in primul capitol, sistemul UNIX organizeaza utilizatorii in grupuri
de utilizatori.
Fiecare grup are asociat un nume (exemplu: studs,profs,admins, etc.) si un numar
unic de identificare a grupului, numit GID.
Fiecare utilizator face parte dintr-un anumit grup si are asociat un nume (i.e., username
= numele contului respectiv), precum si doua numere:
UID, identificatorul utilizatorului, care este un numar unic de identificare a utilizatorului;
55
GID, identificatorul grupului din care face parte utilizatorul.
Aceste ID-uri ii sunt asociate atunci cind se creeaza contul acelui utilizator.
Fiecare fisier are asociat ca proprietar, un anumit utilizator (care, de obicei, este utiliza-
torul ce a creat acel fisier, dar proprietarul poate fi schimbat). Grupul acelui utilizator
este grupul proprietar al fisierului.
Utilizatorii sunt clasificati in trei categorii in functie de relatia fata de un fisier:
proprietarul fisierului (owner);
colegii de grup ai proprietarului (group);
ceilalti utilizatori (others).
Fiecare fisier are asociate trei tipuri de drepturi de acces pentru fiecare dintre cele trei
categorii de utilizatori de mai sus:
r(read) : drept de citire a fisierului;
w(write) : drept de scriere a fisierului;
x(execute) : drept de executie a fisierului.
In cazul directoarelor, drepturile au o semnificatie putin modificata:
r(read) : drept de citire a continutului directorului (i.e., drept de aflare a numelor
fisierelor din director);
w(write) : drept de scriere a continutului directorului (i.e., drept de adaugare/stergere
de fisiere din director);
x(execute) : drept de inspectare a continutului directorului (i.e., drept de acces la
fisierele din director).
Pe linga cele 3 x 3 = 9 drepturi pentru fisiere (si directoare) amintite mai sus, mai exista
inca trei drepturi suplimentare, ce au sens doar pentru fisierele executabile, si anume:
s(setuid bit) : pe durata de executie a fisierului, proprietarul efectiv al procesului va
proprietarul fisierului, si nu utilizatorul care il executa;
s(setgid bit) : la fel, pentru grupul proprietar efectiv;
t(sticky bit) : imaginea text (i.e., codul programului) a acelui fisier executabil este
salvata pe partitia de swap pentru a fi incarcata mai repede la executia acelui program.
Pentru modificarea drepturilor de acces, respectiv a proprietarului si grupului proprietar
ale unui fisier sunt disponibile urmatoarele comenzi: chmod,chown,chgrp. Ele pot fi
folosite numai de catre superuser (i.e., utilizatorul root) sau de proprietarul fisierului.
Comanda chmod este folosita pentru modificarea drepturilor de acces. Pentru specificarea
argumentelor ei, se poate folosi fie o notatie simbolica, fie o reprezentare in octal.
In notatia simbolica, utilizatorii sunt identificati prin:
u(user) = proprietarul;
g(group) = grupul proprietarului, exceptind proprietarul insusi;
o(others) = altii (restul utilizatorilor);
a(all) = toti utilizatorii,
56
prin +sau se specifica adaugarea, respectiv eliminarea de drepturi, iar drepturile r,w,x
sunt drepturile mentionate mai sus.
De exemplu, comanda:
UNIX>chmod g-rw prg1.c
are ca efect: se elimina drepturile de citire si scriere a fisierului prg1.c din directorul
curent pentru grupul proprietar al fisierului (deci pentru colegii de grup ai proprietarului).
Alt exemplu:
UNIX>chmod go+x prg1.exe
are ca efect: se adauga dreptul de executie a fisierului prg1.exe pentru toti utilizatorii,
mai putin proprietarul lui.
Observatie: celelalte drepturi, nespecificate prin notatia simbolica, ramin neschimbate.
Pentru notatia in octal, trebuie avut in vedere faptul ca r= 4, w= 2, x= 1, si pentru
fiecare categorie de utilizatori se aduna cifrele corespunzatoare acestor optiuni, rezultind
cite o cifra octala pentru fiecare categorie de utilizatori.
De exemplu, comanda:
UNIX>chmod 640 prg1.c
are ca efect: proprietarul are drept de citire si scriere (dar nu are drept de executie) asupra
fisierului prg1.c, utilizatorii din grupul lui au doar drept de citire, iar altii nu au nici un
drept.
Observatie: spre deosebire de notatia simbolica, prin notatia in octal toate cele 4 x 3 = 12
drepturi ale fisierului sunt modificate, conform specificarii din notatia in octal.
Cind un fisier este creat, ii sunt asociate atit identificatorul proprietarului, cit si cel de
grup al procesului care a creat respectivul fisier. Comanda chown permite modificarea
proprietarului unui fisier, iar comanda chgrp permite similar modificarea grupului de care
apartine fisierul.
De exemplu, comanda:
UNIX>chown vidrascu prg1.c
are ca efect: sierul prg1.c din directorul curent va avea pe utilizatorul vidrascu ca nou
proprietar.
Iar comanda:
57
UNIX>chgrp studs prg1.c
are ca efect: fisierul prg1.c din directorul curent va avea grupul studs ca nou grup
proprietar.
2.2.5 Comenzi de baz˘a ˆın lucrul cu fi¸siere ¸si directoare
1. Comenzi pentru crearea/stergerea/listarea unui director:
Crearea unui director se face cu comanda mkdir, iar stergerea cu comanda rmdir.
Cind se creaza un director, se insereaza automat in acesta intrarile standard “.” si
.. (este un director gol). Daca se doreste stergerea unui director, acesta trebuie
sa fie gol. Este posibila insa si stergerea recursiva (directorul nu mai trebuie sa fie
gol in acest caz).
Afisarea continutului unui director se poate face cu comanda dir, dar cea mai cunos-
cuta si completa comanda este ls. Dintre numeroasele optiuni ale comenzii ls, vor
fi prezentate in continuare doar cele mai importante:
-l : format lung (detaliat) de afisare a informatiilor despre continutul direc-
torului;
-t : continutul directorului este listat sortat dupa data ultimei modificari, cele
mai recente fisiere primele;
-d : se afiseaza informatii despre directoarele propriu-zise date ca parametri,
in loc de a se afisa continuturile acestora;
-a : afisarea inclusiv a fisierelor a caror nume incepe cu punct;
-A : analog cu optiunea -a, doar ca se ignora intrarile standard “. si “..”.
Fara optiunea -a sau -A se vor afisa doar fisierele a caror nume nu incepe cu
punct. Motivul: sierele care incep cu punct sunt, de obicei, fisiere de ini-
tializare/configurare de sistem sau a aplicatiilor, si de aceea sunt “mascate” la un
listing obisnuit (i.e., un ls fara niciuna din aceste optiuni).
La folosirea optiunii -l, pentru fisierele speciale in locul dimensiunii vor fi afisate
numarul major si cel minor al dispozitivului fizic asociat.
Comanda dir este similara comenzii ls fara optiuni.
Spre exemplu, daca se da comanda
UNIX>ls *
vor fi afisate informatii despre continutul directorului curent si a subdirectoarelor
din directorul curent.
2. Comenzi pentru crearea/stergerea fisierelor:
Cu ajutorul comenzii generale mknod se pot crea orice tip de fisiere (inclusiv fisiere
speciale), dar exista si comenzi particulare pentru anumite tipuri de fisiere, ca de
58
exemplu comanda mkfifo care creeaza fisiere de tip fifo, sau comanda mkdir care
creeaza fisiere de tip director.
Comanda mkfs poate fi folosita, doar de catre superuser, pentru a construi un sistem
de fisiere, specificind dimensiunea si dispozitivul.
Stergerea fisierelor se poate face cu comanda rm, in limita dreptului de scriere. Op-
tiunea rm -i va interoga fiecare stergere, iar optiunea rm -r va sterge continutul
unui director cu tot cu subdirectoarele aferente chiar daca contin fisiere (atentie deci
la folosirea acestei optiuni!).
3. Comenzi pentru copierea/mutarea/redenumirea fisierelor:
Comanda mv permite mutarea unui fisier sau redenumirea lui. Primul parametru
specifica sursa, iar al doilea destinatia.
Spre exemplu, comanda
UNIX>mv prg1.c p1.cc
are ca efect: fisierul prg1.c din directorul curent va fi redenumit in p1.cc,
UNIX>mv prg1.c src
are ca efect: sierul prg1.c din directorul curent va mutat in subdirectorul src
(presupunind ca acesta exista), iar
UNIX>mv prg1.c src/p1.cc
are ca efect: fisierul prg1.c din directorul curent va fi mutat si redenumit in
src/p1.cc.
Cu ajutorul comenzii mv se pot redenumi si directoare, dar numai in cazul cind
acestea apartin de acelasi director parinte. De asemenea, se pot muta si mai multe
fisiere deodata, specificind ca destinatie un director existent.
Comanda cp permite copierea unui fisier (i.e., crearea unui nou fisier ce este o copie
fidela a fisierului initial). La fel ca la mv, primul parametru specifica sursa, iar al
doilea destinatia. Daca destinatia este un director, se poate face copierea mai multor
fisiere deodata.
Spre exemplu, comanda
UNIX>cp prg1.c p1.cc
are ca efect: fisierul prg1.c din directorul curent va fi copiat in p1.cc.
Comanda ln permite crearea unor pseudonime,i.e. alias-uri de nume (link-uri), ale
unui fisier.
Diferenta intre cp si ln este ca fisierele create prin cp sunt independente (o modificare
a unuia nu implica si modificarea automata a celuilalt), in timp ce fisierele create cu
ln refera acelasi fisier fizic.
Exista doua tipuri de alias-uri: link-uri hard si link-uri simbolice (create cu optiunea
ln -i).
Spre exemplu, comanda:
UNIX>ln prg1.c p1.cc
59
are ca efect: creeaza un link hard, cu numele p1.cc, catre fisierul prg1.c din direc-
torul curent, iar comanda
UNIX>ls -i prg1.c p1.cc
2156 prg1.c
2156 p1.cc
afiseaza numarul i-nodului corespunzator celor doua nume de fisiere, si se observa
ca ambele refera acelasi i-nod (adica acelasi fisier fizic).
Exist˘a o restrictie de utilizare a link-urilor hard: atit fisierul referit, cit si alias-ul
creat trebuie sa se afle pe acelasi sistem de fisiere fizic (i.e., pe aceeasi partitie), pe
cind link-urile simbolice nu au aceasta restrictie, datorita faptului ca ele folosesc un
mecanism de referire indirecta.
4. Comenzi de cautare a fisierelor:
O comanda des utilizata pentru cautare este comanda find. Cu ajutorul acestei
comenzi se pot localiza fisiere prin examinarea unei structuri arborescente de direc-
toare, pe baza unor diverse criterii de cautare. Comanda find este foarte puternica
– ea permite, spre exemplu, executia a diverse comenzi pentru fiecare aparitie gasita.
Spre exemplu, comanda
UNIX>find . -name p*.* -print
./prg1.c
./src/p1.cc
are ca efect: se vor cauta, incepind din directorul curent, fisierele a caror nume
incepe cu ’p’ si se vor afisa numele complete ale fisierelor gasite.
Un alt posibil criteriu de cautare: comanda find poate fi folosita pentru a gasi
fisierele mai mari sau mai mici fata de o anumita dimensiune:
UNIX>find . -size -10 -print
./prg1.c
./src/p1.cc
./.addressbook
./.addressbook.lu
./.bash history
./.bash profile
./mail/sent
O alta comanda de cautare este comanda which. Ea cauta directorul in care se
gaseste un fisier specificat, dar, spre deosebire de comanda find, il cauta numai in
directoarele din variabila de mediu PATH.
5. Comenzi pentru afisarea unui fisier:
Comenzile cat,tac,more,less,head,tail,pg,lp,od permit vizualizarea contin-
utului unui fisier, dupa anumite formate.
Comanda cat permite afisarea continutului unui fisier. Ea se poate folosi ˆınl˘ant¸uit˘a
cu comanda more sau cu comanda less pentru a afisa ecran cu ecran acel fisier, sau
se pot folosi in acest scop direct cele doua comenzi amintite. Iar comanda tac este
60
inversa comenzii cat, producind afisarea unui fisier linie cu linie de la sfirsitul lui
catre inceputul acestuia.
Comenzile head si tail produc afisarea primelor, respectiv ultimelor n linii dintr-un
fisier (n fiind specificat ca optiune).
Comanda pg face afisare cu paginare, lp face listare la imprimanta, iar od permite
obtinerea imaginii memorie a unui fisier.
6. Comenzi ce ofera diverse informatii despre fisiere:
Obtinerea de diverse informatii despre continutul unui fisier se poate face cu comen-
zile:
file : incearca sa determine tipul unui fisier;
wc : precizeaza numarul de caractere, cuvinte si linii dintr-un fisier;
sum : calculeaza suma de control a unui fisier.
Spre exemplu:
UNIX>file p*.*
prg1.c: c program text
p1.cc: c program text
Compararea a doua fisiere se poate face cu comenzile:
cmp : face o comparare binara a doua fisiere;
comm : afiseaza liniile comune a doua fisiere text;
diff : afiseaza liniile diferite a doua fisiere text.
Obtinerea de informatii despre sistemul de fisiere se poate face cu comenzile:
df : afiseaza cit spatiu liber mai exista pe fiecare partitie;
du : afiseaza cite blocuri ocupa fiecare fisier si suma blocurilor din directorul
respectiv.
Spre exemplu, comanda:
UNIX>du -ks
are ca efect: se va afisa, in kB (kilo-bytes), cit spatiu ocupa (in intregime, cu tot cu
subdirectoare) directorul home al utilizatorului ce da aceasta comanda.
7. Comenzi pentru cautarea unui sablon intr-un fisier:
Comanda grep este o comanda puternica, ce permite cautarea unui pattern (sablon),
adica a unei expresii regulate, intr-unul sau mai multe fisiere specificate, si afisarea
tuturor liniilor de text ce contin acel pattern.
Spre exemplu, comanda
UNIX>grep vidrascu /etc/passwd
61
va afisa linia cu informatii corespunzatoare contului vidrascu din fisierul /etc/passwd,
comanda
UNIX>grep -c main p*.*
prg1.c: 1
p1.cc: 1
va afi¸sa num˘arul de linii pe care apare cuvˆıntul “main” pentru fiecare fi¸sier ce se
potrive¸ste specificatorului p*.* (opt¸iunea -c a comenzii grep doar contorizeaz˘a
num˘arul de aparit¸ii, ˆın loc a afi¸seze toate aparit¸iile), iar comanda
UNIX>ps -aux | grep -w so
are ca efect: afisarea tuturor sesiunilor deschise in acel moment ale utilizatorului so si
a proceselor rulate in foreground in aceste sesiuni de catre acest utilizator (opt¸iunea
-w a comenzii grep selecteaz˘a doar potrivirile exacte ale cuvˆıntului “so”, adic˘a nu va
selecta ¸si aparit¸iile de cuvinte ce cont¸in ˆın ele particula “so” ca subcuvˆınt propriu).
Asadar, utilitarul grep este util pentru a extrage dintr-un fisier (sau grup de fisiere)
acele linii care contin siruri de caractere ce satisfac un sablon dat, forma generala de
apel fiind
UNIX>grep [optiuni]sablon fisiere
Pe linga optiunile -c si -w deja amintite, alte optiuni utile pentru grep ar mai fi
optiunea -i care face selectie “case insensitive”, si optiunea -v ce selecteaza liniile
care nu satisfac sablonul specificat, plus multe altele pe care le puteti afla consultind
pagina de manual a comenzii grep.
Precum am spus, pentru specificarea sablonului se pot utiliza expresii regulate. O
expresie regulata este o secventa combinata de caractere obisnuite si de caractere
speciale (ce au un rol special – ele descriu modul de interpretare al sablonului).
Caracterele speciale ce se pot folosi intr-o expresie regulata si semnificatiile lor sunt
urmatoarele:
^= indicator inceput de linie;
$= indicator sfarsit de linie;
*= caracterul anterior, repetat de oricate ori (i.e., de n0 ori);
.= orice caracter (exact un caracter);
[...] = un caracter dintre cele din lista specificata (la fel ca la specificatorii
de fisiere);
[^...] = orice caracter cu exceptia celor din lista specificata;
\ = scoate din contextul uzual caracterul care urmeaza (util pentru a inhiba
interpretarea caracterelor speciale);
Caracterele speciale extinse (activate prin optiunea -E sablon a comenzii grep)
sunt urmatoarele:
+= caracterul anterior, repetat cel putin o data (i.e., de n > 0 ori);
?= caracterul anterior, cel mult o aparitie (una sau zero aparitii);
62
• {n}sau {n,}sau {n,m}= repetarea unui caracter de un numar precizat de ori
(exact de nori in primul caz, de cel putin nori pentru a doua forma, si de un
numar de ori cuprins intre nsi m, pentru a treia forma);
expr1 |expr2 = lista de optiuni alternative (SAU);
expr1 expr2 = concatenarea expresiilor;
(expr)= parantezele sunt utile pentru gruparea subexpresiilor, pentru a “scurt-
circuita” precedenta operatorilor, care este urmatoarea: repetitia este mai pri-
oritara decit concatenarea, iar aceasta este mai prioritara decit alternativa “|”.
O alta comanda de tip filtru este comanda cut, ce permite selectarea anumitor
portiuni din fiecare linie de text a fisierului specificat.
Portiunile selectate pot fi de tip octet (daca se foloseste optiunea -b), sau de tip
caracter (daca se foloseste optiunea -c), sau de tip cˆımp (daca se foloseste optiunea
-f), caz in care se poate specifica si caracterul ce delimiteaza cˆımpurile in cadrul
fiecarei linii de text, cu ajutorul optiunii -d (daca nu se specifica aceasta optiune,
atunci implicit se foloseste ca separator caracterul TAB).
Fiecare dintre cele 3 optiuni -b,-c sau -f este urmata de o lista de numere naturale
(separate prin ’,’ sau prin ’-’ , caz in care semnifica un interval de numere), lista ce
indica numerele de ordine ale portiunilor (de tipul specificat) ce vor fi selectate din
fiecare linie de text a fisierului de intrare si scrise in fisierul de iesire.
Exemplu. Comanda
UNIX>cut -b1-8 /etc/passwd
va afisa numele conturilor de utilizatori existente in sistemul respectiv, iar comanda
UNIX>cut -f1,3-5 -d: /etc/passwd
va afisa numele conturilor de utilizatori, fiecare cont fiind insotit de UID-ul asociat
si GID-ul grupului din care face parte, precum si de sectiunea de comentarii (care de
obicei contine numere real al utilizatorului si alte date personale).
8. Comenzi pentru schimbarea caracteristicilor unui fisier:
pentru schimbarea drepturilor de acces: chmod
pentru schimbarea proprietarului: chown
pentru schimbarea grupului proprietarului: chgrp
Pe acestea le-am prezentat in subsectiunea anterioara.
63
2.3 Interpretoare de comenzi UNIX, partea I-a: Prezentare
general˘a
1. Introducere
2. Comenzi shell. Lansarea ˆın execut¸ie
3. Execut¸ia secvent¸ial˘a, condit¸ional˘a, ¸si paralel˘a a comenzilor
4. Specificarea numelor de fi¸siere
5. Redirect˘ari I/O
6. ˆ
Inl˘ant¸uiri de comenzi (prin pipe)
7. Fi¸sierele de configurare
8. Istoricul comenzilor tastate
2.3.1 Introducere
Interpretorul de comenzi (numit uneori si shell) este un program executabil ce are, in
UNIX, aceeasi destinatie ca si in MS-DOS, realizind doua functii de baza:
1. aceea de a prelua comenzile introduse de utilizator si de a afisa rezultatele executiei
acestora, realizind astfel interfata dintre utilizator si sistemul de operare;
2. aceea de a oferi facilitati de programare intr-un limbaj propriu, cu ajutorul caruia
se pot scrie script-uri, adica fisiere cu comenzi UNIX.
Daca in MS-DOS este utilizat practic un singur interpretor de comenzi, si anume programul
command.com (desi teoretic acesta poate fi inlocuit de alte programe similare, cum ar
ndos.com-ul), in UNIX exista in mod traditional mai multe interpretoare de comenzi: sh
(Bourne SHell), bash (Bourne Again SHell), csh (C SHell), ksh (Korn SHell), tcsh (o
varianta de csh), ash,zsh, ¸s.a., utilizatorul avind posibilitatea sa aleaga pe oricare dintre
acestea.
Shell-urile din UNIX sunt mai puternice decit analogul (command.com) lor din MS-DOS,
fiind asemanatoare cu limbajele de programare de nivel inalt: au structuri de control
alternative si repetitive de genul if,case,for,while, etc., ceea ce permite scrierea de
programe complexe ca simple script-uri. Un script este un fisier de comenzi UNIX (analogul
fisierelor batch *.bat din MS-DOS).
64
In continuare ne vom referi la interpretorul de comenzi bash (Bourne Again SHell), care
este instalat implicit in Linux drept shell de login, dar facem observatia ca majoritatea
facilitatilor din bash sunt comune tuturor interpretoarelor de comenzi UNIX (chiar daca
uneori difera prin sintaxa).
In cele ce urmeaza, ca si pina cum, prin cuvintul UNIX>vom desemna prompterul afisat
de interpretor pentru introducerea de comenzi de catre utilizator, iar textul ce urmeaza
dupa prompter (pina la sfirsitul liniei) va fi indicat folosind fontul comanda de introdus
, pentru a indica comanda ce trebuie tastata de utilizator la prompter.
2.3.2 Comenzi shell. Lansarea ˆın execut¸ie
Dupa cum am amintit deja, la fel ca in MS-DOS, si in UNIX exista doua categorii de comenzi:
comenzi interne (care se gasesc in fisierul executabil al shell-ului respectiv) si comenzi
externe (care se gasesc separat fiecare intr-un fisier executabil, avind acelasi nume cu
comanda respectiva).
In cele ce urmeaza prin comenzi (sau comenzi UNIX, sau comenzi shell) vom intelege:
1. comenzi interne, de exemplu: cd,help, ¸s.a.;
(Observat¸ie: lista tuturor comenzilor interne se poate obtine cu comanda help)
2. comenzi externe:
(a) fisiere executabile (i.e., programe executabile obtinute prin compilare din pro-
grame sursa scrise in C sau alte limbaje de programare), de exemplu: passwd,
bash, ¸s.a.;
(b) script-uri (i.e., fisiere cu comenzi), de exemplu: .profile,.bashrc, ¸s.a.
Forma generala de lansare in executie a unei comenzi UNIX este urmatoarea:
UNIX>comanda [optiuni ] [argumente ],
unde optiunile si argumentele pot lipsi, dupa caz. Prin conventie, optiunile sunt precedate
de caracterul ’-’ (in MS-DOS este folosit caracterul ’/’). Argumentele sunt cel mai adesea
nume de fisiere. Daca este vorba de o comanda externa, aceasta poate fi specificata si prin
calea ei (absoluta sau relativa).
Separatorul intre numele comenzii si ceilalti parametri ai acesteia, precum si intre fiecare
dintre parametri este caracterul SPACE sau caracterul TAB (unul sau mai multe spatii sau
tab-uri).
65
O comanda poate fi scrisa pe mai multe linii, caz in care fiecare linie trebuie terminata cu
caracterul ’\, cu exceptia ultimei linii.
Exceptind cazul unei comenzi interne, sau cazul cind comanda este data prin specificarea
caii sale (absoluta sau relativa), shell-ul va cauta fisierul executabil cu numele comanda
in directoarele din variabila de mediu PATH, in ordinea in care apar in ea, si, in caz ca-l
gaseste, il executa, altfel afiseaza mesajul de eroare:
bash: comanda : command not found
Observatie:shell-ul nu cauta fisierul executabil mai intii in directorul curent si apoi in
directoarele din variabila de mediu PATH, asa cum se intimpla in MS-DOS; acest neajuns
poate fi inlaturat in UNIX prin adaugarea directorului curent “. la variabila PATH, sau
prin specificarea relativa ./comanda pentru o comanda externa avind fisierul asociat in
directorul curent.
Observatie: pentru a putea executa fisierul asociat acelei comenzi, utilizatorul care a
lansat acea comanda trebuie sa aiba drept de executie pentru acel fisier (i.e., atributul x
corespunzator acelui utilizator sa fie setat).
O alta posibilitate de a lansa in executie o comanda este prin apelul unui shell:
UNIX>bash comanda [optiuni ] [argumente ]
In sfirsit, doar pentru comenzi ce sunt script-uri, o a treia posibilitate este de a lansa in
executie comanda prin apelul:
UNIX>.comanda [optiuni ] [argumente ]
Toate formele de lansare a unei comenzi pomenite mai sus au ca efect executia acelei
comenzi in foreground (i.e., in “planul din fata”) : shell-ul va astepta terminarea executiei
acelei comenzi inainte de a afisa din nou prompterul pentru a accepta noi comenzi din
partea utilizatorului.
Exista insa si posibilitatea de a lansa o comanda in executie in background (i.e., in “fun-
dal”) : in aceasta situatie shell-ul nu va mai astepta terminarea executiei acelei comenzi,
ci va afisa imediat prompterul pentru a accepta noi comenzi din partea utilizatorului, in
timp ce acea comanda isi continua executia (fara a putea primi input din partea tasta-
turii).
Lansarea comenzii in background se face adaugind caracterul ’&’ la sfirsitul liniei:
UNIX>comanda [parametri ] &
Vom vedea in continuare alte posibilitati de a lansa in executie comenzi.
66
2.3.3 Execut¸ia secvent¸ial˘a, condit¸ional˘a, ¸si paralel˘a a comenzilor
Pina acum am vazut cum se pot lansa comenzi in executie, doar cite una o data (i.e., cite
o singura comanda tastata pe un rind la prompterul shell-ului).
Exista insa posibilitatea de a tasta pe un singur rind la prompter doua sau mai multe
comenzi, caz in care comenzile trebuie separate fie prin caracterul ’;’, fie prin caracterul
|’, sau putem avea combinatii ale acestora daca sunt mai mult de doua comenzi. Efectul
lor este urmatorul:
caracterul ’;’ – executie secventiala:
comenzile separate prin caracterul ’;’ sunt executate secvential, in ordinea in care
apar.
caracterul ’|’ (pipe) executie paralela, inlantuita:
comenzile separate prin caracterul ’| sunt executate simultan (i.e., in paralel, in
acelasi timp), fiind inlantuite prin pipe (adica iesirea standard de la prima comanda
este “conectata” la intrarea standard a celei de a doua comenzi); vom reveni mai
tirziu asupra acestui aspect.
Exemplu. Secventa de comenzi
UNIX>ls ; cd so ; ls -l
are ca efect listarea continutului directorului curent, apoi schimbarea acestuia in directorul
home al utilizatorului so, urmata apoi de listarea continutului acestuia. Iar comanda
inlantuita
UNIX>cat /etc/passwd | grep so
are ca efect afisarea liniei, din fisierul /etc/passwd, care contine informatiile despre contul
so.
In sfirsit, UNIX-ul permite si executia conditionala de comenzi. Mai precis, exista doua
posibilitati de a conditiona executia unei comenzi de rezultatul executiei unei alte comenzi,
si anume:
prima forma este conjunct¸ia a doua comenzi:
UNIX>comanda 1&& comanda 2
Efect: intii se executa prima comanda, iar apoi se va executa a doua comanda numai
daca executia primei comenzi intoarce codul de retur 0 (ce corespunde terminarii cu
succes).
67
a doua forma este disjunct¸ia a doua comenzi:
UNIX>comanda 1 || comanda 2
Efect: intii se executa prima comanda, iar apoi se va executa a doua comanda
numai daca executia primei comenzi intoarce un cod de retur nenul (ce corespunde
terminarii cu eroare).
2.3.4 Specificarea numelor de fi¸siere
Referitor la specificarea fisierelor ca argumente pentru comenzi, dupa cum am amintit
deja, sunt mai multe feluri de specificari:
1. prin calea absoluta (i.e., pornind din directorul radacina), de exemplu:
/home/vidrascu/so/file0003.txt
2. prin calea relativa (la directorul curent) (i.e., pornind din directorul curent de lucru),
de exemplu (presupunind ca directorul curent este /home/vidrascu):
so/file0003.txt
3. prin calea relativa la un director home al unui anumit utilizator (i.e., pornind din
directorul home al acelui utilizator), de exemplu:
vidrascu/so/file0003.txt
(Observatie: pentru directorul home propriu, i.e. al utilizatorului ce tasteaza respec-
tiva comanda, se poate folosi doar caracterul in loc de username.)
Toate cele de mai sus sunt specificari unice de fisiere (i.e., fisierul se specifica prin numele
sau complet).
La fel ca in MS-DOS, o alta posibilitate de a indica specificatori de fisier ca argumente in
linia de apel pentru orice comanda UNIX, este de a specifica o lista de fisiere (un “sablon”)
prin folosirea caracterelor speciale de mai jos, efectul fiind ca acel sablon este inlocuit
cu (i.e., este “expandat” in linia de apel a comenzii prin) numele tuturor fisierelor ce
se potrivesc acelei specificatii sablon (eventual nici unul), aflate in directorul specificat
(toate cele trei feluri de specificare a caii de mai sus se pot folosi si pentru specificarea
prin sablon):
1. caracterul ’*’: inlocuieste orice sir de caractere, inclusiv sirul vid;
Exemplu: vidrascu/so/file*.txt
2. caracterul ’?’: inlocuieste orice caracter (dar exact 1 caracter!);
Exemplu: vidrascu/so/file000?.txt
68
3. specificatorul multime de caractere [. . . ] : inlocuieste exact 1 caracter, dar nu cu
orice caracter, ci doar cu cele specificate intre parantezele ’[’ si ’]’, sub forma de
enumerare (separate prin ’,’ sau nimic) si/sau interval (dat prin capetele intervalului,
separate prin ’-’);
Exemple: vidrascu/so/file000[1,2,3].txt
vidrascu/so/file000[123].txt
vidrascu/so/file000[1-3].txt
vidrascu/so/file00[0-9][1-9].txt
vidrascu/so/file000[1-3,57-8].txt
4. specificatorul multime exclusa de caractere [ˆ. . . ] : inlocuieste exact 1 caracter, dar
nu cu orice caracter, ci doar cu cele din complementara multimii specificate intre
parantezele ’[’ si ’]’ similar ca mai sus, doar cu exceptia faptului ca primul caracter
de dupa ’[’ trebuie sa fie ’ˆ’ pentru a indica complementariere (excludere);
Exemplu: vidrascu/so/file000[^1-3].txt
5. caracterul ’\’: se foloseste pentru a inhiba interpretarea operator a caracterelor
speciale de mai sus, si anume \c (unde c este unul dintre caracterele ’*’, ’?’, ’[’, ’]’,
’ˆ’, ’\’) va interpreta acel caracter c ca text (i.e., prin el insusi) si nu ca operator
(i.e., prin “sablonul” asociat lui in felul descris mai sus).
Exemple: ce mai faci\?.txt ,lectie\[lesson].txt
Alte exemple:
Presupunem ca in directorul curent avem fisierele: file01.dat,file02.dat,form.txt,
probl.c,results.doc,results.bak, si test param, ultimul fiind un fisier de comenzi.
In acest caz, comanda
UNIX>test param f*
este echivalenta cu comanda explicita
UNIX>test param file01.dat file02.dat form.txt
De asemenea, comanda
UNIX>test param m*.doc abcd
este echivalenta cu comanda explicita
UNIX>test param abcd
69
2.3.5 Redirect˘ari I/O
Una dintre facilitatile comune tuturor interpretoarelor de comenzi UNIX este cea de redi-
rectare a intrarii si iesirilor standard (similar ca in MS-DOS).
Exista trei dispozitive logice I/O standard:
a) intrarea standard (stdin), de la care se citesc datele de intrare in timpul executiei unei
comenzi;
b) iesirea standard (stdout), la care sunt scrise datele de iesire in timpul executiei unei
comenzi;
c) iesirea de eroare standard (stderr), la care sunt scrise mesajele de eroare in timpul
executiei unei comenzi.
In mod implicit, comunicarea directa cu utilizatorul se face prin intermediul tastaturii
(intrarea standard) si al ecranului (iesirea standard si iesirea de eroare standard). Cu alte
cuvinte dispozitivul logic stdin este atasat dispozitivului fizic tastatura (= terminalul de
intrare), iar dispozitivele logice stdout si stderr sunt atasate dispozitivului fizic ecran
(= terminalul de iesire).
Interpretorul de comenzi poate “fort¸a” un program (o comanda) ca, in timpul executiei
sale, sa primeasca datele de intrare dintr-un fisier in locul tastaturii, si/sau sa trimita
rezultatele intr-un fisier in locul ecranului. Realizarea redirectarilor se face in modul
urmator:
1. redirectarea intrarii standard (stdin):
UNIX>comanda <fisier intrare
Efect: datele de intrare se preiau nu de la tastatura, ci din fisierul de intrare precizat.
2. redirectarea iesirii standard (stdout):
UNIX>comanda >fisier iesire
sau
UNIX>comanda >> fisier iesire
Efect: rezultatele executiei nu sunt afisate pe ecran, ci sunt scrise in fisierul de iesire
precizat. Diferenta intre cele doua forme este ca in primul caz vechiul continut al
fisierului de iesire (daca acesta exista deja) este sters (adica se face rewrite), iar in al
doilea caz datele de iesire sunt adaugate la sfirsitul fisierului (adica se face append).
3. redirectarea iesirii de eroare standard (stderr):
UNIX>comanda 2>fisier iesire err
sau
UNIX>comanda 2>> fisier iesire err
Efect: mesajele de eroare din timpul executiei nu sunt afisate pe ecran, ci sunt
scrise in fisierul de iesire precizat. Diferenta intre cele doua forme este la fel ca la
redirectarea stdout-ului (i.e.,rewrite vs. append).
70
Pentru a redirecta toate dispozitivele standard pentru o comanda, se combina formele de
mai sus, ca in exemplele urmatoare:
UNIX>comanda <fis1 >fis2
UNIX>comanda >fis1 2>>fis2
UNIX>comanda <fis1 >>fis2 2>>fis2
De asemenea, se poate folosi o anumita redirectare pentru mai multe comenzi, ca in
exemplul urmator:
UNIX>(comanda1 ; comanda2) <fis1 >>fis2
Efect: cele doua comenzi sunt executate secvential, dar amindoua cu stdin redirectat din
fisierul fis1 si stdout redirectat in fisierul fis2.
Dupa cum cunoasteti deja de la programarea in limbajul C, in programele C dispozitivele
standard au asociate, in mod implicit, ca descriptori de fisiere deschise urmatoarele valori:
stdin = 0 ,stdout = 1 ,stderr = 2. De aceea, se pot face redirectari spre fisiere
deschise specificate nu prin numele fisierului, ci prin descriptorul de fisier asociat. Spre
exemplu:
UNIX>comanda 2>&1
Efect: similar ca la specificarea fisierelor de iesire prin nume simbolice, doar ca se utilizeaza
&1 pentru a specifica ca fisier de iesire acel fisier deschis asociat descriptorului de fisier 1.
Un alt exemplu:
UNIX>echo ‘‘Mesaj de eroare’’ >&2
Efect: acest mesaj nu va fi afisat pe stdin, ci in fisierul asociat descriptorului de fisier 2
(care este de obicei stderr, daca nu a fost redirectata anterior).
Observatie: intr-un program C, in mod normal pentru a scrie ceva intr-un fisier se apeleaza
primitiva write(file-descriptor, ...); , dar pentru a o putea apela in program este nevoie
ca mai intii sa fie deschis fisierul respectiv, cu apelul:
int file-descriptor = open("nume-fisier",...); ,
iar la sfirsitul executiei programului sistemul va inchide toate fisierele deschise, chiar daca
ati uitat sa le inchideti explicit prin apelul functiei close(file-descriptor); .
Totusi, pentru dispozitivele standard stdin,stdout,stderr nu mai este nevoie de apelul
explicit al functiei open deoarece aceste dispozitive sunt deschise automat de catre sistem
– terminalele de intrare/iesire sunt pastrate deschise pe toata durata sesiunii de lucru, iar
eventualele fisiere folosite ca redirectari I/O prin mijloacele descrise de mai sus, sunt de-
schise de sistem atunci cind shell-ul interpreteaza linia respectiva introdusa si se pregateste
s-o execute (si sunt inchise la sfirsitul executiei acelei comenzi).
71
2.3.6 ˆ
Inl˘ant¸uiri de comenzi (prin pipe)
Alta dintre facilitatile comune tuturor interpretoarelor de comenzi UNIX este cea de in-
lantuire de comenzi (prin pipe), de care am amintit deja mai sus, cind am vorbit despre
executia paralela a comenzilor.
ˆ
Inlantuirea de comenzi consta in: este posibil de a conecta iesirea standard a unei comenzi
la intrarea standard a alteia intr-o singura linie de comanda, eliminindu-se astfel necesi-
tatea comunicarii intre cele doua programe prin intermediul unor fisiere temporare. Sin-
taxa acestei actiuni este urmatoarea:
UNIX>comanda 1|comanda 2 | ... | comanda N
Simbolul ’|’ (pipe) este cel care marcheaza “legarea” iesirii unei comenzi la intrarea comen-
zii urmatoare. Mecanismul utilizat va fi studiat ulterior, reprezentind una din formele de
comunicare inter-procese specifice sistemelor de operare din familia UNIX. Dupa cum am
spus mai sus, toate aceste comenzi sunt executate simultan, in paralel (nu secvential!),
inlantuite prin pipe-uri (iesirea standard de la prima devine intrare standard pentru a
doua, ¸s.a.m.d.).
Restrictie: interpretoarele de comenzi poseda o serie de comenzi interne; din motive de
arhitectura a sistemului, acestea nu pot face parte din lanturi de comenzi.
Observatie: atit redirectarea intrarii si iesirii standard, cit si inlantuirea comenzilor sint
posibile si in MS-DOS, dar sint mult mai des folosite in UNIX, pe de o parte datorita car-
acterului multitasking al sistemului, iar pe de alta parte datorita multitudinii de utilitare
care preiau datele de intrare de la tastatura si afiseaza rezultatele pe ecran.
Exemplu. Iata citeva exemple de comenzi:
UNIX>comanda1 2>&1 |comanda2
Efect: iesirile stdout si stderr de la comanda1 sunt “conectate” la intrarea stdin a
comenzii comanda2.
UNIX>ls -Al |wc -l
Efect: afiseaza numarul fisierelor (si subdirectoarelor) din directorul curent.
UNIX>cat fis1 fis2 >fis3
Efect: concateneaza primele doua fisiere in cel de-al treilea.
Sa mai vedem citeva exemple. Comanda urmatoare
UNIX>finger | cut -f1 -d’’
va afisa lista numelor utilizatorilor conectati la sistem (prefixata de cuvintul “Login”),
lista care se mai poate obtine si cu comanda:
72
UNIX>who | cut -f1 -d"" | sort -u
Efect: va afisa lista ordonata a numelor utilizatorilor conectati la sistem.
UNIX>echo "username:"; read x ; echo `who | cut -d"" -f1 | grep -c $x`
Efect: se afiseaza “username:” si se asteapta citirea de la tastatura a unui nume de
utilizator, apoi se afiseaza num˘arul de sesiuni deschise de utilizatorul introdus.
Observat¸ie: fi¸sierul /etc/passwd cont¸ine linii de text de forma urm˘atoare:
username:password:uid:gid:comments:directory:shell
cu informatii despre conturile de utilizatori din sistem.
Iar fi¸sierul /etc/group cont¸ine linii de text de forma uratoare:
groupname:password:gid:userlist
cu informatii despre grupurile de utilizatori din sistem.
UNIX>cat /etc/passwd | tail -20 | cut -f3,7 -d : | less
Efect: se vor afi¸sa UID-ul si shell-ul de login al ultimelor 20 de conturi din /etc/passwd.
UNIX>(tail /etc/group | cut -f3-4 -d: | less ) >/dev/null
Efect: nu se va afisa nimic pe ecran, datorita redirectarii iesirii standard catre sierul
nul. In absenta redirectarii, s-ar fi afisat GID-urile ¸si listele de utilizatori ale ultimelor 10
grupuri de utilizatori din /etc/group.
(Not˘a:/dev/null este un fisier logic cu rol de “nul”: nu se poate citi nimic din el, si orice
scriere in el “se pierde”, i.e. nu are nici un efect.)
2.3.7 Fi¸sierele de configurare
In UNIX, pentru fiecare shell exista o serie de fisiere de initializare, de configurare si de is-
toric ashell-ului respectiv. Numele acestor fisiere este un alt detaliu prin care se deosebesc
shell-urile UNIX.
La fel ca in MS-DOS, fiecare utilizator isi poate scrie un fisier script care sa fie executat la
fiecare inceput de sesiune de lucru (analogul fisierului autoexec.bat din MS-DOS), script
numit $HOME/.profile sau $HOME/.bash profile in cazul cind se utilizeaza bash-ul ca
shell implicit (pentru alte shell-uri acest fisier de initializare este denumit altfel).
In plus poate avea un script care sa fie rulat atunci cind se deconecteaza de la sistem
(adica la logout); acest script se numeste $HOME/.bash logout in cazul shell-ului bash.
Dupa cum se observa, toate aceste fisiere se gasesc in directorul home al acelui utilizator
73
($HOME este o variabila de mediu ce are ca valoare calea absoluta a directorului home al
utilizatorului).
Mai exista doua fisiere de initializare valabile pentru toti utilizatorii, si anume fisierele
/etc/profile si /etc/environment. La deschiderea unei noi sesiuni de lucru, mai intii
sunt executate script-urile de sistem (i.e., cele din directorul /etc/), si abia apoi cele
particulare utilizatorului respectiv (i.e., cele din $HOME/).
In plus, exista si niste fisiere de configurare, si anume un fisier global, numit /etc/bashrc,
si un fisier specific fiecarui utilizator, numit $HOME/.bashrc (acestea sunt numele in cazul
shell-ului bash).
Aceste fisiere sunt executate ori de cite ori este lansat un proces shell interactiv, exceptind
shell-ul de login. Ca, de exemplu, atunci cind de sub browser-ul lynx se apeleaza interpre-
torul de comenzi printr-o anumita comanda (mai exact, apasind tasta “!”), sau dintr-un
editor de texte, sau la pornirea programului mc (i.e., a managerului de fisiere Midnight
Commander), etc. Sau ca atunci cind, din linia de comanda, startam un nou shell (fara
parametri).
De asemenea, mai exista un fisier de istoric, numit $HOME/.bash history, care pastreaza
ultimele Ncomenzi tastate (exista o variabila de mediu care specifica aceasta dimensiune
N). Formatul de pastrare este urmatorul: fiecare linie de comanda tastata se pastreaza pe
o linie de text in fisier, in ordine cronologica (ultima comanda tastata este pe ultima linie
din fisier). Acest nume este specific shell-ului bash (in alte shell-uri se numeste altfel, sau
nu exista – spre exemplu shell-ul sh nu are facilitatea de istoric).
2.3.8 Istoricul comenzilor tastate
Lista comenzilor tastate (pastrata in fisierul de istoric $HOME/.bash history) poate
vizualizata cu comanda history. Aceasta comanda afiseaza, in ordine cronologica, aceasta
lista a comenzilor tastate anterior, fiecare linie de comanda fiind prefixata de un numar
(de la 1 pina la numarul total de linii de comenzi salvate in fisierul de istoric).
Numerele afisate de comanda history pot fi folosite pentru a executa din nou comenzile
din istoric, fara a fi nevoie sa mai fie tastate din nou. Si anume, pentru a invoca din
nou o comanda, se tasteaza la prompter caracterul ’!’ urmat imediat de numarul asociat
comenzii respective.
Exemplu. a presupunem a la un moment dat avem urm˘atorul istoric de comenzi:
UNIX>history
1 ls -Al
2 pwd
3 cd mail/
74
4 ls -l
5 joe inbox.txt
. ...
. ...
. ...
99 cat .bash_profile
100 less .profile
atunci comanda
UNIX>!99
are ca efect: se executa din nou comanda cat .bash profile.
Mai exista o facilitate a shell-ului ce permite invocarea comenzilor tastate anterior, precum
si editarea lor, si anume: prin apasarea, la prompterul shell-ului, a tastei UP (i.e., tasta
sageata-sus) de un anumit numar de ori, se vor afisa la prompter comenzile anterioare, in
ordine inversa celei in care au fost tastate, si anume cite o comanda anterioara la fiecare
apasare a tastei UP.
Comanda astfel selectata din istoric, poate fi editata (modificata), iar apoi executata (prin
apasarea tastei ENTER).
75
2.4 Interpretoare de comenzi UNIX, partea a II-a: Progra-
mare bash
1. Introducere
2. Proceduri shell (script-uri)
3. Variabile de shell
4. Structuri de control pentru script-uri
5. Alte comenzi shell utile pentru script-uri
2.4.1 Introducere
Dupa cum am mai spus, shell-ul are si functia de limbaj de programare. El recunoaste
toate notiunile de baza specifice oricarui limbaj de programare, cum ar fi: variabile, in-
structiuni de atribuire, instructiuni de control structurate (if,while,for,case), proce-
duri si functii, parametri. In cele ce urmeaza le vom trece pe rind in revista.
Toate aceste facilitati permit automatizarea unor actiuni pe care utilizatorul le desfasoara
in mod repetat. La fel ca in MS-DOS/Windows, este posibila scrierea unor fisiere care sa
contina comenzi, fisiere ce pot fi executate la cererea utilizatorului de catre interpretorul
de comenzi. In terminologia UNIX, un asemenea fisier de comenzi se numeste script.
2.4.2 Proceduri shell (script-uri)
Procedurile shell sunt fisiere de comenzi UNIX(numite script-uri), analog cu fisierele batch
*.bat din MS-DOS. Apelul lor se face la fel ca pentru orice comanda UNIX. Recuperarea in
procedura script a argumentelor de apel se face cu ajutorul unor variabile speciale (ce vor
fi deci parametrii formali in procedura script), variabile pe care le vom discuta mai ˆıncolo.
In fisierele script se foloseste caracterul ’#’ pentru a indica un comentariu, ce este valabil
pina la sfirsitul acelei linii de text, analog cu forma //comentariu din limbajul C++.
Dupa cum am mai spus in sectiunea 2.1, lansarea in executie a unui fisier de comenzi se
poate face in mai multe moduri:
76
1. prin numele lui, la fel ca orice comanda:
UNIX>nume fisier comenzi [parametri]
Observatie: acest mod de executie este posibil numai in cazul in care fisierul a fost
facut executabil setindu-i dreptul de executie, i.e. atributul x, cu comanda chmod.
2. prin comanda interna .(sau comanda interna source) a interpretorului de comenzi:
UNIX>.nume fisier comenzi [parametri]
sau
UNIX>source nume fisier comenzi [parametri]
3. prin apelul unui anumit shell, ca de exemplu al bash-ului:
UNIX>bash nume fisier comenzi [parametri]
Exista unele diferente semnificative intre aceste forme de apel, dupa cum vom vedea mai
jos.
Datorita existentei mai multor interpretoare de comenzi UNIX, este necesar un mecanism
prin care sistemul de operare sa poata fi informat asupra interpretorului de comenzi pentru
care a fost scris un anumit fisier de comenzi, in caz contrar acesta riscind sa nu poata fi
executat.
Printr-o conventie respectata de mai multe dialecte de UNIX (inclusiv de catre Linux),
prima linie a fisierului de comenzi poate contine numele interpretorului caruia ii este
destinat, in forma urmatoare: #!nume interpretor comenzi .
Iata citeva exemple:
#!/bin/bash
#!/bin/sh
#!/bin/csh
#!/bin/perl
Se observa ca trebuie precizata si calea absoluta pentru interpretorul de comenzi cu care se
doreste a fi executat acel script. Acest mecanism este folosit si de fisierele care sint scrise
in limbaje mai puternice, dar de acelasi tip – limbaje interpretate (de exemplu limbajul
Perl).
Important: apelul shell-ului specificat intr-un script in felul descris mai sus (i.e., pe prima
linie a fisierului), pentru a executa acel script, se face doar pentru prima forma de apel
amintita mai sus:
UNIX>nume script [parametri]
In acest caz, procesul shell in care se da aceasta comanda (la prompterul acestuia) va crea
un nou proces ce va executa shell-ul specificat in fisierul script in felul descris mai sus (i.e.,
pe prima linie a sierului), sau, in cazul ca nu se specifica un shell in fisierul script, va fi o
copie a shell-ului de comanda. In ambele situatii posibile, noul proces shell creat va rula
77
in mod neinteractiv (adica nu va afisa la rindul sau un prompter pentru a interactiona cu
utilizatorul) si va executa linie cu linie comenzile din fisierul script specificat.
Pentru a doua forma de apel amintita mai sus:
UNIX>.nume script [parametri]
(sau cea echivalenta cu comanda source), acel script va fi executat de fapt tot de catre
shell-ul in care se da aceasta comanda (deci nu se mai creeaza un nou proces shell care sa
execute acel script, ca la prima forma de apel).
Iar pentru a treia forma de apel:
UNIX>nume shell nume script [parametri]
acel script va fi executat de shell-ul specificat pe prima pozitie a acestui apel. Mai precis,
in acest caz procesul shell in care se da aceasta comanda (la prompterul acestuia) va crea,
asemanator ca la prima forma de apel, un nou proces ce va executa, de aceasta data,
shell-ul specificat prin linia de comanda. Acest nou proces shell creat va rula in mod
neinteractiv si va executa linie cu linie comenzile din script-ul specificat.
2.4.3 Variabile de shell
O alta facilitate comuna tuturor interpretoarelor de comenzi UNIX este utilizarea de vari-
abile (similar ca in MS-DOS). Pentru a creste flexibilitatea sistemului este posibila definirea,
citirea si modificarea de variabile de catre utilizator.
Trebuie retinut faptul ca variabilele sunt numai de tip sir de caractere (exceptie facind
partial interpretorul csh).
Instructiunea de atribuire are forma:
UNIX>var =expr
unde var este un identificator (i.e., un nume) de variabila, iar expr este o expresie care
trebuie sa se evalueze la un sir de caractere.
Atentie: in operatia de atribuire a unei valori unei variabile, caracterele spatiu puse inainte
de ’=’ sau dupa ’=’ vor da eroare. Asadar, aveti grija sa nu puneti spatii!
Variabilele sunt pastrate intr-o zona de memorie a procesului shell respectiv, sub forma
de perechi nume =valoare.
78
Pentru a vedea ce variabile sunt definite, se foloseste comanda:
UNIX>set
care va afisa lista acestor perechi.
Comanda:
UNIX>var =
sau
UNIX>unset var
are ca efect nedefinirea variabilei var (adica acea variabila este stearsa din memorie si orice
referire ulterioara la ea va cauza afisarea unui mesaj de eroare).
Referirea la valoarea unei variabile (i.e., atunci cind avem nevoie de valoarea variabilei
intr-o expresie) se face prin numele ei precedat de simbolul ’$’, ceea ce cauzeaza substitutia
numelui variabilei prin valoarea ei in expresia in care apare.
De exemplu, comanda
UNIX>echo $var
are ca efect: se va afisa valoarea variabilei var. De observat diferenta fata de comanda
UNIX>echo var
al carui efect este acela de afisare a sirului de caractere “var”.
Exemplu. Iata citeva exemple de folosire a variabilelor in comenzi:
UNIX>v=a123b
are ca efect: variabilei cu numele vi se atribuie valoarea “a123b”.
UNIX>cat xy$v
are ca efect: este echivalenta cu comanda:
UNIX>cat xya123b
care va afisa continutul fisierului xya123b din directorul curent de lucru (in caz ca exista
acest fisier).
UNIX>v=zz$v
are ca efect: variabila veste modificata – mai precis, se observa ca are loc o operatie de
concatenare: la valoarea anterioara a variabilei vse adauga prefixul “zz”.
UNIX>v=
are ca efect: variabila vprimeste valoarea nula (este distrusa).
Important: pentru substitutia numelui unei variabile prin valoarea ei, interpretorul de
comenzi considera ca nume de variabila cel mai lung sir de caractere care incepe dupa
caracterul ’$’ si care formeaza un identificator.
Prin urmare, pentru substitutia numelui unei variabile prin valoarea ei, vor trebui folosite
79
caracterele ’{’ si ’}’ pentru a indica numele variabilei atunci cind acesta nu este urmat de
spatiu (i.e., caracterele SPACE sau TAB) sau alt caracter care nu formeaza un identificator.
Mai precis, daca se foloseste intr-o comanda o substitutie de forma: ${var}sir , atunci
se va substitui variabila cu numele var si nu cea cu numele varsir , cum s-ar fi intimplat
daca nu se foloseau acoladele.
Spre exemplu, comenzile:
UNIX>rad=/home/others/
UNIX>ls -l ${rad}so
au ca efect: se va lista continutul directorului /home/others/so.
Alte forme de substitutie:
a) ${var:-sir}
Efect: rezultatul expresiei este valoarea variabilei var, daca aceasta este definit˘a,
altfel este valoarea sir.
b) ${var:-}
Efect: rezultatul expresiei este valoarea variabilei var, daca aceasta este definit˘a, alt-
fel este afisat un mesaj standard de eroare care spune ca acea variabila este nedefinita.
c) ${var:=sir}
Efect: rezultatul expresiei este valoarea variabilei var, dupa ce eventual acesteia i se
asigneaza valoarea sir (asignarea are loc doar in cazul in care var era nedefinita).
d) ${var:?sir}
Efect: rezultatul expresiei este valoarea variabilei var, daca aceasta este definit˘a,
altfel este afisat mesajul sir (sau un mesaj standard de eroare, daca sir lipseste).
e) ${var:+sir}
Efect: daca variabila var este definita (are o valoare), atunci i se asigneaza valoarea
sir, altfel ramine in continuare fara valoare (deci asignarea are loc doar in cazul in
care var era deja definita).
O alta constructie speciala recunoscuta de interpretorul de comenzi este expresia
$(comanda ), sau echivalent `comanda `(unde caracterul ` este apostroful invers), al carei
efect este acela de a fi substituita, in linia de comanda sau contextul in care este folosita,
cu textul afisat pe iesirea standard prin executia comenzii specificate.
Spre exemplu, comanda:
UNIX>v=`wc -l fis.txt`
80
are ca efect: variabila vprimeste drept valoare iesirea standard a comenzii specificate intre
caracterele apostroafe inverse. In acest caz, va primi drept valoare numarul de linii ale
fisierului text fis.txt din directorul curent de lucru.
Iata si alte exemple pentru modul de interpretare de catre shell-ul bash al caracterelor
`. . . ` sau $( . . . ) :
UNIX>dir curent=$(pwd) ; ls $dir curent
are ca efect: variabila cu numele dir curent va primi ca valoare iesirea standard a comen-
zii pwd, care este tocmai numele directorului curent de lucru (specificat prin cale absoluta),
iar apoi se va lista continutul acestui director.
Iar succesiunea de comenzi:
UNIX>a=10
UNIX>a=`expr $a + 5`
UNIX>echo $a
are ca efect: se va afisa 15 (iar variabila ava avea in final valoarea 15).
Observatie:expr este o comanda care evalueaza expresii aritmetice ca si siruri de caractere
– consultati help-ul pentru detalii (cu comanda man expr).
Pe linga comanda set, mai sunt si alte comenzi pentru variabile, si anume:
Comanda de exportare:
UNIX>export var [var2 var3 ... ]
care are ca efect “exportul” variabilelor specificate in procesele fii ale respectivului
proces shell (in mod obisnuit variabilele nu sunt vizibile in procesele fii, ele fiind
locale procesului shell respectiv, fiind pastrate in memoria acestuia).
O alta forma de apel a comenzii export este:
UNIX>export var =valoare [var2 =valoare2 ... ]
care are ca efect atribuirea si exportul variabilei printr-o singura comanda.
Comanda de citire:
UNIX>read var [var2 var3 ... ]
care are ca efect citirea, de la intrarea standard stdin, de valori si atribuirea lor
variabilelor specificate.
Comanda de declarare read-only:
UNIX>readonly var [var2 var3 ... ]
care are ca efect declararea variabilelor specificate ca fiind read-only (i.e. ele nu mai
pot fi modificate dupa executia acestei comenzi, ci ramin cu valorile pe care le aveau
cind s-a executat aceasta comanda).
81
In terminologia UNIX, se folosesc termenii de variabila de shell si variabila de mediu,
cu semnificatii diferite: variabila de shell este o variabila accesibila doar procesului shell
curent, pe cind variabila de mediu este o variabila accesibila tuturor proceselor fii ale acelui
proces shell (i.e. este o variabila exportata).
Exista o serie de variabile ce sunt modificate dinamic de catre procesul shell pe parcursul
executiei de comenzi, cu scopul de a le pastra semnificatia pe care o au. Aceste vari-
abile dinamice, ce pot fi folosite in script-uri, in conformitate cu semnificatia lor, sunt
urmatoarele:
1) $0
Semnificatia: numele procesului curent (numele script-ului in care este referita).
2) $1,$2,...,$9
Semnificatia: parametrii cu care a fost apelat procesul curent (i.e. parametrii din
linia de apel in cazul unui script).
Observatie: un fisier de comenzi poate primi, la lansarea in executie, o serie de
parametri din linia de comanda. Acestia sint referiti in corpul fisierului prin vari-
abilele $1,...,$9. La fel ca in MS-DOS, si in UNIX interpretorul de comenzi pune
la dispozitia utilizatorului comanda interna shift, prin care se poate realiza o de-
plasare a valorilor parametrilor din linia de comanda: vechea valoare a lui $2 va fi
referita prin $1, vechea valoare a lui $3 va fi referita prin $2, ¸s.a.m.d., iar vechea
valoare a lui $1 se pierde). Comanda shift este utila pentru cazurile cind avem mai
mult de 9 parametri in linia de comanda, pentru a putea avea acces la toti acestia.
3) $#
Semnificatia: numarul parametrilor din linia de comanda (fara argumentul $0).
4) $*
Semnificatia: lista parametrilor din linia de comanda (fara argumentul $0).
5) $@
Semnificatia: lista parametrilor din linia de comanda (fara argumentul $0).
Observatie: diferenta dintre $@ si $* apare atunci cind sunt folosite intre ghilimele:
la substitutie "$*" produce un singur cuvint ce contine toti parametrii din linia de
comanda, pe cind "$@" produce cite un cuvint pentru fiecare parametru din linia de
comanda.
6) $$
Semnificatia: PID-ul procesului curent (i.e.,PID-ul shell-ului ce executa acel fisier
script).
Observatie: variabila $$ poate fi folosita pentru a crea fisiere temporare cu nume
unic, cum ar fi de exemplu numele /tmp/err$$.
82
7) $?
Semnificatia: codul returnat de ultima comanda shell executata.
Observatie: la fel ca in MS-DOS, si in UNIX fiecare comanda returneaza la terminarea
ei un cod de retur, care este fie 0 (in caz de succes), fie o valoare nenula – codul
erorii (in cazul unei erori).
8) $!
Semnificatia: PID-ul ultimului proces executat in background.
9) $-
Semnificatia: optiunile cu care a fost lansat procesul shell respectiv. Aceste optiuni
pot fi:
-x = modul xtrace de executie – afiseaza fiecare linie din script pe masura ce
este executata (astfel se afiseaza rezultatul interpretarii liniei – adica ceea ce se
executa efectiv);
-v = modul verbose de executie – afiseaza fiecare linie citita de shell (astfel se
afiseaza linia efectiv citita, inainte de a fi interpretata);
¸s.a.
Aceste optiuni se pot folosi cu comanda set in felul urmator:
set -v ; script
Aceasta optiune este utila pentru depanare: se seteaza intii optiunea -v si apoi
se executa fisierul script specificat in modul verbose.
set -x
Aceasta optiune este utila pentru a vedea istoricul ultimelor comenzi executate.
set -u
Aceasta optiune verifica daca variabilele au fost initializate, cauzind eroare la
substitutie in cazul variabilelor neinitializate.
set -n
Aceasta optiune cauzeaza citirea comenzilor ulterioare fara a fi si executate.
set -i
Folosind aceasta optiune shell-ul devine interactiv (optiunea este utila pentru
depanare).
Pentru dezactivarea acestor optiuni, se foloseste tot comanda set, cu +in loc de -,
si anume: set +o, unde oeste optiunea ce se doreste a fi dezactivata.
Comanda set cu parametri are ca efect atribuirea acestor parametri ca valori variabilelor
$1,$2, . . . , $9 (in functie de numarul acestor parametri). Spre exemplu, comanda:
UNIX>set a b c ; echo $3
83
are ca efect: determina crearea si initializarea variabilelor $1,$2,$3 respectiv cu valorile
a, b, c, iar apoi se afiseaza ’c’ (i.e., valoarea variabilei $3).
Iata un alt exemplu pentru modul de actiune al caracterelor `. . . ` recuperarea rezultat-
ului unei comenzi in variabilele $1,$2, . . . , $9:
UNIX>set `date` ; echo $6 $2 $3 $4
are ca efect: prima comanda determina initializarea variabilelor $1,$2, . . . , $9 cu cˆımpurile
rezultatului comenzii date (not˘a: cˆımpurile sunt cuvintele, adica sirurile de caractere ce
sunt separate prin spatii, din iesirea standard a comenzii). Iar a doua comanda va afisa
anul (cˆımpul 6), luna (cˆımpul 2), ziua (cˆımpul 3) si ora (cˆımpul 4) curente.
Studiat¸i toate optiunile comenzii set, care este o comanda interna si, prin urmare, detaliile
despre ea se pot obtine cu comanda help set sau cu comanda man set.
Iata alte exemple referitoare la caracterele ce au interpretare speciala din partea shell-ului:
Caracterul ’&’ (folosit pentru executie in background):
a) UNIX>who |grep an2&so
are ca efect: se va interpreta caracterul ’&’ drept executie in background.
b) UNIX>who |grep an2\&so
sau UNIX>who |grep "an2&so"
au ca efect: se va interpreta caracterul ’&’ prin el insusi si nu ca fiind executie in
background.
Caracterul ’$’ (folosit pentru substitutie de variabila):
a) UNIX>who |grep "an2$PATH"
are ca efect: se va interpreta caracterul ’$’ drept substitutie de variabila, si, ca
urmare, se va substitui variabila de mediu $PATH cu valoarea sa.
b) UNIX>who |grep ’an2$PATH’
are ca efect: se va interpreta caracterul ’$’ prin el insusi si nu ca substitutie de
variabila.
De asemenea, exista o serie de variabile de mediu predefinite (i.e., care au anumite sem-
nificatii fixate, aceleasi pentru toata lumea), si anume:
1) $HOME
Semnificatia: directorul home (directorul de login) al acelui utilizator.
2) $USER
Semnificatia: username-ul (numele de login) al acelui utilizator.
84
3) $LOGNAME
Semnificatia: la fel ca $USER.
4) $SHELL
Semnificatia: numele (calea absoluta a) shell-ului implicit al acelui utilizator.
5) $MAIL
Semnificatia: numele (calea absoluta a) sierului de posta electronica al acelui uti-
lizator (este utilizat de shell pentru a ne anunta daca a fost primit un nou mesaj,
necitit inca, adica acel binecunoscut mesaj “You have new mail in $MAIL” ce apare
dupa etapa de autentificare la login).
6) $PS1
Semnificatia: sirul de caractere al prompterului principal asociat shell-ului.
7) $PS2
Semnificatia: sirul de caractere al prompterului secundar asociat shell-ului.
(Nota: prompterul secundar este prompterul folosit pentru liniile de continuare ale
unei comenzi scrise pe mai multe linii.)
8) $TERM
Semnificatia: specifica tipul de terminal utilizat (vt100, vt102, xterm, ¸s.a.).
9) $PATH
Semnificatia: o lista de directoare in care shell-ul cauta fisierul corespunzator unei
comenzi tastate, ce a fost specificata doar prin nume, nu si prin cale (absoluta sau
relativa).
10) $CDPATH
Semnificatia: o lista de directoare in care shell-ul cauta directorul dat ca parametru
comenzii cd, in cazul cind acesta a fost specificat doar prin nume, nu si prin cale
(absoluta sau relativa); este similar ca $PATH pentru fisiere.
11) $IFS
Semnificatia: specifica multimea caracterelor ce sunt interpretate drept spatiu de
catre shell.
Mai sunt si alte variabile de mediu (lista tuturor variabilelor definite la un moment dat
este afisata de catre comanda set fara parametri).
Aceste variabile de mediu sunt initializate de catre procesul shell la deschiderea unei
sesiuni de lucru, cu valorile specificate in fisierele de initializare ale sistemului (despre
aceste fisiere am vorbit mai devreme). De fapt, aceste variabile sunt exportate de shell-ul
de login, dupa cum se poate constata consultind aceste siere de initializare.
85
2.4.4 Structuri de control pentru script-uri
Fiecare interpretor de comenzi furnizeaza o serie de structuri de control de nivel inalt, si
anume structurile de control alternative si repetitive uzuale din programarea structurata,
ceea ce confera fisierelor de comenzi o putere mult mai mare decit este posibil in fisierele
de comenzi din MS-DOS.
Structurile de control puse la dispozitie de interpretorul bash sint: for,while,until,
if, si case. Aceste structuri sunt comenzi interne ale shell-ului bash (prin urmare se pot
obtine informatii despre sintaxa si semantica lor cu comanda help).
I. Structurile repetitive
1) Bucla iterativa FOR – are urmatoarea sintax˘a:
for variabila [ in text ]
do
lista comenzi
done
sau
for variabila [ in text ] ; do lista comenzi ; done
Semantica comenzii for:
Port¸iunea text descrie o lista de valori pe care le ia succesiv variabila, si, pentru fiecare
asemenea valoare a lui variabila, se executa comenzile din lista comenzi.
Observatii:
a) In comenzile din corpul buclei for se poate folosi valoarea variabilei variabila.
b) Toate instructiunile, exceptind for,do si done, trebuie sa fie urmate de caracterul ’;’
sau de caracterul sfirsit de linie (newline).
c) Daca lipseste partea optionala (i.e., partea in text), atunci ca valori pentru variabila
se folosesc argumentele din variabila de shell $* (i.e., parametrii din linia de comanda).
Exemplu. Comanda:
for i in `ls -t`
do
echo $i
done
sau, echivalent:
86
for i in `ls -t` ; do echo $i ; done
are acelasi efect cu comanda ls -t (i.e., se afiseaza continutul directorului curent, sortat
dupa data ultimei modificari).
2) Bucla repetitiva WHILE – are urmatoarea sintax˘a:
while lista comenzi 1
do
lista comenzi 2
done
sau
while lista comenzi 1 ; do lista comenzi 2 ; done
Semantica comenzii while:
Se executa comenzile din lista comenzi 1 si daca codul de retur al ultimei comenzi din ea
este 0 (i.e. terminare cu succes), atunci se executa comenzile din lista comenzi 2 si se reia
bucla. Altfel, se termina executia buclei while.
Observatii:
a) Bucla while se executa repetitiv atit timp cit codul returnat de lista comenzi 1 este 0
(succes).
b) Toate instructiunile, exceptind while,do si done, trebuie sa fie urmate de caracterul
’;’ sau de caracterul sfirsit de linie (newline).
c) Adeseori lista comenzi 1 poate fi comanda: test argumente sau, echivalent: [
argumente ], care este o comanda ce exprima testarea unei conditii, dupa cum vom
vedea mai ˆıncolo.
Exemplu. Comanda:
while true
do
date;
sleep 60;
done
sau, echivalent:
while true ; do date; sleep 60; done
are ca efect: afiseaza in mod continuu pe ecran, din minut in minut, data si ora curenta.
3) Bucla repetitiva UNTIL are urmatoarea sintax˘a:
87
until lista comenzi 1
do
lista comenzi 2
done
sau
until lista comenzi 1 ; do lista comenzi 2 ; done
Semantica comenzii until:
Se executa comenzile din lista comenzi 1 si daca codul de retur al ultimei comenzi din ea
este diferit de 0 (i.e. terminare cu eroare), atunci se executa comenzile din lista comenzi 2
si se reia bucla. Altfel, se termina executia buclei until.
Observatii:
a) Bucla until se executa repetitiv atit timp cit codul returnat de lista comenzi 1 este
diferit de 0 (eroare), sau, cu alte cuvinte, bucla until se executa repetitiv pina cind codul
returnat de lista comenzi 1 este 0 (succes).
b) Toate instructiunile, exceptind until,do si done, trebuie sa fie urmate de caracterul
’;’ sau de caracterul sfirsit de linie (newline).
c) Adeseori lista comenzi 1 poate fi comanda de testare a unei conditii.
II. Structurile alternative
4) Structura alternativa IF are urmatoarea sintax˘a:
if lista comenzi 1
then
lista comenzi 2
[ else
lista comenzi 3 ]
fi
sau
if lista comenzi 1 ; then lista comenzi 2 ; [ else lista comenzi 3 ; ] fi
Semantica comenzii if:
Se executa comenzile din lista comenzi 1 si daca codul de retur al ultimei comenzi din
ea este 0 (i.e. terminare cu succes), atunci se executa comenzile din lista comenzi 2. Iar
altfel, se executa comenzile din lista comenzi 3 (daca este prezenta ramura else).
Observatii:
a) Ramura else este optionala.
88
b) Toate instructiunile, exceptind if,then,else si fi, trebuie sa fie urmate de caracterul
’;’ sau de caracterul sfirsit de linie (newline).
c) Adeseori lista comenzi 1 poate fi comanda de testare a unei conditii.
Important: Structura if are si o forma sintactica imbricata:
if lista comenzi 1
then
lista comenzi 2
elif lista comenzi 3
then
lista comenzi 4
elif lista comenzi 5
then
lista comenzi 6
...
else
lista comenzi N
fi
5) Structura alternativa CASE are urmatoarea sintax˘a:
case expresie in
sir valori 1 )lista comenzi 1 ;;
sir valori 2 )lista comenzi 2 ;;
...
sir valori N-1 )lista comenzi N-1 ;;
sir valori N )lista comenzi N
esac
Semantica comenzii case:
Daca valoarea expresiei expresie se gaseste in lista de valori sir valori 1 , atunci se executa
lista comenzi 1 si apoi executia comenzii case se termina. Altfel, daca valoarea expresiei
expresie se gaseste in lista de valori sir valori 2 , atunci se executa lista comenzi 2 si apoi
executia comenzii case se termina. Altfel, . . . ¸s.a.m.d.
Observatii:
a) Deci se intra in prima lista de valori cu care se potriveste, fara a se verifica unicitatea
(i.e., daca mai sunt si alte liste de valori cu care se potriveste). Iar apoi se iese direct afara,
fara a continua cu urmatoarele linii (desi nu se pune o comanda analoaga instructiunii
break de pe ramurile case ale instructiunii switch din limbajul C);
b) Ultima linie dinainte de esac nu este obligatoriu sa se termine cu caracterele ”;;” precum
celelalte linii;
c) Lista de valori sir valori X poate fi o enumerare de valori de forma:
89
valoare 1 |valoare 2 |... |valoare M
sau poate fi o expresie regulata, ca de exemplu:
case $opt in
-[ax-z] ) comenzi ;;
...
esac
ceea ce este echivalent cu:
case $opt in
-a|-x|-y|-z ) comenzi ;;
...
esac
Exemplu. Iata si un exemplu:
case $var1 in
*.c ) var2=`basename $var1 .c` ; gcc $var1 -o$var2 ;;
...
esac
echo Source $var1 was compiled into executable $var2.
Efect: daca variabila var1 are ca valoare “fisier.c” (i.e. numele unui fisier sursa), atunci
variabila var2 va avea ca valoare “fisier” si apoi fisierul sursa $var1 este compilat
obtinindu-se un fisier executabil cu numele $var2.
Observatie: comanda
UNIX>basename arg1 arg2
afiseaza in iesirea standard valoarea argumentului arg1 dupa ce se inlatura din el sufixul
arg2.
Important: aceste structuri de control fiind comenzi interne, puteti folosi comanda de help
pentru comenzi interne:
UNIX>help nume structura
pentru a afla mai multe detalii despre fiecare structura in parte.
90
2.4.5 Alte comenzi shell utile pentru script-uri
Comanda de testare a unei conditii este comanda (predicatul) test, avind forma:
test conditie
sau:
[conditie ]
unde expresia conditie poate fi:
1. o comparatie intre doua siruri de caractere (utilizind simbolurile “=” si “!=”):
test expr 1=expr 2
Efect: returneaza true (i.e., codul de retur 0) daca cele doua expresii au aceeasi
valoare, altfel returneaza false (i.e., cod de retur nenul);
test expr 1!= expr 2
Efect: returneaza true (i.e., codul de retur 0) daca cele doua expresii au valori
diferite, altfel returneaza false (i.e., cod de retur nenul).
2. conditii relationale: test val 1 -rel val 2
Efect: returneaza true (i.e., codul de retur 0) daca valoarea val 1este in relatia rel
cu valoarea val 2, unde rel este unul dintre operatorii relationali urmatori:
eq :equal (egal, adica =);
gt :greater-than (mai mare decit, adica >);
ge :greater-equal (mai mare sau egal cu, adica );
lt :less-than (mai mic decit, adica <);
le :less-equal (mai mic sau egal cu, adica ).
3. conditii referitoare la fisiere: test -opt nume fisier
Efect: returneaza true (i.e., codul de retur 0) daca sierul nume fisier satisface
optiunea de testare opt specificata, care poate fi una dintre urmatoarele:
e: testeaza daca exista un fisier de orice tip (i.e., obisnuit, director, fifo, fisier
special, etc.) avind numele nume fisier;
d: testeaza daca nume fisier este un director;
f: testeaza daca nume fisier este un fisier obisnuit;
p: testeaza daca nume fisier este un fisier de tip fifo;
b: testeaza daca nume fisier este un fisier de tip dispozitiv in mod bloc;
c: testeaza daca nume fisier este un fisier de tip dispozitiv in mod caracter;
s: testeaza daca fisierul nume fisier are continut nevid (i.e., are lungimea mai
mare decit 0);
91
r: testeaza daca fisierul nume fisier poate fi citit de catre utilizatorul curent
(i.e., daca acesta are drept de citire asupra fisierului);
w: testeaza daca fisierul nume fisier poate fi modificat de catre utilizatorul
curent (i.e., daca acesta are drept de scriere asupra fisierului);
x: testeaza daca fisierul nume fisier poate fi lansat in executie de catre utiliza-
torul curent (i.e., daca acesta are drept de executie asupra fisierului).
4. o expresie logica – negatie, conjunctie, sau disjunctie de conditii:
test !conditie 1
Efect: NOT – negatia conditiei conditie 1;
test conditie 1 -a conditie 2
Efect: AND – conjunctia conditiilor conditie 1si conditie 2 ;
test conditie 1 -o conditie 2
Efect: OR – disjunctia conditiilor conditie 1si conditie 2,
unde conditie 1si conditie 2 sunt conditii de oricare dintre cele patru forme de mai
sus.
Exemplu.Script-ul urmator
#!/bin/bash
for i in *
do
if test -f $i
then
echo $i
fi
done
are ca efect: se listeaza fisierele obisnuite din directorul curent.
Observatie: caracterul *joaca un rol special: in evaluare acest caracter se inlocuieste
cu numele oricarui fisier din directorul curent, cu exceptia acelora al caror nume incepe
cu caracterul punct “.”. Pentru ca *sa nu se evalueze in acest fel, ci sa se evalueze
prin el insusi, trebuie sa se utilizeze apostroafele: ’*’ ; reamintesc faptul ca am vazut
mai devreme un alt exemplu (si anume: who |grep ’an2$PATH’ ) in care apostroafele
determinau evaluarea caracterelor speciale prin ele insele.
Iata si alte comenzi (instructiuni) ce pot fi folosite in script-uri (sau la linia de comanda):
1. Comanda break, cu sintaxa:
break [n]
unde neste 1 in caz ca lipseste. Efect: se iese afara din nbucle do-done imbricate,
executia continuind cu urmatoarea instructiune de dupa done.
92
2. Comanda continue, cu sintaxa:
continue [n]
unde neste 1 in caz ca lipseste. Efect: pentru n= 1 se reincepe bucla curenta
do-done (de la pasul de reinitializare), respectiv pentru n > 1 efectul este ca si cum
s-ar executa de nori comanda continue 1.
3. Comanda exit, cu sintaxa:
exit [cod]
unde cod este 0 in caz ca lipseste. Efect: se termina (se opreste) executia script-ului
in care apare si se intoarce drept cod de retur valoarea specificata. (nota: executata
la prompterul shell-ului de login, va determina terminarea sesiunii de lucru.)
4. Comanda exec, cu sintaxa:
exec lista comenzi
Efect: se executa comenzile specificate fara a se crea o noua instanta de shell (astfel
shell-ul ce executa aceasta comanda se va “reacoperi” cu procesul asociat comenzii,
deci nu este reentrant).
5. Comanda wait, cu sintaxa:
wait pid
Efect: se intrerupe executia script-ului curent, asteptindu-se terminarea procesului
avind PID-ul specificat.
6. Comanda eval, cu sintaxa:
eval parametri
Efect: se evalueaza parametrii specificati.
7. Comanda export, cu sintaxa:
export variabile
Efect: se exporta variabilele de shell specificate.
8. Comanda trap, cu sintaxa:
trap comanda eveniment
Efect: cind se va produce evenimentul specificat (i.e., cind se va primi semnalul
respectiv), se va executa comanda specificata.
Evenimente (semnale) posibile:
semnalul 1 = hang-up signal;
semnalul 2 = interrupt signal (generat prin apasarea tastelor CTRL+C);
semnalul 3 = quit signal (generat prin apasarea tastelor CTRL+D);
semnalul 9 = kill signal (semnal ce “omoara” procesul);
semnalul 15 = semnal de terminare normala a unui proces;
93
¸s.a. (vom vedea mai multe detalii despre semnale UNIX in capitolul 4 din partea II
a manualului).
Exemplu. Iata si un exemplu de folosire a comenzii trap:
UNIX>trap ’rm /tmp/ps$$ ; exit’ 2
Efect: ulterior, cind se va primi semnalul 2 (i.e., la apasarea tastelor CTRL+C), se va sterge
fisierul temporar /tmp/ps$$ si apoi se va termina executia procesului respectiv (in cazul
de fata, ind vorba de shell-ul de login, se va inchide sesiunea de lucru).
Important: majoritatea acestor comenzi fiind comenzi interne, puteti folosi comanda de
help pentru comenzi interne:
UNIX>help comanda
pentru a afla mai multe detalii despre fiecare comanda in parte.
Recomandare: cititi pagina de manual a interpretorului de comnezi bash, ce este accesibila
cu comanda man bash.
Exemplu. Sa scriem un script care sa afiseze lista subdirectoarelor din directorul curent.
O posibila solutie este urmatoarea:
#!/bin/bash
for dir in *
do
if [ -d $dir ]
then
echo $dir
fi
done
Exemplu. Iata un alt exemplu de script al carui scop este acela de a apela programul de
dezarhivare potrivit pe baza sufixului din numele unui fisier arhivat, dat ca parametru:
#!/bin/bash
#Script pentru dezarhivare diverse arhive
if [ $# != 1 ] ; then
echo -e "\a\nUsage: $0 <arh-file>\n"
fi
if [ ! -f $1 ] ; then
echo "Error: $1 is not a file or does not exist at the given location."
exit
94
fi
case $1 in
*.tar ) tar -xf $1 ;;
*.tgz | *.taz ) tar -zxf $1 ;;
*.gz | *.Z ) gunzip $1 ;;
*.zip ) unzip $1 ;;
*.Z ) uncompress $1 ;;
* ) exit
esac
In continuare vom mai vedea citeva exemple de script-uri.
Exemplu.Script-ul urmator are scopul de a “face curatenie”: sterge fisierele bak (care au
numele terminat cu caracterul ~, si reprezinta o versiune mai veche a unui fisier) dintr-un
director dat ca parametru:
#!/bin/bash
# Utilitar de stergere a fisierelor bak (de forma *~) dintr-un director dat
if test $# = 0 ; then
echo Usage: $0 "<directory>"
exit
fi
# optiunea -rec este folosita de acest script pentru apel recursiv
if test $1 = -rec ; then
shift
else
if test ! -d $1 ; then
echo "Error: $1 is not a directory !"
exit
fi
echo -ne "\a\nDeleting bak files from directory $1 recursively (y/n) ? "
read rasp
if test $rasp != y ; then
echo "Cancelled !"
exit
fi
fi
# bucla de stergere
for i in $1/*~ $1/.*~
do
if test -f $i ; then
echo -n "Deleting bak file $i (y/n) ? "
read rasp
if test $rasp = y ; then
rm $i && echo "$i was deleted..."
fi
95
fi
done
# apelul recursiv
for i in $1/* $1/.*
do
if test "$i" = "$1/." -o "$i" = "$1/.." ; then
continue
fi
if test -d $i ; then
$0 -rec $i # apel recursiv
fi
done
Exemplu. Putem afisa numele de cont si numele real, sortate alfabetic, ale tuturor
utilizatorilor care au primit mesaje noi prin posta electronica cu urmatorul script:
#!/bin/bash
#Listeaza toti userii care au new mail, sortati dupa numele de cont
for username in `cut -f1 -d: /etc/passwd` ;
do finger -pm $username | grep "New mail" -B 15 | head -1 ;
done | sort | less
O alta facilitate a shell-ului bash este aceea de definire de functii shell, ce se apeleaza apoi
prin numele lor, ca orice comanda.
Iata un exemplu de definire a unei functii shell:
#De adaugat rindurile de mai jos in fisierul personal .bash profile
#Efectul: cind veti parasi mc-ul, directorul curent va fi ultimul director in
#care ati navigat in mc, in loc sa fie directorul din care ati intrat in mc.
function mc ()
{
MC=`/usr/bin/mc -P "$@"`
[ -n "$MC" ] && cd "$MC";
unset MC ;
}
declare -x mc
Observatie: datorita utilitatii acestei functii, ea a fost inclusa in fisierele de initializare
globale de catre administratorii serverului studentilor, fenrir, astfel ˆıncˆıt nu mai trebuie
adaugat in fisierul de initializare personal.
Iata inca un exemplu de script ce foloseste o functie:
96
#!/bin/bash
function cntparm ()
{
echo -e "$# params: $*\n"
}
cntparm "$*"
cntparm "$@"
Daca apelam acest script cu urmatoarea linie de comanda:
UNIX>./script a b c
1 parms: a b c
3 parms: a b c
mesajele afisate pe ecran ne demonstreaza diferenta dintre modul de evaluare al variabilelor
$* si $@ atunci cind sunt cuprinse intre ghilimele.
2.5 Exercit¸ii
Exercit¸iul 1. De cˆıte tipuri sunt comenzile UNIX?
Exercit¸iul 2. Ce este un shell UNIX?
Exercit¸iul 3. Care sunt comenzile de help? Ce cont¸in paginile de manual UNIX?
Exercit¸iul 4. Ce editoare de texte pentru UNIX cunoa¸stet¸i? Dar compilatoare pentru
limbajele C ¸si C++?
Exercit¸iul 5. Care sunt comenzile ce ofer˘a informat¸ii despre utilizatori?
Exercit¸iul 6. Care sunt comenzile ce ofer˘a informat¸ii despre terminale? Dar despre dat˘a
¸si timp?
Exercit¸iul 7. Care sunt comenzile ce ofer˘a informat¸ii despre procesele din sistem?
Exercit¸iul 8. Care sunt comenzile ce permit conectarea la un sistem UNIX pentru o sesiune
de lucru? Dar pentru deconectare?
Exercit¸iul 9. Cu ce comenzi putem scrie mesaje pe ecran, eventual destinate altor utiliza-
tori?
Exercit¸iul 10. Care sunt comenzile de arhivare a fi¸sierelor?
Exercit¸iul 11. Care sunt comenzile pentru pst˘a electronic˘a, transfer de fi¸siere, navigare
pe WWW, ¸si alte programe de INTERNET?
97
Exercit¸iul 12. Care sunt comenzile de c˘autare de ¸sabloane ˆın fi¸siere?
Exercit¸iul 13. Studiat¸i cu ajutorul comenzii man opt¸iunile ¸si efectul tuturor comenzilor
UNIX amintite ˆın sect¸iunea 2.1.
Exercit¸iul 14. Ce caracteristici au sistemele de fi¸siere din UNIX, ce le deosebesc de cele din
MS-DOS ¸si MS-Windows?
Exercit¸iul 15. De cˆıte tipuri sunt fi¸sierele ˆın UNIX?
Exercit¸iul 16. Care este structura sistemului de fi¸siere UNIX?
Exercit¸iul 17. Cu ce comand˘a se navigheaz˘a prin sistemul de fi¸siere? Dar pentru
vizualizarea unui director?
Exercit¸iul 18. Cˆıte moduri de specificare a numelor de fi¸siere exist˘a ˆın UNIX?
Exercit¸iul 19. Ce ˆınseamn˘a operat¸ie de montare a unui sistem de fi¸siere ¸si la ce este ea
util˘a? Care sunt comenzile pentru montare ¸si demontare de sisteme de fi¸siere?
Exercit¸iul 20. Care sunt comenzile ce ofer˘a informat¸ii despre terminale? Dar despre dat˘a
¸si timp?
Exercit¸iul 21. Cum se realizeaz˘a protect¸ia accesului la fi¸siere ˆın UNIX? Explicat¸i drepturile
de acces la fi¸siere.
Exercit¸iul 22. Care sunt comenzile ce permit modificarea drepturilor de acces, a propri-
etarului ¸si a grupului proprietar ale unui fi¸sier?
Exercit¸iul 23. Care sunt comenzile pentru directoare?
Exercit¸iul 24. Care sunt comenzile pentru operat¸iile uzuale asupra fi¸sierelor (i.e., creare,
¸stergere, copiere, mutare, etc.) ?
Exercit¸iul 25. Care sunt comenzile de c˘autare pentru fi¸siere?
Exercit¸iul 26. Care sunt comenzile de afi¸sare a fi¸sierelor?
Exercit¸iul 27. Cu ce comenzi putem obt¸ine diverse informat¸ii despre fi¸siere?
Exercit¸iul 28. Care sunt comenzile ce permit arhivarea ¸si comprimarea fi¸sierelor?
Exercit¸iul 29. Care sunt comenzile de c˘autare de ¸sabloane ˆın fi¸siere?
Exercit¸iul 30. Studiat¸i cu ajutorul comenzii man opt¸iunile ¸si efectul tuturor comenzilor
UNIX amintite ˆın sect¸iunea 2.2.
98
Exercit¸iul 31. Ce rol are interpretorul de comenzi (shell-ul) UNIX?
Exercit¸iul 32. Cˆıte shell-uri UNIX cunoa¸stet¸i?
Exercit¸iul 33. Care sunt formele generale de lansare ˆın execut¸ie a unei comenzi? Dar
pentru execut¸ie ˆın background?
Exercit¸iul 34. Care sunt formele de lansare ˆın execut¸ie secvent¸ial˘a, paralel˘a ¸si condit¸ional˘a
a unui grup de comenzi?
Exercit¸iul 35. Care sunt cele trei forme de specificare a unui nume de fi¸sier?
Exercit¸iul 36. Care sunt ¸sabloanele ce pot fi folosite pentru a specifica grupuri de nume
de fi¸siere?
Exercit¸iul 37. Care sunt perifericele I/O standard, ¸si ce se ˆınt¸elege prin redirectarea
acestora? Cum se realizeaz˘a redirectarea lor pentru o anumit˘a comand˘a?
Exercit¸iul 38. Ce ˆınseamn˘a ˆınl˘ant¸uirea de comenzi, ¸si cum se realizeaa practic?
Exercit¸iul 39. Care sunt fi¸sierele de init¸ializare, de configurare, ¸si respectiv de istoric ale
shell-ului bash? Cum se pot refolosi informat¸iile salvate ˆın istoric?
Exercit¸iul 40. Avem in directorul curent un fisier “tst”, continind 2 linii de text:
!@#$%^&*()
({}<>$^***
Ce se va afisa pe iesirea standard in urma executiei comenzii:
UNIX>cat tst|cut -d^ -f2|cut -d$ -f1|tail -1
Exercit¸iul 41. Avem in directorul curent trei fisiere cu numele 1, 2 si 3, continind fiecare
cite o linie de text specificata mai jos. Ce va afisa pe iesirea standard comanda urmatoare:
UNIX>cat 2 2 1 2 3 3 | grep ’#$@’| cut -f1 -d$ | tail 2 | cut -f1 -d@
Fisierul 1: Fisierul 2: Fisierul 3:
%#^%#$@ #$%@^&# #%#%@$%
Exercit¸iul 42. a se scrie comanda care afi¸seaz˘a numele ¸si UID-urile tuturor utilizatorilor
sistemului.
(Indicat¸ie: folosit¸i informat¸iile din fi¸sierul /etc/passwd .)
Exercit¸iul 43. a se scrie comanda care afi¸seaz˘a numele ¸si GID-urile tuturor grupurilor de
utilizatori ai sistemului.
(Indicat¸ie: folosit¸i informat¸iile din fi¸sierul /etc/group .)
99
Exercit¸iul 44. a se scrie comanda care va scrie ˆın fi¸sierul users-logati.txt,username-
urile tuturor utilizatorilor prezent¸i ˆın sistem la momentul execut¸iei comenzii, ˆın ordine
alfabetic˘a si unic˘a).
Exercit¸iul 45. a se scrie comanda care afi¸seaz˘a (ˆın mod unic) toate shell-urile folosite de
utilizatorii sistemului.
(Indicat¸ie: folosit¸i informat¸iile din fi¸sierul /etc/passwd .)
Exercit¸iul 46. a se scrie comanda care verific˘a dac˘a exist˘a mai mult de 2 sesiuni deschise
ale unui utilizator specificat ¸si ˆın caz afirmativ a se afi¸seze un mesaj informativ.
Exercit¸iul 47 . Testat¸i ce se ˆıntˆımpl˘a cˆınd avet¸i ˆın directorul home ambele fi¸siere de
init¸ializare .profile ¸si .bash profile. Cum sunt ele executate, ˆın ce ordine sau care
dintre ele? Ce se ˆıntˆımpl˘a dac˘a avet¸i doar fi¸sierul .profile, nu ¸si .bash profile, sau
invers?
Exercit¸iul 48 . Care vor efectiv cele 3 dispozitive I/O standard ˆın timpul execut¸iei unei
comenzi (i.e., care vor fi efectele redirect˘arilor), pentru fiecare din liniile de comand˘a de
mai jos?
UNIX>comanda 2>&1 >fisier
UNIX>comanda 2>>&1 >fisier
UNIX>comanda 2>&1 >>fisier
UNIX>comanda 2>>&1 >>fisier
UNIX>comanda >fisier 2>&1
UNIX>comanda >fisier 2>>&1
UNIX>comanda >>fisier 2>&1
UNIX>comanda >>fisier 2>>&1
Ce se ˆıntˆımpl˘a de fapt ˆın fiecare caz? Se pierde ceva, ¸si dac˘a da, atunci ce anume?
Exercit¸iul 49. Ce sunt procedurile shell (script-urile) ? Care sunt modurile de lansare a
lor ˆın execut¸ie?
Exercit¸iul 50. Ce sunt variabilele de shell? Cum li se atribuie valori?
Exercit¸iul 51. Cum se realizeaz˘a referirea la valoarea unei variabile? Cˆıte forme de
substitut¸ie exist˘a?
Exercit¸iul 52. Cum interpreteaz˘a shell-ul construct¸iile `...` sau $(...) ?
Exercit¸iul 53. Care sunt variabilele de shell dinamice cu semnificat¸ie special˘a?
Exercit¸iul 54. Ce ˆınseamn˘a variabil˘a de mediu? Cum se realizeaz˘a exportul variabilelor?
Exercit¸iul 55. Care sunt variabilele de mediu predefinite?
Exercit¸iul 56. Descriet¸i sintaxa ¸si semantica structurilor repetitive (for,while ¸si until).
100
Exercit¸iul 57. Descriet¸i sintaxa ¸si semantica structurilor alternative (if ¸si case).
Exercit¸iul 58. Care sunt principalele opt¸iuni ale comenzii test?
Exercit¸iul 59. Ce realizeaz˘a comenzile break,continue,exit,wait,exec, ¸si trap ?
Exercit¸iul 60. Scriet¸i un script care a afi¸seze permisiunile tuturor fi¸sierelor ¸si subdirec-
toarelor (recursiv) din directorul dat ca argument.
(Indicat¸ie: folositi comenzile find si stat.)
Exercit¸iul 61. Ce se va afi¸sa pe iesirea standard ˆın urma execut¸iei script-ului de mai jos?
#!/bin/bash
a=3
b=5
c=$a+$b
echo ’suma este $c’ </dev/null
Exercit¸iul 62. Ce se va afi¸sa pe iesirea standard ˆın urma execut¸iei script-ului de mai jos?
#!/bin/true
arhiva=YES
case $1 in
*.tar ) tar -vxf $1;;
*.tgz|*.taz ) tar -vzxf $1;;
*.gz|*.Z ) gunzip $1;;
* ) arhiva=NO
esac
[ $arhiva != NO ] && echo "Ok" || echo ’Nu
Exercit¸iul 63. Fie scriptul urm˘ator:
#!/bin/shellulmeu
gata=0
until [ $gata != 0 -o $# == 0 ]
do
echo $1 ; ls -lA $1
echo Continuati? (y/n)
read x
[ x == n ] && gata=1
shift
done
Ce va produce acest script dac˘a este apelat cu linia urm˘atoare:
UNIX>cd ; . script mail /etc ./html
Exercit¸iul 64. Care este efectul script-ului de mai jos?
101
#!/bin/bash
F=0
for f in $(ls -Al /bin/f*)
do
[ -f $f ] && F=$(expr $F + 1)
done
echo $F
Exercit¸iul 65. Care este efectul script-ului urm˘ator?
#/bin/bash
nr=0
while [ $1 ]
do
nr=$(expr $nr + 1)
echo ${nr}: $1
shift
done
echo "-----"
Exercit¸iul 66. Fie script-ul bash de mai jos.
while test $# -ge 1 ; do
case $1 in
n ) N=$2; shift;;
u ) U=$2; shift;;
esac
shift
done
echo -n $N && echo $U
Ce va afisa acest script pe iesirea standard daca se apeleaza cu linia urmatoare:
UNIX>./script n u u i
Exercit¸iul 67. Scriet¸i un script care s˘a efectueze calculul puterii n la m.
Exercit¸iul 68. Scriet¸i un script care s˘a efectueze calculul recursiv al factorialului.
Exercit¸iul 69. Scriet¸i un script care s˘a afi¸seze utilizatorii din anul 2 care au pagini web
locale.
(Indicat¸ie: testat¸i existent¸a ¸si vizibilitatea pentru toat˘a lumea a unuia dintre fi¸sierele
index.htm sau index.html sau index.php, care trebuie s˘a se afle ˆın subdirectorul html
din directorul home al utilizatorului.)
Exercit¸iul 70. Scriet¸i un script care s˘a simuleze comanda du : a afi¸seze suma (ˆın octet¸i ¸si
ˆın megaoctet¸i) a dimensiunilor fi¸sierelor obi¸snuite din directorul curent.
Exercit¸iul 71. Scriet¸i un script care s˘a decid˘a dac˘a un ¸sir oarecare de caractere este num˘ar.
(Indicat¸ie: folosit¸i comanda grep cu expresii regulate ca argument.)
102
Exercit¸iul 72. Scriet¸i un script care s˘a produc˘a acela¸si efect cu comanda ps, dar f˘ar˘a a
utiliza comanda ps; nu este necesar˘a implementarea tuturor opt¸iunilor comenzii ps.
(Indicat¸ie: utilizati informatiile din directorul /proc.)
Exercit¸iul 73 .Script pentru automatizarea procesului de dezvoltare de programe C
Scriet¸i un script care s˘a v˘a ajute la scrierea programelor ˆın C, prin care s˘a se automatizeze
ciclul:
modificare surs˘a compilare testare (execut¸ie).
Cu alte cuvinte, script-ul va trebui s˘a lanseze editorul de texte preferat pentru fi¸sierul
program.c specificat ca parametru ˆın linia de comand˘a (sau citit de la tastatur˘a, ˆın caz
contrar), apoi a interogheze utilizatorul dac˘a dore¸ste lansarea compilatorului ¸si ˆın caz
afirmativ s-o fac˘a (fi¸sierul executabil a aib˘a numele program, deci f˘ar˘a sufixul “.c”),
apoi dac˘a sunt erori de compilare (lucru observabil prin erorile de compilare afi¸sate de
compilator) s˘a reia ciclul de la editare (bineˆınt¸eles cu o pauz˘a pentru ca utilizatorul s˘a
aib˘a timp s˘a citeasc˘a erorile afi¸sate pe ecran), iar dac˘a nu sunt erori la compilare, a
interogheze utilizatorul dac˘a dore¸ste testarea (execut¸ia) acelui program ¸si ˆın caz afirmativ
a execute acel fi¸sier executabil rezultat prin compilare.
Deci la fiecare pas s˘a fie o interogare a utilizatorului dac˘a dore¸ste s˘a continue cu urm˘atorul
pas!
(Observat¸ie: acest script ˆıl vet¸i putea folosi la urm˘atoarele lect¸ii, cˆınd vom trece la pro-
gramarea ˆın C sub Linux.)
103
Partea II
Programare concurent˘a ˆın Linux
104
ˆ
In aceast˘a a doua parte a manualului vom aborda programarea de sistem ˆın limbajul de
programare C pentru sistemele de operare din familia UNIX, ˆın particular pentru Linux.
Mai precis, obiectivul principal al acestei p˘art¸i a manualului este acela de a v˘a ˆınat¸a
conceptele fundamentale ale program˘arii concurente / paralele, adic˘a al scrierii de programe
ce vor fi executate mai multe “ˆın paralel” (i.e. concomitent, ˆın acela¸si timp), ˆıntr-un mediu
concurent¸ial: pe parcursul execut¸iei lor, programele concureaz˘a unele cu altele pentru
resursele sistemului de calcul (procesor, memorie, periferice de intrare–ie¸sire), puse la
dispozit¸ia lor ¸si gestionate de c˘atre sistemul de operare.
Am ales ca variana de tratare a acestui subiect, al program˘arii concurente, a utilizez
sistemul Linux ca suport, limbajul C ca limbaj de programare, ¸si utilizarea ˆın programe
a interfet¸ei clasice (i.e., ˆın mod text) cu utilizatorul, deoarece, conform celor explicate
ˆın prefat¸a manualului, acesta este cel mai adecvat cadru de predare al program˘arii con-
curente, pentru a permite concentrarea atent¸iei asupra aspectelor referitoare la execut¸ia
mai multor programeˆın regim concurent¸ial de folosire a resurselor calculatorului, ar˘a s˘a ne
distrag˘a atent¸ia aspectele referitoare la interfat¸a cu utilizatorul a programelor respective.
Primul capitol al acestei p˘art¸i a manualului trateaz˘a gestiunea fi¸sierelor prin program ¸si
accesul ˆın mod concurent sau exclusiv la ¸siere.
Al doilea capitol trateaz˘a gestiunea proceselor, pe lˆıng˘a conceptul de fi¸sier, procesul fi-
ind cel˘alalt concept fundamental al UNIX-ului, ce se refer˘a la execut¸ia unui program.
Sunt prezentate operat¸iile de baz˘a cu procese: crearea proceselor, sincronizarea lor,
“reacoperirea” lor (i.e., ˆınc˘arcarea pentru execut¸ie a unui fi¸sier executabil) ¸si tratarea
except¸iilor prin intermediul semnalelor UNIX.
Al treilea capitol abordeaz˘a mecanismele disponibile ˆın UNIX pentru comunicat¸ia ˆıntre
procese, descriind am˘anunt¸it dou˘a astfel de mecanisme: canalele de comunicat¸ie interne
(pipe-urile) ¸si canalele de comunicat¸ie externe (fifo-urile).
Bibliografie util˘a pentru studiu individual suplimentar: [1], [5], [6], [7], [10].
105
Dezvoltarea aplicat¸iilor C sub Linux
Introducere
Sistemul de operare Linuxdispune de utilitare performante destinate dezvoltarii de aplicat¸ii
C. Dintre acestea un rol aparte ˆıl au:
compilatorul de C, gcc (acronim ce provine de la GNU C Compiler);
link-editorul, ld ;
depanatorul, gdb (acronim ce provine de la GNU DeBugger) ;
bibliotecarul (= utilitarul pentru construct¸ia bibliotecilor), ar ;
utilitarul pentru construirea fisierelor proiect, make ;
editorul emacs (tot o aplicat¸ie din proiectul GNU), cu puternice facilit˘at¸i de integrare
cu compilatorul gcc ¸si depanatorul gdb.
Limbajul C ofer˘a suportul cel mai adecvat dezvoltarii aplicat¸iilor sub Linux. Acest lucru
este motivat ˆın principal de modul convenabil (din punctul de vedere al programatorului)
ˆın care se face accesul la serviciile sistemului de operare – funct¸iile (apelurile) sistem.
Aceste funct¸ii permit o multitudine de operat¸ii referitoarela lucrul cu fi¸siere, alocarea
memoriei, controlul proceselor, etc. Mai mult, fiind scrise in C, funct¸iile sistemului de
operare au un mecanism de apelare comod, similar oric˘arei rutine scris˘a de utilizator.
Crearea unei aplicat¸ii C simple
Dezvoltarea unei aplicat¸ii C presupune parcurgerea urm˘atoarelor etape:
1. editarea textului surs˘a
Pentru aceasta se poate folosi un editor de texte de tipul joe,vim,pico sau mcedit
(editorul intern al programului mc).
Fi¸sierul creat trebuie s˘a aib˘a extensia “.c”, extensie care prin convent¸ie este atribuit˘a
surselor scrise ˆın limbajul C.
a presupunem c˘a edit˘am uratorul fi¸sier surs˘a C:
106
/*hello.c*/
#include <stdio.h>
int main()
{
printf("Hello world!\n");
return 0;
}
Dup˘a editarea textului surs˘a, ˆıl vom salva ˆıntr-un fi¸sier denumit hello.c.
2. compilarea programului ¸si editarea de leaturi
Se face cu compilatorul gcc astfel:
UNIX>gcc hello.c
se va obt¸ine fisierul executabil cu numele implicit a.out. Sau, cu
UNIX>gcc -o hello hello.c
se va obt¸ine fisierul executabil cu numele hello.
3. lansarea ˆın execut¸ie
Se face fie specificˆınd numele complet (i.e., calea absolut˘a) a executabilului, de ex-
emplu $HOME/programe c/hello, e specificˆınd numele prin cale relativ˘a al exe-
cutabilului, de exemplu, din directorul ˆın care se g˘ase¸ste ca director curent de lucru,
ˆıi putem specifica doar numele:
UNIX>./hello
Observat¸ie: comanda gcc hello.c -o hello semnific˘a compilarea ¸si link -editarea (i.e.,
rezolvarea apelurilor de funct¸ii) fisierului surs˘a hello.c, generˆandu-se un executabil al
carui nume este specificat prin optiunea -o. De fapt, se execut˘a ˆın mod transparent
urm˘atoarea secvent¸˘a de operat¸ii:
preprocesorul expandeaz˘a macro-urile ¸si include fi¸sierele header corespunz˘atoare;
prin compilare, se genereaz˘a cod obiect (i.e., fi¸siere obiect, cu extensia “.o”);
editorul de leg˘aturi ld rezolv˘a apelurile de funct¸ii ¸si creeaz˘a executabilul din fi¸sierele
obiect.
Atent¸ie: a¸sadar compilatorul gcc apeleaz˘a automat ¸si editorul de leg˘aturi, de aceea etapa
de editare a leg˘aturilor poate fi transparent˘a pentru utilizator, ˆıns˘a acest lucru nu este
obligatoriu.
ˆ
Intr-adev˘ar, secvent¸a de mai sus poate fi parcurs˘a ¸si manual:
UNIX>gcc -E hello.c -o hello.cpp
Efect: se execut˘a doar preprocesarea, generˆandu-se fi¸sierul intermediar hello.cpp;
examinat¸i-i cont¸inutul!
107
UNIX>gcc -x cpp-output -c hello.cpp -o hello.o
Efect: se genereaz˘a un fi¸sier obiect utilizˆand un fi¸sier surs˘a deja preprocesat.
UNIX>gcc hello.o -o hello
Efect: fi¸sierul obiect este link -editat ¸si se creeaz˘a executabilul.
Utilizarea compilatorului
Principalele funct¸ii ale compilatorului de C sunt urm˘atoarele :
verifia corectitudinea codului sura C;
genereaz˘a instruct¸iuni ˆın limbaj ma¸sin˘a pentru codul surs˘a C;
grupeaz˘a instruct¸iunile ˆıntr-un modul obiect care poate fi prelucrat de editorul de
leg˘aturi.
Lansarea compilatorului se fac cu comanda gcc, a c˘arei sintax˘a este:
UNIX>gcc -optiuni fisier sursa1 [fisier sursa2 ... ]
Efectul ei const˘a ˆın urm˘atoarele:
Compilatorul creeaz˘a ate un fi¸sier obiect pentru fiecare fi¸sier surs˘a, iar editorul de leg˘aturi
construie¸ste automat fi¸sierul executabil din fi¸sierele obiect create (dac˘a nu au fost erori la
compilare ¸si dac˘a nu s-a specificat altfel).
Numele fi¸sierelor obiect sunt acelea¸si cu numele surselor, dar au extensia “.o”.
Numele implicit al fi¸sierului executabil este a.out, dar a¸sa cum am azut el poate fi
modificat (cu opt¸iunea -o).
Cele mai frecvent utilizate opt¸iuni ale compilatorului sunt prezentate ˆın tabelul de mai
jos:
Opt¸iune Efect
-o nume fi¸sierul executabil se va numi nume ¸si nu a.out
-c suprim˘a editarea de leg˘aturi, se creeaz˘a doar fi¸siere obiect
-w suprim˘a toate avertismentele ¸si mesajele de informare
-Wall afi¸seaz˘a toate avertismentele ¸si mesajele de informare
-std genereaz˘a avertismente pentru cod care nu este standard ANSI C
Alte opt¸iuni utile ale compilatorului:
108
1. Includerea de fi¸siere header ¸si de biblioteci:
Opt¸iune Efect
-Idirector fi¸sierele header sunt autate ¸si ˆın directorul director
-Ldirector bibliotecile sunt autate ¸si ˆın directorul director
-lnume link-editeaa biblioteca nume
-static link-editeaa static
Exemplu:
UNIX>gcc myapp.c -I/home/so/include -L/home/so/libs -lmy \
-static -o myapp
Observat¸ie: ˆın mod implicit, compilatorul gcc link-editeaz˘a dinamic (i.e., la exe-
cutie), utilizˆand biblioteci partajate (shared libraries); extensia librariilor partajate
este .so.
Prin comanda de mai sus, se cere link -editarea ˆın mod static a bibliotecii cu numele
libmy.so. Se garanteaz˘a astfel executarea aplicat¸iei ¸si pe un sistem pe care nu este
instalat˘a biblioteca ˆın cauz˘a, cu pret¸ul cre¸sterii dimensiunii finale a executabilului.
2. Optimizarea codului:
(optimizarea codului constituie o ˆıncercare de a ˆımbun˘at˘at¸i performant¸ele progra-
mului. Atent¸ie: este vorba aici de “compiler magic” ¸si nu de eficient¸a algoritmilor
utilizat¸i ˆın faza de proiectare a aplicat¸iei!)
Opt¸iune Efect de optimizare
-O0 nu optimizeaz˘a codul generat
-Onspecifica un nivel nde optimizare, cu n= 1,2,3
Pentru uzul general se recomand˘a utilizarea opt¸iunii -O2.
3. Depanarea programului
Opt¸iune Efect
-g include ˆın executabil informat¸ii standard utile la depanare
-ggdb include ˆın executabil informat¸ii utilizabile doar de gdb
Interpretarea mesajelor compilatorului
Mesajele compilatorului au urm˘atoarea form˘a:
nume fisier: nr linie : severitate : mesaj
Severitatea mesajului specific˘a gravitatea erorii. Ea poate fi:
informativ˘a : pentru o act¸iune benign˘a;
avertisment : pentru un cod care nu este incorect dar poate fi ˆımbun˘at˘at¸it;
109
eroare : pentru un cod incorect; compilarea continu˘a, dar nu este creat fi¸sier obiect;
eroare fatal˘a : pentru un cod a c˘arui incorectitudine nu poate fi toleraa; compilarea
se opre¸ste ¸si bineˆınt¸eles nu este creat fi¸sierul obiect.
Depanatorul GNU
Depanatorul gdb (GNU db) este programul de depanare utilizat ˆın Linux.
Depanarea necesit˘a compilarea codului surs˘a cu optiunea -g (creˆandu-se astfel o tabel˘a
de simboluri ˆımbog˘at¸it˘a); informatii specifice gdb se includ in executabil, la compilare, cu
opt¸iunea -ggdb.
Pornirea depanatorului se realizeaz˘a cu comanda:
UNIX>gdb -q myprog [corefile]
Utilizarea fisierului core este optionala; el imbunatateste funct¸ionalit˘at¸ile depanatorului
gdb. Optiunea -q (quiet) inlatura mesajele privind licentierea. O optiune utila este si -d,
cu care se poate stabili directorul de lucru.
Dup˘a ce am pornit depanatorul cu comanda de mai sus, el afi¸seaz˘a un prompter de forma
(gdb), la care a¸steapt˘a introducerea de comenzi.
Pentru a porni sesiunea de depanare, se utilizeaza comanda run (accepta si argumente,
pe care le paseaza programului):
(gdb) run [parametri]
ˆ
In plus, valoarea variabilei $SHELL determina valorile variabilelor de mediu in care se
executa programul. Modificarea argumentelor din linia de comand˘a a programului ¸si a
valorilor variabilelor de mediu, dupa ce a fost pornita sesiunea de depanare, se realizeaza
cu comenzile:
(gdb) set args arg1 arg2 . . .
¸si respectiv
(gdb) set environment env1 env2 . . .
Pentru a inspecta secvent¸a de execut¸ie a funct¸iilor programului, se folose¸ste comanda
backtrace.
Cu comanda list [m,n] se afiseaza portiuni din codul surs˘a.
110
Valoarea unei expresii legale este afisata cu comanda print; de exemplu:
(gdb) print i //valoarea varabilei i
(gdb) print array[i] //valoarea componentei i din tabloul array
(gdb) print array@5 //adresele de memorie ale primelor 5 locatii
(gdb) print array[0]@5 //valorile pastrate in primele 5 locatii
Observat¸ie: daca print afiseaza valoarea unei expresii, eventualele efecte colaterale (side-
effects) ale expresiei se regasesc in programul depanat.
Valoarea unei variabile se poate modifica cu comanda:
(gdb) set variable i=10
Tipul unei expresii este afisat de catre comanda whatis; de exemplu:
(gdb) whatis i
(gdb) whatis array
(gdb) whatis function name
Not˘a:whatis afiseaza tipul unui pointer; informatii despre tipul de baza al unui pointer
se obtin cu comanda ptype.
Comanda break creeaz˘a un punct de ˆıntrerupere (breakpoint) al execut¸iei programului:
(gdb) break <filename:line number>
(gdb) break <filename:function name>
Se pot crea ¸si puncte de ˆıntrerupere condit¸ionat˘a de valoarea (nenul˘a) a unei expresii:
(gdb) break 25 if i==15
Continuarea executiei, dupa atingerea unui punct de intrerupere, se realizeaza cu comanda
continue. Pentru a sterge un punct de intrerupere se utilizeaza delete; punctul de
intrerupere poate fi doar dezafectat daca se utilizeaza disable (respectiv enable pentru
reactivare). Informatii despre punctele de intrerupere setate se obtin cu comanda info
breakpoints.
Comanda step executa cate o instructiune. La intalnirea unui apel de functie, step
paseste in respectiva functie; pentru a executa intr-un pas o functie se utilizeaza comanda
next.
Comanda call function name(arguments)apeleaza si executa o functie. Comanda
finish termina executia functiei curente si afiseaza valoarea returnata, iar comanda
return value opreste executia functiei curente, returand valoarea precizata.
111
Capitolul 3
Gestiunea fi¸sierelor
3.1 Primitivele I/O pentru lucrul cu fi¸siere
1. Introducere
2. Principalele primitive I/O
3. Funct¸iile I/O din biblioteca standard de C
3.1.1 Introducere
In UNIX/Linux functiile utilizate de sistemul de gestiune a fisierelor pot fi clasificate in
urmatoarele categorii:
1. primitive de creare de noi fisiere: mknod,creat,mkfifo, etc.;
2. primitive de accesare a fisierelor existente: open,read,write,lseek,close;
3. primitive de manipulare a i-nodului: chdir,chroot,chown,chmod,stat,fstat;
4. primitive de implementare a pipe-urilor: pipe,dup;
5. primitive de extindere a sistemului de fisiere: mount,umount;
6. primitive de schimbare a structurii sistemului de fisiere: link,unlink.
112
In literatura de specialitate despre UNIX, aceste functii sunt denumite apeluri sistem
(system calls).
O observatie generala este aceea ca in caz de eroare toate aceste primitive returneaza
valoarea -1, precum si un numar de eroare in variabila globala errno.
3.1.2 Principalele primitive I/O
In continuare vom trece in revista principalele apeluri sistem referitoare la fisiere:
1) Crearea de fisiere ordinare:
Se face cu ajutorul functiei creat. De asemenea, se mai poate face si cu functia open,
folosind un anumit parametru. Interfata functiei creat este urmatoarea:
int creat(char* nume cale, int perm acces)
unde:
nume cale = numele fisierului ce se creeaza;
perm acces = drepturile de acces pentru fisierul ce se creeaza;
iar valoarea int returnata este descriptorul de fisier deschis, sau -1 in caz de eroare.
Efect: in urma executiei functiei creat se creeaza fisierul specificat (sau, in caz ca deja
exista acel fisier, atunci el este trunchiat la zero, pastrindu-i-se drepturile de acces pe care
le avea) si este deschis in scriere.
2) Transmiterea mastii drepturilor de acces la crearea unui fisier:
Se face cu ajutorul functiei umask.
3) Controlul dreptului de acces la un fisier:
Se face cu ajutorul functiei access. Interfata functiei access este urmatoarea:
int access(char* nume cale, int drept)
unde:
nume cale = numele fisierului;
drept = dreptul de acces ce se verifica, si anume poate lua urmatoarele valori:
1 - se verifica daca este setat dreptul de executie;
2 - se verifica daca este setat dreptul de scriere;
4 - se verifica daca este setat dreptul de citire;
0 - se verifica daca fisierul exista;
113
iar valoarea int returnata este 0, daca accesul verificat este permis, respectiv -1 in caz
de eroare.
4) Deschiderea unui fisier:
Se face cu ajutorul functiei open. Interfata functiei open este urmatoarea:
int open(char* nume cale, int tip desch, int perm acces)
unde:
nume cale = numele fisierului ce se deschide;
perm acces = drepturile de acces pentru fisier (utilizat numai in cazul crearii acelui
fisier);
tip desch = specifica tipul deschiderii, fiind o combinatie (i.e., disjunctie logica pe bit¸i)
a urmatoarelor constante simbolice:
O RDONLY - deschidere pentru citire;
OWRONLY - deschidere pentru scriere;
ORDWR - deschidere pentru citire si scriere;
OAPPEND - pozitioneaza cursorul la sfirsitul fisierului, astfel incit orice scriere in el
se adauga la sfirsitul lui;
O CREAT - creare fisier (daca deja exista, este trunchiat la zero);
OTRUNC - daca fisierul exista, este trunchiat la zero;
OEXCL - open exclusiv: daca fisierul exista si este setat O CREAT, atunci se re-
turneaza eroare;
iar valoarea int returnata este descriptorul de fisier deschis, sau -1 in caz de eroare.
5) Inchiderea unui fisier:
Se face cu ajutorul functiei close. Interfata functiei close este urmatoarea:
int close(int df)
unde:
df = descriptorul de fisier (cel returnat de functia open);
iar valoarea int returnata este 0, daca inchiderea a reusit, respectiv -1 in caz de eroare.
6) Citirea dintr-un fisier:
Se face cu ajutorul functiei read.
Interfata functiei read este urmatoarea:
int read(int df, char* buffer, unsigned nr oct)
unde:
df = descriptorul fisierului din care se citeste (cel returnat de functia open);
buffer = adresa de memorie la care are loc depunerea octetilor cititi;
114
nr oct = numarul de octeti de citit din fisier;
iar valoarea int returnata este numarul de octeti efectiv cititi, daca citirea a reusit (chiar
si partial), si -1 in caz de eroare.
Observatie: numarul de octeti efectiv cititi poate fi inclusiv 0, daca la inceputul citirii
fisierul este pe pozitia EOF.
Efect: prin executia functiei read se incearca citirea a nr oct octeti din sier incepind de
la cursor (i.e., pozitia curenta in fisier). Cererea de citire se termina intr-una din situatiile
urmatoare:
a) s-au citit efectiv nr oct octeti din fisier;
b) fisierul nu mai contine date;
c) apare o eroare in citirea datelor din fisier sau in copierea lor la adresa de memorie
specificata.
La sfirsitul citirii cursorul va fi pozitionat pe urmatorul octet dupa ultimul octet efectiv
citit.
7) Scrierea intr-un fisier:
Se face cu ajutorul functiei write. Interfata functiei write este urmatoarea:
int write(int df, char* buffer, unsigned nr oct)
unde:
df = descriptorul fisierului din care se citeste (cel returnat de functia open);
buffer = adresa de memorie al carei continut se scrie in fisier;
nr oct = numarul de octeti de scris in fisier;
iar valoarea int returnata este numarul de octeti efectiv scrisi, daca scrierea a reusit (chiar
si partial), si -1 in caz de eroare.
Efect: prin executia functiei write se incearca scrierea a nr oct octeti incepind de la
cursor (i.e., pozitia curenta in fisier).
La sfirsitul scrierii cursorul va fi pozitionat pe urmatorul octet dupa ultimul octet efectiv
scris.
8) Pozitionarea cursorului (i.e., ajustarea deplasamentului) intr-un fisier:
Se face cu ajutorul functiei lseek. Interfata functiei lseek este urmatoarea:
long lseek(int df, long val ajust, int mod ajust)
unde:
df = descriptorul sierului ce se pozitioneaza;
val ajust = valoarea de ajustare a deplasamentului;
mod ajust = modul de ajustare, indicat dupa cum urmeaza:
0 - ajustarea se face fata de inceputul fisierului;
115
1 - ajustarea se face fata de deplasamentul curent;
2 - ajustarea se face fata de sfirsitul fisierului;
iar valoarea int returnata este noul deplasament in fisier (ˆıntotdeauna, fata de inceputul
fisierului), sau -1 in caz de eroare.
Maniera uzual˘a de prelucrare a unui fi¸sier const˘a ˆın urm˘atoarele: deschiderea fi¸sierului, o
bucl˘a de parcurgere a lui cu citire/scriere, ¸si apoi ˆınchiderea sa (toate aceste operat¸ii se
fac cu ajutorul apelurilor sistem amintite mai sus). Pentru exemplificare, vom ilustra ˆın
continuare un program C care transform˘a un fi¸sier text MS-DOS ˆın fi¸sier text UNIX.
Exemplu. Se cunoa¸ste faptul c˘a fi¸sierele text din MS-DOS difer˘a de cele din UNIX prin
modul de reprezentare a caracterului newline (sfˆır¸sit de linie): ˆın MS-DOS acesta se
reprezina printr-o secvent¸˘a de 2 caractere ASCII (i.e., 2 octet¸i): primul este caracterul
CR (ce are codul 13), iar al doilea este caracterul LF (ce are codul 10), pe cˆınd ˆın UNIX el
se reprezina doar printr-un singur caracter ASCII (i.e., 1 octet): caracterul LF (cu codul
10).
Ne propunem s˘a scriem un filtru MS-DOSUNIX, adic˘a un program C care transform˘a un
fi¸sier text MS-DOS ˆın fi¸sier text UNIX.
Vom proceda ˆın felul urm˘ator: vom parcurgem fi¸sierul de intrare specificat ca parametru
ˆın linia de comand˘a ¸si vom ˆınlocui fiecare aparit¸ie a secvent¸ei de caractere CR+LF cu car-
acterul LF, salvˆınd rezultatul ˆıntr-un fi¸sier de ie¸sire specificat tot ca parametru ˆın linia de
comand˘a.
Un program care realizeaa acest lucru este urm˘atorul:
/*
File: d2u.c (efect: filtru dos 13,10 -> unix 10)
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
extern int errno;
int main(int argc,char *argv[])
{
char bufin[512],bufout[600];
int i,m,in,out;
char *pin,*pout;
if (argc!=3)
{ printf("\nUsage: %s file[dos] outfile\n\n",v[0]);
exit(1);
}
in=open(argv[1],O_RDONLY);
printf("Outputfile: %s \n",argv[2]);
out=creat(argv[2], O_WRONLY | 0644 );
while ( (m=read(in,bufin,512)) !=0)
116
{
pin=bufin;
pout=bufout;
for(i=0; i<m; i++,pin++)
{ /* if (*pin==’\015’ && *(pin+1)==’\012’)
{ pin++; *pout=’\012’;} */
if (*pin!=’\015’)
{ *pout=*pin; pout++; }
}
*pout=’\0’;
i=write(out,bufout,strlen(bufout));
if (i==-1)
{ printf("errno = %d ->",errno); perror(0);
}
}
printf("Terminat\n");
close(in);
close(out);
return 0;
}
9) Duplicarea unui descriptor de fisier:
Se face cu ajutorul functiei dup. Mai exista inca o functie asemanatoare, si anume functia
dup2.
10) Controlul operatiilor I/O:
Se face cu ajutorul functiei fcntl.
11) Obtinerea de informatii continute de i-nodul unui fisier:
Se face cu ajutorul functiilor stat sau fstat.
12) Stabilirea/eliberarea unei legaturi pentru un fisier:
Se face cu ajutorul functiei link, respectiv unlink.
13) Schimbarea drepturilor de acces la un fisier:
Se face cu ajutorul functiei chmod.
14) Schimbarea proprietarului unui fisier:
Se face cu ajutorul functiei chown.
15) Crearea fisierelor pipe (i.e., canale fara nume):
117
Se face cu ajutorul functiei pipe.
16) Crearea fisierelor fifo (i.e., canale cu nume):
Se face cu ajutorul functiei mkfifo. Interfata functiei mkfifo este urmatoarea:
int mkfifo(char* nume cale, int perm acces)
unde:
nume cale = numele fisierului fifo ce se creeaza;
perm acces = drepturile de acces pentru acesta;
iar valoarea int returnata este 0in caz de succes, sau -1 in caz de eroare.
Efect: in urma executiei functiei mkfifo se creeaza fisierul fifo specificat, cu drepturile
de acces specificate.
17) Montarea/demontarea unui sistem de fisiere:
Se face cu ajutorul functiei mount, respectiv umount.
18) Crearea/stergerea unui director:
Se face cu ajutorul functiei mkdir, respectiv rmdir.
19) Aflarea directorului curent de lucru:
Se face cu ajutorul functiei getcwd.
20) Schimbarea directorului curent:
Se face cu ajutorul functiei chdir.
21) Prelucrarea fisierelor dintr-un director:
Lucrul cu directoare decurge asemanator ca cel cu fisiere, tot o bucla de forma: deschidere,
citire/scriere, inchidere. Se folosesc structurile de date si functiile urmatoare:
DIR * dd; // descriptor de director deschis
struct dirent * de; // intrare in director
// deschiderea directorului
if( ( dd = opendir( nume director)) == NULL)
{
... // trateaza eroarea
}
// prelucrarea secventiala a tuturor intrarilor din director
while( ( de = readdir( dd)) != NULL)
118
{
... // prelucreaza intrarea curenta, avind numele dat de cimpul: de->dname
}
// inchiderea directorului
closedir(dd);
22) Crearea de fisiere speciale:
Se face cu ajutorul functiei mknod. Interfata functiei mknod este urmatoarea:
int mknod(char* nume cale, int perm acces, int disp)
unde:
nume cale = numele fisierului ce se creeaza;
perm acces = tipul sierului si drepturile de acces pentru acesta;
disp = dispozitivul specificat printr-un numar intreg (numarul minor si numarul major
al dispozitivului);
iar valoarea int returnata este descriptorul de fisier deschis, sau -1 in caz de eroare.
Efect: in urma executiei functiei mknod se creeaza fisierul cu tipul specificat (sau, in caz
ca deja exista acel fisier, atunci el este trunchiat la zero, pastrindu-i-se drepturile de acces
pe care le avea) si este deschis in scriere.
Observatie: aceasta este o functie generala ce permite crearea oricarui tip de fisier
UNIX/Linux. De fapt, toate celelalte functii de creare de fisiere amintite mai sus, i.e.
functiile creat (sau open(...O CREAT...)), mkdir,mkfifo, etc., apeleaza la rindul lor
functia mknod cu un parametru corespunzator tipului de fisier dorit a se crea.
3.1.3 Funct¸iile I/O din biblioteca standard de C
Pe ling˘a apelurile sistem amintite mai sus care permit prelucrarea unui fi¸sier ˆın maniera
uzual˘a: deschidere, bucl˘a de parcurgere cu citire/scriere, ¸si apoi ˆınchidere (i.e., primitivele
open,read,write,close), mai exist˘a un set de funct¸ii I/O din biblioteca standard de C
(cele din stdio.h), care permit ¸si ele prelucrarea unui fi¸sier ˆın maniera uzual˘a:
fopen = pentru deschidere;
fread,fwrite = pentru citire, respectiv scriere binar˘a;
fscanf,fprintf = pentru citire, respectiv scriere formatat˘a;
fclose = pentru ˆınchidere;
Nu voi mai aminti prototipul acestor funct¸ii de bibliotea, deoarece le cunoa¸stet¸i deja de la
limbajul C din anul I. V˘a voi reaminti doar faptul c˘a aceste funct¸ii de bibliotec˘a lucreaz˘a
buffer-izat, cu stream-uri I/O, iar descriptorii de fi¸siere utilizat¸i de ele nu mai sunt de tip
int, ci sunt de tip FILE*.
119
Practic, aceste funct¸ii I/O de nivel ˆınalt folosesc un buffer (i.e., o zon˘a de memorie cu rol
de cache) pentru operat¸iile de citire/scriere.
Un apel de scriere nu va scrie direct pe disc, ci doar va copia datele de scris ˆın buffer .
Acesta va fi “golit” (i.e., cont¸inutul s˘au va fi scris pe disc) abia ˆın momentul ˆın care se
umple, sau dac˘a se ˆıntˆılne¸ste caracterul newline. O alt˘a posibilitate de a fort¸a “golirea”
buffer-ului pe disc ˆınainte de a se umple, este de a apela funct¸ia de bibliotec˘a fflush.
Observat¸ie: scrierea efectiv˘a pe disc a cont¸inutului buffer-ului se face cu ajutorul apelului
sistem write.
Asem˘an˘ator se petrec lucrurile ¸si la citire: o operat¸ie de citire va citi date direct din buffer,
dac˘a sunt ˆındeajuns, sau, ˆın caz contrar, dac˘a buffer-ul s-a golit, el va fi “umplut” printr-o
singur˘a operat¸ie de citire de pe disc, chiar dac˘a funct¸ia de bibliotec˘a solicitase citirea unei
cantit˘at¸i mai mici de date. Observat¸ie: citirea efectiv˘a de pe disc ˆın buffer se face cu
ajutorul apelului sistem read.
Deosebirea ˆıntre aceste perechi de funct¸ii const˘a ˆın faptul c˘a primele (i.e.,open, . . . etc.)
sunt de nivel sc˘azut, lucrˆınd la nivelul nucleului sistemului de operare (sunt apeluri sistem,
implementate ˆın nucleu), pe cˆınd ultimele (i.e.,fopen, . . . etc.) sunt de nivel ˆınalt, ind
funct¸ii de bibliotec˘a (implementate cu ajutorul celor de nivel sc˘azut).
3.2 Accesul concurent/exclusiv la ¸siere ˆın UNIX: blocaje pe
¸siere
1. Introducere
2. Blocaje pe fi¸siere. Primitivele folosite
3. Fenomenul de interblocaj. Tehnici de eliminare a interblocajului
3.2.1 Introducere
UNIX-ul fiind un sistem multi-tasking, in mod uzual este permis accesul concurent la fisiere,
adica mai multe procese pot accesa “simultan” in citire ¸si/sau scriere un acelasi fisier, sau
chiar o aceeasi inregistrare dintr-un fisier.
Acest acces concurent (“simultan”) la un fisier de catre procese diferite poate avea uneori
efecte nedorite (ca de exemplu, distrugerea integritatii datelor din fisier), si din acest motiv
s-au implementat in UNIX mecanisme care sa permita accesul exclusiv (adica un singur
proces are permisiunea de acces) la un fisier, sau chiar la o anumita inregistrare dintr-un
fisier.
120
3.2.2 Blocaje pe fi¸siere. Primitivele folosite
In continuare vom exemplifica prin citeva programe modul de acces concurent/exclusiv la
fisiere, si vom trece in revista apelurile sistem folosite pentru accesul exclusiv la fisiere.
Urmatorul program este un exemplu de asemenea acces concurent la fisiere:
Exemplu. Iat˘a sursa programului access1.c (programul acces versiunea 1.0):
/*
File: access1.c (versiunea 1.0)
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
extern int errno;
int main(int argc, char* argv[])
{
int fd;
char ch;
if(argv[1] == NULL)
{
fprintf(stderr,"Trebuie apelat cu cel putin un parametru.\n");
exit(1);
}
if( (fd=open("fis.dat", O_RDWR)) == -1)
{ /* trateaza cazul de eroare ... */
fprintf(stderr,"Nu pot deschide fisierul fis.dat: %s\n",strerror(errno));
exit(2);
}
/* parcurgerea fisierului caracter cu caracter pina la EOF */
while( read(fd,&ch,1) != 0)
{
if(ch == ’#’)
{
lseek(fd,-1L,1);
sleep(10);
write(fd,argv[1],1);
printf("Terminat. S-a inlocuit primul # gasit [ProcesID:%d].\n",getpid());
return 0;
}
}
printf("Terminat. Nu exista # in fisierul dat [ProcesID:%d].\n",getpid());
return 0;
}
121
Compilarea programului se va face cu comanda:
UNIX>gcc access1.c -o access1
Apoi creati un fisier fis.dat care sa contina urmatoarea linie de text:
abc#def#ghi#jkl
acest lucru putindu-l realiza, de exemplu, cu comanda:
UNIX>echo abc#def#ghi#jkl >fis.dat
Observatie: sa folositi aceasta comanda inaintea fiecarei executii a programului de mai
sus, deoarece fisierul fis.dat va fi modificat la executia programului si va trebui sa faceti
mai multe executii pe acelasi fisier original.
Dupa cum se observa din codul sursa, efectul acestui program este urmatorul: ˆınlocuie¸ste
prima aparitie a caracterului ’#’pe care o gaseste ˆın fisierul fis.dat cu primul caracter
din primul argument din linia de comanda.
Spre exemplificare, executati comenzile:
UNIX>access1 500 6 salut
Terminat. S-a inlocuit primul # gasit [ProcesID:9055].
UNIX>cat fis.dat
abc5def#ghi#jkl
Observatie: Nu uitati sa refaceti originalul fis.dat inaintea fiecarei executii, cu comanda
echo de mai sus.
Sa vedem acum ce se intimpla daca lansam concurent dou˘a procese care sa execute acest
program, lucru realizat prin comanda:
UNIX>access1 1 & access1 2 &
Terminat. S-a inlocuit primul # gasit [ProcesID:9532].
Terminat. S-a inlocuit primul # gasit [ProcesID:9533].
iar apoi putem vizualiza rezultatul execut¸iei cu comanda:
UNIX>cat fis.dat
Probabil va asteptati ca dupa executie fisierul fis.dat sa arate cam asa:
abc1def2ghi#jkl sau abc2def1ghi#jkl
ˆın funct¸ie de care dintre cele 2 procese a apucat primul sa suprascrie primul caracter ’#’din
acest fisier, celuilalt proces raminindu-i al doilea caracter ’#’pentru a-l suprascrie.
Cu toate acestea, oricite executii s-ar face, intotdeauna se va obtine rezultatul urmator:
abc1def#ghi#jkl sau abc2def#ghi#jkl
122
Explicatie: daca cititi cu atentie sursa programului access1.c, veti constata ca exista
o asteptare de 10 secunde intre momentul depistarii primei inregistrari din fisier care
este ’#’si momentul suprascrierii acestei inregistrari cu alt caracter (si anume cu primul
caracter din primul argument din linia de comanda). Din acest motiv ambele procese se
vor opri pe aceeasi inregistrare pentru a o suprascrie (dupa 10 secunde de la depistarea
ei). Procesul care suprascrie ultimul va fi deci cel care determina rezultatul final.
Concluzie: acest exemplu ilustreaza ce se intimpla cind se acceseaza un fisier de catre mai
multe procese in mod concurent (fara blocaj).
Tocmai pentru a evita asemenea fenomene ce nu sunt de dorit in anumite situatii, sistemul
UNIX pune la dispozitie un mecanism de blocare (i.e. de punere de “lac˘ate”) pe portiuni de
fisier pentru acces exclusiv. Prin acest mecanism se defineste o zona de acces exclusiv la
fisier (sau o “sect¸iune critic˘a”, cum mai este denumita in limbajul programarii paralele).
O asemenea portiune nu va putea fi accesata in mod concurent de mai multe procese pe
toata durata existentei blocajului.
Pentru a pune un blocaj (lacat) pe fisier trebuie utilizat˘a urmatoarea structura de date:
struct flock /* este definita in fisierul header fcntl.h */
{
short l type; /* indica tipul blocarii */
short l whence; /* indica pozitia relativa (originea) */
long l start; /* indica pozitia in raport cu originea */
long l len; /* indica lungimea portiunii blocate */
int l pid;
}
unde:
– cˆımpul ltype indic˘a tipul blocarii, putˆınd avea ca valoare una dintre constantele:
FRDLCK : blocaj in citire;
FWRLCK : blocaj in scriere;
FUNLCK : deblocaj (se inlatura lacatul);
– cˆımpul lwhence indic˘a pozitia relativa (originea) in raport cu care este interpretat
cimpul lstart, putˆınd avea ca valoare una dintre constantele:
SEEK SET = 0 : originea este BOF(=begin of file);
SEEK CUR = 1 : originea este CURR(=current position in file);
SEEK END = 2 : originea este EOF(=end of file);
– cˆımpul lstart indic˘a pozitia (i.e.,offset-ul in raport cu originea l whence) de la care
incepe zona blocata. Observatie: trebuie sa fie negativ pentru lwhence = 2.
– cˆımpul llen indic˘a lungimea in octeti a portiunii blocate
– cˆımpul lpid este gestionat de functia fcntl care pune blocajul, fiind utilizat pentru
a memora PID-ul procesului proprietar al acelui lacat. Observatie: are sens consultarea
acestui cˆımp doar atunci cind functia fcntl se apeleaza cu parametrul F GETLK.
Pentru a pune lacatul pe fisier, dupa ce s-au completat cimpurile structurii de mai sus,
trebuie apelat˘a functia fcntl. Interfata functiei fcntl este urmatoarea:
123
#include <fcntl.h>
int fcntl(int fd, int mod, struct flock* sfl)
unde:
fd = descriptorul de fisier deschis pe care se pune lacatul;
sfl = adresa structurii flock ce defineste acel lacat;
mod = indica modul de punere, putind lua una dintre valorile:
F SETLK :
permite punerea unui lacat pe fisier (ˆın citire sau ˆın scriere, functie de tipul specificat
in structura flock). In caz de esec se seteaza variabila errno la valoarea EACCES
sau la EAGAIN;
FGETLK :
permite extragerea informatiilor despre un lacat pus pe fisier;
FSETLKW :
permite punerea/scoaterea blocajelor in mod “blocant”, adica se asteapta (i.e., func-
tia nu returneaza) pina cind se poate pune blocajul. Posibile motive de asteptare:
se incearca blocarea unei zone deja blocate de un alt proces, ¸s.a.
Indicat¸ie: a se citi neaparat paginile de manual despre functia fcntl si structura flock.
Observat¸ii:
1. Cˆımpul lpid din structura flock este actualizat de functia fcntl;
2. Blocajul este scos automat daca procesul care l-a pus fie inchide fisierul, e isi termina
executia;
3. Scoaterea (deblocarea) unui segment dintr-o portiune mai mare anterior blocata
poate produce doua segmente blocate.
4. Blocajele (lacatele) nu se transmit proceselor fii in momentul crearii acestora cu
functia fork. Motivul: fiecare lacat are in structura flock asociata PID-ul procesului
care l-a creat (si care este deci proprietarul lui), iar procesele fii au, bineinteles, PID-
uri diferite de cel al parintelui.
Revenind la exemplul nostru, pentru a obtine ca dupa executia celor doua procese fisierul
fis.dat sa arate ˆın felul urm˘ator:
abc1def2ghi#jkl sau abc2def1ghi#jkl
124
va trebui sa rescriem programul pentru a folosi acces exclusiv la fisier prin intermediul
lacatelor, obtinind astfel a doua versiune a programului nostru.
Iat˘a sursa programului access2.c (programul acces versiunea 2.0):
/*
File: access2.c (versiunea 2.0)
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
extern int errno;
int main(int argc, char* argv[])
{
int fd,codblocaj;
char ch;
struct flock lacat;
if(argv[1] == NULL)
{
fprintf(stderr,"Trebuie apelat cu cel putin un parametru.\n");
exit(1);
}
if( (fd=open("fis.dat", O_RDWR)) == -1)
{ /* trateaza cazul de eroare ... */
perror("Nu pot deschide fisierul fis.dat deoarece ");
exit(2);
}
/* pregateste lacat pe fisier */
lacat.l_type = F_WRLCK;
lacat.l_whence = SEEK_SET;
lacat.l_start = 0;
lacat.l_len = 1; /* aici se poate pune orice valoare, inclusiv 0,
deoarece pentru problema noastra nu conteaza lungimea zonei blocate.*/
/* Incercari repetate de punere a lacatului pina cind reuseste */
while( ((codblocaj=fcntl(fd,F_SETLK,&lacat)) == -1)
&& ((errno==EACCES)||(errno==EAGAIN)) )
{
fprintf(stderr, "Blocaj imposibil [ProcesID:%d].\n", getpid());
perror("\tMotivul");
sleep(6);
}
if(codblocaj == -1)
{
fprintf(stderr,"Eroare unknown la blocaj [ProcesID:%d].\n", getpid());
perror("\tMotivul");
125
exit(3);
}
else
printf("Blocaj reusit [ProcesID:%d].\n", getpid());
/* parcurgerea fisierului caracter cu caracter pina la EOF */
while( read(fd,&ch,1) != 0)
{
if(ch == ’#’)
{
lseek(fd,-1L,1);
sleep(10);
write(fd,argv[1],1);
printf("Terminat. S-a inlocuit primul # gasit [ProcesID:%d].\n",getpid());
return 0;
}
}
printf("Terminat. Nu exista # in fisierul dat [ProcesID:%d].\n",getpid());
return 0;
}
Compilarea programului se va face cu comanda:
UNIX>gcc access2.c -o access2
Dac˘a facet¸i mai multe executii, lansand concurent dou˘a procese care sa execute acest
program, lucru realizat prin comanda:
UNIX>access2 1 & access2 2 &
(nu uitati sa refaceti fisierul original fis.dat la fiecare executie), veti observa ca obtinem
rezultatul scontat.
Explicatie: dintre cele doua procese, doar unul va reusi sa puna lacatul, iar celalalt proces
va astepta pina cind va reusi sa puna lacatul. Aceasta se va intimpla abia cind primul
proces se va termina (si deci va fi scos lacatul pus de catre el). Mai mult, daca va uitati la
pauzele puse in program prin apelurile functiei sleep, veti intelege de ce la fiecare executie
va apare exact de 2 ori mesajul:
Blocaj imposibil [Proces:...].
(Motivul: 2 = min{knumar intreg |6k > 10}. Ginditi-va la justificarea acestei formule!!!)
Observat¸ie: ˆın programul anterior apelul de punere a lacatului era neblocant (i.e., cu
parametrul FSETLK). Se poate face si un apel blocant, i.e. functia fcntl nu va returna
imediat, ci va sta in asteptare pina cind reuseste sa puna lacatul.
Iat˘a sursa programului access2w.c (programul acces versiunea 2.0 cu apel blocant):
/*
File: access2w.c (versiunea 2.0 cu lacat pus in mod blocant)
126
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
extern int errno;
int main(int argc, char* argv[])
{
int fd;
char ch;
struct flock lacat;
if(argv[1] == NULL)
{
fprintf(stderr,"Trebuie apelat cu cel putin un parametru.\n");
exit(1);
}
if( (fd=open("fis.dat", O_RDWR)) == -1)
{ /* trateaza cazul de eroare ... */
perror("Nu pot deschide fisierul fis.dat deoarece ");
exit(2);
}
/* pregateste lacat pe fisier */
lacat.l_type = F_WRLCK;
lacat.l_whence = SEEK_SET;
lacat.l_start = 0;
lacat.l_len = 1; /* aici se poate pune orice valoare, inclusiv 0,
deoarece pentru problema noastra nu conteaza lungimea zonei blocate.*/
/* O singura incercare de punere a lacatului in mod blocant
(intra in asteptare pina cind reuseste) */
printf("Incep punerea blocajului in mod blocant [Proces:%d].\n",getpid());
if( fcntl(fd,F_SETLKW,&lacat) == -1)
{
if(errno == EINTR)
fprintf(stderr,"Apelul fcntl a fost intrerupt [ProcesID:%d].\n",getpid());
else
fprintf(stderr,"Eroare unknown la blocaj [ProcesID:%d].\n",getpid());
perror("\tMotivul");
exit(3);
}
else
printf("Blocaj reusit [ProcesID:%d].\n",getpid());
/* parcurgerea fisierului caracter cu caracter pina la EOF */
while( read(fd,&ch,1) != 0)
{
127
if(ch == ’#’)
{
lseek(fd,-1L,1);
sleep(10);
write(fd,argv[1],1);
printf("Terminat. S-a inlocuit primul # gasit [ProcesID:%d].\n",getpid());
return 0;
}
}
printf("Terminat. Nu exista # in fisierul dat [ProcesID:%d].\n",getpid());
return 0;
}
Lansˆınd concurent dou˘a procese care s˘a execute acest program, vet¸i observa c˘a obt¸inem
acela¸si rezultat ca ¸si ˆın cazul variantei neblocante.
Observat¸ie importana:
Se poate constata faptul ca versiunea 2.0 a programului nostru (ambele variante, ¸si cea
neblocana, ¸si cea blocana) nu este optima: practic, cele doua procese ˆı¸si fac treaba
secvent¸ial, unul dup˘a altul, ¸si nu concurent, deoarece de abia dupa ce se termina acel
proces care a reusit primul sa puna lacatul pe fisier, va putea incepe si celalalt proces sa-si
faca treaba (i.e., parcurgerea fisierului si inlocuirea primului caracter ’#’intilnit).
Aceasta observatie ne sugereaza ca putem imbunatati timpul total de executie, permitind
celor doua procese sa se execute intr-adevar concurent, pentru aceasta fiind nevoie sa
punem lacat doar pe un singur caracter (si anume pe primul caracter ’#’intilnit), in loc
sa blocam tot fisierul.
Exercit¸iu. Scrieti versiunea 3.0 a acestui program, cu blocaj la nivel de caracter.
Ideea de rezolvare: programul va trebui sa faca urmatorul lucru: cind intilneste primul
caracter ’#’in fisier, pune lacat pe el (i.e., pe exact un caracter) si apoi il rescrie.
Rezolvare: daca nu ati reusit sa scrieti singuri programul, atunci iat˘a cum ar trebui s˘a
arate programul access3w.c (programul acces versiunea 3.0, varianta cu apel blocant):
/*
File: access3w.c (versiunea 3.0, cu lacat pus in mod blocant)
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
extern int errno;
int main(int argc, char* argv[])
{
128
int fd;
char ch;
struct flock lacat;
if(argv[1] == NULL)
{
fprintf(stderr,"Trebuie apelat cu cel putin un parametru.\n");
exit(1);
}
if( (fd=open("fis.dat", O_RDWR)) == -1)
{ /* trateaza cazul de eroare ... */
perror("Nu pot deschide fisierul fis.dat deoarece ");
exit(2);
}
/* pregateste lacat pe caracterul de la pozitia curenta */
lacat.l_type = F_WRLCK;
lacat.l_whence = SEEK_CUR;
lacat.l_start = 0;
lacat.l_len = 1;
/* parcurgerea fisierului caracter cu caracter pina la EOF */
while( read(fd,&ch,1) != 0)
{
if(ch == ’#’)
{
lseek(fd,-1L,1);
/* O singura incercare de punere a lacatului in mod blocant */
printf("Pun blocant lacatul pe #-ul gasit deja [Proces:%d].\n",getpid());
if( fcntl(fd,F_SETLKW,&lacat) == -1)
{
fprintf(stderr,"Eroare la blocaj [ProcesID:%d].\n", getpid());
perror("\tMotivul");
exit(3);
}
else
printf("Blocaj reusit [ProcesID:%d].\n", getpid());
sleep(5);
write(fd,argv[1],1);
printf("Terminat. S-a inlocuit primul # gasit [ProcesID:%d].\n",getpid());
return 0;
}
}
printf("Terminat. Nu exista # in fisierul dat [ProcesID:%d].\n",getpid());
return 0;
}
129
Observat¸ie: ideea de rezolvare expusa mai sus (aplicata in programul access3w.c) nu este
intrutotul corecta, in sensul ca nu se va obtine intotdeauna rezultatul scontat, deoarece
ˆıntre momentul primei depistari a ’#’-ului si momentul reusitei blocajului exista posibil-
itatea ca acel ’#’sa fie suprascris de celalalt proces (tocmai pentru a fort¸a aparit¸ia unei
situat¸ii care cauzeaz˘a producerea unui rezultat nedorit, am introdus ˆın program acel apel
sleep(5) ˆıntre punerea blocajului pe caracterul ’#’si rescrierea lui).
Aceasta idee de rezolvare se poate corecta astfel: dupa punerea blocajului, se verifica din
nou daca acel caracter este intr-adevar ’#’(pentru ca intre timp s-ar putea sa fi fost rescris
de celalalt proces), si daca nu mai este ’#’, atunci trebuie scos blocajul si reluata bucla
de cautare a primului ’#’din fisier.
Incercati singuri sa adaugati aceasta corectie la programul access3w.c (daca nu reusiti,
atunci consultati ˆın anexa B programul access4w.c).
Observat¸ie: funct¸ionarea corect˘a a lac˘atelor se bazeaa pe cooperarea proceselor pentru
asigurarea accesului exclusiv la fisiere, i.e. toate procesele care vor a acceseze mutual
exclusiv un fi¸sier (sau o port¸iune dintr-un fi¸sier) vor trebui s˘a foloseasc˘a lac˘ate pentru
accesul respectiv. Altfel, dac˘a un proces scrie direct un fi¸sier (sau o port¸iune dintr-un
fi¸sier), apelul s˘au de scriere nu va ˆımpiedicat de un eventual lac˘at pus pe acel fi¸sier (sau
acea port¸iune de fi¸sier) de c˘atre un alt proces.
Cu alte cuvinte, lac˘atele sunt ni¸ste semafoare pentru accesul exclusiv la (port¸iuni din)
fi¸siere, spre deosebire de semafoarele clasice cunoscute din teoria sistemelor de operare,
care asigur˘a accesul exclusiv la variabile de memorie ¸si/sau buat¸i de cod, adic˘a la (port¸iuni
din) memoria intern˘a.
ˆ
In continuare vom exemplifica cele spuse mai sus.
Exemplu. Dac˘a lans˘am concurent dou˘a procese, unul care a execute programul acces
versiunea 2.0 (aceea care blocheaz˘a imediat fi¸sierul fis.dat ¸si apoi ˆıncepe autarea primu-
lui ’#’din fi¸sier), iar altul care a suprascrie fi¸sierul fis.dat, lucru realizat prin comanda:
UNIX>access2w 1 & echo "text-fara-diez">fis.dat &
vom constata a suprascrierea fi¸sierului de c˘atre comanda echo reu¸se¸ste, indiferent de
faptul c˘a programul acces2w blocheaz˘a ˆın scriere fi¸sierul respectiv, acesta nereu¸sind a
mai g˘aseasc˘a nici un ’#’ˆın fi¸sier.
3.2.3 Fenomenul de interblocaj. Tehnici de eliminare a interblocajului
Observatie: la fel ca ˆın cazul utiliz˘arii semafoarelor clasice cunoscute din teoria sistemelor
de operare, prin folosirea lac˘atelor pe fi¸siere poate apare fenomenul de interblocaj.
Exemplu. Sa consideram doua procese P1 si P2 care au nevoie sa blocheze pentru ac-
ces exclusiv aceleasi doua resurse R1 si R2 (de exemplu doua inregistrari intr-un fisier),
acapararea resurselor facindu-se in ordine inversa:
Pasii executati de P1:
130
1. Acaparare resursa R1
2. Acaparare resursa R2
3. Utilizare resurse
4. Eliberare resurse
Pasii executati de P2:
1. Acaparare resursa R2
2. Acaparare resursa R1
3. Utilizare resurse
4. Eliberare resurse
Daca cele doua procese sunt executate concurent, poate surveni situatia de interblocaj: P1
a reusit sa blocheze R1 (a executat pasul 1) si P2 a reusit sa blocheze R2 (a executat pasul
1) si acum ambele procese incearca sa execute pasul 2:P1 asteapta eliberarea resursei R2
pentru a o acapara, iar P2 asteapta eliberarea resursei R1 pentru a o acapara. Deci ambele
procese vor astepta la infinit, fiind vorba de un interblocaj.
Exista trei modalitati de tratare a interblocajului:
a) tehnici de prevenire;
b) tehnici de evitare;
c) tehnici de reacoperire (eliminare).
In continuare sa vedem doua tehnici de tratare a interblocajului:
1) Tehnica punctelor de retur (“rollback ”)
Tipul ei: eliminarea interblocajului.
Ideea consta in: pentru exemplul anterior, cind procesul P1 a reusit blocarea resursei R1
si a esuat pe resursa R2, va face urmatoarul lucru: elibereaza resursa deja ocupata R1,
asteapta un anumit interval de timp aleator (pentru a impiedica sincronizarea proceselor)
si apoi reia executia de la pasul 1. Acelasi algoritm (eliberare resurse in caz de esec, si
reluare dupa o asteptare) il va executa si procesul P2.
2) Tehnica ordonarii resurselor
Tipul ei: prevenirea interblocajului.
Ideea consta in: se defineste o relatie de ordine pe multimea resurselor. Atunci cind un
proces vrea sa acapareze mai multe resurse, va trebui sa le ocupe in ordinea crescatoare
a acestora (ordinea de deblocare nu conteaza, dar pentru eficienta ar trebui tot in or-
dine crescatoare); daca nu reuseste, atunci asteapta pina se elibereaza respectiva resursa.
Datorita acestei ordini a resurselor si algoritmului de acaparare a resurselor, nu mai este
nevoie de retur pentru eliminarea interblocajului.
131
Cum se poate defini relat¸ia de ordine a resurselor?
Exemplul 1: pentru un fisier, putem lua ca ordine ordinea inregistrarilor din fisier (adica
offset-ul in fisier).
Exemplul 2: pentru un arbore, putem aplica blocarea de la radacina spre frunze; alteori
se poate utiliza ordinea de la frunze spre radacina.
O alta situatie care poate apare in programarea concurenta ar fi urmatoarea: sa consideram
secventa de cod
if (E)
then I1
else I2
Se poate intimpla ca expresia Esa fie evaluata la true, dar pina cind se executa instruc-
tiunea I1, un alt proces poate face ca Esa devina false (si deci am vrea de fapt sa se
execute instructiunea I2).
Din acest motiv, in programarea concurenta se utilizeaza “sectiuni critice” (implemen-
tate prin blocaje, semafoare, monitoare, etc.) pentru asemenea portiuni de cod ce trebuie
executate exclusiv.
3.3 Exercit¸ii
Exercit¸iul 1. Care sunt principalele apeluri de sistem pentru lucrul cu fi¸siere?
Exercit¸iul 2. Studiat¸i cu ajutorul comenzii man prototipurile tuturor apelurilor de sistem
pentru lucrul cu fi¸siere amintite ˆın sect¸iunea 3.1.
Exercit¸iul 3 . Ce efect are fragmentul de cod C urm˘ator?
char file[]="a.txt";
int rval;
rval=access(file, F_OK);
if (rval)
printf("%s exista!\n", file);
else
printf("Eroare!\n");
Exercit¸iul 4 . Ce efect are fragmentul de cod C urm˘ator?
struct stat* info;
char file[] = "a.txt";
stat(file,info);
if ((info->st_mode & S_IFMT) == S_IFREF)
printf("fisier obisnuit!\n");
else
printf("alt tip de fisier!\n");
132
Exercit¸iul 5 . Dac˘a nu suntet¸i utilizatorul root, ce se va afi¸sa pe ecran ˆın urma execut¸iei
programului urm˘ator?
#include <stdio.h>
#include <fcntl.h>
main() {
int f = open("/etc/passwd",O_RDWR);
switch(f) {
case 0x1 : printf("0x01"); break;
case 0xFF: printf("0xFF"); break;
case 0x0 : printf("0x00"); break;
case 0x2 : printf("0x02"); break;
default: printf("descriptor=%d",f);
}
}
Exercit¸iul 6 . Ce se poate obt¸ine pe ecran ˆın urma execut¸iei programului urm˘ator?
#include<stdio.h>
#include<sys/stat.h>
int get_file_size(char *path) {
struct stat file_stats;
if(stat(path,&file_stats))
return file_stats.st_size;
else return 0;
}
int main() {
printf("%d\n",get_file_size("/etc/passwd"));
}
Exercit¸iul 7 . Consider˘am programului urm˘ator:
#include ...
main(){
int fd; char *s="Linux";
unlink("fis2");
fd = open("fis1", O_CREAT|O_WRONLY|O_TRUNC,0700);
write(fd,s,strlen(s));
link("fis1","fis2");
}
Presupunˆınd c˘a sunt incluse fi¸sierele header necesare, ¸si c˘a avet¸i suficiente drepturi ˆın direc-
torul ˆın care vet¸i executa acest program, ce va afisa comanda cat fis1 fis2 executat˘a
dup˘a execut¸ia acestui program?
Exercit¸iul 8. Scriet¸i un program C care s˘a simuleze comanda tac.
Exercit¸iul 9. Scriet¸i un program C care s˘a simuleze comanda head.
Exercit¸iul 10. Scriet¸i un program C care a afiseze toate intrarile dintr-un director dat ca
parametru.
133
Exercit¸iul 11. Scriet¸i un program C care a adauge la sfirsitul unui fisier continutul altui
fisier.
Exercit¸iul 12. Scriet¸i un program C care s˘a afiseze permisiunile tuturor fisierelor si subdi-
rectoarelor (recursiv) din directorul dat ca argument.
Exercit¸iul 13 . Scriet¸i un program C care a afi¸seze ˆıncontinuu ˆıntr-o bucl˘a numele direc-
torului curent de lucru ¸si urm˘atorul meniu de operat¸ii posibile:
– [M]kdir = creeaza directorul specificat (ca subdirector in directorul curent)
– [R]mdir = sterge directorul curent
– [C]hdir = schimba directorul curent in cel specificat
– [L]ist = listeaza continutul directorului curent
– [S]elect = selecteaza sierul specificat din directorul curent si aplica-i urmatorul subme-
niu de operatii:
- [C]opy = copie-l in fisierul specificat
- [D]elete = sterge-l
- [R]ename = redenumeste-l cu numele specificat
- [V]iew = afiseaza-l pe ecran
– [Q]uit = terminare program
Deci intr-o bucla se va afisa directorul curent si acest meniu, apoi se va citi cite o tasta
din meniul anterior si se va executa operatia asociata ei, iar apoi se reia bucla (pina cind
se va da Quit).
Exercit¸iul 14. Scriet¸i un script bash care a realizeze acelea¸si operat¸ii ca ¸si programul C
de la exercit¸iul precedent.
Exercit¸iul 15. Cˆıte tipuri de blocaje pe fi¸siere exist˘a? Care sunt deosebirile dintre ele?
Exercit¸iul 16. Care sunt structurile de date ¸si apelurile sistem utilizate pentru lucrul cu
blocaje pe fi¸siere?
Exercit¸iul 17 . Scriet¸i programul access4w.c care a corecteze neajunsul versiunii 3.0 a
programului access, dat˘a ˆın sect¸iunea 3.2.
Exercit¸iul 18.ˆ
In ce const˘a fenomenul de interblocaj? Cum poate fi el tratat?
Exercit¸iul 19. Realizat¸i o implementare practic˘a a exemplului cu cele dou˘a procese P1 ¸si
P2 ¸si cele dou˘a resurse R1 ¸si R2 amintit ˆın sect¸iunea 3.2, ¸si tratat¸i interblocajul. Indicat¸ie:
procesele vor fi dou˘a programe C, resursele dou˘a fi¸siere de date, iar acapararea/eliberarea
unei resurse va ˆınsemna punerea/scoaterea blocajului pe fi¸sierul respectiv.
Exercit¸iul 20. a se implementeze un semafor binar folosind lac˘atele pe fi¸siere.
134
Capitolul 4
Gestiunea proceselor
4.1 Procese UNIX. Introducere
1. Not¸iuni generale despre procese
2. Primitive referitoare la procese
4.1.1 Not¸iuni generale despre procese
Termenul de program specific˘a de obicei un fi¸sier executabil (evident, obt¸inut prin com-
pilare dintr-un fi¸sier surs˘a), aflat pe un suport de memorare extern (i.e.,harddisk). Un
program este ˆınc˘arcat ˆın memorie ¸si executat de nucleul sistemului de operare prin inter-
mediul primitivei exec (despre care vom vorbi mai ˆıncolo).
O instant¸˘a a unui program aflat ˆın execut¸ie poart˘a numele de proces. Acesta este o entitate
gestionat˘a de nucleul sistemului de operare, entitate ce cont¸ine imaginea ˆın memorie a
fi¸sierului executabil (zonele de cod, date ¸si stiv˘a), precum ¸si resursele utilizateˆın momentul
execut¸iei (registre, fi¸siere deschise, ¸s.a.). Prin urmare,
DEFINIT¸ IE: “Un proces este un program ˆın curs de execut¸ie.”
Mai sunt si alte definitii ale notiunii de proces, dar aceasta este mai potrivita deoarece
face referire si la timp, adica la caracterul temporal al procesului.
Deci un proces este executia unui program, ind caracterizat de: o durata de timp (pe-
rioada de timp in care se executa acel program), o zona de memorie alocata (zona de cod
+ zona de date + stiva), timp procesor alocat, ¸s.a.
135
Evident, la un moment dat de timp pot exista in curs de executie doua procese (adica
executii) diferite ale aceluiasi program (i.e., fi¸sier executabil).
Facind o analogie cu programarea orientata obiect, am avea corespondenta:
program conceptul de clasa
proces conceptul de obiect (i.e., instanta a unei clase)
UNIX-ul fiind un sistem de operare multi-tasking, aceasta ˆınseamn˘a c˘a, la un moment dat,
exist˘a o list˘a de procese aflate ˆın evident¸a sistemului de operare pentru execut¸ie. Fiecare
proces este identificat de un num˘ar ˆıntreg unic numit PID (Process IDentifier), iar lista
proceselor poate fi aflat˘a cu comanda ps (ce a fost prezentat˘a ˆın capitolul 2 din partea I
a acestui manual).
Practic, nucleul sistemului de operare gestioneaz˘a aceast˘a list˘a de procese prin intermediul
unei tabele a proceselor (alocat˘a ˆın spat¸iul de memorie al nucleului). Aceast˘a tabel˘a cont¸ine
cˆıte o intrare pentru ecare proces existent ˆın sistem, intrare referit˘a prin identificatorul de
proces (i.e.,PID-ul) acelui proces, ¸si care cont¸ine o serie de informat¸ii despre acel proces.
A¸sadar, ˆın UNIX fiecare proces este caracterizat de PID-ul s˘au (i.e., un identificator de
proces unic), ¸si, ˆın plus, are un unic proces arinte (sau tat˘a), ¸si anume acel proces care l-a
creat (crearea se face prin intermediul primitivei fork, despre care vom vorbi mai ˆıncolo).
Un proces poate crea oricˆıte procese (evident, ˆın limita resurselor sistemului), procese
care se vor numi procese fii (sau copii) ai procesului respectiv care le-a creat. Pe baza
relat¸iei p˘arinte–fiu, procesele sunt organizate ˆıntr-o ierarhie arborescena de procese, a
arei r˘ad˘acin˘a este procesul cu PID-ul 0.
De asemenea, ecare proces are un proprietar, acel utilizator care l-a lansat ˆın execut¸ie, ¸si
un grup proprietar, ¸si anume grupul utilizatorului care este proprietarul acelui proces.
Procese speciale (ale sistemului de operare):
procesul “swapper ”, cu PID=0 :
este planificatorul de procese, un proces de sistem ce are rolul de a planifica toate pro-
cesele existente ˆın sistem. El este creat la incarcarea sistemului de operare de catre
boot-loader, devenind radacina arborelui de procese (din el se nasc toate celelalte
procese, pe baza apelului fork despre care vom discuta mai ˆıncolo);
procesul “init”, cu PID=1 :
este procesul de initializare invocat de procesul “swapper la terminarea incarcarii
sistemului de operare;
procesul “pagedaemon”, cu PID=2 :
este procesul care se ocupa de paginarea memoriei.
Observat¸ie: pe parcursul exploat˘arii sistemului, procesele existente in sistem evolueaza
dinamic: se nasc procese si sunt distruse (la terminarea lor) in functie de programele
136
rulate de utilizatori. Ca atare, ierarhia arborescenta a proceselor din sistem, despre care
am vorbit mai devreme, precum si, implicit, structura de date a nuclelului ce gestioneaza
aceasta ierarhie (i.e., tabela de procese), nu sunt statice, ci au un caracter dinamic – sunt
intr-o continua evolutie, in functie de programele rulate de utilizatori.
Totusi, radacina ierarhiei (i.e., procesul “swapper” cu PID=0) este fixa, in sensul ca acest
proces nu se termina niciodata (mai exact, se termina atunci cind este inchis sistemul de
calcul, sau doar resetat). La fel se intimpla si cu alte citeva procese – procesele de sistem
(i.e., procesele componente ale nucleului sistemului de operare), dintre care trei le-am
amintit mai sus.
Revenind la tabela proceselor gestionat˘a de nucleu, fiecare intrare din ea, corespunz˘atoare
unui anumit proces, cont¸ine o serie de informat¸ii despre acel proces (dintre care o parte
au fost deja amintite mai sus), ¸si anume:
PID-ul = identificatorul de proces – este un intreg pozitiv, de tipul pid t(tip definit
in header-ul sys/types.h);
PPID-ul = identificatorul procesului parinte;
terminalul de control
UID-ul proprietarului = identificatorul utilizatorului care executa procesul;
GID-ul proprietarului = identificatorul grupului din care face parte utilizatorul ce
executa procesul;
EUID-ul si EGID-ul = UID-ul si GID-ul proprietarului efectiv, adic˘a acel utilizator ce
determina drepturile procesului de acces la resurse (pe baza bitilor setuid bit si setgid
bit din masca de drepturi de acces a fisierului executabil asociat acelui proces);
(not˘a: reamintesc faptul ca, daca bitul setuid este 1, atunci, pe toata durata de
executie a fisierului respectiv, proprietarul efectiv al procesului va fi proprietarul
fisierului, si nu utilizatorul care il executa; similar pentru setgid bit.)
starea procesului – poate fi una dintre urm˘atoarele:
ready = pregatit pentru executie;
run = in executie;
wait = in asteptarea producerii unui eveniment (ca, de exemplu, terminarea
unei operatii I/O);
finished = terminare normala.
linia de comanda si mediul (i.e., variabilele de mediu transmise de parinte);
¸s.a.
137
Accesul in program la parametrii din linia de comanda prin care s-a lansat in executie
programul respectiv, precum si la variabilele de mediu transmise acestuia de catre parinte
la crearea sa, se poate realiza declarind functia main a programului in felul urmator:
int main (int argc, char* argv[ ], char* env[ ])
unde variabila argv este un vector de pointeri catre parametrii din linia de comanda (sub
forma de siruri de caractere), ultimul element al tabloului fiind pointerul NULL, iar variabila
argc contine numarul acestor parametri. Similar, variabila env este un vector de pointeri
catre variabilele de mediu (care sunt siruri de caractere), ultimul element al tabloului ind
pointerul NULL.
Accesul la variabilele de mediu se poate realiza si prin intermediul functiilor getenv()
si setenv() din biblioteca stdlib.h. Iata un exemplu de cod C prin care se poate afla
valoarea unei variabile de mediu:
char* path;
path=getenv("PATH");
printf("The value of variable PATH is %s\n", path ? path : "not set!");
ˆ
In continuare vom prezenta cˆıteva apeluri sistem cu ajutorul c˘arora putem afla aceste
informat¸ii.
4.1.2 Primitive referitoare la procese
ˆ
In continuare vom trece ˆın revist˘a cˆıteva primitive (i.e., apeluri sistem) referitoare la
procese:
1) Primitive pentru aflarea PID-urilor unui proces si a parintelui acestuia: getpid,
getppid. Interfetele acestor functii sunt urmatoarele:
#include <unistd.h>
pid t getpid(void)
pid t getppid(void)
Efect: functia getpid returneaza PID-ul procesului apelant, iar getppid returneaza PID-
ul parintelui procesului apelant.
Exemplu. Urm˘atorul program exemplific˘a apelul acestor primitive:
138
/*
File: exemplu1.c
*/
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("\n\nProcesul: %d , avind parintele: %d\n",getpid(),getppid());
return 0;
}
2) Primitive pentru aflarea ID-urilor proprietarului unui proces si a grupului acestuia:
getuid,getgid si geteuid,getegid. Interfetele acestor functii sunt urmatoarele:
#include <unistd.h>
uid t getuid(void)
gid t getgid(void)
uid t geteuid(void)
gid t getegid(void)
Efect: functia getuid returneaza UID-ul (i.e.,User ID-ul) proprietarului, adic˘a al uti-
lizatorului care a lansat in executie procesul apelant, iar functia getgid returneaza GID-ul
(i.e.,Group ID-ul) grupului proprietar, adic˘a al grupului utilizatorului care a lansat in
executie procesul apelant.
Functia geteuid returneaza Effective User ID-ul, adic˘a UID-ul proprietarului efectiv,
iar functia getegid returneaza Effective Group ID-ul, adic˘a GID-ul grupului propri-
etarului efectiv.
3) Alte primitive ce ofera diverse informatii, sau modifica diverse atribute ale proceselor,
ar mai fi urmatoarele primitive (definite tot in header-ul unistd.h): setuid(),setgid(),
getpgrp(),getpgid(),setpgrp(),setpgid(). (Consultat¸i paginile de manual electronic
corespunz˘atoare acestor primitive pentru a afla detalii despre ele).
4) Primitive de suspendare a executiei pe o durata de timp specificata: sleep si usleep.
Interfetele acestor functii sunt urmatoarele:
void sleep(int nr sec)
void usleep(int nr msec)
Efect: functia sleep suspend˘a executia procesului apelant timp de nr sec secunde, iar
functia usleep suspend˘a executia procesului apelant timp de nr msec milisecunde.
Observat¸ie: dup˘a cum vom vedea ˆın lect¸ia despre semnale UNIX, apelul sleep, respec-
tiv usleep, este ˆıntrerupt ˆın momentul cˆınd procesul prime¸ste un semnal, i.e. apelul
returneaz˘a imediat, f˘ar˘a a a¸stepta scurgerea intervalului de timp specificat.
Exemplu. Urm˘atorul program exemplific˘a apelul acestor primitive:
139
/*
File: exemplu2.c
*/
#include <stdio.h>
#include <unistd.h>
void main(void)
{
printf("\n\nProcesul: %d , avind parintele: %d\n",getpid(),getppid());
printf("\n\nProprietarul procesului: UID=%d, GID=%d\n",getuid(),getgid());
printf("\n\nProprietarul efectiv: UID=%d, GID=%d\n",geteuid(),getegid());
printf("Start of sleeping for 3 seconds...\n");
sleep(3);
printf("Finish of sleeping for 3 seconds.\n");
return 0;
}
5) Primitiva de terminare a executiei: exit. Interfata acestei functii este urmatoarea:
void exit(int cod retur)
Efect: functia exit termin˘a executia procesului apelant si returneaza sistemului de ope-
rare codul de terminare specificat ca argument.
Observatie: acelasi efect are si instructiunea return cod retur;, dar numai daca apare in
functia main a programului.
6) Functia system permite lansarea de comenzi UNIX dintr-un program C, printr-un apel
de forma:
system(comanda );
Efect: se creeaz˘a un nou proces, ˆın care se incarc˘a shell-ul implicit, ce va executa comanda
specificat˘a.
Iat˘a ¸si dou˘a exemple:
system("ps");
Efect: se execut˘a comanda ps, ce afi¸seaz˘a procesele curente ale utilizatorului;
system("who | cut -b 1-8 > useri-logati.txt");
Efect: se execut˘a comanda ˆınl˘ant¸uit˘a specificat˘a, care creeaz˘a un fi¸sier cu numele
useri-logati.txt, ˆın care se vor asi numele utilizatorilor conectat¸i la sistem.
4.2 Crearea proceselor ¸si terminarea lor
1. Crearea proceselor – primitiva fork()
2. Terminarea proceselor
140
4.2.1 Crearea proceselor – primitiva fork()
Singura modalitate de creare a proceselor in UNIX/Linux este cu ajutorul apelului sistem
fork. Prototipul lui este urmatorul:
#include <unistd.h>
pid t fork(void)
Efect: prin acest apel se creeaza o copie a procesului apelant, si ambele procese – cel nou
creat si cel apelant – isi vor continua executia cu urmatoarea instructiune (din programul
executabil) ce urmeaz˘a dupa apelul functiei fork.
Singura diferenta dintre procese va fi valoarea returnata de functia fork, precum si, binein-
teles, PID-urile proceselor.
Procesul apelant va fi parintele procesului nou creat, iar acesta va fi fiul procesului apelant
(mai exact, unul dintre procesele fii ai acestuia).
Observat¸ie referitoare la aceast˘a “copiere”:
Memoria ocupata de un proces poate fi impartita in urmatoarele zone:
CODE
DATA
ENV
FILE
zona de cod
zona de date
zona de mediu
zona descriptorilor de fisiere
Zona de cod contine instructiunile programului, zona de date contine variabilele progra-
mului (datele statice/dinamice, stiva, registrii, etc.), zona de mediu contine variabilele de
mediu (ce sunt primite la crearea procesului, de la procesul parinte), iar zona descriptorilor
de fisiere contine descriptorii de fisiere deschise.
Aceasta impartire a memoriei ocupate de un proces corespunde organizarii logice a mem-
oriei calculatorului, ce este impartita in pagini logice de memorie. Fiecarei pagini logice
ii corespunde o pagina fizica de memorie in memoria calculatorului. Aceasta corespon-
denta poate varia pe parcursul executiei procesului respectiv. (Este vorba aici despre
tehnicile de gestionare a memoriei in UNIX - vezi cursul de sisteme de operare din anul I.)
Totalitatea paginilor fizice la un moment formeaza memoria ocupata de acel proces la mo-
mentul respectiv, cu observatia evidenta ca memoria fizica ocupata nu este obligatoriu o
zona contigua de memorie (doar memoria logica ocupata este intotdeauna o zona contigua
de memorie).
Important: practic, noul proces creat cu primitiva fork va avea aceeasi zona de cod fiz-
ica ca si procesul parinte, doar zonele de date (i.e., zonele DATA,ENV,FILE) vor fi, fizic,
diferite. Insa, imediat dupa executia functiei fork, aceste zone vor contine aceleasi val-
ori, deoarece, ˆın cursul apelului, se face copierea zonelor de date ale procesului parinte in
zonele de date, fizice, ale fiului.
Din acest motiv – deoarece se duplica zona de date, deci inclusiv registrii, deci inclusiv reg-
istrul PC (“Program Counter”) – executia procesului fiu va continua din acelasi punct ca
141
si a parintelui: se va executa instructiunea imediat urmatoare dupa apelul fork.Atent¸ie:
este vorba de urm˘atoarea instruct¸iune ˆın limbaj ma¸sin˘a, a nu se confunda cu limbajul
surs˘a a programului respectiv, care de obicei este limbajul C.
Tot din acest motiv, imediat dupa apelul fork procesul fiu va avea aceleasi valori ale
variabilelor din program si aceleasi siere deschise ca si procesul parinte. Mai departe
insa, fiecare proces va lucra pe zona sa de memorie. Deci, daca fiul modifica valoarea
unei variabile, aceasta modificare nu va fi vizibila si in procesul tata (si nici invers). ˆ
In
concluzie, nu avem memorie partajata (shared memory) ˆıntre procesele tata si fiu.
Valoarea returnat˘a: funct¸ia fork returneaza valoarea -1, in caz de eroare (daca nu s-a
putut crea un nou proces), iar in caz de succes, returneaza respectiv urmatoarele valori in
cele doua procese, tata si fiu:
n, in procesul tata, unde neste PID-ul noului proces creat;
0, in procesul fiu.
Observat¸ii:
1. PID-ul unui nou proces nu poate fi niciodata 0, deoarece procesul cu PID-ul 0nu este
fiul nici unui proces, ci este radacina arborelui proceselor (arbore ce descrie relatiile
parinte-fiu dintre toate procesele existente in sistem). Mai mult, procesul cu PID-ul
0este singurul proces din sistem ce nu se creeaza prin apelul fork, ci el este creat
atunci cind se boot-eaz˘a sistemul UNIX/Linux pe calculatorul respectiv.
2. Procesul nou creat poate afla PID-ul tatalui cu ajutorul primitivei getppid, pe cind
procesul tata nu poate afla PID-ul noului proces creat, fiu al lui, prin alta maniera
decit prin valoarea returnata de apelul fork. Nu s-a creat o primitiv˘a pentru aflarea
PID-ului fiului deoarece, spre deosebire de p˘arinte, fiul unui proces nu este unic – un
proces poate avea zero, unul, sau mai mult¸i fii la un moment dat.
Exemplu. Urmatorul program exemplifica crearea unui nou proces cu primitiva fork si
ilustreaza unele diferente dintre cele doua procese, p˘arinte si fiu:
/*
File: fork.c (exemplu de creare a unui fiu)
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int a=0;
if( (pid=fork() ) == -1)
142
{
perror("Eroare la fork");
exit(1);
}
if (pid == 0)
{ /* fiu */
printf("Procesul fiu id=%d, cu parintele: id=%d\n",getpid(),getppid());
printf("Procesul fiu: dupa fork, variabila a=%d\n", a);
a = 5;
printf("Procesul fiu: dupa modificare, variabila a=%d\n", a);
}
else
{ /* parinte */
printf("Procesul tata id=%d, cu parintele: id=%d si fiul: id=%d\n",
getpid(),getppid(),pid);
sleep(2);
printf("Procesul tata: variabila a=%d\n", a);
}
/* zona de cod comuna */
printf("Zona de cod comuna, executata de %s.\n", pid==0?"fiu":"tata");
return 0;
}
4.2.2 Terminarea proceselor
Procesele se pot termina ˆın dou˘a moduri:
1. Terminarea normal˘a:
se petrece ˆın momentul ˆıntˆılnirii ˆın program a apelului primitivei exit, ce a fost
prezentat˘a ˆın sect¸iunea anterioar˘a, sau la sfˆır¸situl funct¸iei main a programului, sau
la ˆıntˆılnirea instruct¸iunii return ˆın funct¸ia main.
Ca efect, procesul este trecut ˆın starea finished, se ˆınchid fi¸sierele deschise (¸si se
salveaz˘a pe disc cont¸inutul buffer -elor folosite), se dealoa zonele de memorie alocate
procesului respectiv, ¸s.a.m.d.
Codul de terminare (furnizat de primitiva exit sau de instruct¸iunea return) este
salvat ˆın intrarea corespunz˘atoare procesului respectiv din tabela proceselor; intrarea
respectiv˘a nu este dealocat˘a (“¸stears˘a”) imediat din tabel˘a, astfel ˆıncˆıt codul de
terminare a procesului respectiv s˘a poat˘a fi furnizat procesului p˘arinte la cererea
acestuia (ceea ce se face cu ajutorul primitivei wait despre care vom discuta mai
tˆırziu); de abia dua ce s-a furnizat codul de terminare arintelui, intrarea este
¸stears˘a” din tabel˘a.
143
2. Terminarea anormaa:
se petrece ˆın momentul primirii unui semnal UNIX (mai multe detalii vom vedea mai
tˆırziu, cˆınd vom discuta despre semnale UNIX).
S¸i ˆın acest caz se dealoc˘a zonele de memorie ocupate de procesul respectiv, ¸si se
astreaz˘a doar intrarea sa din tabela proceselor pˆına cˆınd arintele s˘au va cere codul
de terminare (reprezentat ˆın acest caz de num˘arul semnalului ce a cauzat terminarea
anormal˘a).
4.3 Sincronizarea proceselor
1. Introducere
2. Primitiva wait
4.3.1 Introducere
In programarea concurenta exista notiunea de punct de sincronizare a doua procese: este
un punct din care cele doua procese au o executie simultana (i.e., este un punct de asteptare
reciproca).
Punctul de sincronizare nu este o notiune dinamica, ci una statica (o notiune fixa): este
precizat in algoritm (i.e., program) locul unde se gaseste acest punct de sincronizare.
Citeva caracteristici ale punctului de sincronizare ar fi urmatoarele:
procesele isi reiau executia simultan dupa acest punct;
punctul de sincronizare este valabil pentru un numar fixat de procese (nu neaparat doar
pentru doua procese), si nu pentru un numar variabil de procese.
Primitiva fork este un exemplu de punct de sincronizare: cele doua procese – procesul
apelant al primitivei fork si procesul nou creat de apelul acestei primitive – isi reiau
executia simultan din acest punct (i.e., punctul din program in care apare apelul functiei
fork).
Un exemplu de utilizare a punctului de sincronizare:
Exemplu. Sa consideram problema calculului maximului unei secvente de numere: un
proces master imparte secventa de numere la mai multe procese slave, fiecare dintre acestea
va calcula maximul subsecventei primite, si apoi se va sincroniza cu procesul master printr-
un punct de sincronizare pentru ca sa-i transmita rezultatul partial obtinut. Dupa primirea
tuturor rezultatelor partiale, procesul master va calcula rezultatul final.
144
4.3.2 Primitiva wait
Un alt exemplu de sincronizare, des intilnita in practica, ar fi urm˘atorul:
Procesul parinte poate avea nevoie de valoarea de terminare returnata de procesul fiu.
Pentru a realiza aceasta facilitate, trebuie stabilit un punct de sincronizare intre sfirsitul
programului fiu si punctul din programul parinte in care este nevoie de acea valoare, si
apoi transferata acea valoare de la procesul fiu la procesul p˘arinte.
Aceasta situatie a fost implementata in UNIX printr-o primitiva, numita wait.
Apelul sistem wait este utilizat pentru a astepta un proces fiu sa se termine. Interfata
acestei functii este urmatoarea:
#include <sys/types.h>
#include <sys/wait.h>
pid t wait(int* stat loc)
Efect: apelul functiei wait suspena executia procesului apelant pina ˆın momentul cind
unul (oricare) dintre fiii lui se termin˘a sau este stopat (i.e., terminat anormal) printr-un
semnal. Dac˘a exist˘a deja vreun fiu care s-a terminat sau a fost stopat, atunci functia wait
returneaza imediat.
Functia wait returneaza ca valoare PID-ul acelui proces fiu, iar in locatia referita de
pointerul stat loc este salvata urmatoarea valoare:
valoarea de terminare a acelui proces fiu (si anume, in octetul high al acelui int), daca
functia wait returneaza deoarece s-a terminat vreun proces fiu;
codul semnalului (si anume, in octetul low al acelui int), daca functia wait returneaza
deoarece un fiu a fost stopat de un semnal.
Daca procesul apelant nu are procese fii, atunci functia wait returneaza valoarea -1, iar
variabila errno este setata in mod corespunzator pentru a indica eroarea (ECHILD sau
EINTR).
Observat¸ie importana: daca procesul parinte se termina inaintea vreunui proces fiu, atunci
acestui fiu i se va atribui ca parinte procesul init (ce are PID-ul 1), iar acest lucru se face
pentru toate procesele fii neterminate in momentul terminarii parintelui lor.
Iar daca un proces se termina inaintea parintelui lui, atunci el devine zombie – procesul
se termina in mod obisnuit (i.e., se inchid fisierele deschise, se elibereaza zona de memorie
ocupata de proces, ¸s.a.m.d.), dar se pastreaza totusi intrarea corespunzatoare acelui proces
in tabela proceselor din sistem, pentru ca aceasta intrare va pastra codul de terminare
a procesului, cod ce va putea fi consultat, eventual, de catre parintele procesului prin
intermediul functiei wait.
(Aceasta observatie este valabila intotdeauna, nu doar numai in cazul folosirii functiei
wait.)
Pe lˆıng˘a primitiva wait, care asteapta terminarea oricarui fiu, mai exista inca o primitiv˘a,
numita waitpid, care va astepta terminarea unui anumit fiu, mai exact a procesului fiu
avind PID-ul specificat ca argument.
145
Indicat¸ie: a se citi neaparat paginile de manual despre functiile wait si waitpid.
Exemplu. Iat˘a un exemplu simplu ce ilustreaza cazul terminarii normale a fiului:
/*
File: wait-ex1.c (terminare normala a fiului)
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
if (fork() == 0)
{ /* fiu */
printf("Proces fiu id=%d\n", getpid());
exit(3);
}
else
{ /* parinte */
int pid_fiu, cod_term;
pid_fiu = wait(&cod_term);
printf("Tata: sfirsit fiul %d cu valoarea %d\n", pid_fiu, cod_term);
}
}
In urma executiei acestui program, se va afisa valoarea 768 (adica 3*256), deoarece este
o terminare normala a fiului si deci valoarea de terminare se depune in octetul high al
locat¸iei date ca argument apelului wait.
Observatie: exista unele macro-uri ce fac conversia valorii de terminare (a se vedea help-ul
de la functia wait).
Exemplu. Iat˘a ¸si un alt exemplu, ce ilustreaza cazul terminarii anormale a fiului:
/*
File: wait-ex2.c (terminare anormala a fiului)
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
if (fork() == 0)
{ /* fiu */
printf("Proces fiu id=%d\n", getpid());
for(;;);
}
146
else
{ /* parinte */
int pid_fiu, cod_term;
pid_fiu = wait(&cod_term);
printf("Tata: sfirsit fiul %d cu valoarea %d\n", pid_fiu, cod_term);
}
}
Executati acest program in background, prin comanda:
UNIX>wait-ex2 &
Se observa ca procesele nu se opresc niciodata (intrucit procesul fiu executa o bucla infinita,
iar procesul tata il asteapta cu wait). Ca atare, pentru a le opri, aflati PID-ul fiului (cu
comanda ps) si apoi omoriti-l (cu comanda kill -9 pid fiu); astfel de fapt ii transmiteti
semnalul cu numarul 9. Ca urmare, cele doua procese se vor termina, iar in urma executiei
lor se va afisa valoarea 9, adica numarul semnalului care l-a stopat pe fiu, deoarece este o
terminare anormala a fiului si deci numarul semnalului se depune in octetul low al locat¸iei
date ca argument apelului wait.
4.4 Reacoperirea proceselor
1. Introducere
2. Primitivele din familia exec
4.4.1 Introducere
Dupa cum am vazut deja, singura modalitate de a crea un nou proces in UNIX este prin
apelul functiei fork. Numai ca in acest fel se creeaza o copie a procesului apelant, adica
o noua instanta de executie a aceluiasi fisier executabil.
Si atunci, cum este posibil sa executam un alt fisier executabil decit cel care apeleaza
primitiva fork?
Raspuns: printr-un alt mecanism, acela de “reacoperire a proceselor, disponibil in UNIX
prin intermediul primitivelor de tipul exec.
147
4.4.2 Primitivele din familia exec
In UNIX/Linux exista o familie de primitive exec care transforma procesul apelant intr-un
alt proces specificat (prin numele fisierului executabil asociat) ca argument al apelului
exec.
Noul proces se spune ca “reacoper˘a procesul ce a executat apelul exec, si el mo¸stene¸ste
caracteristicile acestuia (inclusiv PID-ul), cu exceptia citorva dintre ele (vom reveni mai
jos cu lista acestora).
Observat¸ie: in caz de succes, apelul exec nu returneaz˘a !!!, deoarece nu mai exista procesul
apelant. Prin urmare, exec este singurul exemplu de functie (cu exceptia primitivei exit)
al carei apel nu returneaza inapoi in programul apelant.
Exista in total 6 functii din familia exec. Ele difera prin nume si prin lista parametrilor
de apel, si sunt impartite in 2 categorii (ce difera prin forma in care se dau parametrii de
apel):
a) numarul de parametri este variabil;
b) numarul de parametri este fix.
1. Prima pereche de primitive exec este perechea execl si execv, ce au interfetele
urmatoare:
a1)int execl(char* ref, char* argv0, ..., char* argvN)
b1)int execv(char* ref, char* argv[])
Argumentul ref reprezinta numele procesului care va reacoperi procesul apelant al
respectivei primitive exec. El trebuie sa fie un nume de fisier executabil care sa se
afle in directorul curent (sau sa se specifice si directorul in care se afla, prin cale
absoluta sau relativa), deoarece nu este cautat in directoarele din variabila de mediu
PATH.
Argumentul ref este obligatoriu, celelalte argumente pot lipsi; ele exprima parametrii
liniei de comanda pentru procesul ref.
Ultimul argument argvN, respectiv ultimul element din tabloul argv[], trebuie sa fie
pointerul NULL.
Prin conventie argv0, respectiv argv[0], trebuie sa coincida cu ref (deci cu numele
fisierului executabil). Aceasta este insa doar o conventie, nu se produce eroare in
caz ca este incalcata. De fapt, argumentul ref specifica numele real al fisierului
executabil ce se va incarca si executa, iar argv0, respectiv argv[0], specifica numele
afisat (de comenzi precum ps,w, ¸s.a.) al noului proces.
2. A doua pereche de primitive exec este perechea execle si execve, ce au interfetele
urmatoare:
a2)int execle(char* ref, char* argv0, ..., char* argvN, char* env[])
b2)int execve(char* ref, char* argv[], char* env[])
Efect: similar cu perechea anterioara, doar ca acum ultimul parametru permite
transmiterea catre noul proces a unui environment (i.e., un mediu: o multime de
148
¸siruri de caractere de forma variabila=valoare).
La fel ca pentru argv[], ultimul element din tabloul env[] trebuie sa fie pointerul
NULL.
3. A treia pereche de primitive exec este perechea execlp si execvp, ce au interfetele
urmatoare:
a3)int execlp(char* ref, char* argv0, ..., char* argvN)
b3)int execvp(char* ref, char* argv[])
Efect: similar cu perechea execl si execv, cu observatia ca fisierul ref este cautat
si in directoarele din variabila de mediu PATH, in cazul in care nu este specificat
impreuna cu calea, relativa sau absoluta, pina la el.
In caz de esec (datorita memoriei insuficiente, sau altor cauze), toate primitivele exec
returneaza valoarea -1. Altfel, functiile exec nu mai returneaza, deoarece procesul apelant
nu mai exista (a fost reacoperit de noul proces).
Caracteristicile procesului dup˘a exec:
Noul proces mosteneste caracteristicile vechiului proces (are acelasi PID, aceeasi prioritate,
acelasi proces parinte, aceeasi descriptori de fisiere deschise, etc.), cu exceptia citorva dintre
ele.
Si anume, diferitele caracteristici ale procesului sunt conservate in timpul reacoperirii
cu oricare dintre functiile din familia exec, cu exceptia urmatoarelor caracteristici, in
conditiile specificate:
Caracteristica Condit¸ia ˆın care nu se conserv˘a
Proprietarul efec-
tiv
Daca este setat bitul setuid al sierului incarcat, proprietarul aces-
tui fisier devine proprietarul efectiv al procesului.
Grupul efectiv Daca este setat bitul setgid al fisierului incarcat, grupul proprietar
al acestui sier devine grupul proprietar efectiv al procesului.
Handler-ele
de semnale
Sunt reinstalate handler-ele implicite pentru semnalele corupte
(interceptate).
Descriptorii
de fisiere
Daca bitul FD CLOEXEC de inchidere automata in caz de exec, al
vreun descriptor de fisier a fost setat cu ajutorul primitivei fcntl,
atunci acest descriptor este inchis la exec (ceilalti descriptori de
fisiere ramin deschisi).
Exemplul urmator ilustreaza citeva dintre aceste proprietati.
Exemplu. Consideram urm˘atoarele doua programe, primul este before exec.c care
apeleaza exec pentru a se reacoperi cu al doilea, numit after exec.c:
/*
File: before_exec.c
149
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
char tab_ref[1000];
void main()
{
printf("Caracteristici inainte de exec\n");
printf("------------------------------\n");
printf("ID-ul procesului : %d\n",getpid());
printf("ID-ul parintelui : %d\n",getppid());
printf("Proprietarul real : %d\n",getuid());
printf("Proprietarul efectiv : %d\n",geteuid());
printf("Directorul de lucru : %s\n\n",getcwd(tab_ref,1000));
/* cerere de inchidere a intrarii standard la reacoperire */
fcntl(STDIN_FILENO, F_SETFD, FD_CLOEXEC);
/* reacoperire */
execl("after_exec","after_exec",NULL);
}
/*
File: after_exec.c
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
char tab_ref[1000];
void main()
{
int nrBytesRead;
char ch;
printf("Caracteristici dupa exec\n");
printf("------------------------\n");
printf("ID-ul procesului : %d\n",getpid());
printf("ID-ul parintelui : %d\n",getppid());
printf("Proprietarul real : %d\n",getuid());
printf("Proprietarul efectiv : %d\n",geteuid());
printf("Directorul de lucru : %s\n\n",getcwd(tab_ref,1000));
nrBytesRead = read(STDIN_FILENO, &ch, 1);
printf("Numarul de caractere citite: %d\n",nrBytesRead);
if( nrBytesRead = -1 )
perror("Error reading stdin (because it is closed !) ");
}
150
Compilati cele doua programe, dindu-le ca nume de executabil numele sursei fara extensia
.c, si apoi lansati-l in executie pe primul dintre ele. El va fi reacoperit de cel de-al doilea,
iar in urma executiei veti constata ca variabila nrBytesRead are valoarea -1, motivul fiind
a intrarea standard stdin este inchisa in procesul after exec.
Exemplu. Iat˘a ¸si un alt exemplu – un program care se reacopera cu el insusi, dar la al
doilea apel isi modifica parametrii de apel pentru a-si da seama ca este la al doilea apel si
astfel sa nu intre intr-un apel recursiv la infinit.
/*
File: exec-rec.c (exemplu de apel recursiv prin exec)
*/
#include <stdio.h>
#include <errno.h>
extern int errno;
void main(int argc, char* argv[], char* env[])
{
char **r, *s, *w[5];
printf("PID=%d, PPID=%d, OWNER=%d\n",getpid(),getppid(),getuid());
printf("ENVIRONMENT:\n");
r=env;
while( s=*r++)
{ printf("%s\n",s); }
putchar(’\n’);
env[0]="Salut.";
env[1]=NULL;
w[0]=argv[0]; /* numele executabilului ! */
w[1]="2nd call";
w[2]=NULL;
if( (argv[1] != NULL) && (argv[1][0] == ’2’) )
{
exit(0); /* oprire recursie la al doilea apel ! */
}
else
{
printf("Urmeaza apelul primitivei exec.\n");
if( execve(argv[0], w, env) == -1)
{
printf("Error on exec: err=%d\n", errno);
exit(1);
}
}
}
Observat¸ii:
151
1. Apelul exec consuma mai multa memorie decit apelul fork.
2. Comportamentul in cazul fisierelor deschise in momentul apelului primitivelor exec:
daca s-au folosit instructiuni de scriere buffer-izate (ca de exemplu funct¸iile fprintf,
fwrite ¸s.a. din biblioteca standard de C), atunci buffer -ele nu sunt scrise automat
in fisier pe disc ˆın momentul apelulul exec, deci informatia din ele se pierde.
Comentariu: in mod normal buffer-ul este scris in fisier abia in momentul cind s-
a umplut, sau la intilnirea caracterului ’\n’(newline). Dar se poate fort¸a scrierea
buffer-ului in fisier cu ajutorul functiei fflush din biblioteca standard de C.
Iat˘a ¸si un exemplu referitor la ultima observatie:
Exemplu. Sa consideram urmatoarele trei programe, com-0.c,com-1.c ¸si com-2.c,
dintre care primele doua se reacopera fiecare cu al treilea, ¸si care vor fi executate in
maniera specificata mai jos:
/*
File: com-0.c
*/
#include <stdio.h>
void main()
{
int fd;
fd=creat("fis.txt",0666);
close(1); /* inchid stdout */
dup(fd); /* duplic fd cu 1 (primul gasit liber) */
close(fd); /* inchid fd */
/* practic astfel am redirectat stdout in fisierul fis.txt */
write(1,"Salut",5);
execl("com-2","com-2",NULL);
}
/*
File: com-1.c
*/
#include <stdio.h>
void main()
{
printf("Salut");
fflush(stdout);
execl("com-2","com-2",NULL);
}
/*
File: com-2.c
152
*/
#include <stdio.h>
void main()
{
write(1," la toti!",9);
}
Compilati cele trei programe, dindu-le ca nume de executabil numele sursei fara extensia
.c, si apoi lansati-le in executie astfel:
UNIX>com-1
Salut la toti!
Obsevatie: Daca eliminam apelul fflush din programul com-1.c, atunci pe ecran se va
afisa doar mesajul “la toti!”, deoarece “Salut” se pierde prin exec,buffer-ul nefiind
golit pe disc.
UNIX>com-0
UNIX>cat fis.txt
Salut la toti!
Deci programul com-0 a scris mesajul in sierul fis.txt si nu pe ecran.
Concluzie: descriptorii de fisiere deschise din com-0 s-au “mo¸stenit” prin exec ˆın com-2.
Observat¸ie: functia dup(fd)cauta primul descriptor de fisier nefolosit si il redirecteaza
catre descriptorul primit ca parametru (a se consulta help-ul acestei functii pentru detalii
suplimentare). Cu ajutorul ei, ˆın programul com-0 am redirectat ie¸sirea standard stdout
a programului ˆın fisierul fis.txt.
4.5 Semnale UNIX
1. Introducere
2. Categorii de semnale
3. Tipurile de semnale predefinite ale UNIX-ului
4. Cererea explicit˘a de generare a unui semnal – primitiva kill
5. Coruperea semnalelor – primitiva signal
6. Definirea propriilor handler-ere de semnal
7. Blocarea semnalelor
8. A¸steptarea unui semnal
153
4.5.1 Introducere
Semnalele UNIX reprezinta un mecanism fundamental de manipulare a proceselor si de
comunicare intre procese, ce asigur˘a tratarea evenimentelor asincrone ap˘arute ˆın sistem.
Un semnal UNIX este o intrerupere software generat˘a ˆın momentul producerii unui anumit
eveniment ¸si transmisa de sistemul de operare unui anumit proces (deci este intrucitva
similar intreruperilor din MS-DOS).
Un semnal este generat de aparitia unui eveniment exceptional (care poate fi o eroare,
un eveniment extern sau o cerere explicita). Orice semnal are asociat un tip, reprezentat
printr-un numar intreg pozitiv, si un proces destinatar (i.e., procesul caruia ii este destinat
acel semnal). Odata generat, semnalul este pus in coada de semnale a sistemului, de unde
este extras si transmis procesului destinatar de catre sistemul de operare.
Transmiterea semnalului destinatarului se face imediat dupa ce semnalul a ajuns in coada
de semnale, cu o exceptie: daca primirea semnalelor de tipul respectiv a fost blocat˘a de
catre procesul destinatar (vom vedea mai tirziu cum anume se face acest lucru), atunci
transmiterea semnalului se va face abia in momentul cind procesul destinatar va debloca
primirea acelui tip de semnal.
In momentul in care procesul destinatar primeste acel semnal, el isi intrerupe executia si
va executa o anumita actiune (i.e., o functie de tratare a acelui semnal), functie numita
handler de semnal si care este atasata tipului de semnal primit, dupa care procesul isi va
relua executia din punctul in care a fost intrerupt (cu anumite exceptii: unele semnale vor
cauza terminarea sau intreruperea acelui proces).
In concluzie, fiecare tip de semnal are asociat o actiune (un handler) specifica acelui tip
de semnal.
4.5.2 Categorii de semnale
In general, evenimentele ce genereaza semnale se impart in trei categorii: erori, evenimente
externe si cereri explicite.
1) O eroare inseamna ca programul a facut o operatie invalida si nu poate sa-si continue
executia.
Nu toate erorile genereaza semnale, ci doar acele erori care pot apare in orice punct al
programului, cum ar fi: impartirea la zero, accesarea unei adrese de memorie invalide,
etc.
Exemplu de erori ce nu genereaza semnale: erorile in operatiile I/O – apelul de functie
respectiv va intoarce un cod de eroare, de obicei -1.
154
2) Evenimentele externe sunt in general legate de operatiile I/O sau de actiunile altor
procese, cum ar fi: sosirea datelor (pe un socket sau un pipe, de exemplu), expirarea
intervalului de timp setat pentru un timer (o alarma), terminarea unui proces fiu, sau
suspendarea/terminarea programului de catre utilizator (prin apasarea tastelor CTRL+Z
sau CTRL+C).
3) O cerere explicita inseamna generarea unui semnal de catre un proces, prin apelul
functiei de sistem kill, a carei sintaxa o vom discuta mai tirziu.
Important: semnalele pot fi generate sincron sau asincron.
Un semnal sincron este un semnal generat de o anumita actiune specifica in program si
este livrat (daca nu este blocat) in timpul acelei actiuni. Evenimentele care genereaza
semnale sincrone sunt: erorile si cererile explicite ale unui proces de a genera semnale
pentru el insusi.
Un semnal asincron este generat de un eveniment din afara zonei de control a procesului
care il receptioneaza, cu alte cuvinte, un semnal ce este receptionat, in timpul executiei
procesului destinatar, la un moment de timp ce nu poate fi anticipat. Evenimentele care
genereaza semnale asincrone sunt: evenimentele externe si cererile explicite ale unui proces
de a genera semnale destinate altor procese.
Pentru fiecare tip de semnal exista o actiune implicita de tratare a acelui semnal, specifica
sistemului de operare UNIX respectiv. Aceasta actiune este denumita handler-ul de semnal
implicit atasat acelui tip de semnal.
Atunci cind semnalul este livrat procesului, acesta este intrerupt si are trei posibilitati de
comportare: fie sa execute aceasta actiune implicita, fie sa ignore semnalul, fie sa execute
o anumita functie handler utilizator (i.e., scrisa de programatorul respectiv).
Setarea unuia dintre cele trei comportamente se face cu ajutorul apelului primitivelor
signal sau sigaction, despre care vom vorbi mai tirziu. Asadar, la ecare primire a
unui anumit tip de semnal, se va executa acea actiune (comportament) ce a fost setata la
ultimul apel al uneia dintre cele doua primitive, apel efectuat pentru acel tip de semnal.
Observatie: daca actiunea specificata pentru un anumit tip de semnal este de a-l ignora,
atunci orice semnal de acest tip este inlaturat din coada de semnale imediat dupa primire,
chiar si in cazul in care acel tip de semnal este blocat pentru procesul respectiv (vom
discuta mai tirziu despre blocarea semnalelor).
4.5.3 Tipurile de semnale predefinite ale UNIX-ului
In fisierul header signal.h se gaseste lista semnalelor UNIX predefinite, mai exact numarul
intreg asociat fiecarui tip de semnal, impreuna cu o constanta simbolica, cu observatia ca
in programe se recomanda folosirea constantelor simbolice in locul numerelor (deoarece
numerele asociate semnalelor pot diferi de la o versiune de UNIX la alta).
155
Aceasta lista poate fi obtinuta si cu comanda urmatoare:
UNIX>kill -l
iar pagina de manual ce contine descrierea semnalelor poate fi obtinuta astfel:
UNIX>man 7 signal
Aceste tipuri predefinite de semnale se pot clasifica in mai multe categorii:
1. semnale standard de eroare: SIGFPE,SIGILL,SIGSEGV,SIGBUS;
2. semnale de terminare: SIGHUP,SIGINT,SIGQUIT,SIGTERM,SIGKILL;
3. semnale de alarma: SIGALRM,SIGVTALRM,SIGPROF;
4. semnale asincrone I/O: SIGIO,SIGURG;
5. semnale pentru controlul proceselor: SIGCHLD,SIGCONT,SIGSTOP,SIGTSTP,
SIGTTIN,SIGTTOU;
6. alte tipuri de semnale: SIGPIPE,SIGUSR1,SIGUSR2.
In continuare, sa trecem in revista categoriile de semnale amintite mai sus.
Observatie: semnalele notate cu * mai jos au urmatorul comportament: procesul des-
tinatar, cind se intrerupe la primirea semnalului, provoaca crearea unui fisier core (ce
contine imaginea memoriei procesului in momentul intreruperii), care poate fi inspectat
pentru depanarea programului; fisierul core este scris pe disc in directorul de unde a fost
lansat acel proces.
1. Semnale standard de eroare:
SIGFPE * = signal floating point error, semnal sincron generat in caz de eroare
aritmetica fatala, cum ar fi impartirea la zero sau overflow-ul.
SIGILL * = signal illegal instruction, semnal sincron generat cind se incearca
executarea unei instructiuni ilegale, adica programul incearca sa execute zone
de date (in loc de functii), situatie ce poate apare daca fisierul executabil este
stricat, sau daca se paseaza un pointer la o data acolo unde se asteapta un
pointer la o functie, sau daca se corupe stiva prin scrierea peste sfirsitul unui
array de tip automatic.
SIGSEGV * = signal segmentation violation, semnal sincron generat in caz de
violare a segmentului de memorie, adica procesul incearca sa acceseze o zona
de memorie care nu ii apartine (care nu ii este alocata – apartine altor procese,
etc.).
Cauze de producere a acestui eveniment: folosirea pentru acces la memorie a
unui pointer NULL sau neinitializat, ori folosirea unui pointer pentru parcurgerea
unui array, fara a verifica depasirea sfirsitului array-ului.
156
SIGBUS * = signal bus error, semnal sincron generat in caz de eroare de magis-
trala, ce poate apare tot atunci cind se utilizeaza un pointer NULL sau neinitial-
izat, numai ca, spre deosebire de SIGSEGV care raporteazaun acces nepermis la
o zona de memorie valida (existenta), SIGBUS raporteaza un acces nepermis la
o zona de memorie invalida: adresa inexistenta, sau un pointer dezaliniat, cum
ar fi referirea la un intreg (reprezentat pe 4 octeti), la o adresa nedivizibila cu 4
(fiecare tip de calculator si sistem de operare are o anumita politica de aliniere
a datelor).
Observatie: toate aceste semnale au drept actiune implicita terminarea proce-
sului (cu afisarea unui mesaj de eroare specific) si crearea acelui fisier core.
2. Semnale de terminare: ele sunt folosite pentru a indica procesului sa-si termine
executia, intr-un fel sau altul. Motivul pentru care se folosesc este acela de a putea
“face curat” inainte de terminarea propriu-zisa: se pot salva date in fisiere, sterge
fisierele temporare, restaura vechiul tip de terminal in caz ca el a fost modificat de
catre program, etc.
SIGHUP =signal hang-up, semnal generat in momentul deconectarii terminalului
(datorita unei erori in retea, sau altor cauze), ori la terminarea procesului de
control a terminalului, si este trimis proceselor asociate cu acea sesiune de lucru,
avind ca efect deconectarea efectiva a acestor procese de terminalul de control.
Actiunea implicita const˘a ˆın terminarea procesului.
SIGINT =signal program interrupt, semnal de intrerupere, generat atunci cind
utilizatorul foloseste caracterul INTR (adica: apasa tastele CTRL+C) pentru a
termina executia programului.
SIGQUIT =signal quit, semnal de intrerupere, generat cind utilizatorul folos-
este caracterul QUIT (de obicei, tastele CTRL+\), fiind asemanator cu semnalul
SIGINT: provoaca terminarea procesului.
SIGTERM = semnal generic, folosit pentru terminarea proceselor; spre deosebire
de SIGKILL, acest semnal poate fi blocat, ignorat, sau sa i se asigneze un handler
propriu.
SIGKILL =signal kill, semnal utilizat pentru terminarea imediata a proceselor;
el nu poate fi blocat, ignorat, sau sa i se asigneze un handler propriu, deci are
o comportare fixa – terminarea procesului, de aceea se spune ca este un semnal
fatal. Poate fi generat doar de evenimentul cerere explicita, folosind apelul
kill().
3. Semnale de alarma: ele indica expirarea timpului pentru timer-e si alarme, care pot
fi setate prin apelul primitivelor alarm() si setitimer(). Comportamentul implicit
al acestor semnale este terminarea procesului, de aceea este indicata asignarea de
handler-e proprii pentru ele.
SIGALRM =signal time alarm, semnal emis la expirarea timpului pentru un
timer care masoara timpul “real” (i.e., intervalul de timp scurs intre inceputul
si sfirsitul procesului).
157
SIGVTALRM =signal virtual time alarm, semnal emis la expirarea timpului pen-
tru un timer care masoara timpul “virtual” (i.e., timpul in care procesul uti-
lizeaza efectiv CPU -ul).
SIGPROF = semnal emis la expirarea timpului pentru un timer care masoara
timpul in care procesul utilizeaza efectiv CPU -ul si timpul in care CPU -ul
asteapta indeplinirea unor conditii (cum ar fi,de exemplu, terminarea unor
cereri de I/O) pentru acel proces. Acest tip de semnal se utilizeaza pentru
a implementa facilitati de optimizare a codului programelor.
4. Semnale asincrone I/O: ele sunt utilizate impreuna cu facilitatile I/O ale sistemului;
trebuie apelata explicit functia fcntl() asupra unui descriptor de fisiere pentru a
se putea ajunge in situatia de a se genera aceste semnale.
SIGIO = semnal folosit pentru a indica ca un anumit descriptor de fisiere este
gata de a realiza operatii I/O; doar descriptorii asociati unui socket sau unui pipe
pot genera acest tip de semnal; semnalul este generat in momentul cind, spre
exemplu, se receptioneaza niste date pe un socket, pentru a indica programului
ca trebuie sa faca un read() pentru a le citi.
SIGURG = semnal transmis atunci cind date “urgente” (asa-numitele out-of-band
data) sunt receptionate pe un socket.
5. Semnale pentru controlul proceselor:
SIGCHLD =signal child, semnal trimis procesului parinte atunci cind procesul
fiu (i.e., emitatorul semnalului) isi termina executia.
In general, este util ca sa se asigneze un handler propriu pentru acest tip de
semnal, in care sa se utilizeze apelurile wait() sau waitpid() pentru a accepta
codul de terminare al proceselor fii.
Observatie: astfel kernel-ul va elibera intrarea corespunzatoare acelui fiu din
tabela proceselor; in caz contrar acest lucru se petrece abia la terminarea pro-
cesului tata.
SIGCONT =signal continue, semnal transmis pentru a cauza continuarea exe-
cutiei unui proces, care a fost anterior suspendat prin semnalul SIGSTOP sau
prin celelate semnale ce suspenda procese.
SIGSTOP =signal stop, semnal utilizat pentru suspendarea executiei unui pro-
ces. La fel ca si SIGKILL, acest semnal are o comportare fixa, neputind fi
blocat, ignorat, sau sa i se asigneze un handler propriu.
SIGTSTP = semnal interactiv de suspendare a executiei unui proces, generat
prin tastarea caracterului SUSP (de obicei, tastele CTRL+Z). Spre deosebire de
SIGSTOP, el poate fi blocat, ignorat, sau sa i se asigneze un handler propriu.
SIGTTIN = semnal transmis unui proces, ce ruleaza in background, in momentul
in care incearca sa citeasca date de la terminalul asociat. Actiunea sa implicita
este de a suspenda executia procesului.
SIGTTOU = semnal transmis unui proces, ce ruleaza in background, in momen-
tul in care incearca sa scrie date la terminalul asociat, sau sa schimbe tipul
terminalului. Actiunea sa implicita este de a suspenda executia procesului.
158
Observat¸ii:
i) Atunci cind procesele sunt suspendate, acestora nu li se mai pot transmite sem-
nale, cu exceptia semnalelor SIGKILL si SIGCONT. Semnalul SIGKILL nu poate fi
corupt, si duce la terminarea procesului; desi semnalul SIGCONT poate fi corupt
(i.e., blocat sau ignorat), el va duce oricum la reluarea executiei procesului.
ii) Transmiterea unui semnal SIGCONT unui proces duce la eliminarea din coada de
semnale a tuturor semnalelor SIGSTOP destinate acelui proces (deci care inca
nu au fost transmise procesului).
iii) Daca un proces dintr-un grup de procese orfane (adica procesul parinte al grupu-
lui si-a terminat executia inaintea proceselor fii) primeste unul dintre semnalele
SIGTSTP,SIGTTIN sau SIGTTOU, si nu are un handler propriu asignat pentru
acel semnal, atunci il va ignora, deci nu-si suspenda executia (motivul fiind ca,
acest proces fiind orfan, nu exista posibilitatea sa-si reia executia).
6. Alte tipuri de semnale: sunt utilizate pentru a raporta alte conditii ce pot apare.
SIGPIPE = semnal emis in caz de tentativa de scriere intr-un pipe din care nu
mai are cine sa citeasca.
Motivul: cind se folosesc pipe-uri (sau fifo-uri), aplicatia trebuie astfel constru-
ita incit un proces sa deschida pipe-ul pentru citire inainte ca celalalt sa inceapa
sa scrie. Daca procesul care trebuie sa citeasca nu este startat, sau se termina
in mod neasteptat, atunci scrierea in pipe cauzeaza generarea acestui semnal.
Daca procesul blocheaza sau ignora semnalele SIGPIPE, atunci scrierea in pipe
esueaza cu errno=EPIPE.
Actiunea implicita a acestui semnal este terminarea procesului si afisarea unui
mesaj de eroare corespunzator (“Broken pipe”).
SIGUSR1 si SIGUSR2 = semnale furnizate pentru ca programatorul sa le
foloseasca dupa cum doreste. Sunt utile pentru comunicatia inter-procese.
Actiunea implicita a acestor semnale fiind terminarea procesului, este necesara
asignarea unor handler-e proprii pentru aceste semnale.
Singurul eveniment care genereaza aceste semnale este cererea explicita, folosind
apelul kill().
Alte semnale UNIX:
SIGTRAP * = signal trap, semnal emis dupa executia fiecarei instructiuni, atunci cind
procesul este executat in modul de depanare.
SIGIOT * = signal I/O trap, semnal emis in caz de probleme hardware (de exemplu,
probleme cu discul).
SIGSYS * = semnal emis in caz de apel sistem cu parametri eronati.
¸s.a.
159
Observat¸ie: o parte din aceste tipuri de semnale depind si de suportul oferit de partea de
hardware a calculatorului respectiv, nu numai de partea sa de software (i.e., sistemul de
operare de pe acel calculator). Din acest motiv, exista mici diferente in implementarea
acestor semnale pe diferite tipuri de arhitecturi de calculatoare, adica unele semnale se
poate sa nu fie implementate deloc, sau sa fie implementate (adica, actiunea implicita
asociata lor) cu mici diferente.
Exemple de semnale ce pot diferi de la un tip de arhitectura la altul: cele generate de
erori, cum ar fi SIGBUS, etc. Astfel, in Linux nu este implementat semnalul SIGBUS,
deoarece hardware-ul Intel386 pentru care a fost scris Linux-ul, nu permite detectarea
acelui eveniment descris mai sus, asociat semnalului SIGBUS.
4.5.4 Cererea explicit˘a de generare a unui semnal – primitiva kill
Cererea explicita de generare a unui semnal se face apelind primitiva kill, ce are urma-
toarea interfata:
int kill (int pid, int id-signal);
Argumente:
pid =PID-ul procesului destinatar;
id-signal = tipul semnalului (i.e., constanta simbolica asociata).
Valoarea returnata: 0, in caz de reusita, si -1, in caz de eroare.
Observatie: daca al doilea argument este 0, atunci nu se trimite nici un semnal, dar este
util pentru verificarea validitatii PID-ului respectiv (i.e., daca exista un proces cu acel PID
in momentul apelului, sau nu): apelul kill(pid,0); returneaza 0daca PID-ul specificat
este valid, sau -1, in caz contrar.
Pentru cererea explicita de generare a unui semnal se poate folosi si comanda kill (la
prompterul shell-ului):
UNIX>kill -nr-semnal pid
cu observatia ca trebuie dat numarul semnalului, nu constanta simbolica asociata (astfel,
pentru semnalul SIGKILL, numarul este 9). Se pot specifica mai multe PID-uri, sau nume
de procese. Consultati help-ul (cu comanda man kill) pentru detalii.
Un proces poate trimite semnale atre sine ˆınsu¸si folosind funct¸ia raise, ce are uratorul
format:
int raise(int id-signal)
160
Efect: prin apelul raise(id-signal); un proces isi auto-expediaza un semnal de tipul
specificat; este echivalent cu apelul kill(getpid(),id-signal); .
4.5.5 Coruperea semnalelor – primitiva signal
Specificarea actiunii la receptia semnalelor se poate face cu apelurile de sistem signal()
sau sigaction(), functii ale caror prototipuri se gasesc in fisierul header signal.h, fisier
in care mai sunt definite si constantele simbolice: SIG DFL (default), SIG IGN (ignore), si
SIG ERR (error), al caror rol va fi explicat mia jos.
Dupa cum am mai spus, actiunea asociata unui semnal poate fi una dintre urmatoarele
trei:
– o actiune implicita, specifica sistemului de operare respectiv;
– ignorarea semnalului;
– sau un handler propriu, definit de programator.
Se utilizeaza termenul de corupere a unui semnal cu sensul de: setarea unui handler propriu
pentru acel tip de semnal. Uneori, se mai foloseste si termenul de tratare a semnalului.
Observatie: dupa cum am mai spus, semnalele SIGKILL si SIGSTOP nu pot fi corupte,
ignorate sau blocate!
Primitiva signal(), utilizata pentru specificarea actiunii la receptia semnalelor, are ur-
matorul prototip:
sighandler t signal (int id-signal, sighandler t action);
Apelul functiei signal() stabileste ca, atunci cind procesul receptioneaza semnalul id-
signal, sa se execute functia (handler-ul de semnal) action.
Argumentul action poate fi numele unei functii definite de utilizator, sau poate lua una
dintre urmatoarele valori (constante simbolice definite in fisierul header signal.h):
SIG DFL : specifica actiunea implicita (cea stabilita de catre sistemul de operare) la
receptionarea semnalului.
SIG IGN : specifica faptul ca procesul va ignora acel semnal. Sistemul de operare nu
permite sa se ignore sau corupa semnalele SIGKILL si SIGSTOP, de aceea functia signal()
va returna o eroare daca se face o asemenea incercare.
Observatie: in general nu este bine ca programul sa ignore semnalele (mai ales pe acelea
care reprezinta evenimente importante). Daca nu se doreste ca programul sa receptioneze
semnale in timpul executiei unei anumite portiuni de cod, solutia cea mai indicata este sa
se blocheze semnalele, nu ca ele sa fie ignorate.
161
Functia signal() returneaza vechiul handler pentru semnalul specificat, deci astfel poate
fi apoi restaurat daca este nevoie.
In caz de esec (daca, spre exemplu, numarul id-signal nu este numar valid de semnal, sau
se incearca coruperea semnalelor SIGKILL sau SIGSTOP), functia signal() returneaza ca
valoare constanta simbolica SIG ERR.
In cazul cind argumentul action este numele unei functii definite de utilizator, aceasta
functie trebuie sa aiba prototipul sighandler t, unde tipul sighandler t este definit
astfel:
typedef void (*sighandler t)(int);
adica este tipul “functie ce intoarce tipul void, si are un argument de tip int”.
La momentul executiei unui handler de semnal, acest argument va avea ca valoare numarul
semnalului ce a determinat executia acelui handler. In acest fel, se poate asigna o aceeasi
functie ca handler pentru mai multe semnale, in corpul ei putind sti, pe baza argumentului
primit, care dintre acele semnale a cauzat apelul respectiv.
Exemplu. Sa scriem un program care sa ignore intreruperile de tastatura, adica semnalul
SIGINT (generat de tastele CTRL+C) si semnalul SIGQUIT (generat de tastele CTRL+\).
a) Iat˘a o prim˘a versiune a programului, fara ignorarea semnalelor:
/*
File: sig-ex1a.c (fara ignorarea semnalelor)
*/
#include <stdio.h>
void main()
{
printf("Inceput bucla infinita; poate fi oprita cu ^C sau ^\\ !\n");
for(;;);
printf("Sfirsit program.\n");
}
b) Iat˘a a doua versiune a programului, cu ignorarea semnalelor:
/*
File: sig-ex1b.c (cu ignorarea semnalelor)
*/
#include <stdio.h>
#include <signal.h>
void main()
{
/* ignorarea semnalelor SIGINT si SIGQUIT */
162
signal( SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
printf("Inceput bucla infinita in care ^C si ^\\ sunt ignorate...\n");
for(;;);
printf("Sfirsit program.\n");
}
Acest program va rula la infinit, neputind fi oprit cu CTRL+C sau cu CTRL+\. Pentru a-l
termina, va trebui sa-l suspendati (cu CTRL+Z), sa-i aflati PID-ul si apoi sa-l omoriti cu
comanda kill -9 pid (sau: CTRL+Z si apoi comanda kill % ).
Exemplu. Sa modificam exemplul anterior in felul urmator: corupem semnalele sa ex-
ecute un handler propriu, care sa afiseze un anumit mesaj. Iar apoi refacem comporta-
mentul implicit al semnalelor.
/*
File: sig-ex2.c (cu coruperea semnalelor)
*/
#include <stdio.h>
#include <signal.h>
/* handler-ul propriu de semnal pentru SIGINT si SIGQUIT */
void my_handler(int nr_sem)
{
/* actiuni dorite de utilizator */
printf("Nu mai tasta CTRL+%s caci nu are efect.\n",
(nr_sem==SIGINT ? "C":"\\"));
}
int main()
{
int i;
/* coruperea semnalelor SIGINT si SIGQUIT */
signal( SIGINT, my_handler);
signal(SIGQUIT, my_handler);
/* portiune de cod pe care ^C si ^\ sunt corupte */
printf("Inceput portiune de cod pe care ^C si ^\\ sunt corupte...\n");
for(i=0;i<10;i++) { printf("Portiune corupta...\n"); sleep(1); }
printf("Sfirsit portiune.\n");
/* refacerea comportamentului implicit pentru cele doua semnale */
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
/* portiune de cod pe care ^C si ^\ nu sunt corupte */
for(i=0;i<10;i++) { printf("Portiune necorupta...\n"); sleep(1); }
printf("Sfirsit program.\n");
return 0;
}
163
Cealalta primitiva utilizata pentru specificarea actiunii la receptia semnalelor este functia
sigaction(). Ea are, in principiu, aceeasi utilizare ca si functia signal(), dar permite
un control mai fin al comportamentului procesului la receptionarea semnalelor. Consultati
help-ul pentru detalii suplimentare despre functia sigaction.
4.5.6 Definirea propriilor handler -ere de semnal
Un handler de semnal propriu este o functie definita de programator, care se compileaza
deci impreuna cu restul programului; in loc insa de a apela direct aceasta functie, sistemul
de operare este instruit, prin apelul functiilor signal sau sigaction, sa o apeleze atunci
cind procesul receptioneaza semnalul respectiv.
Ultimul exemplu de mai sus ilustreaza folosirea unui handler de semnal propriu.
Exista doua strategii principale care se folosesc in handler-ele de semnal:
1. Se poate ca handler-ul sa notifice primirea semnalului prin setarea unei variabile
globale si apoi sa returneze normal, urmind ca in bucla principala a programului,
acesta sa verifice periodic daca acea variabila a fost setata, in care caz va efectua
operatiile dorite.
2. Se poate ca handler-ul sa termine executia procesului, sau sa transfere executia intr-
un punct in care procesul poate sa-si recupereze starea in care se afla in momentul
receptionarii semnalului.
Atentie: trebuie luate masuri speciale atunci cind se scrie codul pentru handler-ele de
semnal, deoarece acestea pot fi apelate asincron, deci la momente imprevizibile de timp.
Spre exemplu, in timp ce se executa handler -ul asociat unui semnal primit, acesta poate
fi intrerupt prin receptia unui alt semnal (al doilea semnal trebuie sa fie de alt tip decit
primul; daca este acelasi semnal, el va fi blocat pina cind se termina tratarea primului
semnal).
Important: prin urmare, primirea unui semnal poate intrerupe nu doar executia progra-
mului respectiv, ci chiar executia handler-ului unui semnal anterior primit, sau poate
intrerupe executia unui apel de sistem efectuat de program in acel moment.
Apelurile de sistem ce pot fi intrerupte de semnale sunt urmatoarele:
close,fcntl [operatia FSETLK], open,read,recv,recvfrom,select,send,sendto,
tcdrain,waitpid,wait si write.
In caz de intrerupere, aceste primitive returneaza valoarea -1 (mai putin read si write,
care returneaza numarul de octeti cititi, respectiv scrisi cu succes), iar variabila errno
este setata la valoarea EINTR.
164
4.5.7 Blocarea semnalelor
Blocarea semnalelor inseamna ca procesul spune sistemului de operare sa nu ii transmita
anumite semnale (ele vor ramine in coada de semnale, pina cind procesul va debloca
primirea lor).
Nu este recomandat ca un program sa blocheze semnalele pe tot parcursul executiei sale, ci
numai pe durata executiei unor parti critice ale codului lui. Astfel, daca un semnal ajunge
in timpul executiei acelei parti de program, el va fi livrat procesului dupa terminarea
acesteia si deblocarea acelui tip de semnal.
Blocarea semnalelor se realizeaza cu ajutorul functiei sigprocmask(), ce utilizeaza struc-
tura de date sigset t (care este o masca de biti), cu semnificatia de set de semnale ales
pentru blocare.
Iar cu ajutorul functiei sigpending() se poate verifica existenta, in coada de semnale, a
unor semnale blocate, deci care asteapta sa fie deblocate pentru a putea fi livrate proce-
sului.
Consultati help-ul pentru detalii suplimentare despre aceste functii.
4.5.8 A¸steptarea unui semnal
Daca aplicatia este influentata de evenimente externe, sau foloseste semnale pentru sin-
cronizare cu alte procese, atunci ea nu trebuie sa faca altceva decit sa astepte semnale.
Functia pause, cu prototipul:
int pause();
are ca efect suspendarea executiei programului pina la sosirea unui semnal.
Daca semnalul duce la executia unui handler, atunci functia pause returneaza valoarea
-1, deoarece comportarea normala este de a suspenda executia programului tot timpul,
asteptind noi semnale. Daca semnalul cauzeaza terminarea executiei programului, apelul
pause() nu returneaza.
Simplitatea acestei functii poate ascunde erori greu de detectat. Deoarece programul
principal nu face altceva decit sa apeleze pause(), inseamna ca cea mai mare parte a
activitatii utile in program o realizeaza handler-ele de semnal. Insa, cum am mai spus,
codul acestor handler -e nu este indicat sa fie prea lung, deoarece poate fi intrerupt de alte
semnale.
De aceea, modalitatea cea mai indicata, atunci cind se doreste asteptarea unui anumit
semnal (sau o multime fixata de semnale), este de a folosi functia sigsuspend(), ce are
prototipul:
165
int sigsuspend(const sigset t *set);
Functia aceasta are ca efect: se inlocuieste masca de semnale curenta a procesului cu cea
specificata de parametrul set si apoi se suspenda executia procesului pina la receptionarea
unui semnal, de catre proces (deci un semnal care nu este blocat, adica nu este cuprins in
masca de semnale curenta).
Masca de semnale ramine la valoarea setata (i.e., valoarea lui set) numai pina cind functia
sigsuspend() returneaza, moment in care este reinstalata, in mod automat, vechea masca
de semnale.
Valoarea returnata: 0, in caz de succes, respectiv -1, in caz de esec (iar variabila errno
este setata in mod corespunzator: EINVAL,EFAULT sau EINTR).
Exemplu. Sa scriem un program care sa-si suspende executia in asteptarea semnalului
SIGQUIT (generat de tastele CTRL+\), fara a fi intrerupt de alte semnale.
/*
File: sig-ex5.c (asteptarea unui semnal)
*/
#include <signal.h>
void main()
{
sigset_t mask, oldmask;
/* stabileste masca de semnale ce vor fi blocate:
toate semnalele, exceptind SIGQUIT */
sigfillset(&mask);
sigdelset (&mask, SIGQUIT);
printf("Suspendare program pina la apasarea tastelor CTRL+\\.\n");
/* se asteapta sosirea unui semnal SIGQUIT, restul fiind blocate */
sigsuspend(&mask);
printf("Sfirsit program.\n");
}
In incheiere, as dori sa va recomand consultarea unei variante mai detaliate a acestei lectii
despre semnale UNIX, folosita pentru studentii de la Sectia la zi, si care este disponibila la
adresa web http://fenrir.infoiasi.ro/so.
4.6 Exercit¸ii
Exercit¸iul 1. Care sunt apelurile de sistem care ofer˘a informat¸ii despre un proces, ¸si ce
informat¸ii ofer˘a fiecare dintre acestea?
166
Exercit¸iul 2. Care este modalitatea prin care se pot crea noi procese ˆın UNIX?
Exercit¸iul 3. Ce efect are apelul fork? Ce valoare returneaz˘a?
Exercit¸iul 4. Cum putem deosebi arintele de fiu ˆın urma cre˘arii acestuia din urm˘a prin
apelul fork?
Exercit¸iul 5. Pot comunica p˘arintele ¸si fiul prin intermediul variabilelor de memorie?
Justificat¸i r˘aspunsul.
Exercit¸iul 6 . Ce se poate obt¸ine pe ecran ˆın urma execut¸iei programului urm˘ator?
#include <unistd.h>
#include <stdio.h>
void main() {
int pid;
printf("A");
pid=fork();
if(pid<0) printf("err");
printf("B");
}
Exercit¸iul 7 . Cˆıte procese fiu sunt create ˆın urma execut¸iei programului urm˘ator, dac˘a
toate apelurile fork reu¸sesc?
#include <unistd.h>
void main() {
int contor;
for(contor=1; contor<=7; ++contor)
if(contor & 1) fork();
}
Exercit¸iul 8.Lista de procese: scrieti un program C care sa creeze o lista de procese de
lungime n(valoare citita de la tastatura). Si anume, procesul P1va avea ca fiu pe procesul
P2, acesta la rindul lui il va avea ca fiu pe procesul P3, ¸s.a.m.d. pina la procesul Pn, care
nu va avea nici un fiu.
Incercati o rezolvare nerecursiva si una recursiva a acestei probleme.
Exercit¸iul 9.Arborele de procese: scrieti un program C care sa creeze un arbore k-ar
complet cu nnivele, de procese (valorile ksi nvor fi citite de la tastatura). Si anume,
unicul proces P1,1 de pe nivelul 1al arborelui (i.e., radacina arborelui) va avea kprocese
fii, si anume procesele P2,1,. . . ,P2,k de pe nivelul 2al arborelui, fiecare dintre acestea la
rindul lui va avea kprocese fii pe nivelul 3al arborelui, ¸s.a.m.d. pina la cele 2n-1 procese
de pe nivelul nal arborelui, care nu vor avea nici un fiu.
Incercati o rezolvare nerecursiva si una recursiva a acestei probleme.
Exercit¸iul 10. Ce ˆınseamn˘a not¸iunea de punct de sincronizare?
167
Exercit¸iul 11. Cum se poate realiza o sincronizare ˆıntre un proces p˘arinte ¸si terminarea
unui fiu al acestuia?
Exercit¸iul 12. Ce efect are apelul wait? Ce valoare returneaz˘a ¸si cˆınd anume?
Exercit¸iul 13 .Suma distribuit˘a: scrieti un program C care sa realizeze urmatoarele:
un proces P0 citeste numere de la tastatura si le trimite la doua procese fii P1 si P2, acestea
calculeaza sumele si le trimit inapoi la parintele P0, iar P0 aduna cele doua sume partiale
si afiseaza rezultatul final.
Indicat¸ie de rezolvare: pentru comunicatia intre procese puteti folosi fisiere obisnuite –
procesul P0 scrie numerele citite in fisierele f1i si f2i, de unde sunt citite de procesele
P1, respectiv P2, care le aduna si scriu sumele partiale in sierele f1o si f2o, de unde sunt
citite de procesul P0 si adunate.
Observat¸ie: va trebui sa rezolvati si unele probleme de sincronizare ce apar la comunicatiile
intre cele trei procese. Mai precis, apar doua tipuri de probleme:
o sincronizare la fi¸sierele de intrare: procesul fiu P1, respectiv P2, va trebui sa citeasc˘a
datele din fi¸sierul de intrare corespunz˘ator, f1i si respectiv f2i, abia dup˘a ce arintele
P0 a terminat de scris datele ˆın ¸sierul respectiv;
o sincronizare la fi¸sierele de ie¸sire: procesul p˘arinte P0 va trebui sa citeasc˘a datele din
fi¸sierul de ie¸sire corespunz˘ator, f1o si respectiv f2o, abia dup˘a ce fiul P1, respectiv P2, a
terminat de scris datele ˆın fi¸sierul respectiv.
Exercit¸iul 14. Ce ˆınseamn˘a not¸iunea de “reacoperire” a proceselor?
Exercit¸iul 15. Ce apel sistem permite ˆınc˘arcarea ¸si execut¸ia unui fi¸sier executabil?
Exercit¸iul 16. Ce efect are apelul exec? Ce valoare returneaz˘a ¸si cˆınd anume?
Exercit¸iul 17. Cˆıte primitive de tipul exec exist˘a ¸si prin ce se deosebesc ele? Care este
semnificat¸ia argumentelor pentru fiecare primitiv˘a ˆın parte?
Exercit¸iul 18. Scrieti un program C care sa execute comanda ls -a -l. La sfˆır¸situl
executiei trebuie sa fie afisat textul: “Comanda a fost executata ...”.
Indicat¸ie: folositi primitiva execlp.
Exercit¸iul 19. Rezolvat¸i exercit¸iul precedent utilizˆınd primitiva execl.
Exercit¸iul 20 . De cˆıte ori va afi¸sa pe ecran ¸sirul “Hello!” programul de mai jos?
168
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(){ int fd;
fd=creat("a",O_WRONLY|0606);
close(2);
dup(fd); close(fd);
fprintf(stderr,"Hello!");
execlp("cat","cat","a",NULL);
if(fork() != -1)
printf("Hello!");
}
Exercit¸iul 21. Rescriet¸i programul cu suma distribuit˘a de la exercit¸iul 11, folosind wait in
procesul master pentru a astepta terminarea celor dou˘a procese slave ¸si, ˆın plus, rescrieti
procedura slave ˆıntr-un program C separat, care a e apelat prin exec din procesul fiu
corespunz˘ator creat prin fork de procesul master, iar numele fisierelor de intrare/iesire
sa-i fie trasferate ca argumente in linia de comanda.
Exercit¸iul 22. Studiat¸i cu ajutorul comenzii man toate apelurile de sistem pentru gestiunea
proceselor amintite ˆın sect¸iunile precedente.
Exercit¸iul 23. Ce sunt semnalele UNIX?
Exercit¸iul 24. Ce categorii de evenimente genereaz˘a semnale?
Exercit¸iul 25. Ce se ˆıntˆımpl˘a cˆınd se genereaz˘a un semnal?
Exercit¸iul 26. Ce se ˆınt¸elege prin handler-ul asociat unui semnal?
Exercit¸iul 27. Descriet¸i categoriile de semnale UNIX.
Exercit¸iul 28. Ce efect are apelul kill?
Exercit¸iul 29. Ce ˆınseamn˘a coruperea (sau tratarea) unui semnal, ¸si cum se realizeaz˘a?
Exercit¸iul 30. Ce ˆınseamn˘a handler propriu de semnal?
Exercit¸iul 31. Ce ˆınseamn˘a blocarea unui semnal, ¸si cum se realizeaz˘a?
Exercit¸iul 32. Cum se realizeaz˘a a¸steptarea unui semnal?
Exercit¸iul 33. Ce semnale nu pot fi tratate sau ignorate?
Exercit¸iul 34. Ap˘asarea c˘aror taste genereaz˘a semnalul SIGINT? Dar pentru SIGQUIT?
169
Exercit¸iul 35 . De cˆıte ori va afi¸sa pe ecran ¸sirul “death of child!” programul de mai jos,
presupunˆınd c˘a apelul fork reu¸se¸ste de fiecare dat˘a ?
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void my_handler(int signal){
printf("death of child!\n"); fflush(stdout);
}
int main(){
int i;
for(i=1; i<=3; ++i)
if(fork() == 0)
signal(SIGCHLD, my_handler);
while (wait(NULL) > 0) continue;
return 0;
}
Exercit¸iul 36. Scriet¸i un program C care creeaz˘a un proces nou ce scrie ˆıntr-o bucl˘a infinit˘a
numere ˆıntregi consecutive pornind de la 0; dup˘a 3 secunde procesul tat˘a ˆıi va trimite un
semnal de oprire procesului fiu.
Exercit¸iul 37 .Hi-ho: scriet¸i dou˘a programe, primul sa scrie pe ecran “hi-” in mod
repetat, iar al doilea sa scrie “ho, in mod repetat, si sa se foloseasca semnale UNIX
pentru sincronizarea proceselor, astfel ca, atunci cˆınd sunt lansate ˆın paralel cele dou˘a
programe, pe ecran s˘a fie afi¸sat˘a exact succesiunea:
hi-ho, hi-ho, hi-ho, . . .
si nu alte combinatii posibile de interclasare a mesajelor afi¸sate de cele dou˘a procese.
170
Capitolul 5
Comunicat¸ia inter-procese
5.1 Introducere. Tipuri de comunicat¸ie ˆıntre procese
Dup˘a cum se cunoa¸ste de la teoria sistemelor de operare, exist˘a dou˘a categorii principale
de comunicat¸ie ˆıntre procese, ¸si anume:
comunicat¸ia prin memorie partajat˘a (“shared-memory communication”):
procesele trebuie s˘a partajeze (i.e., s˘a aib˘a acces ˆın comun la) o zon˘a de memorie,
comunicat¸ia realizˆındu-se prin scrierea ¸si citirea de valori la adrese de memorie din
cadrul zonei partajate;
comunicat¸ia prin schimb de mesaje (“message-passing communication”):
procesele nu mai trebuie s˘a aib˘a o zon˘a de memorie comua, ci ele comunic˘a prin
schimbul de informat¸ii (mesaje), care circul˘a de la un proces la altul prin intermediul
unor canale de comunicat¸ie.
La rˆındul ei, comunicat¸ia prin schimb de mesaje se poate clasifica ˆın mai multe
subcategorii, ˆın funct¸ie de tipul ¸si caracteristicile canalelor de comunicat¸ie utilizate.
Astfel, o prim˘a clasificare grosier˘a a canalelor de comunicat¸ie utilizate ˆın comunicat¸ia
prin schimb de mesaje ar consta ˆın uratoarele:
1. comunicat¸ie local˘a:
se utilizeaz˘a canale ce permit comunicarea doar ˆıntre procese aflate ˆın execut¸ie
pe un acela¸si calculator;
2. comunicat¸ie la distant¸˘a:
se utilizeaz˘a canale ce permit comunicarea ˆıntre procese aflate ˆın execut¸ie pe
calculatoare diferite (conectate prin intermediul unei ret¸ele de calculatoare).
171
Sistemele UNIX/Linux ofer˘a mecanisme pentru toate categoriile de comunicat¸ie ˆıntre
procese enumerate mai sus. ˆ
In continuare, le vom studia doar pe acelea care permit
comunicat¸ia local˘a, prin schimb de mesaje. Este vorba despre a¸sa-numitele canale de
comunicat¸ie UNIX (numite ¸si pipes, de la termenul ˆın limba englez˘a).
Practic, un canal de comunicat¸ie UNIX, sau pipe, este o “conduct˘a” prin care pe la un cap˘at
se scriu mesajele (ce constau ˆın ¸siruri de octet¸i), iar pe la cel˘alalt cap˘at acestea sunt citite
– deci este vorba despre o structur˘a de tip coad˘a, adica o list˘a FIFO (First-In,First-Out).
Aceast˘a “conduct˘a” FIFO poate fi folosit˘a pentru comunicare de c˘atre dou˘a sau mai multe
procese, pentru a transmite date de la unul la altul.
Canalele de comunicat¸ie UNIX se ˆımpart ˆın dou˘a subcategorii:
canale interne:
aceste “conducte” sunt create in memoria interna a sistemului UNIX respectiv;
canale externe:
aceste “conducte” sunt fisiere de un tip special, numit fifo, deci sunt pastrate in
sistemul de fisiere (aceste fisiere fifo se mai numesc si pipe-uri cu nume).
5.2 Comunicat¸ia prin canale interne
1. Introducere
2. Canale interne. Primitiva pipe
5.2.1 Introducere
In aceasta sect¸iune a manualului ne vom referi la canalele interne, iar in urm˘atoarea
sect¸iune le vom trata pe cele externe.
Canalele interne mai sunt numite ¸si canale f˘ar˘a nume, ind, dup˘a cum am specificat
deja, un fel de “conducte” create ˆın memoria intern˘a a sistemului, ce sunt folosite pentru
comunicat¸ia local˘a ˆıntre procese, prin schimb de mesaje. Datorit˘a faptului c˘a ele sunt
anonime (i.e., nu au nume), pot fi utilizate pentru comunicat¸ie doar de c˘atre procese
ˆınrudite” prin fork/exec, dup˘a cum vom vedea mai ˆıncolo.
172
5.2.2 Canale interne. Primitiva pipe
Deci un canal intern este un canal de comunicat¸ie aflat ˆın memorie, prin care pot comunica
local dou˘a sau mai multe procese.
Crearea unui canal intern se face cu ajutorul apelului sistem pipe. Interfata functiei pipe
este urmatoarea:
int pipe(int *p)
unde:
p= parametrul efectiv de apel trebuie sa fie un tablou int[2] ce va fi actualizat de
functie astfel:
p[0] va fi descriptorul de fisier deschis pentru cap˘atul de citire al canalului;
p[1] va fi descriptorul de fisier deschis pentru cap˘atul de scriere al canalului;
iar valoarea int returnata este 0, in caz de succes (i.e., daca s-a putut crea canalul), sau
-1, in caz de eroare.
Efect: in urma executiei primitivei pipe se creeaza un canal intern si este deschis la
ambele capete – in citire la capatul referit prin p[0], respectiv in scriere la capatul referit
prin p[1].
Dupa crearea unui canal intern, scrierea in acest canal si citirea din el se efectueaza la fel
ca pentru fisierele obisnuite.
Si anume, citirea din canal se va face prin intermediul descriptorului p[0] folosind functiile
de citire uzuale pentru fisierele obisnuite (i.e., primitiva read, sau se pot folosi functiile
I/O din biblioteca standard de C, respectiv fread,fscanf, ¸s.a.m.d., dar pentru aceasta
trebuie s˘a se foloseasc˘a un descriptor de tip FILE*, asociat descriptorului p[0] printr-o
modalitate pe care o vom vedea mai ˆıncolo).
Iar scrierea ˆın canal se va face prin intermediul descriptorului p[1] folosind functiile de
scriere uzuale pentru fisierele obisnuite (i.e., primitiva write, sau se pot folosi functiile
I/O din biblioteca standard de C, respectiv fwrite,fprintf, ¸s.a.m.d., daca se foloseste
un descriptor de tip FILE*, asociat descriptorului p[1]).
Observat¸ie importana:
Pentru ca doua (sau mai multe) procese sa poata folosi un canal intern pentru a comunica,
ele trebuie sa aiba la dispozitie cei doi descriptori p[0] si p[1] obtinuti prin crearea
canalului, deci procesul care a creat canalul prin apelul pipe, va trebui sa le “transmita”
cumva celuilalt proces.
De exemplu, in cazul cind se doreste sa se utilizeze un canal intern pentru comunicarea
intre doua procese de tipul parinte-fiu, atunci este suficient sa se apeleze primitiva pipe
de creare a canalului inaintea apelului primitivei fork de creare a procesului fiu. In acest
173
fel in procesul fiu avem la dispozitie cei doi descriptori necesari pentru comunicare prin
intermediul acelui canal intern.
La fel se procedeaza si in cazul apelului primitivelor exec (deoarece descriptorii de fisiere
deschise se mostenesc prin exec).
De asemenea, trebuie retinut faptul ca daca un proces isi inchide vreunul din capetele unui
canal intern, atunci nu mai are nici o posibilitate de a redeschide ulterior acel capat al
canalului.
Caracteristici si restrictii ale canalelor interne:
1. Canalul intern este un canal unidirectional, adica pe la capatul p[1] se scrie, iar pe
la capatul p[0] se citeste.
ˆ
Ins˘a toate procesele pot s˘a scrie la capatul p[1], ¸si s˘a citeasca la capatul p[0].
2. Unitatea de informatie pentru canalul intern este octetul. Adica, cantitatea minima
de informatie ce poate fi scrisa in canal, respectiv citita din canal, este de 1 octet.
3. Canalul intern functioneaza ca o coada, adica o lista FIFO (First-In, First-Out),
deci citirea din canal se face cu distrugerea (i.e., consumul) din canal a informatiei
citite.
Asadar, citirea dintr-un canal difera de citirea din fisiere obisnuite, pentru care citirea
se face fara consumul informatiei din fisier.
4. Capacitatea canalului intern este limitata la o anumita dimensiune maxima (4 Ko,
16 Ko, etc.), ce difera de la un sistem UNIX la altul.
5. Citirea dintr-un canal intern (cu primitiva read) funct¸ioneaz˘a ˆın felul urm˘ator:
Apelul read va citi din canal si va returna imediat, fara sa se blocheze, numai
daca mai este suficienta informatie in canal, iar in acest caz valoarea returnata
reprezinta numarul de octeti cititi din canal.
Altfel, daca canalul este gol, sau nu contine suficienta informatie, apelul de
citire read va ramine blocat pina cind va avea suficienta informatie in canal
pentru a putea citi cantitatea de informatie specificata, ceea ce se va intimpla
in momentul cind alt proces va scrie in canal.
Alt caz de exceptie la citire, pe linga cazul golirii canalului:
daca un proces incearca sa citeasca din canal si nici un proces nu mai este capa-
bil sa scrie in canal vreodata (deoarece toate procesele si-au inchis deja capatul
de scriere), atunci apelul read returneaza imediat valoarea 0corespunzatoare
faptului ca a citit EOF din canal.
ˆ
In concluzie, pentru a se putea citi EOF din canal, trebuie ca mai intii toate
procesele sa inchida canalul in scriere (adica sa inchida descriptorul p[1]).
Observat¸ie: la fel se comport˘a la citirea din canale interne si functiile de citire de
nivel ˆınalt (fread,fscanf, etc.), doar a acestea lucreaz˘a buffer-izat.
6. Scrierea intr-un canal intern (cu primitiva write) funct¸ioneaz˘a ˆın felul urm˘ator:
174
Apelul write va scrie in canal si va returna imediat, fara sa se blocheze, numai
daca mai este suficient spatiu liber in canal, iar in acest caz valoarea returnata
reprezinta numarul de octeti efectiv scrisi in canal (care poate sa nu coincida
intotdeauna cu numarul de octeti ce se doreau a se scrie, caci pot apare erori
I/O).
Altfel, daca canalul este plin, sau nu contine suficient spatiu liber, apelul de
scriere write va ramine blocat pina cind va avea suficient spatiu liber in canal
pentru a putea scrie informatia specificata ca argument, ceea ce se va intimpla
in momentul cind alt proces va citi din canal.
Alt caz de exceptie la scriere, pe linga cazul umplerii canalului:
daca un proces incearca sa scrie in canal si nici un proces nu mai este capabil sa
citeasca din canal vreodata (deoarece toate procesele si-au inchis deja capatul de
citire), atunci sistemul va trimite acelui proces semnalul SIGPIPE, ce cauzeaza
intreruperea sa si afisarea pe ecran a mesajului “Broken pipe”.
Observat¸ie: la fel se comport˘a la scrierea ˆın canale interne si functiile de citire de
nivel ˆınalt (fwrite,fprintf, etc.), doar c˘a acestea lucreaz˘a buffer-izat. Acest fapt
cauzeaz˘a uneori erori dificil de depistat, datorate neatent¸iei programatorului, care
poate uita uneori aspectele legate de modul de lucru buffer -izat al funct¸iilor de scriere
din biblioteca standard de C, i.e. poate uita s˘a fort¸eze “golirea” buffer -ului ˆın canal
cu ajutorul funct¸iei fflush, imediat dup˘a apelul funct¸iei de scriere propriu-zise.
Observat¸ie:
Cele afirmate mai sus, despre blocarea apelurilor de citire sau de scriere in cazul canalului
gol, respectiv plin, corespund comportamentului implicit, blocant, al canalelor interne.
Insa, exista posibilitatea modificarii acestui comportament implicit, intr-un comportament
neblocant, situatie in care apelurile de citire sau de scriere nu mai ramin blocate in cazul
canalului gol, respectiv plin, ci returneaza imediat valoarea -1, si seteaza corespunzator
variabila errno.
Modificarea comportamentului implicit in comportament neblocant se realizeaza prin
setarea atributului ONONBLOCK pentru descriptorul corespunzator acelui capat al canalului
intern pentru care se doreste modificarea comportamentului, cu ajutorul primitivei fcntl.
Spre exemplu, apelul
fcntl(p[1],F SETFL,O NONBLOCK);
va seta atributul ONONBLOCK pentru capatul de scriere al canalului intern referit de vari-
abila p.
Atent¸ie: dup˘a cum am mai spus, functiile I/O de nivel inalt (i.e.,fread/fwrite,
fscanf/fprintf, ¸si celelalte din biblioteca standard de C) lucreaza buffer -izat. Ca atare,
la scrierea intr-un canal folosind functiile fwrite,fprintf, etc., informatia nu ajunge ime-
diat in canal in urma apelului, ci doar in buffer-ul asociat acelui descriptor, iar “golirea”
buffer-ului in canal se va intimpla abia in momentul umplerii buffer-ului, sau la intilnirea
175
caracterului ’\n’(newline) in specificatorul de format al functiei de scriere, sau la apelul
functiei fflush pentru acel descriptor.
Prin urmare, daca se doreste garantarea faptului ca informatia ajunge in canal imediat
in urma apelulului de scriere cu functii de nivel inalt, trebuie sa se apeleze, imediat dupa
apelul de scriere, si functia fflush pentru descriptorul asociat capatului de scriere al acelui
canal.
Exemplu. Urmatorul program exemplifica modul de utilizare a unui canal intern pentru
comunicatia intre doua procese, cu observatia ca programul foloseste primitivele read si
write (i.e., functiile I/O de nivel scazut, ne-buffer-izate) pentru a citi din canal, respectiv
pentru a scrie in canal.
/*
File: pipe-ex1.c
Exemplu de utilizare a unui pipe intern pentru comunicatia intre
doua procese, folosind functii I/O de nivel scazut (nebufferizate).
*/
#include<stdio.h>
#include<errno.h>
extern int errno;
#define NMAX 1000
int main(void)
{
int pid, p[2];
char ch;
/* creare pipe intern */
if(pipe(p) == -1)
{
fprintf(stderr,"Error: can’t open a channel, errno=%d\n",errno);
exit(1);
}
/* creare proces fiu */
if( (pid=fork()) == -1)
{
fprintf(stderr,"Error: can’t create a child!\n");
exit(2);
}
if(pid)
{ /* in tata */
/* tatal isi inchide capatul Read */
close(p[0]);
176
/* citeste caractere de la tastatura,
pentru terminare: CTRL+D (i.e. EOF in Unix),
si le transmite doar pe acelea care sunt litere mici */
while( (ch=getchar()) != EOF)
if((ch>=’a’) && (ch<=’z’))
write(p[1],&ch,1);
/* tatal isi inchide capatul Write,
pentru ca fiul sa poata citi EOF din pipe */
close(p[1]);
/* asteapta terminarea fiului */
wait(NULL);
}
else
{ /* in fiu */
char buffer[NMAX];
int nIndex = 0;
/* fiul isi inchide capatul Write */
close(p[1]);
/* fiul citeste caracterele din pipe si salveaza in buffer,
pina depisteaza EOF, apoi afiseaza continutul bufferului. */
while( read(p[0],&ch,1) != 0)
if(nIndex < NMAX)
buffer[nIndex++] = ch;
buffer[ (nIndex==NMAX) ? NMAX-1 : nIndex ] = ’\0’;
printf("Fiu: am citit buffer=%s\n",buffer);
/* fiul isi inchide capatul Read */
close(p[0]);
/* Obs: nici nu mai era nevoie de acest close explicit, deoarece
oricum toti descriptorii sunt inchisi la terminarea programului.*/
}
return 0;
}
Efectul acestui program:
Procesul tata citeste un sir de caractere de la tastatura, sir terminat cu combinatia de
taste CTRL+D (i.e., caracterul EOF in UNIX), si le transmite procesului fiu, prin intermediul
canalului, doar pe acelea care sunt litere mici. Iar procesul fiu citeste din canal caracterele
trasmise de procesul parinte si le afiseaza pe ecran.
Exemplu. Programul urmator este un alt exemplu de utilizare a unui canal intern pentru
comunicatia intre doua procese, cu observatia ca programul foloseste functiile de bibliotec˘a
fscanf si fprintf (i.e., funct¸iile I/O de nivel inalt, buffer-izate) pentru a citi din canal,
respectiv pentru a scrie in canal.
177
/*
File: pipe-ex2.c
Exemplu de utilizare a unui pipe intern pentru comunicatia intre
doua procese, folosind functii I/O de nivel inalt (bufferizate).
*/
#include<stdio.h>
#include<errno.h>
extern int errno;
int main(void)
{
int pid, nr, p[2];
FILE *fin,*fout;
if(pipe(p) == -1)
{
fprintf(stderr,"Error: can’t open channel, err=%d\n",errno);
exit(1);
}
/* atasare descriptori de tip FILE* la cei de tip int */
fin = fdopen(p[0],"r"); /* capatul de citire */
fout= fdopen(p[1],"w"); /* capatul de scriere */
/* creare proces fiu */
if( (pid=fork()) == -1)
{
fprintf(stderr,"Error: can’t create a child!\n");
exit(2);
}
if(pid)
{ /* in tata */
/* tatal isi inchide capatul Read */
fclose(fin);
/* citeste numere de la tastatura,
pentru terminare: CTRL+D (i.e. EOF in Unix),
si le transmite prin pipe procesului fiu.
OBSERVATIE: in pipe numerele sunt scrise formatat, nu binar, si
de aceea trebuie separate printr-un caracter care nu-i cifra
(in acest caz am folosit ’\n’) pentru a nu se "amesteca"
cifrele de la numere diferite atunci cind sunt citite din pipe! */
while(scanf("%d",&nr) != EOF)
{
fprintf(fout,"%d\n",nr);
fflush(fout);
}
/* tatal isi inchide capatul Write,
178
pentru ca fiul sa poata citi EOF din pipe */
fclose(fout);
/* asteapta terminarea fiului */
wait(NULL);
}
else
{ /* in fiu */
/* fiul isi inchide capatul Write */
fclose(fout);
/* fiul citeste numerele din pipe si le afiseaza pe ecran,
pina depisteaza EOF in pipe.
OBS: conform celor de mai sus, caracterul ’\n’ este folosit
ca separator de numere ! */
while(fscanf(fin,"%d",&nr) != EOF)
{
printf("%d\n",nr);
fflush(stdout);
}
/* fiul isi inchide capatul Read */
fclose(fin);
/* Obs: nici nu mai era nevoie de acest fclose explicit, deoarece
oricum toti descriptorii sunt inchisi la terminarea programului.*/
}
return 0;
}
Efectul acestui program:
Procesul tata citeste un sir de numere de la tastatura, sir terminat cu combinatia de
taste CTRL+D (i.e., caracterul EOF in UNIX), si le transmite procesului fiu, prin intermediul
canalului. Iar procesul fiu citeste din canal numerele trasmise de procesul parinte si le
afiseaza pe ecran.
Observat¸ie: deoarece acest program foloseste functiile I/O de nivel inalt, este necesara
conversia descriptorilor de fisiere de la tipul int la tipul FILE*, lucru realizat cu ajutorul
funct¸iei de bibliotec˘a fdopen.
Alt˘a observat¸ie: ˆın acest exemplu, numerele au fost scrise in canal ca text (i.e., ca secventa
a cifrelor care le compun) si nu ca reprezentare binara. Din acest motiv ele trebuie separate
printr-un caracter care nu este cifra (in program s-a folosit caracterul ’\n’, dar poate fi
folosit oricare altul), cu scopul ca aceste numere sa poata fi citite la destinatar in mod
corect, fara ca cifrele lor sa se “amestece” intre ele.
179
5.3 Comunicat¸ia prin canale externe
1. Introducere
2. Canale externe (fi¸siere fifo)
3. Aplicat¸ie: implementarea unui semafor
4. Aplicat¸ie: programe de tip client-server
5.3.1 Introducere
In aceasta sect¸iune a manualului ne vom referi la canalele externe, dupa ce ˆın sect¸iunea
precedena le-am tratat pe cele interne.
Canalele externe, numite si canale cu nume, sunt tot un fel de “conducte” pentru
comunicat¸ia local˘a ˆıntre procese prin schimb de mesaje, la fel ca si canalele interne, doar
ca in cazul canalelor externe avem de a face cu niste “conducte” ce sunt fisiere UNIX de un
tip special, numit fifo, deci sunt pastrate in sistemul de fisiere (aceste fisiere fifo se mai
numesc si pipe-uri cu nume).
5.3.2 Canale externe (fi¸siere fifo)
Deci un canal extern este un canal de comunicat¸ie prin care pot comunica local dou˘a sau
mai multe procese, comunicat¸ia acˆındu-se ˆın acest caz printr-un fisier UNIX de tip fifo.
Comunicat¸iaˆıntre procese prin intermediul unui canal fifo poate avea loc daca acele procese
cunosc numele fisierului fifo respectiv, deci nu mai avem restrict¸ia de la canale interne,
aceea c˘a procesele trebuiau s˘a fie ˆınrudite” prin fork/exec.
Modul de utilizare al unui fisier fifo este similar ca la fisierele obisnuite: mai intii se
deschide fisierul, apoi se scrie in el si/sau se citeste din el, iar la sfirsit se inchide fisierul.
Crearea unui fisier fifo se face cu ajutorul primitivei mkfifo (sau, echivalent, cu primitiva
mknod apelat˘a cu flag-ul SIFIFO). Urmatorul program exemplifica modul de creare a unui
fisier fifo.
180
/*
File: mkf.c (creare fisier fifo)
*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
extern int errno;
int main(int argc, char** argv)
{
if(argc != 2)
{
fprintf(stderr,"Sintaxa apel: mkf nume_fifo\n");
exit(1);
}
if( mkfifo(argv[1], 0666) == -1 )
/* sau, echivalent: if( mknod(argv[1], S_IFIFO | 0666, 0) == -1 ) */
{
if(errno == 17) // 17 = errno for "File exists"
{
fprintf(stdout,"Note: fifo %s exista deja !\n",argv[1]);
exit(0);
}
else
{
fprintf(stderr,"Eroare: creare fifo imposibila, errno=%d\n",errno);
perror(0);
exit(2);
}
}
return 0;
}
Alternativ, un fisier fifo poate fi creat si direct de la prompterul shell-ului, folosind comen-
zile mkfifo sau mknod.
Restul operatiilor asupra canalelor fifo se fac la fel ca la fisiere obisnuite, fie cu primitivele
I/O de nivel scazut (i.e.,open,read,write,close), fie cu functiile I/O de nivel ˆınalt din
biblioteca standard de C (i.e.,fopen,fread/fscanf,fwrite/fprintf,fclose, ¸s.a.).
Prin urmare, deschiderea unui fisier fifo se poate face cu apelul functiei open sau fopen,
intr-unul din urmatoarele trei moduri posibile:
read &write (deschiderea ambelor capete ale canalului),
read-only (deschiderea doar a capatului de citire),
sau write-only (deschiderea doar a capatului de scriere),
modul de deschidere fiind specificat prin parametrul transmis functiei de deschidere.
181
Observat¸ie importana:
ˆ
In mod implicit, deschiderea se face in mod blocant: o deschidere read-only trebuie sa se
“sincronizeze” cu una write-only. Cu alte cuvinte, daca un proces incearca o deschidere
a unui capat al canalului extern, apelul functiei de deschidere ramine blocat (i.e., functia
nu returneaza) pina cind un alt proces va deschide celalalt capat al canalului extern.
Si in cazul canalelor externe apar restrictiile si problemele de la canale interne si anume:
Caracteristici si restrictii ale canalelor externe:
1. Canalul extern este un canal unidirectional, adica pe la un capat se scrie, iar pe la
capatul opus se citeste.
Insa toate procesele pot s˘a scrie la capatul de scriere, ¸si s˘a citeasca la capatul de
citire.
2. Unitatea de informatie pentru canalul extern este octetul. Adica, cantitatea minima
de informatie ce poate fi scrisa in canal, respectiv citita din canal, este de 1 octet.
3. Canalul extern functioneaza ca o coada, adica o lista FIFO (First-In, First-Out),
deci citirea din canal se face cu distrugerea (i.e., consumul) din canal a informatiei
citite.
Asadar, citirea dintr-un canal extern (i.e., fisier fifo) difera de citirea din fisiere
obisnuite, pentru care citirea se face fara consumul informatiei din fisier.
4. Capacitatea canalului extern este limitata la o anumita dimensiune maxima (4 Ko,
16 Ko, etc.), ce difera de la un sistem UNIX la altul.
5. Citirea dintr-un canal extern (cu primitiva read) funct¸ioneaz˘a ˆın felul urm˘ator:
Apelul read va citi din canal si va returna imediat, fara sa se blocheze, numai
daca mai este suficienta informatie in canal, iar in acest caz valoarea returnata
reprezinta numarul de octeti cititi din canal.
Altfel, daca canalul este gol, sau nu contine suficienta informatie, apelul de
citire read va ramine blocat pina cind va avea suficienta informatie in canal
pentru a putea citi cantitatea de informatie specificata, ceea ce se va intimpla
in momentul cind alt proces va scrie in canal.
Alt caz de exceptie la citire, pe linga cazul golirii canalului:
daca un proces incearca sa citeasca din canal si nici un proces nu mai este capa-
bil sa scrie in canal vreodata (deoarece toate procesele si-au inchis deja capatul
de scriere), atunci apelul read returneaza imediat valoarea 0corespunzatoare
faptului ca a citit EOF din canal.
ˆ
In concluzie, pentru a se putea citi EOF din canalul fifo, trebuie ca mai in-
tii toate procesele sa inchida canalul in scriere (adica sa inchida descriptorul
corespunzator capatului de scriere).
Observat¸ie: la fel se comport˘a la citirea din canale interne si functiile de citire de
nivel ˆınalt (fread,fscanf, etc.), doar a acestea lucreaz˘a buffer-izat.
182
6. Scrierea intr-un canal intern (cu primitiva write) funct¸ioneaz˘a ˆın felul urm˘ator:
Apelul write va scrie in canal si va returna imediat, fara sa se blocheze, numai
daca mai este suficient spatiu liber in canal, iar in acest caz valoarea returnata
reprezinta numarul de octeti efectiv scrisi in canal (care poate sa nu coincida
intotdeauna cu numarul de octeti ce se doreau a se scrie, caci pot apare erori
I/O).
Altfel, daca canalul este plin, sau nu contine suficient spatiu liber, apelul de
scriere write va ramine blocat pina cind va avea suficient spatiu liber in canal
pentru a putea scrie informatia specificata ca argument, ceea ce se va intimpla
in momentul cind alt proces va citi din canal.
Alt caz de exceptie la scriere, pe linga cazul umplerii canalului:
daca un proces incearca sa scrie in canal si nici un proces nu mai este capabil sa
citeasca din canal vreodata (deoarece toate procesele si-au inchis deja capatul de
citire), atunci sistemul va trimite acelui proces semnalul SIGPIPE, ce cauzeaza
intreruperea sa si afisarea pe ecran a mesajului “Broken pipe”.
Observat¸ie: la fel se comport˘a la scrierea ˆın canale interne si functiile de citire de
nivel ˆınalt (fwrite,fprintf, etc.), doar c˘a acestea lucreaz˘a buffer-izat. Acest fapt
cauzeaz˘a uneori erori dificil de depistat, datorate neatent¸iei programatorului, care
poate uita uneori aspectele legate de modul de lucru buffer -izat al funct¸iilor de scriere
din biblioteca standard de C, i.e. poate uita s˘a fort¸eze “golirea” buffer -ului ˆın canal
cu ajutorul funct¸iei fflush, imediat dup˘a apelul funct¸iei de scriere propriu-zise.
Observat¸ie:
La fel ca la canale interne, cele afirmate mai sus, despre blocarea apelurilor de citire sau
de scriere in cazul canalului gol, respectiv plin, corespund comportamentului implicit,
blocant, al canalelor externe.
Insa, exista posibilitatea modificarii acestui comportament implicit, intr-un comportament
neblocant, situatie in care apelurile de citire sau de scriere nu mai ramin blocate in cazul
canalului gol, respectiv plin, ci returneaza imediat valoarea -1, si seteaza corespunzator
variabila errno.
Modificarea comportamentului implicit in comportament neblocant se realizeaza prin
setarea atributului ONONBLOCK pentru descriptorul corespunzator acelui capat al canalului
extern pentru care se doreste modificarea comportamentului, ceea ce se poate face fie direct
la deschiderea canalului, fie dupa deschidere cu ajutorul primitivei fcntl. Spre exemplu,
apelul
fd out = open("canal fifo”, O WRONLY |O NONBLOCK );
va seta la deschidere atributul ONONBLOCK pentru capatul de scriere al canalului extern
cu numele canal fifo din directorul curent de lucru. Iar secvent¸a de cod
fd out = open("canal fifo”,O WRONLY);
...
fcntl(fd out,F SETFL,O NONBLOCK);
183
va seta, dupa deschidere, atributul O NONBLOCK pentru capatul de scriere al canalului
extern cu numele canal fifo din directorul curent de lucru.
Atent¸ie: dup˘a cum am mai spus ¸si la canale interne, functiile I/O de nivel inalt (i.e.,
fread/fwrite,fscanf/fprintf, ¸si celelalte din biblioteca standard de C) lucreaza buffer-
izat. Ca atare, la scrierea intr-un canal extern folosind functiile fwrite,fprintf, etc.,
informatia nu ajunge imediat in canal in urma apelului, ci doar in buffer-ul asociat acelui
descriptor, iar “golirea” buffer-ului in canal se va intimpla abia in momentul umplerii
buffer-ului, sau la intilnirea caracterului ’\n’(newline) in specificatorul de format al func-
tiei de scriere, sau la apelul functiei fflush pentru acel descriptor.
Prin urmare, daca se doreste garantarea faptului ca informatia ajunge in canal imediat
in urma apelulului de scriere cu functii de nivel inalt, trebuie sa se apeleze, imediat dupa
apelul de scriere, si functia fflush pentru descriptorul asociat capatului de scriere al acelui
canal.
Deosebiri ale canalelor externe fat¸˘a de cele interne:
1. Functia de creare a unui canal extern nu produce si deschiderea automata a celor
doua capete, acestea trebuie dupa creare sa fie deschise explicit prin apelul unei
funct¸ii de deschidere a unui fi¸sier.
2. Un canal extern poate fi deschis, la oricare din capete, de orice proces, indiferent
daca acel proces are sau nu vreo legatura de rudenie (prin fork/exec) cu procesul
care a creat canalul extern.
Aceasta este posibil deoarece un proces trebuie doar sa cunoasca numele fisierului
fifo pe care doreste sa-l deschida, pentru a-l putea deschide. Bineinteles, procesul
respectiv mai trebuie sa aiba si drepturi de acces pentru acel fisier fifo.
3. Dupa ce un proces inchide un capat al unui canal fifo, acel proces poate redeschide
din nou acel capat pentru a face alte operatii I/O asupra sa.
Pentru mai multe detalii despre canalele fifo, va recomand citirea paginii de help a comenzii
de creare a unui canal extern:
UNIX>man 3 mkfifo
si a paginii generale despre canale externe:
UNIX>man fifo
5.3.3 Aplicat¸ie: implementarea unui semafor
ˆ
In continuare vom da un exemplu de utilizare a canalelor cu nume, ¸si anume ele pot fi
folosite pentru implementarea unui semafor.
184
Semaforul este o structura de control clasica in programarea concurenta, ce permite exe-
cutia exclusiva a unei secvente de cod, i.e. controleaza accesul intr-o sectiune critica.
Executia exclusiva a unei secvente de cod se bazeaza pe doua rutine exportate de tipul de
data semafor: sem enter si sem exit (ele mai sunt ˆıntˆılnite ˆın literatura de specialitate ¸si
sub numele de wait(semafor)¸si respectiv signal(semafor)).
Acestea vor fi apelate in programe in ordinea urmatoare:
sem enter();
...
sem exit();
Portiunea de cod dintre cele doua apeluri se numeste portiune critica, ea mai fiind numita
si zona critica sau sectiune critica.
Prin folosirea semaforului avem certitudinea ca portiunea critica se va executa in exclu-
sivitate de primul proces care a executat cu succes apelul sem enter.
Deci primul proces care executa cu succes sem enter, trece de punctul de sincronizare si
capata acces in portiunea critica. Celelalte procese care au ajuns la punctul de sincronizare
(i.e., la apelul sem enter), intra intr-o coada de asteptare. Dupa ce acel proces a iesit
din portiunea critica (i.e., a executat sem exit), este deblocat primul proces din coada de
asteptare, adica i se permite accesul in portiunea critica, ¸s.a.m.d.
Cind apare nevoia utilizarii unei zone critice?
In programarea paralela sunt situatii cind dou˘a sau mai multe procese (ce executa fie ace-
lasi cod, fie programe diferite), executate in paralel, trebuie sa partajeze (i.e., sa utilizeze
in comun) o resursa nepartajabila (i.e., o resursa care poate fi utilizata de cel mult un
proces la un moment dat), sau sa comunice intre ele, sau sa execute exclusiv o secventa
de cod, etc., deci situatii in care apar probleme de sincronizare intre procese.
Exemplu. Sa presupunem, spre exemplu, ca avem de implementat o structura de tip lista
simplu inlantuita, utilizabila concurent de mai multe procese:
CapLista (I1,L1)(I2,L2)... (In,Ln)
unde un element al listei este o pereche de forma E=(I,L), fiind format˘a din I=informatia
propriu-zisa, si L=legatura (pointerul) catre urmatorul element din lista.
Operatia de inserare in capul listei al unui element E0=(I0,L0)s-ar face prin urmatoarea
secventa de pasi:
1. L0:= CapLista //leaga E0E1
2. CapLista := E0//rupe legatura CapLista E1si leaga CapLista E0
Daca doua (sau mai multe) procese ar executa concurent operatia de inserare in capul
listei, se pot obtine erori – prin anumite amestecari ale secventelor de pasi executate de
185
cele doua procese se obtin date eronate.
Din acest motiv este nevoie ca operatia de inserare sa se execute in mod atomic (i.e., exclu-
siv, adic˘a s˘a fie neintreruptibil˘a). Pentru aceasta, trebuie sa se trateze prin exclusivitate
accesul in read si write la variabila CapLista.
Rutina de inserare in capul listei ar trebui sa arate cam ˆın felul urm˘ator:
semafor s; // declarare data de tip semafor
...
alloc(E0); // alocare memorie pentru elementul E0
I0:= info; // asignare informatie in cimpul I0
s.sem enter(); // punctul de sincronizare (punctul de intrare)
L0:=CapLista; // modificare cimpul L0
CapLista:=E0; // modificare variabila CapLista
s.sem exit(); // punctul de iesire
unde seste o data de tip semafor comuna acelor procese care vor executa concurent
operatii de inserare in lista, init¸ializat cu valoarea 1(deci un semafor binar).
Observat¸ie importana:
Apelurile sem enter si sem exit trebuie sa fie perechi, pe orice fir posibil de execut¸ie!
Daca un proces executa sem exit fara sa fi executat anterior sem enter, iar un al doilea
proces este in portiunea critica, atunci executia acestei portiuni de catre al doilea proces
nu mai este exclusiva (un alt proces poate intra in portiunea critica, in timp ce al doilea
proces nu a parasit-o inca).
O alta situatie de pereche punct intrare – punct iesire este urmatoarea:
semafor s;
...
s.sem enter();
if( TEST )
{
s.sem exit();
...
}
else
{
s.sem exit();
...
}
Deci daca avem nevoie de executie exclusiva doar pentru testul din instructiunea if, nu si
pentru actiunile de pe cele doua ramuri then-else, atunci este indicat sa se puna apelul
sem exit pe cele doua ramuri imediat dupa test.
Concluzie: deblocarea semaforului trebuie facuta cit mai repede, imediat ce acest lucru
este posibil (pentru a permite cit mai repede celorlalte procese sa intre in portiunea critica).
186
Atent¸ie: daca se folosesc doua semafoare “imbricate”, ca mai jos, poate apare fenomenul
de interblocaj (deadlock):
Procesul P1: Procesul P2:
... ...
s1.sem enter(); s2.sem enter();
... ...
s2.sem enter(); s1.sem enter();
... ...
{s1,s2}.sem exit(); {s1,s2}.sem exit(); // nu conteaza ordinea
... ... // apelurilor sem exit()
O secventa de executie posibila este urmatoarea: procesul P1 intra in zona sa critica
controlata de semaforul s1,P2 intra in zona sa critica controlat˘a de s2, apoi P1 incearca
sa intre in zona sa critica controlat˘a de s2 (dar ramine blocat, deoarece semaforul s2 este
“ocupat” de procesul P2), iar P2 incearca sa intre in zona sa critica controlat˘a de s1
(dar ramine blocat, deoarece semaforul s1 este “ocupat” de procesul P1); deci a aparut o
situatie de interblocaj.
Observat¸ie: din acest motiv in unele sisteme ce utilizeaza semafoare nu este permis ca in
sectiunea critica a unui semafor sa se apeleze intrarea intr-un alt semafor.
Indicat¸ii generale de folosire a semafoarelor:
– portiunea critica sa fie cit mai scurta;
– in portiunea critica sa nu se intimple erori (cicluri infinite, etc.), deoarece acestea duc la
blocarea intregului sistem de procese ce utilizeaza acel semafor.
S¸i acum, a vedem cum am putea implementa un semafor folosind canale fifo.
Ideea este foarte simpl˘a: init¸ializarea semaforului ar consta ˆın crearea unui fi¸sier fifo de
atre un proces cu rol de supervizor (poate fi oricare dintre procesele ce vor folosi acel
semafor, sau poate fi un proces separat); init¸ial acest proces supervizor va scrie ˆın canal 1
caracter oarecare, dac˘a e vorba de un seminar binar (respectiv ncaractere oarecare, dac˘a
e vorba de un seminar general n-ar), ¸si va p˘astra deschise ambele capete ale canalului
pe toat˘a durata de execut¸ie a proceselor ce vor folosi acel semafor (cu scopul de a nu
se pierde pe parcurs informat¸ia din canal datorit˘a inexistent¸ei la un moment dat pentru
fiecare cap˘at a m˘acar unui proces care s˘a-l aib˘a deschis).
Operat¸ia sem enter va consta ˆın citirea unui caracter din fi¸sierul fifo (mai precis, ˆıntˆıi de-
schiderea lui, urmat˘a de citirea efectiv˘a a unui octet, ¸si apoi eventual ˆınchiderea fi¸sierului).
Citirea se va face ˆın modul implicit, blocant, ceea ce va asigura steptarea procesului la
punctul de intrare ˆın zona sa critic˘a ˆın situat¸ia cˆınd semaforul este “pe ro¸su”, adic˘a dac˘a
canalul fifo este gol.
Operat¸ia complementar˘a, sem exit, va consta ˆın scrierea unui caracter ˆın fi¸sierul fifo (mai
precis, ˆıntˆıi deschiderea lui, urmat˘a de scrierea efectiv˘a a unui octet, ¸si apoi eventual
ˆınchiderea fi¸sierului).
187
5.3.4 Aplicat¸ie: programe de tip client-server
ˆ
In continuare vom da un alt exemplu de utilizare a canalelor cu nume, ¸si anume ele pot fi
folosite pentru implementarea programelor de tip client-server.
Oaplicat¸ie de tip client-server este compus˘a din dou˘a componente:
serverul:
este un program care dispune de un anumit num˘ar de servicii (i.e. funct¸ii/operat¸ii),
pe care le pune la dispozit¸ia client¸ilor.
clientul:
este un program care “interogheaz˘a” serverul, solicitindu-i efectuarea unui serviciu
(dintre cele puse la dispozit¸ie de acel server).
Exemplu.Browser-ele pe care le folosit¸i pentru a naviga pe INTERNET (cum ar fi,
spre exemplu, MS Internet Explorer-ul, Netscape Navigator/Mozilla, sau Opera) sunt un
exemplu de program client, care se conecteaz˘a la un program server, numit server de
web, solicitˆındu-i transmiterea unei pagini web, care apoi este afi¸sat˘a ˆın fereastra grafic˘a
abrowser-ului.
Folosirea unei aplicat¸ii de tip client-server se face ˆın felul urm˘ator:
Programul server va rulat ˆın background, ¸si va sta ˆın steptarea cererilor din partea
client¸ilor, putˆınd servi mai mult¸i client¸i simultan.
Iar client¸ii vor putea fi rulat¸i mai mult¸i simultan, din acela¸si cont sau din conturi utilizator
diferite, ¸si se vor conecta la serverul rulat ˆın background.
Deci putem avea la un moment dat mai multe procese client, care ˆıncearc˘a, fiecare inde-
pendent de celelalte, a foloseasc˘a serviciile puse la dispozit¸ie de procesul server.
Observat¸ie: ˆın realitate, programul server este rulat pe un anumit calculator, iar client¸ii
pe diverse alte calculatoare, conectate la INTERNET, comunicat¸ia realizˆındu-se folosind
socket-uri, prin intermediul ret¸elelor de calculatoare. ˆ
Ins˘a putem simula aceasta folosind
comunicat¸ie prin canale externe (fifo-uri) ¸si executˆınd toate procesele (i.e., serverul ¸si
client¸ii) pe un acela¸si calculator.
Tipurile de servere existente ˆın realitate, d.p.d.v. al servirii “simultane” a mai multor
client¸i, se ˆımpart ˆın dou˘a categorii:
server iterativ
Cˆıt timp dureaz˘a efectuarea unui serviciu (i.e., rezolvarea unui client), serverul este
blocat: nu poate r˘aspunde cererilor venite din partea altor client¸i. Deci nu poate
rezolva mai mult¸i client¸i ˆın acela¸si timp!
188
server concurent
Pe toat˘a durata de timp necesar˘a pentru efectuarea unui serviciu (i.e., rezolvarea
unui client), serverul nu este blocat, ci poate aspunde cererilor venite din partea
altor client¸i. Deci poate rezolva mai mult¸i client¸i ˆın acela¸si timp!
Detalii legate de implementare:
Pentru comunicarea ˆıntre procesele client ¸si procesul server este necesar s˘a se
foloseasc˘a, drept canale de comunicat¸ie, canalele externe (adic˘a pipe-urile cu nume),
sau se mai pot utiliza socket-uri, a¸sa cum se ˆıntˆımpl˘a ˆın realitate, dup˘a cum am
discutat mai sus. Atent¸ie: nu se pot folosi pipe-uri interne, deoarece procesul server
¸si procesele client¸i nu sunt ˆınrudite (prin fork/exec).
Mai mult, trebuie avut grij˘a la gestiunea drepturilor de acces la fi¸sierele fifo folosite
pentru comunicat¸ie, astfel ˆıncˆıt s˘a se poat˘a rula procesele client simultan, din diferite
conturi utilizator (deci, de pe terminale de lucru diferite, conectate - evident - la
acela¸si calculator, ˆın cazul de fat¸˘a la serverul UNIX al student¸ilor, fenrir, pe care
lucrat¸i dumneavoastr˘a).
Un alt aspect legat tot de comunicat¸ie: serverul nu cunoa¸ste ˆın avans client¸ii ce
se vor conecta la el pentru a le oferi servicii, ˆın schimb clientul trebuie s˘a cunoasc˘a
serverul la care se va conecta pentru a beneficia de serviciul oferit de el. Ce ˆınseamn˘a
aceasta d.p.d.v. practic?
Serverul va crea un canal fifo cu un nume fixat, cunoscut ˆın programul client, ¸si va
a¸stepta sosirea informat¸iilor pe acest canal. Un client oarecare se va conecta la acest
canal fifo cunoscut ¸si va transmite informat¸ii de identificare a sa, care vor folosite
ulterior pentru realizarea efectiv˘a a comunicat¸iilor implicate de serviciul solicitat (s-
ar putea s˘a fie nevoie de canale suplimentare, particulare pentru acel client, ca s˘a nu
se amestece comunicat¸iile destinate unui client cu cele destinate altui client conectat
la server ˆın acela¸si timp cu primul).
Referitor la modul de servire “simultan˘a” a mai multor client¸i, exist˘a diferent¸e de
implementare a serverului, ˆın cazul serverelor iterative fat¸˘a de cele concurente. Mai
precis, pentru serverele de tip iterativ este suficient un singur proces UNIX, pe cˆınd
pentru serverele de tip concurent este nevoie de mai multe procese UNIX: un proces
master, care a¸steapt˘a sosirea cererilor din partea client¸ilor, ¸si la fiecare cerere sosit˘a,
el va crea un nou proces fiu, un slave care va fi responsabil cu rezolvarea propriu-zis˘a
a clientului respectiv, iar master-ul va relua imediat a¸steptarea unei noi cereri, f˘ar˘a
a stepte terminarea procesului fiu.
Observat¸ie: ca o analogie cu execut¸ia comenzilor tastate la prompterul oferit de
shell-urile UNIX, serverul iterativ corespunde execut¸iei de comenzi ˆın foreground, iar
serverul concurent corespunde execut¸iei de comenzi ˆın background (situat¸ie ˆın care
prompterul este reafi¸sat imediat, f˘ar˘a s˘a se a¸stepte terminarea execut¸iei comenzii).
189
5.4 Alte mecanisme pentru comunicat¸ia inter-procese
Pe lˆıng˘a pipe-urile interne ¸si externe, studiate deja, mai exist˘a ¸si alte mecanisme utile
pentru comunicat¸ia ˆıntre procese:
Socket-uri:
Este vorba despre o alt˘a categorie de canale de comunicat¸ie, diferit˘a de pipe-urile
interne ¸si externe, categorie ce are avantajul de a putea fi folosit˘a pentru transmisia
de date ˆıntre procese aflate ˆın execut¸ie pe calculatoare diferite, conectate ˆıntre ele
printr-o ret¸ea de calculatoare. Aceast˘a categorie este reprezentat˘a de a¸sa-numitele
socket-uri, introduse pentru prima dat˘a ˆın varianta BSD de UNIX.
Socket-urile sunt tot un tip special de fi¸siere UNIX, la fel ca ¸si fi¸sierele fifo, putˆınd
fi exploatate ¸si ele prin intermediul interfet¸ei standard a primitivelor de lucru cu
fi¸siere (read,write, etc.), principala deosebire fat¸˘a de fi¸sierele fifo (i.e., canalele
externe) fiind aceea a pot folosite pentru comunicat¸ia ˆıntre procese aflate ˆın
execut¸ie pe calculatoare diferite, spre deosebire de canalele externe (¸si cele interne),
ce pot fi folosite doar pentru comunicat¸iaˆıntre procese aflate ˆın execut¸ie pe un acela¸si
calculator.
Particularit˘at¸ile folosirii socket-urilor, ¸siˆın generalˆıntreaga problematic˘a a comunicat¸iei
ˆıntre procese printr-o ret¸ea de calculatoare, nu constituie subiectul acestui manual,
ele urmˆınd a fi abordate ˆın cadrul unei discipline pe care o vet¸i studia ulterior,
intitulat˘a “Ret¸ele de calculatoare”.
biblioteca IPC:
Un alt mecanism de comunicat¸ie inter-procese (de fapt mai multe) este pus la
dispozit¸ie de biblioteca IPC (care este o prescurtare de la Inter-Process Communica-
tion). Aceast˘a bibliotec˘a ofer˘a trei facilit˘at¸i utile: memorie partajat˘a, semafoare, ¸si
cozi de mesaje. Din lips˘a de spat¸iu, biblioteca IPC nu este tratat˘a ˆın cadrul acestui
manual, r˘amˆınˆınd ca studiu individual pentru cei care doresc s˘a-¸si ˆınsseasc˘a mai
multe mecanisme utile ˆın programarea concurena sub UNIX/Linux. Ca punct de
plecare ˆın studiul individual, v˘a recomand citirea paginii de manual a bibliotecii IPC
de pe sistemul UNIX pe care lucrat¸i.
5.5 S¸abloane de comunicat¸ie ˆıntre procese
ˆ
In continuare vom trece ˆın revist˘a cˆıteva situat¸ii ce pot apare la comunicatia prin canale
interne ¸si externe, ¸si care pot ridica anumite probleme la implementarea lor.
Dup˘a cum am v˘azut, un canal (intern sau extern) este o “conduct˘a” unidirect¸ional˘a prin
care “curge” informat¸ia de la un anumit cap˘at (i.e., cel de scriere) c˘atre cel˘alalt cap˘at
(i.e., cel de citire). ˆ
Ins˘a, la un moment dat, pot exista mai multe procese cu rol de
190
“scriitori” (i.e., care scriu date ˆın acel canal, pe la cap˘atul de scriere), ¸si pot exista mai
multe procese cu rol de “cititori” (i.e., care citesc date din acel canal, pe la caatul de
citire); bineˆınt¸eles, o parte dintre procese pot fi atˆıt “scriitori”, cˆıt ¸si “cititori”.
Dup˘a num˘arul de procese “scriitori” ¸si “cititori” ce utilizeaz˘a un canal (intern sau extern)
pentru a comunica ˆıntre ele, putem diferent¸ia urm˘atoarele pattern-uri (i.e., ¸sabloane) de
comunicat¸ie inter-procese:
comunicat¸ie unul la unul:
atunci cˆınd canalul este folosit de un singur proces “scriitor” pentru a transmite date
unui singur proces “cititor”;
comunicat¸ie unul la mult¸i:
atunci cˆınd canalul este folosit de un singur proces “scriitor” pentru a transmite date
mai multor procese “cititori”;
comunicat¸ie mult¸i la unul:
atunci cˆınd canalul este folosit de mai multe procese “scriitori” pentru a transmite
date unui singur proces “cititor”;
comunicat¸ie mult¸i la mult¸i:
atunci cˆınd canalul este folosit de mai multe procese “scriitori” pentru a transmite
date mai multor procese “cititori”.
Primul caz, cel al comunicat¸iei unul la unul, este cel mai simplu, neridicˆınd probleme
deosebite. Exemplele de programe date anterior ˆın sect¸iunea 5.2 despre canale interne, se
ˆıncadreaz˘a ˆın acest ¸sablon de comunicat¸ie.
Celelalte cazuri ridic˘a anumite probleme, de care trebuie s˘a se t¸in˘a cont la implementarea
lor. a le trecem pe rˆınd ˆın revist˘a.
ˆ
In cazul comunicat¸iei unul la mult¸i pot interveni dou˘a categorii de factori ce pot genera
anumite probleme, cum ar fi aceea de corupere a mesajelor:
1. O prim˘a categorie se refer˘a la lungimea mesajelor:
mesaje de lungime constant˘a
Dac˘a toate mesajele transmise au o lungime constana (cunoscut˘a de tot¸i “citi-
torii”), atunci nu sunt probleme deosebite – fiecare mesaj poate fi citit atomic
(i.e., dintr-o dat˘a, printr-un singur apel read); pentru aceasta, la fiecare citire
din canal (i.e., fiecare apel read) se vor citi exact atˆıt¸ia octet¸i cˆıt este constanta
ce indic˘a lungimea mesajelor.
191
mesaje de lungime variabil˘a
Dac˘a ˆıns˘a mesajele transmise au lungimi variabile (necunoscute deci de “citi-
torii”), atunci pot apare probleme deoarece mesajele nu mai pot fi citite atomic
(i.e., dintr-o dat˘a, printr-un singur apel read); solut¸ia ˆın acest caz este a se
foloseasc˘a mesaje formatate astfel:
mesaj = header + mesajul propriu-zis ,
partea de header fiind un mesaj de lungime fix˘a ce contine lungimea p˘art¸ii de
mesaj propriu-zis.
Pentru ca citirea mesajelor din canal s˘a se poat˘a face atomic, fiecare proces “citi-
tor” va trebui s˘a respecte urm˘atorul protocol: ˆıntˆıi se cite¸ste un header (deci un
mesaj de lungime fia), ¸si apoi se cite¸ste mesajul propriu-zis, cu lungimea de-
terminat˘a din header-ul abia citit. ˆ
In plus, deoarece este nevoie de dou˘a apeluri
atomice read prin acest protocol pentru a citi un mesaj ˆın ˆıntregime, trebuie
asigurat faptul c˘a nici un alt proces nu va face citiri din canal ˆıntre momentele
corespunz˘atoare celor dou˘a apeluri read. Cu alte cuvinte, trebuie garantat ac-
cesul exclusiv la canalul intern (sau extern). Aceasta se poate rezolva folosind
un semafor (implementat, de exemplu, folosind blocaje pe fi¸siere).
2. O a doua categorie se refer˘a la destinatarul mesajelor:
mesaje cu destinatar oarecare
Dac˘a mesajele transmise de “scriitor” nu sunt pentru un anumit destinatar
specific, atunci nu sunt probleme deosebite din acest punct de vedere – fiecare
mesaj poate fi citit ¸si prelucrat de oricare dintre procesele “cititori”.
mesaje cu destinatar specificat
Dac˘a ˆıns˘a mesajele transmise de “scriitor” sunt pentru un anumit destinatar
specific, atunci trebuie asigurat faptul c˘a mesajul este citit exact de c˘atre “citi-
torul” aruia ˆıi era destinat. Solut¸ia ˆın acest caz este a se foloseasc˘a mesaje
formatate astfel:
mesaj = header + mesajul propriu-zis ,
partea de header cont¸inˆınd un identificator al destinatarului (apoi, la mesajul
formatat astfel, se aplic˘a tehnicile discutate mai sus referitoare la lungimea
mesajelor).
Pentru ca citirea mesajelor din canal s˘a se poat˘a face corect, fiecare proces
“cititor” va trebui s˘a respecte urm˘atorul protocol: dac˘a a citit un mesaj care
nu-i era destinat lui, ˆıl va scrie ˆınapoi ˆın canal, ¸si apoi va face o pauz˘a aleatoare
(i.e., ˆısi va suspenda execut¸ia pentru un timp aleator) ˆınainte de a ˆıncerca s˘a
citeasc˘a din nou din canal.
Evident, ambele categorii de factori, atˆıt lungimea mesajelor, cˆıt ¸si destinatarul lor, depind
de logica aplicat¸iei (i.e. de funct¸ionalit˘at¸ile implementate ˆın programele care folosesc acel
canal pentru a comunica ˆıntre ele).
Observat¸ie: uneori, din motive de u¸surint¸˘a de scriere a codului ¸si de eficient¸˘a a execut¸iei
codului, se poate prefera ˆınlocuirea unui singur canal folosit pentru comunicat¸ie unul la
192
mult¸i, cu mai multe canale folosite pentru comunicat¸ie unul la unul, cˆıte un canal pentru
fiecare proces “cititor” existent.
ˆ
In cazul comunicat¸iei mult¸i la unul pot interveni urm˘atoarele dou˘a categorii de factori ce
pot genera anumite probleme:
1. O prim˘a categorie se refer˘a la lungimea mesajelor:
mesaje de lungime constant˘a
mesaje de lungime variabil˘a
Aceast˘a situat¸ie se trateaz˘a similar ca la comunicat¸ia unul la mult¸i, cu singura
observat¸ie c˘a nu mai este necesar˘a folosirea unui semafor pentru accesul exclusiv
la canal, deoarece avem un singur “cititor”.
(Observat¸ie: spre deosebire de citirea din canal, scrierea ˆın canal nu ridic˘a probleme
d.p.d.v. al lungimii mesajelor, indiferent dac˘a avem mai mult¸i “scriitori”, ca ˆın acest
caz, sau doar unul singur, ca ˆın cazul precedent, datorit˘a faptului a se poate realiza
ˆın mod atomic, printr-un singur apel write, deoarece “scriitorul” cunoa¸ste dinainte
lungimea mesajului pe care urmeaz˘a a-l scrie ˆın canal.)
2. O a doua categorie se refer˘a la expeditorul mesajelor:
mesaje cu expeditor oarecare
Dac˘a mesajele recept¸ionate de “cititor” nu trebuie tratate ca venind de la un
anumit expeditor specific, atunci nu sunt probleme deosebite din acest punct
de vedere – fiecare mesaj poate fi citit ¸si prelucrat ˆın acela¸si fel indiferent de la
care proces “scriitor” provine.
mesaje cu expeditor specificat
Dac˘a ˆıns˘a mesajele recept¸ionate de “cititor” trebuie tratate ca venind de la
un anumit expeditor specific, atunci trebuie asigurat faptul a ecare mesaj ˆıi
indic˘a “cititorului” care este exact “scriitorul” care i l-a trimis. Solut¸ia ˆın acest
caz este s˘a se foloseasc˘a mesaje formatate astfel:
mesaj = header + mesajul propriu-zis ,
partea de header cont¸inˆınd un identificator al expeditorului (apoi, la mesajul
formatat astfel, se aplic˘a tehnicile discutate mai sus referitoare la lungimea
mesajelor).
Evident, ¸si ˆın acest caz ambele categorii de factori, atˆıt lungimea mesajelor, cˆıt ¸si expedi-
torul lor, depind de logica aplicat¸iei (i.e. de funct¸ionalit˘at¸ile implementate ˆın programele
care folosesc acel canal pentru a comunica ˆıntre ele).
Observat¸ie: ¸si ˆın acest caz se poate prefera uneori ˆınlocuirea unui singur canal folosit
pentru comunicat¸ie mult¸i la unul, cu mai multe canale folosite pentru comunicat¸ie unul la
unul, cˆıte un canal pentru fiecare proces “scriitor” existent.
ˆ
In sfˆır¸sit, ˆın cazul comunicat¸iei mult¸i la mult¸i pot interveni toate categoriile de factori pe
care le-am v˘azut la comunicat¸iile unul la mult¸i ¸si mult¸i la unul:
193
1. lungimea mesajelor
2. expeditorul mesajelor
3. destinatarul mesajelor
Tratarea acestora se face prin combinarea solut¸iilor prezentate mai sus la comunicat¸iile
unul la mult¸i ¸si mult¸i la unul.
Observat¸ie: aceste ¸sabloane de comunicat¸ie prezentate mai sus pot apare nu numai la
comunicat¸ia prin canale interne ¸si externe, ci la orice fel de comunicat¸ie prin schimb de
mesaje (prin ret¸ea – folosind socket-uri, prin fi¸siere obi¸snuite, etc.).
ˆ
In final, un ultim aspect care trebuie ment¸ionat este urm˘atorul: ˆın discut¸ia de pˆın˘a acum
am presupus c˘a mediul de comunicat¸ie nu impune vreo limit˘a asupra lungimii maxime
pe care o pot avea mesajele transmise prin el. Ce facem ˆıns˘a ˆın situat¸ia cˆınd vrem s˘a
transmitem mesaje nelimitate printr-un canal de comunicat¸ie care permite doar mesaje de
lungime limitat˘a? Acest aspect este foarte important, de exemplu, ˆın cazul comunicat¸iei
prin ret¸ea – folosind socket-uri –, unde exist˘a o limit˘a de cca. 1500 octet¸i. Dup˘a cum at¸i
azut, ¸si pipe-urile, interne ¸si externe, sunt limitate, dar ˆın cazul lor limita nu pune prea
mari probleme, dac˘a se folosesc operat¸iile de citire ¸si scriere ˆın modul implicit, blocant
(deoarece, atunci cˆınd se umple canalul, apelul write va ˆıntrerupt pˆın˘a cˆınd se va face
loc ˆın canal printr-o citire).
A¸sadar, ˆın situat¸ia ˆın care mediul de comunicat¸ie impune o anumit˘a limitare a lungimii
maxime pe care o pot avea mesajele transmise prin acel mediu, ¸si se dore¸ste totu¸si
transmiterea unor mesaje de lungime mai mare decˆıt limita admisibil˘a, solut¸ia este de
a ˆımp˘art¸i” mesajele ˆın pachete (= mesaje de lungime fix˘a, cel mult egal˘a cu limita ad-
misibil˘a), ¸si atunci:
mesaj = secvent¸˘
a ordonat˘
a de pachete ,
pachet = header + cont¸inutul propriu-zis al pachetului ,
partea de header cont¸inˆınd un identificator al mesajului ¸si un identificator al pachetu-
lui ˆın cadrul mesajului din care face parte. Identificatorul mesajului este necesar pentru
“separat¸ia mesajelor” (i.e., pentru a putea deosebi pachetele apart¸inˆınd unui mesaj de
pachetele apart¸inˆınd altui mesaj). Identificatorul pachetului este necesar pentru refacerea
mesajului din pachete la destinat¸ie, deoarece ordinea de emisie a pachetelor nu este obli-
gatoriu s˘a coincid˘a cu ordinea ˆın care sunt recept¸ionate pachetele la destinat¸ie (ˆın situat¸ia
cˆınd mediul de comunicat¸ie poate cauza inversarea ordinii pachetelor ˆın timpul tranzit˘arii
lor prin el).
194
5.6 Exercit¸ii
Exercit¸iul 1. Cˆıte categorii de comunicat¸ie ˆıntre procese exist˘a?
Exercit¸iul 2. Ce ˆınseamn˘a not¸iunea de canal de comunicat¸ie?
Exercit¸iul 3. De cˆıte tipuri sunt canalele de comunicat¸ie ˆın UNIX?
Exercit¸iul 4. Ce efect are apelul pipe? Ce valoare returneaz˘a el?
Exercit¸iul 5.ˆ
In ce condit¸ii pot comunica ˆıntre ele dou˘a procese folosind un canal intern?
Exercit¸iul 6. Cum ne putem referi la capetele canalului creat de apelul pipe, ¸si ˆın ce scop
le folosim?
Exercit¸iul 7. Cum funct¸ioneaz˘a citirea dintr-un canal intern, ¸si care sunt situat¸iile de
except¸ie?
Exercit¸iul 8. Cum funct¸ioneaz˘a scrierea ˆıntr-un canal intern, ¸si care sunt situat¸iile de
except¸ie?
Exercit¸iul 9. Care este diferent¸a dintre comportamentul blocant ¸si cel neblocant al
canalelor interne? Cum se poate schimba comportamentul implicit?
Exercit¸iul 10 . Ce efect va avea programul urm˘ator (ce se va afi¸sa pe ecran la execut¸ie) ?
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){ int p[2]; char c=0;
pipe(p);
switch(fork()) {
case -1: fprintf(stderr,"eroare la fork"); exit(1);
case 0: close(1); dup(p[1]);
system("echo ’test message’ root");
break;
default: close(p[1]);
while(0!=read(p[0],&c,1)) printf("%c",c);
wait(NULL);
}
return 0;
}
Exercit¸iul 11 . Scrieti un program C care sa determine capacitatea canalelor interne
pentru sistemul UNIX pe care il utilizati.
195
Indicat¸ie de rezolvare: programul va crea un canal intern si apoi va scrie in mod repetat
in el, fara a citi nimic, si contorizind numarul de octeti scrisi efectiv in canal, pina cind
apelul de scriere va esua datorita umplerii canalului (atent¸ie: trebuie sa setati atributul
neblocant pentru capatul de scriere al canalului, pentru ca apelul de scriere sa nu ramina
blocat in momentul umplerii canalului).
Exercit¸iul 12 . Rescriet¸i programul din exercit¸iul 12din capitolul 3 care calcula o sua
distribuit˘a folosind, pentru comunicatia intre procese, canale interne in loc de fisiere obis-
nuite.
Exercit¸iul 13. Ce sunt fi¸sierele fifo?
Exercit¸iul 14.ˆ
In ce condit¸ii pot comunica ˆıntre ele dou˘a procese folosind un canal extern?
Care ar fi deosebirea fat¸˘a de canalele interne?
Exercit¸iul 15. Ce efect are apelul mkfifo? Ce parametri trebuie transmi¸si acestuia?
Exercit¸iul 16. Cum se realizeaz˘a deschiderea capetelor unui canal extern, ¸si ce probleme
pot apare?
Exercit¸iul 17. Cum funct¸ioneaz˘a citirea dintr-un canal extern, ¸si care sunt situat¸iile de
except¸ie?
Exercit¸iul 18. Cum funct¸ioneaz˘a scrierea ˆıntr-un canal extern, ¸si care sunt situat¸iile de
except¸ie?
Exercit¸iul 19. Care este diferent¸a dintre comportamentul blocant ¸si cel neblocant al
canalelor externe? Cum se poate schimba comportamentul implicit?
Exercit¸iul 20. Care sunt principalele diferent¸e dintre canalele interne ¸si cele externe?
Exercit¸iul 21 . Ce efect va avea programul urm˘ator?
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int fd; char sir[300];
mknod("a.txt", S_IFIFO|0666, 0);
fd=open("a.txt", O_WRONLY);
while(gets(sir), !feof(stdin))
write(fd, sir, strlen(sir));
return 0;
}
196
Exercit¸iul 22 . Scrieti un program C care sa determine capacitatea canalelor externe
pentru sistemul UNIX pe care il utilizati.
Indicat¸ie de rezolvare: la fel ca la canale interne – programul va crea un canal intern si apoi
va scrie in mod repetat in el, fara a citi nimic, si contorizind numarul de octeti scrisi efectiv
in canal, pina cind apelul de scriere va esua datorita umplerii canalului (atent¸ie: trebuie
sa setati atributul neblocant pentru capatul de scriere al canalului, pentru ca apelul de
scriere sa nu ramina blocat in momentul umplerii canalului).
Exercit¸iul 23. Rescriet¸i programele pipe-ex1.c si pipe-ex2.c date ca exemplu ˆın
sect¸iunea 5.2 a acestui manual folosind, pentru comunicatia intre procese, canale externe
ˆın locul celor interne.
Exercit¸iul 24 . Rescriet¸i programul din exercit¸iul 12din capitolul 3 care calcula o sua
distribuit˘a folosind, pentru comunicatia intre procese, canale externe.
Exercit¸iul 25.Hi-ho: scriet¸i dou˘a programe, primul sa scrie pe ecran “hi- in mod
repetat, iar al doilea sa scrie “ho, ” in mod repetat, si sa se foloseasca canale fifo pentru
sincronizarea proceselor, astfel ca, atunci cˆınd sunt lansate ˆın paralel cele dou˘a programe,
pe ecran a fie afi¸sat˘a exact succesiunea:
hi-ho, hi-ho, hi-ho, . . .
si nu alte combinatii posibile de interclasare a mesajelor afi¸sate de cele dou˘a procese.
Exercit¸iul 26. La ce sunt utile semafoarele?
Exercit¸iul 27. Scriet¸i un program ˆın care s˘a implementat¸i un semafor binar folosind canale
fifo.
Exercit¸iul 28. Ce ˆınseamn˘a aplicat¸ii de tip client-server?
Exercit¸iul 29. De cˆıte tipuri sunt serverele?
Exercit¸iul 30.Mini-FTP: Scriet¸i o aplicat¸ie de tip client-server pentru transferul de
fi¸siere. Clientul va oferi utilizatorului o interfat¸˘a text (i.e., un prompter de genul FTP>) la
care va putea tasta urm˘atoarele 6 comenzi:
1. FTP> ls director
va afi¸sa cont¸inutul directorului specificat de pe “calculatorul” server;
2. FTP> lls director
va afi¸sa cont¸inutul directorului specificat de pe “calculatorul” client;
3. FTP> cd director
va schimba directorul curent ˆın cel specificat pe “calculatorul” server;
197
4. FTP> lcd director
va schimba directorul curent ˆın cel specificat pe “calculatorul” client;
5. FTP> get fisier
va transfera fi¸sierul specificat de pe “calculatorul” server pe “calculatorul” client;
6. FTP> put fisier
va transfera fi¸sierul specificat de pe “calculatorul” client pe “calculatorul” server.
Evident, atˆıt programul client, cˆıt ¸si programul server ˆı¸si vor p˘astra cˆıte un director curent
de lucru propriu, ˆın raport cu care se vor considera numele de fi¸siere sau directoare spec-
ificate prin cale relativ˘a ˆın comenzile de mai sus. Operat¸iile locale lls ¸si lcd se vor
executa direct de atre client, f˘ar˘a ajutorul serverului, ˆın schimb pentru toate celelalte pa-
tru operat¸ii clientul va contacta serverul pentru a realiza operat¸ia respectiv˘a cu ajutorul
acestuia din urm˘a.
Cerint¸˘a: pentru programul server, ˆıncercat¸i ˆıntˆıi scrierea unui server iterativ, ¸si apoi a
unuia concurent.
Exercit¸iul 31. Ce ¸sabloane de comunicat¸ie pot apare la comunicat¸ia inter-procese prin
intermediul canalelor de comunicat¸ie?
Exercit¸iul 32. Care sunt factorii de care trebuie s˘a se t¸in˘a cont la implementare, pentru
a nu apare diverse probleme (gen “coruperea” mesajelor) la folosirea acestor ¸sabloane de
comunicat¸ie?
Exercit¸iul 33. Ce alte mecanisme de comunicat¸ie inter-procese mai sunt disponibile ˆın
UNIX, pe lˆına canalele interne ¸si cele externe?
198
Bibliografie
[1] D. Acost˘achioaie: Programare C ¸si C++ pentru Linux, editura Polirom, Ia¸si, 2002.
[2] D. Acost˘achioaie: Administrarea ¸si configurarea sistemelor Linux, edit¸ia a doua, edi-
tura Polirom, Ia¸si, 2003.
[3] D. Acost˘achioaie, S. Buraga: Utilizare Linux. Not¸iuni de baz˘a ¸si practic˘a, editura
Polirom, Ia¸si, 2004.
[4] B. Ball, S. Smoogen: Teach yourself Linux in 24 hours, editura SAMS Publishing,
Indianapolis, 1998.
[5] I. Ignat, E. Muntean, K. Pusztai: UNIX: Gestionarea ¸sierelor, editura Micro-
Informatica, Cluj-Napoca, 1992.
[6] I. Ignat, A. Kacso: UNIX: Gestionarea proceselor, editura MicroInformatica, Cluj-
Napoca, 1995.
[7] M. Mitchell, J. Oldham, A. Samuel: Advanced Linux Programming, editura New
Riders Publishing, Indianapolis, 2001.
[8] R.G. Sage: UNIX pentru profesioni¸sti, Editura de Vest, Timi¸soara, 1993.
[9] A. Silberschatz, J. Peterson, P. Galvin: Operating Systems Concepts, editura
Addison-Wesley, Reading MA, 2001.
[10] R. Stevens: Advanced UNIX Programming in the UNIX Environments, editura
Addison-Wesley, Reading MA, 1992.
[11] A. Tanenbaum: Modern Operating Systems, editura Prentice Hall International, 2001.
[12] ***, The Linux Documentation Project:http://metalab.unc.edu/LDP/
199
Anexa A
Rezolvare exercit¸ii din partea I
ˆ
In aceast˘a anex˘a vom prezenta rezolvarea celor mai dificile exercit¸ii (indicate prin semnul
ad˘augat num˘arului exercit¸iului ˆın textul manualului) din partea I.
Observat¸ie: din lips˘a de spat¸iu, ¸si pentru a nu r˘api cititorului pl˘acerea de a ˆıncerca singur
a le rezolve, am omis rezolvarea exercit¸iilor considerate a fi mai simple, precum ¸si a tuturor
exercit¸iilor de tip ˆıntrebare, al aror aspuns se poate afla direct din textul manualului de
fat¸˘a, sau din paginile de manual UNIX, disponibile prin comanda man pe sistemul UNIX pe
care lucrat¸i.
Exercit¸iul 47din capitolul 2.
Indicat¸ii de rezolvare. Pentru a testa modul de execut¸ie a fi¸sierelor de init¸ializare, folosit¸i
comenzi de afi¸sare pe ecran a unor mesaje. De exemplu, ad˘augat¸i ˆın fiecare fi¸sier la sfˆır¸sit
cˆıte o linie de forma:
echo "Executing file .profile"
¸si, respectiv,
echo "Executing file .bash profile"
(dac˘a vreunul dintre fi¸siere nu exist˘a, atunci creat¸i-l). Apoi deschidet¸i o nou˘a sesiune de
lucru ¸si urm˘arit¸i mesajele afi¸sate pe ecran.
Pentru a doua parte a exercit¸iului, ¸sterget¸i cˆıte unul din cele dou˘a fi¸siere (sau doar
redenumit¸i-l altfel, ca s˘a nu-i pierdet¸i cont¸inutul) ¸si repetat¸i experimentul.
Exercit¸iul 48din capitolul 2.
Indicat¸ii de rezolvare. Pentru a testa o comand˘a care s˘a scrie mesaje pe ambele ie¸siri
standard (¸si pe stdout, ¸si pe stderr), putet¸i folosi urm˘atorul program C (dup˘a ce-l
compilat¸i):
/*
File: prg.c
(un mic program care scrie mesaje si pe stdout, si pe stderr)
Compilare: gcc prg.c -ocomanda
*/
200
#include <stdio.h>
void main()
{
fprintf(stdout,"prg.c -> message on stdout\n");
fprintf(stderr,"prg.c -> message on stderr\n");
fprintf(stdout,"prg.c -> 2nd message on stdout\n");
}
Modul de testare:
UNIX>comanda redirectari ; cat fisier
pentru fiecare din cele 8 linii de comand˘a specificate ˆın enunt¸ul exercit¸iului.
Exercit¸iul 73din capitolul 2.
Rezolvare. Iat˘a script-ul pentru automatizarea procesului de dezvoltare de programe C:
#!/bin/bash
#
# Efect: editeaza un fisier sursa C , apoi il compileaza si
# afiseaza erorile de compilare , iar apoi il executa.
clear
echo "*************** Begin of script for Edit-Compile-Execute ***********"
if test $# -gt 1
then
echo " Usage: $0 [ namefile[.c] ] "
sleep 1
echo "*************** End of script for Edit-Compile-Execute *************"
exit 0
fi
if test $# -eq 1
then
file=$1
else
echo "************************** Source filename *************************"
echo -n " Input the file’s name for editing/compiling/testing : "
read file
fi
fileexit=‘basename $file .c‘
file=${fileexit}.c
if test -f fileexit
then
mv $fileexit $file
echo " The source file $fileexit was renamed in $file !"
sleep 3
fi
sleep 1
201
while true
do
echo "************************** Editing *********************************"
while true
do
echo -n " Do you want to edit the file $file ? Y/N "
read rasp
case $rasp in
n | N ) break 2 ;;
y | Y ) # pico $file
joe $file
break ;;
esac
done
echo "************************** Compiling *******************************"
while true
do
echo -n " Do you want to compile the file $file ? Y/N "
read rasp
case $rasp in
n | N ) break 2 ;;
y | Y ) gcc -o ${fileexit}.exe $file 2> ${fileexit}.err
break ;;
esac
done
if test -s ${fileexit}.err
then
#joe ${fileexit}.err
echo "************************** Print compiling errors *****************"
head ${fileexit}.err
continue # reluarea buclei de la editare
else
rm ${fileexit}.err
fi
echo "************************** Testing *********************************"
while true
do
echo -n " Do you want to execute the file ${fileexit}.exe ? Y/N "
read rasp
case $rasp in
n | N ) break 2 ;;
y | Y ) ${fileexit}.exe
break ;;
esac
202
done
sleep 2
echo "************************** Finish **********************************"
echo "The execution of the file ${fileexit}.exe was finished!"
sleep 1
echo "Now, you may want to edit again the source for more modifications!"
sleep 2
continue # reluarea buclei de la editare
done
echo "*************** End of script for Edit-Compile-Execute *************"
sleep 1
203
Anexa B
Rezolvare exercit¸ii din partea II
ˆ
In aceast˘a anex˘a vom prezenta rezolvarea celor mai dificile exercit¸ii (indicate prin semnul
ad˘augat num˘arului exercit¸iului ˆın textul manualului) din partea II.
Observat¸ie: din lips˘a de spat¸iu, ¸si pentru a nu r˘api cititorului pl˘acerea de a ˆıncerca singur
a le rezolve, am omis rezolvarea exercit¸iilor considerate a fi mai simple, precum ¸si a tuturor
exercit¸iilor de tip ˆıntrebare, al aror aspuns se poate afla direct din textul manualului de
fat¸˘a, sau din paginile de manual UNIX, disponibile prin comanda man pe sistemul UNIX pe
care lucrat¸i.
Exercit¸iul 3din capitolul 3.
Rezolvare. aspunsul corect: se testeaz˘a dac˘a exist˘a fi¸sierul “a.txt” ˆın directorul curent;
mesajul afi¸sat este tocmai pe dos. (Justificare: datorit˘a faptului c˘a funct¸ia access re-
turneaz˘a valoarea 0 pentru test ˆındeplinit.)
Exercit¸iul 4din capitolul 3.
Rezolvare. aspunsul corect: va da eroare la execut¸ie, afi¸sˆınd mesajul “Segmentation
fault”. (Justificare: programul “crap˘a” la apelul stat datorit˘a faptului a pointerul info
nu este alocat.)
Exercit¸iul 5din capitolul 3.
Rezolvare. aspunsul corect: se va afi¸sa descriptor=-1. (Justificare: deoarece va fi eroare
la open, un utilizator obi¸snuit neavˆınd drept de scriere asupra fi¸sierului /etc/passwd.)
Exercit¸iul 6din capitolul 3.
Rezolvare. aspunsul corect: se va afi¸sa valoarea 0, indiferent dac˘a programul este exe-
cutat de un utilizator obi¸snuit sau de utilizatorul root. (Justificare: deoarece apelul stat
returneaz˘a 0 pentru succes ¸si -1 pentru eroare.)
204
Exercit¸iul 7din capitolul 3.
Rezolvare. aspunsul corect: se va afi¸sa ¸sirul “LinuxLinux”. (Justificare: deoarece pro-
gramul creeaz˘a pe “fis2” drept hard-link atre fi¸sierul ordinar “fis1”.)
Exercit¸iul 13din capitolul 3.
Indicat¸ii de rezolvare. Iata citeva sfaturi pentru implementarea operatiilor din meniu:
– pentru creare director: utilizati functia mkdir ;
– pentru stergere director: utilizati functia rmdir ;
– pentru schimbare director: utilizati functia chdir ;
– pentru listare director: utilizati sablonul descris mai sus pentru parcurgerea secventiala
a intrarilor dintr-un director ;
– pentru operatia de copiere (copy fis src fis dest): scrieti o functie care sa execute
urmatoarele operatii:
1. deschide fisierele fis src si fis dest
2. repeat
3. citeste din fis src si scrie ce a citit in fis dest
4. until EOF on fis src
5. inchide cele doua fisiere
– pentru stergere fisier: utilizati functia unlink ;
– pentru redenumire fisier: utilizati functia rename ;
– pentru vizualizare fisier: in mod asemanator ca la copiere, scrieti o functie care sa execute
urmatoarele operatii:
1. deschide fisierul
2. repeat
3. citeste din fisier si scrie ce a citit stdout
4. until EOF on fisier
5. inchide fisierul
Exercit¸iul 17din capitolul 3.
Rezolvare. Iat˘a sursa programului access4w.c (programul acces versiunea 4.0, varianta
cu apel blocant):
205
/*
File: access4w.c (versiunea 4.0, cu lacat pus in mod blocant)
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
extern int errno;
int main(int argc, char* argv[])
{
int fd;
char ch;
struct flock lacat;
if(argv[1] == NULL)
{
fprintf(stderr,"Trebuie apelat cu cel putin un parametru.\n");
exit(1);
}
if( (fd=open("fis.dat", O_RDWR)) == -1)
{ /* trateaza cazul de eroare ... */
perror("Nu pot deschide fisierul fis.dat deoarece ");
exit(2);
}
/* pregateste lacat pe caracterul dinaintea pozitiei curente */
lacat.l_type = F_WRLCK;
lacat.l_whence = SEEK_CUR;
lacat.l_start = -1;
lacat.l_len = 1;
/* parcurgerea fisierului caracter cu caracter pina la EOF */
while( read(fd,&ch,1) != 0)
{
if(ch == ’#’)
{
/* O singura incercare de punere a lacatului in mod blocant */
printf("Pun blocant lacatul pe #-ul gasit deja [Proces:%d].\n",getpid());
if( fcntl(fd,F_SETLKW,&lacat) == -1)
{
fprintf(stderr,"Eroare la blocaj [ProcesID:%d].\n", getpid());
perror("\tMotivul");
exit(3);
}
else
printf("Blocaj reusit [ProcesID:%d].\n", getpid());
sleep(5);
206
/* Inainte de a face suprascrierea, dar dupa blocare reusita,
mai trebuie verificat inca o data daca caracterul curent
este ’#’, deoarece intre momentul primei depistari a ’#’-ului
si momentul reusitei blocajului exista posibilitatea ca
acel ’#’ sa fie suprascris de celalalt proces ! */
lseek(fd,-1L,1);
read(fd,&ch,1);
if(ch == ’#’)
{
lseek(fd,-1L,1);
write(fd,argv[1],1);
printf("Terminat. S-a inlocuit primul # gasit [ProcesID:%d].\n",getpid());
return 0;
}
else
{
printf("Caracterul # pe care-l gasisem a fost deja suprascris. "
"Voi cauta altul [ProcesID:%d].\n",getpid());
/* Deblocarea acelui caracter (care intre timp fusese schimbat
din ’#’ in altceva de catre celalalt proces). */
lacat.l_type=F_UNLCK;
fcntl(fd,F_SETLK,&lacat);
lacat.l_type=F_WRLCK;
/* Urmeaza reluarea buclei de cautare a ’#’-ului ... */
}
}/*endif*/
}/*endwhile*/
printf("Terminat. Nu exista # in fisierul dat [ProcesID:%d].\n",getpid());
return 0;
}
Exercit¸iul 6din capitolul 4.
Rezolvare. aspunsul corect: se va afi¸sa ¸sirul “ABAB” dac˘a apelul fork reu¸se¸ste, respectiv
“AerrB” ˆın caz contrar (dac˘a e eroare la fork). (Justificare: datorit˘a faptului c˘a apelul
printf lucreaz˘a buffer -izat, iar ˆın momentul cre˘arii procesului u, acesta va “mo¸steni”
cont¸inutul buffer-ului.)
Exercit¸iul 7din capitolul 4.
Rezolvare. aspunsul corect: se vor crea ˆın total 15 procese fii (i.e., fara procesul initial).
Justificare: doar pentru valorile impare ale contorului se apeleaza fork, iar in fiecare fiu
creat se continua bucla for cu valoarea curenta a contorului. Mai precis:
– procesul initial P0creeaza 4 procese fii: P1(pentru contor=1), P2(pentru contor=3),
P3(pentru contor=5), si P4(pentru contor=7);
– fiul P1creeaza 3 fii: P5(pentru contor=3), P6(pentru contor=5), si P7(pentru
207
contor=7) ;
– fiul P2creeaza 2 fii: P8(pentru contor=5), si P9(pentru contor=7) ;
– fiul P3creeaza 1 fiu: P10 (pentru contor=7) ;
– fiul P4nu creeaza nici un fiu ;
– fiul P5creeaza 2 fii: P11 (pentru contor=5), si P12 (pentru contor=7) ;
– fiul P6creeaza 1 fiu: P13 (pentru contor=7) ;
– fiul P7nu creeaza nici un fiu ;
– fiul P8creeaza 1 fiu: P14 (pentru contor=7) ;
– fiii P9,P10 nu creeaza nici un fiu ;
– fiul P11 creeaza 1 fiu: P15 (pentru contor=7) ;
– fiii P12,P13,P14,P15 nu creeaza nici un fiu .
Exercit¸iul 13din capitolul 4.
Rezolvare. Iat˘a sursa programului suma.c care rezolva problema sumei distribuite:
/*
File: suma.c (suma distribuita cu 3 procese)
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
extern int errno;
void master_init();
void master_finish();
void slave_work(char* fi, char* fo);
int main()
{
int pid1,pid2;
printf("\n\n\n\n\n");
/* Curata rezultatele executiilor anterioare -> necesar !!! */
remove("f1o"); remove("f2o");
/* Citire numere de la tastatura si scriere in fisierele f1i si f2i */
master_init();
/* Am citit numerele inainte de crearea fiilor deoarece este nevoie de
sincronizare (fiul trebuie sa astepte tatal sa scrie in fisier pentru a
avea ce citi), iar citirea de la tastatura poate dura oricit de mult */
/* Creare primul proces slave */
if( (pid1=fork()) == -1)
{
fprintf(stderr,"Error fork la fiul 1 !\n");
208
exit(2);
}
if(pid1 == 0)
{ /* sunt in procesul fiu 1 */
slave_work("f1i","f1o");
return 0;
/* sfirsit executie fiu 1 */
}
/* else sunt in procesul master, executat in paralel cu if-ul de mai sus */
/* Creare al doilea proces slave */
if( (pid2=fork()) == -1)
{
fprintf(stderr,"Error fork la fiul 2 !\n");
exit(2);
}
if(pid2 == 0)
{ /* sunt in procesul fiu 2 */
slave_work("f2i","f2o");
return 0;
/* sfirsit executie fiu 2 */
}
/* else sunt in procesul master, executat in paralel cu cei doi fii */
/* Citeste cele 2 sume partiale si afiseaza suma lor. */
master_finish();
return 0;
}
/***********************************************************************/
void master_init()
{
FILE *f1,*f2;
int nr, flag;
if( (f1=fopen("f1i","wt")) == NULL)
{
fprintf(stderr,"Error opening file f1i, err=%d\n",errno); exit(3);
}
if( (f2=fopen("f2i","wt")) == NULL)
{
fprintf(stderr,"Error opening file f2i, err=%d\n",errno); exit(3);
}
printf("Introduceti numerele (0 pentru terminare):\n");
flag=1;
do
209
{
scanf("%d", &nr);
fprintf( (flag==1?f1:f2), "%d ", nr);
/* Atentie: spatiul din format este necesar! */
flag=3-flag;
}while(nr!=0);
fclose(f1); fclose(f2);
}
/***********************************************************************/
void master_finish()
{
/* Aici mai apare o sincronizare: master-ul trebuie sa citeasca sumele
partiale abia dupa ce acestea au fost calculate si scrise in fisierele
f1o si f2o de catre procesele slave.
Rezolvare: incercare repetata de citire cu pauza intre incercari.
(sau: se poate astepta terminarea celor 2 fii folosind primitiva wait)*/
FILE *f1,*f2;
int sp1,sp2, cod;
/* Citeste prima suma partiala */
cod = 0;
do
{
if( (f1=fopen("f1o","rt")) != NULL)
cod = (fscanf(f1,"%d",&sp1)==1);
if(!cod)
sleep(3);
}while(!cod);
fclose(f1);
/* Citeste a doua suma partiala */
cod = 0;
do
{
if( (f2=fopen("f2o","rt")) != NULL)
cod = (fscanf(f2,"%d",&sp2)==1);
if(!cod)
sleep(3);
}while(!cod);
fclose(f2);
/* Afiseaza suma */
printf("Master=%d -> suma nr. introduse este: %d\n", getpid(), sp1+sp2);
}
210
/***********************************************************************/
void slave_work(char* fi, char* fo)
{
FILE *f1,*f2;
int nr,cod, suma_partiala;
if( (f1=fopen(fi,"rt")) == NULL)
{
fprintf(stderr,"Error opening file %s, err=%d\n",fi,errno); exit(3);
}
suma_partiala=0;
do
{
cod=fscanf(f1,"%d", &nr);
if(cod == 1)
suma_partiala += nr;
}while(cod != EOF);
fclose(f1);
if( (f2=fopen(fo,"wt")) == NULL)
{
fprintf(stderr,"Error opening file %s, err=%d\n",fo,errno); exit(3);
}
fprintf(f2,"%d",suma_partiala);
fclose(f2);
printf("Slave=%d -> suma partiala:%d\n", getpid(), suma_partiala);
}
/***********************************************************************/
Exercit¸iul 20din capitolul 4.
Rezolvare. Efectul acestui program const˘a ˆın uratoarele: se redirecteaz˘a ie¸sirea de eroare
standard c˘atre fi¸sierul “a”, apoi apelul fprintf(stderr,"Hello!") va scrie de fapt ˆın
fi¸sierul “a”, iar apoi urmeaz˘a exec-ul, care, dac˘a reu¸se¸ste, afi¸seaz˘a pe ecran fi¸sierul “a”,
iar dac˘a e¸sueaz˘a, atunci urmeaz˘a fork-ul. Ca urmare, aspunsurile corecte sunt: ¸sirul
“Hello! este afi¸sat pe ecran o dat˘a (dac˘a reu¸se¸ste exec-ul), de dou˘a ori (dac˘a nu reu¸se¸ste
exec-ul, dar reu¸se¸ste fork-ul), ¸si respectiv niciodat˘a (dac˘a e¸sueaz˘a ¸si exec-ul, ¸si fork-ul).
Exercit¸iul 35din capitolul 4.
Rezolvare. aspunsul corect: se va afi¸sa de 4 ori. Justificare: din procesul curent se
creeaz˘a, ˆın total, 7 noi procese, din care 3 sunt fii direct¸i ai procesului init¸ial. P˘arintele
din vˆırful ierarhiei (i.e., procesul init¸ial) nu va executa niciodat˘a funct¸ia my handler,
deoarece el este singurul care nu execut˘a primitiva de corupere signal. Prin urmare, doar
211
cele 4 procese care nu sunt fii direct¸i ai procesului init¸ial vor cauza, la terminarea lor,
afi¸sarea ¸sirului “death of child!”, de c˘atre arint¸ii acestora (ˆın corpul handler-ului).
Exercit¸iul 37din capitolul 4.
Rezolvare. Iat˘a sursa programului hi-ho.c care rezolv˘a problema Hi-ho utilizˆınd semnalul
SIGUSR1 pentru sincronizarea celor dou˘a procese (sunt folosite dou˘a procese, unul creat de
cel˘alalt; tat˘al este responsabil cu afi¸sarea “hi”-urilor, iar fiul este responsabil cu afi¸sarea
“ho”-urilor).
/*
File: hi-ho.c (versiunea cu semnale pentru sincronizare)
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#define NR_AFISARI 100
void my_handler(int semnal)
{
}
int main()
{
int pid,i,ppid;
signal(SIGUSR1,my_handler);
if(-1 == (pid=fork()) )
{
perror("eroare la fork");
exit(1);
}
if(0 == pid)
{ /* fiul : responsabil cu ho-urile */
ppid = getppid();
for(i=0; i<NR_AFISARI; i++)
{
pause();
printf("ho, "); fflush(stdout);
kill(ppid, SIGUSR1);
}
}
else
{ /* tatal : responsabil cu hi-urile */
for(i=0; i<NR_AFISARI; i++)
212
{
printf("hi-"); fflush(stdout);
kill(pid, SIGUSR1);
pause();
}
}
printf("\n");
/*
printf("Sfirsit %s.\n", 0==pid?"fiu":"parinte");
*/
return 0;
}
Exercit¸iul 10din capitolul 5.
Rezolvare. aspunsul corect este c˘a programul dat afi¸seaz˘a pe ecran mesajul “test message
root”. Justificare: din procesul curent se creeaz˘a, ˆın total, 2 noi procese, ultimul executˆınd
comanda echo ce scrie mesajul pe ie¸sirea standard, redirectat˘a ˆın cap˘atul de scriere al
canalului intern, din care procesul init¸ial va citi acel mesaj “test message root” ¸si-l va
afi¸sa pe ecran.
Exercit¸iul 11din capitolul 5.
Rezolvare. Iat˘a sursa programului dimens pipe.c care determin˘a capacitatea unui canal
intern:
/*
File: dimens_pipe.c
*/
#include<stdio.h>
#include<fcntl.h>
int main(void)
{
int p[2],dimens;
char c=0;
/* creare pipe */
if( pipe(p) == -1)
{
perror("Error creare pipe"); exit(1);
}
/* setare non-blocking pentru capatul de scriere */
fcntl(p[1],F_SETFL,O_NONBLOCK);
dimens=0;
213
while(1)
{
if( write(p[1],&c,1) == -1)
{
perror("Eroare - umplere pipe");
break;
}
else
{
++dimens;
if(dimens%1024==0) printf(" %d Ko ...\n", dimens/1024);
}
}
printf("Capacitatea pipe-ului este de %d octeti.\n",dimens);
return 0;
}
Exercit¸iul 12din capitolul 5.
Rezolvare. Iat˘a sursa programului suma pipe.c care rezolv˘a problema sumei distribuite
utilizˆınd canale interne pentru comunicat¸ie:
/*
File: suma_pipe.c
Problema suma distribuita de la lectia fork, rezolvata cu canale interne.
*/
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
extern int errno;
void master_init();
void master_work();
void slave_work(int fdi, int fdo);
/* date pentru cele 3 pipe-uri */
int pipe1i[2], pipe2i[2], pipe3o[2];
int main(void)
{
int pid1,pid2;
printf("\n\n\n\n\n");
/* Crearea celor 3 pipe-uri p1i, p2i si p3o */
214
master_init();
/* Creare primul proces slave */
if( (pid1=fork()) == -1)
{
fprintf(stderr,"Error fork la fiul 1 !\n");
exit(2);
}
if(pid1 == 0)
{ /* sunt in procesul fiu 1 */
/* inchid capetele de care nu am nevoie */
close(pipe1i[1]);
close(pipe3o[0]);
close(pipe2i[0]); close(pipe2i[1]);
slave_work(pipe1i[0],pipe3o[1]);
return 0;
/* sfirsit executie fiu 1 */
}
/* else sunt in procesul master, executat in paralel cu if-ul de mai sus */
/* Creare al doilea proces slave */
if( (pid2=fork()) == -1)
{
fprintf(stderr,"Error fork la fiul 2 !\n");
exit(2);
}
if(pid2 == 0)
{ /* sunt in procesul fiu 2 */
/* inchid capetele de care nu am nevoie */
close(pipe2i[1]);
close(pipe3o[0]);
close(pipe1i[0]); close(pipe1i[1]);
slave_work(pipe2i[0],pipe3o[1]);
return 0;
/* sfirsit executie fiu 2 */
}
/* else sunt in procesul master, executat in paralel cu cei doi fii */
/* inchid capetele de care nu am nevoie */
close(pipe1i[0]);
close(pipe2i[0]);
close(pipe3o[1]);
215
/* Citeste secventa de la tastatura si o transmite fiiilor, apoi
primeste de la ei cele 2 sume partiale si afiseaza suma lor. */
master_work();
return 0;
}
/***********************************************************************/
void master_init()
{
/* Crearea celor 3 pipe-uri fifo1i, fifo2i si fifo3o */
if( pipe(pipe1i) == -1 )
{
perror("Eroare la crearea pipe-ului pipe1i");
exit(1);
}
if( pipe(pipe2i) == -1 )
{
perror("Eroare la crearea pipe-ului pipe2i");
exit(1);
}
if( pipe(pipe3o) == -1 )
{
perror("Eroare la crearea pipe-ului pipe3o");
exit(1);
}
}
/***********************************************************************/
void master_work()
{
int nr, flag;
int sump1,sump2;
/* Putem citi numerele de la tastatura dupa crearea fiilor,
desi citirea de la tastatura poate dura oricit de mult (!),
deoarece sincronizarea necesara aici (fiul trebuie sa-si astepte
tatal sa scrie in canal pentru a avea ce citi) este realizata prin
faptul ca citirea din pipe se face, implicit, in mod blocant. */
/* citirea secventei de numere si impartirea ei intre cele doua canale */
printf("Introduceti numerele (0 pentru terminare):\n");
flag=1;
while(1)
{
scanf("%d", &nr);
if(nr == 0) break;
216
write( (flag==1 ? pipe1i[1] : pipe2i[1]), &nr, sizeof(int));
flag=3-flag;
}
close(pipe1i[1]);
close(pipe2i[1]);
/* Aici mai apare o sincronizare: master-ul trebuie sa citeasca
sumele partiale abia dupa ce acestea au fost calculate si scrise
in canalul pipe3o de catre procesele slave.
Rezolvare: citirea din pipe este, implicit, blocanta. */
/* Citeste prima suma partiala (sosita de la oricare din cei doi slaves) */
if( read(pipe3o[0], &sump1, sizeof(int)) != sizeof(int))
{
fprintf(stderr,"Eroare la prima citire din pipe-ul pipe3o\n");
exit(7);
}
/* Citeste a doua suma partiala */
if( read(pipe3o[0], &sump2, sizeof(int)) != sizeof(int))
{
fprintf(stderr,"Eroare la a doua citire din pipe-ul pipe3o\n");
exit(8);
}
close(pipe3o[0]);
/* Afiseaza suma */
printf("Master[PID:%d]> Suma secventei de numere introduse este: %d\n",
getpid(), sump1+sump2);
}
/***********************************************************************/
void slave_work(int fdi, int fdo)
{
int nr, cod_r, suma_partiala;
suma_partiala=0;
/* citirea numerelor din pipe, pina intilneste EOF */
do
{
cod_r = read(fdi, &nr, sizeof(int));
switch(cod_r)
{
case sizeof(int) : suma_partiala += nr ; break;
case 0 : break; /* 0 inseamna EOF */
default : fprintf(stderr,"Eroare la citirea din canalul pipe%ci\n",
(fdi==pipe1i[0] ? ’1’ : ’2’) );
217
exit(3);
}
}while(cod_r != 0);
close(fdi);
/* scrierea sumei in pipe */
if( write(fdo, &suma_partiala, sizeof(int)) == -1)
{
perror("Eroare la scrierea in canalul pipe3o");
exit(4);
}
close(fdo);
/* mesaj informativ pe ecran */
printf("Slave%c[PID:%d]> Suma partiala: %d\n",
(fdi==pipe1i[0] ? ’1’ : ’2’), getpid(), suma_partiala);
}
/***********************************************************************/
Exercit¸iul 21din capitolul 5.
Rezolvare. aspunsul corect este a programul dat se blocheaz˘a la open an˘a apare un
consumator din fi¸sierul fifo “a.txt” creat, ¸si abia apoi copie intrarea standard ˆın fi¸sierul fifo.
Justificare: deoarece programul ˆıncearc˘a a deschid˘a doar cap˘atul de scriere al fifo-ului,
deci trebuie s˘a se sincronizeze cu un “cititor”.
Exercit¸iul 22din capitolul 5.
Rezolvare. Iat˘a sursa programului dimens fifo.c care determin˘a capacitatea unui canal
extern:
/*
File: dimens_fifo.c
*/
#include<stdio.h>
#include<fcntl.h>
#include<errno.h>
extern int errno;
int main(void)
{
int p,dimens;
char c=0;
/* creare fifo */
218
if( mkfifo("canal", 0666) == -1 )
{
if(errno == 17) // 17 = errno for "File exists"
{ fprintf(stdout,"Note: fifo ’canal’ exista deja !\n"); }
else
{ perror("Error creare fifo"); exit(1); }
}
/* setare non-blocking necesara pentru capatul de scriere */
p=open("canal",O_RDWR | O_NONBLOCK);
dimens=0;
while(1)
{
if( write(p,&c,1) == -1)
{
perror("Eroare - umplere fifo");
break;
}
else
{
++dimens;
if(dimens%1024 == 0) printf(" %d Ko ...\n", dimens/1024);
}
}
printf("Capacitatea fifo-ului este de %d octeti.\n", dimens);
return 0;
}
Exercit¸iul 24din capitolul 5.
Rezolvare. Iat˘a sursa programului suma fifo.c care rezolv˘a problema sumei distribuite
utilizˆınd canale externe pentru comunicat¸ie:
/*
File: suma_fifo.c
Problema suma distribuita de la lectia fork, rezolvata cu canale externe.
*/
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
extern int errno;
219
void master_init();
void master_work();
void slave_work(char* fi, char* fo);
int main()
{
int pid1,pid2;
printf("\n\n\n\n\n");
/* Crearea celor 3 fifo-uri fifo1i, fifo2i si fifo3o */
master_init();
/* Creare primul proces slave */
if( (pid1=fork()) == -1)
{
fprintf(stderr,"Error fork la fiul 1 !\n");
exit(2);
}
if(pid1 == 0)
{ /* sunt in procesul fiu 1 */
slave_work("fifo1i","fifo3o");
return 0;
/* sfirsit executie fiu 1 */
}
/* else sunt in procesul master, executat in paralel cu if-ul de mai sus */
/* Creare al doilea proces slave */
if( (pid2=fork()) == -1)
{
fprintf(stderr,"Error fork la fiul 2 !\n");
exit(2);
}
if(pid2 == 0)
{ /* sunt in procesul fiu 2 */
slave_work("fifo2i","fifo3o");
return 0;
/* sfirsit executie fiu 2 */
}
/* else sunt in procesul master, executat in paralel cu cei doi fii */
/* Citeste secventa de la tastatura si o transmite fiiilor, apoi
primeste de la ei cele 2 sume partiale si afiseaza suma lor. */
master_work();
return 0;
}
220
/***********************************************************************/
void master_init()
{
/* Crearea celor 3 fifo-uri fifo1i, fifo2i si fifo3o */
if( mkfifo("fifo1i", 0600) == -1 )
{
if(errno != 17) /* 17 == errno for "File exists" */
{
perror("Eroare la crearea fifo-ului fifo1i");
exit(1);
}
}
if( mkfifo("fifo2i", 0600) == -1 )
{
if(errno != 17)
{
perror("Eroare la crearea fifo-ului fifo2i");
exit(1);
}
}
if( mkfifo("fifo3o", 0600) == -1 )
{
if(errno != 17)
{
perror("Eroare la crearea fifo-ului fifo3o");
exit(1);
}
}
}
/***********************************************************************/
void master_work()
{
int fd1, fd2, fd3;
int nr, flag;
int sump1,sump2;
/* Putem citi numerele de la tastatura dupa crearea fiilor,
desi citirea de la tastatura poate dura oricit de mult (!),
deoarece sincronizarea necesara aici (fiul trebuie sa-si astepte
tatal sa scrie in fifo pentru a avea ce citi) este realizata prin
faptul ca citirea din fifo se face, implicit, in mod blocant. */
/* deschiderea celor doua fifo-uri de intrare
(prin care trimite numere la slaves) */
if( (fd1=open("fifo1i",O_WRONLY)) == -1)
221
{
perror("Eroare la deschiderea fifo-ului fifo1i\n");
exit(5);
}
if( (fd2=open("fifo2i",O_WRONLY)) == -1)
{
perror("Eroare la deschiderea fifo-ului fifo2i\n");
exit(5);
}
/* citirea secventei de numere si impartirea ei intre cele 2 fifo-uri */
printf("Introduceti numerele (0 pentru terminare):\n");
flag=1;
while(1)
{
scanf("%d", &nr);
if(nr == 0) break;
write( (flag==1?fd1:fd2), &nr, sizeof(int));
flag=3-flag;
}
close(fd1);
close(fd2);
/* Aici mai apare o sincronizare: master-ul trebuie sa citeasca
sumele partiale abia dupa ce acestea au fost calculate si scrise
in fifo-ul fifo3o de catre procesele slave.
Rezolvare: deschiderea in master (implicit, blocanta!) a fifo-ului
se va sincroniza cu deschiderea sa in primul din cei doi slaves.
Dar astfel poate fi pierduta suma de la celalalt slave
(scenariu posibil: primul slave inchide fifo-ul si masterul citeste
astfel EOF inainte ca al doilea slave sa apuce sa deschida fifo-ul).
De aceea vom folosi wait in master pentru a fi siguri ca s-a terminat
si al doilea slave.
*/
/* deschiderea fifo-ului de iesire (prin care primeste sumele de la slaves) */
if( (fd3=open("fifo3o",O_RDWR)) == -1)
{
perror("Eroare la deschiderea fifo-ului fifo3o\n");
exit(6);
}
/* Citeste prima suma partiala (sosita de la oricare din cei doi slaves) */
if( read(fd3, &sump1, sizeof(int)) != sizeof(int))
{
fprintf(stderr,"Eroare la prima citire din fifo-ul fifo3o\n");
exit(7);
}
/* Citeste a doua suma partiala.
222
Aici trebuie sa fim siguri ca si al doilea slave a apucat sa
deschida fifo-ul, caci altfel read-ul urmator va citi EOF.
Deci il vom astepta (cu 2 wait-uri):
wait(NULL);
wait(NULL);
Totusi, aceasta solutie nu este cea mai optima - ea functioneaza
numai in acest caz: trimiterea sumei de catre slave se face chiar
inainte de terminarea sa.
In cazul general (daca slave-ul ar mai fi avut apoi si altceva de facut),
solutia este deschiderea in master a fifo-ului fifo3o la ambele capete,
nu doar la cel de citire (cel de care avea nevoie masterul), ceea ce am
si facut in apelul open de mai sus (am folosit O_RDWR in loc de 0_RDONLY).
*/
if( read(fd3, &sump2, sizeof(int)) != sizeof(int))
{
fprintf(stderr,"Eroare la a doua citire din fifo-ul fifo3o\n");
exit(8);
}
close(fd3);
/* Afiseaza suma */
printf("Master[PID:%d]> Suma secventei de numere introduse este: %d\n",
getpid(), sump1+sump2);
}
/***********************************************************************/
void slave_work(char* fi, char* fo)
{
int fd1,fd2;
int nr, cod_r, suma_partiala;
/* deschiderea fifo-ului de intrare (prin care primeste numere de la master) */
if( (fd1=open(fi,O_RDONLY)) == -1)
{
fprintf(stderr,"Eroare la deschiderea fifo-ului %s\n",fi);
perror("");
exit(2);
}
suma_partiala=0;
/* citirea numerelor din fifo, pina intilneste EOF */
do
{
cod_r = read(fd1, &nr, sizeof(int));
switch(cod_r)
223
{
case sizeof(int) : suma_partiala += nr ; break;
case 0 : break; /* 0 inseamna EOF */
default : fprintf(stderr,"Eroare la citirea din fifo-ul %s\n",fi);
exit(3);
}
}while(cod_r != 0);
close(fd1);
/* deschiderea fifo-ului de iesire (prin care trimite suma la master) */
if( (fd2=open(fo,O_WRONLY)) == -1)
{
fprintf(stderr,"Eroare la deschiderea fifo-ului %s\n",fo);
perror("");
exit(4);
}
/* scrierea sumei in fifo */
write(fd2, &suma_partiala, sizeof(int));
close(fd2);
/* mesaj informativ pe ecran */
printf("Slave%c[PID:%d]> Suma partiala: %d\n",
(!strcmp(fi,"fifo1i") ? ’1’ : ’2’), getpid(), suma_partiala);
}
/***********************************************************************/
224

Navigation menu