|
SISTEMUL DE OPERARE LINUX SI APLICATII
1. SCURT ISTORIC
Anul 1965 poate fi considerat ca punct de pornire pentru sistemul de operare UNIX. Atunci a fost lansat la MIT (Massachusets Institute of Technology) un proiect de cercetare pentru crearea unui sistem de operare multiutilizator, interactiv, punandu-se la dispozitie putere de calcul si spatiu de memorie in cantitate mare. Acest sistem s-a numit MULTICS. Din el, Ken Thompson a creat in 1971 prima versiune UNIX. De atunci UNIX-ul a cunoscut o dezvoltare puternica in noi versiuni.
In 1987, profesorul Andrew Tanenbaum creeaza, la Vrije Universiteit din Amsterdam, un sistem de operare , asemanator cu UNIX-ul, cu scop didactic, pentru dezvoltare de aplicatii, numit MINIX.
Pornind de la MINIX, un tanar student finlandez, Linus Torvalds, a realizat in anul 1991 un sistem de operare mai complex, numit LINUX. Datorita faptului ca a fost inca de la inceput "free" , LINUX-ul a cunoscut o puternica dezvoltare, fiind sprijinit da majoritatea firmelor mari, cu exceptia, bineinteles, a MICROSOFT-ului.
In anul 1998 s-a format Free Standars Group in cadrul caruia exista initiative de standardizare pentru a incuraja dezvoltarea de programe si aplicatii in LINUX. Ceea ce a lipsit multa vreme LINUX-ului a fost un standard pentru ierarhia din sistemul de fisiere al programelor si aplicatiilor dar mai ales al bibliotecilor.
Lucrul cel mai important in LINUX este faptul ca intregul sistem de operare si o serie intreaga de operatii sunt puse la dispozitie, inclusiv sursele. Multe universitati si facultati de specialitate isi dezvolta propriile aplicatii in LINUX.
2. DISTRIBUTII IN LINUX
LINUX-ul este un sistem distribuit. Dintre cele trei specii de sisteme cu procesoare multiple si anume: multiprocesoare, multicomputere si sisteme distribuite, sistemele distribuite sunt sisteme slab cuplate, fiecare din noduri fiind un computer complet, cu un set complet de periferice si propriul sistem de operare.
Exista la ora actuala peste 300 de distributii in LINUX. Vom trece in revista cele mai importante si mai utilizate distributii.
2.1. Distributia SLACWARE
Creatorul acestei distributii este Patrick Volkerding, prima versiune fiind lansata de acesta in aprilie 1993. Aceasta distributie are doua prioritati de baza: usurinta folosirii si stabilitatea.
Instalarea se face in mod text, existand posibilitatea alegerii unui anumit KERNEL la bootare, desi cel implicat este suficient, in general, pentru o instalare normala.
Formatul standard al pachetelor SLACKWARE este .tgz si exista o serie de programe de instalarea, upgradarea si stergerea pachetelor, cum ar fi install pkg, upgrade pkgrd, care nu pot fi folosite decat direct din linia de comanda.
2.2. Distributia REDHAT
Este una din cele mai raspandite distributii. Contine un sistem de pachete RPM, sistem ce intretine o baza de date cu fisierele si pachetele din care provin.
Versiunea REDHAT contine codul sursa complet al sistemului de operare si al tuturor utilitarelor, cea mai mare parte a acestuia fiind scrisa in limbajul C.
La instalarea sistemului nu se permit decat partitii tip ext 2 si ext 3. Exista module de kernel si sisteme jurnalizate, ca JFS sau Reiser FS, care pot fi montate si dupa instalare. La instalare se poate configura un sistem FireWall cu trei variante de securitate, High, Medium si N.FireWall.
Exista o suta de aplicatii destinate configurarii in mod grafic a sistemului, aplicatii specifice atat interfetei GNOME cat si KDE.
2.3. Distributia DEBIAN
Este considerata cea mai sigura dintre toate distributiile existente. De aceasta distributie se ocupa o comunitate restransa de dezvoltatori, pachetele fiind foarte bine studiate si testate inainte de a fi lansate ca release-uri intr-o distributie. De obicei, aplicatiile si programele sunt cu cateva versiuni in urma celorlalte distributii. Nu exista, in general, o activitate comanda de promovare a acestei distributii, singurul suport fiind asigurat de o lista de discutii.
Procedura de instalare este una din cele mai dificile. Exista doua posibilitati de instalare:
a)simpla;
b)avansata.
In varianta simpla se aleg taskuri de instalare, fiecare task presupunand instalarea mai multor pachete reunite de acesta.
In cazul unei variante avansate, exista posibilitatea alegerii efective a fiecarui pachet in parte.
Meritul de baza al acestei distributii este acela ca este cea mai apropiata de comunitatea Open Source. Pachetele sunt de tipul deb si sunt administrate cu ajutorul unui utilitar numit ART(Advanced Package Tool).
2.4. Distributia MANDRAKE
Este o distributie care pune baza pe o mare internationalizare, fiind disponibile versiuni de instalare in 40 de limbi. Exista si o distributie in limba romana, in doua variante de manipulare a tastaturii, gwerty si qwertz.
MANDRAKE are un sistem de gestiune dual:
a)Linux Conf;
b)Drak Conf.
Linus Conf reprezinta mostenirea de la RedHat si permite modificarea setarilor obisnuite ale sistemului: gestiunea utilizatorilor, a serviciilor (dns, mail, rtp), configurarea placii de retea.
Drak Conf este un utilitar care se doreste a fi pentru LINUX ceea ce Control Panel reprezinta pentru WINDOWS. El permite configurarea sistemului de la partea hardware ce foloseste Hard Drake (placi de retea, video, sunet, tuner) pana la cele mai utilizate servicii (web rtp, dns, samba, NIS, firewall). Au fost introduse wizard-uri pentru toate serviciile ce pot fi setate grafic.
2.5. Distributia LYCORIS
Este o companie foarte tanara care si-a propus drept scop crearea unei versiuni Linux foarte usor manevrabila. Instalarea acestei distributii este cea mai simpla instalare dintre toate existente pana in prezent. Nu este permisa nici un fel de selectie a pachetelor, individuale sau in grup. Procesul de instalare incepe imediat dupa alegerea partitiilor, ruland in fundal, iar configurarile sistemului se realizeaza in paralel.
O aplicatie utila este Network Browser, realizata de Lycoris, care permite interconectarea simpla cu alte sisteme Linux sau Windows. Aceasta versiune de Linux se adreseaza utilizatorilor obisnuiti cu mediile de lucru Microsoft Windows. In acest sens, desktopul contine MyLinux System si Network Browser. Configurarea sistemului se realizeaza prin intermediul unui Control Panel care este de fapt KDE Control Center. Managerul sistemului de fisiere, Konqueror, reuneste aplicatiile executabile de catre Microsoft Windows si le ruleaza cu ajutorul programului Wine, cu care se pot chiar instala programe Windows.
2.6. Distributia SUSE
SUSE este distributia europeana (de fapt germana) cea mai cunoscuta si de succes. Se axeaza pe personalizarea sistemului in cat mai multe tari europene. Din pacate limba romana lipseste din aceasta disributie.
3. APLICATII LINUX
Vom prezenta in aceste aplicatii o serie de programe pe baza unor apeluri sistem sau a unor functii din LINUX. Mediul de programare este limbajul C.
Aplicatiile sunt structurate pe modelul unui laborator la disciplina Sisteme de operare, fiecare lucrare de laborator prezentand trei parti:
1) Consideratii teoretice asupra lucrarii.
2) Desfasurarea lucrarii, cu exemple de programe.
3) Tema pentru lucru individual, care, de obicei, propune crearea de diferite programe in contextul lucrarii respective.
3.1. Comenzi LINUX
1) Consideratii teoretice
Vom prezenta aici cateva din comenzile principale ale sistemului de operare LINUX, date pe linia de comanda. Aceste comenzi reprezinta interfete intre utilizator si sistemul de operare si ele sunt de fapt programe ce se lanseaza in executie cu ajutorul unui program numit interpretor de comenzi sau, in terminologia UNIX, numit shell.
2)Desfasurarea lucrarii
a) Comenzi pentru operatii asupra proceselor.
Listarea proceselor active in sistem.
Comanda ps (process status) furnizeaza informatii detaliate, in functie de optiunile afisate, despre procesele care apartin utilizatorului.
Comanda $ps, cu urmatorul exemplu de raspuns:
pid
TTY
STAT
TIME
COMMAND
3260
p3
R
0:00
bash
3452
p4
W
1:21
ps
4509
p3
Z
5:35
ps
5120
p9
S
8:55
bash
Prima coloana (pid) reprezinta identificatorul procesului.
A doua coloana (TTY) reprezinta terminalul de control la care este conectat procesul. Pot fi si adrese de ferestre sau terminale virtuale, cum este si in exemplul de fata ( valorile p3, p4, p9 sunt adrese de terminale virtuale).
A treia coloana reprezinta starea procesului.
R(Running)
in executie
S(Sleeping)
adormit pentru mai putin de 20 secunde
I(Idle)
inactiv, adormit pentru mai mult de 20 secunde
W(Swapped out)
scos afara din memoria principala si trecut pe hard disc
Z(Zombie)
terminat si asteapta ca parintele sa se termine
N(Nice)
proces cu prioritate redusa
A patra coloana (TIME) indica timpul de procesor folosit de proces pana in prezent.
A cincia coloana (COMMAND) listeaza numele programului executat de fiecare proces.
Dintre optiunile acestei comenzi amintim:
Optiunea$ps -u, cu urmatorul raspuns:
USER
pid
%CPU
MEM
VSZ
RSS
TTY
STAT
START
CMD.
500
3565
0.0
1.4
4848
1336
pts/0
S
19:15
bash
500
3597
0.0
0.7
3824
688
pts/0
R
19:37
ps-u
unde campurile noi reprezinta:
USER
Numele proprietarului fisierului
%CPU
Utilizarea procesorului de catre proces
%MEM
Procentele de memorie reala folosite de proces
START
Ora la care procesul a fost creat
RSS
Dimensiunea reala in memoria procesului(kB)
Optiunea $ps -l , cu urmatorul raspuns:
F
S
UID
PID
PPID
C
PPI
NI
ADDR
SZ
WCHAN
TTY
TIME
CMD
0
S
500
3565
3563
0
76
0
1212
pts/0
00:00:00
bash
0
R
500
3599
3565
0
78
0
1196
pts/0
00:00:00
ps
unde campurile noi reprezinta:
UID
Identificatorul numeri al proprietarului procesului
F
Fanioane care indica tipul de operatii executate de proces
PPID
Identificatorul procesului parinte
NI
Incrementul de planificare al proceului
SZ
Dimensiunea segmentelor de date al stivei
WCHAN
Evenimentul pe care procesul il asteapta
Optiunea $ps -e determina ca la fiecare comanda sa se afiseze atat argumentele cat si ambianta de executie.
Optiunea $ps -a determina afisarea de informatii si despre procesele altor utilizatori momentani conectati in sistem.
Listarea activitatilor diversilor utilizatori
Comanda $W, cu urmatorul raspuns:
USER
TTY
FROM
LOGING
IDLE
JLPU
PCPU
WHAT
Ion
0
7:13pm
0,00s
0,32s
jusr/bin/gnome
Ion
pts/0
0:0
7:15pm
0,00s
0,06s
0,01s
W
Listarea dinamica a proceselor din sistem
Comanda $top cu care se poate avea o imagine dinamica a proceselor din sistem si nu o imagine statica ca la comanda W. Perioada de actualizare este implicit de 5 secunde.
b) Comenzi pentru operatii generale asupra fisierelor si cataloagelor:
Comanda $pwd , pentru afisarea numelui catalogului curent.
Comanda $ls , pentru afisarea continutului unui catalog ; este echivalenta cu comanda DIR din MS-DOS. Cele mai des utilizate optiuni sunt $ls -l , $ls -al, $ls -li bin.
Comanda $cd , pentru schimbarea catalogului curent; nume catalog.
Comanda $rm , pentru stergerea unei intrari in catalog; nume catalog.
Comanda $cat , pentru listarea continutului unui fisier; nume fisier, cu optiunile cele mai frecvente $cat -n , (afiseaza numarul de ordine la fiecare linie din text) si$cat -v (afiseaza si caracterele netiparibile).
Comanda $cp , pentru copierea unui fisier; nume1, nume2.
Comanda mv, redenumirea unui fisier; sursa destinatie.
3) Tema
Sa se realizeze toate comenzile prezentate in aceasta lucrare, folosind un utilitar al LINUX-ului, de preferinta mc.
3.2. Crearea proceselor
1) Consideratii teoretice
Pentru crearea proceselor LINUX foloseste apelul sistem fork(). Ca urmare a acestui apel un proces parinte creeaza un proces fiu. Functia fork() returneaza o valoare dupa cum urmeaza:
-1,daca operatia nu s-a putut efectua, deci eroare;
0, in codul FIULUI;
PID FIU, in codul parintelui.
In urma unui fork procesul fiu, nou creat, va mosteni de la parinte atat codul cat si segmentele de date si stiva. In ceea ce priveste sincronizarea parintelui cu fiul, nu se poate spune care se va executa mai intai, fiul sau parintele. Se impun doua probleme majore:
a) sincronizarea parintelui cu fiul;
b) posibilitatea ca fiul sa execute alt cod decat parintele.
Pentru a rezolva aceste doua situatii se folosesc doua apeluri: wait() si exec().
Functia wait() rezolva sincronizarea fiului cu parintele . Este utilizata pentru asteptarea terminarii fiului de catre parinte. Daca punem un wait() in cadrul parintelui, se executa intai fiul si apoi parintele. Exista doua apeluri:
pid_t wait(int*status)
pid_twaitpid(pid_t pid, int*status, int flags)
Prima forma wait() este folosita pentru asteptarea terminarii fiului si preluarea valorii returnate de acesta. Parametrul status este utilizat pentru evaluarea valorii returnate, cu ajutorul catorva macro-uri definite special.
Functia waitpid() folosita intr-un proces va astepta un alt proces cu un pid dat.
Functia exec() are rolul de a face ca fiul sa execute alt cod decat parintele. Exista mai multe forme ale acestei functii : execvp, execle, execvl, execlp, execvp. De exemplu, pentru execvp avem sintaxa:
int execvp(const char*filename,const* char arg)
Prin aceasta functie fiul va executa, chiar de la creare fisierul cu nume filename.
Deoarece este prima aplicatie cu programe scrise si executate in limbajul C, prezentam mai jos etapele lansarii unui program in C, in sistemul de operare LINUX.
-Se editeaza un fisier sursa in limbajul C, utilizand un editor de texte, de exemplu vi, kate sau mc. Se numeste fisierul, de exemplu nume fisier.C
-Se compileaza fisierul editat in C cu comanda:
$gcc numefisier.c
-Se executa fisierul rezultat in urma compilarii.
$. /a.out
2)Desfasurarea lucrarii
Se vor executa urmatoarele programe:
-Programul a
#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
#include<sys/types.h>
main()
if(pid==0)
exit(1)}
else }}
Aceasta este o schema posibila de apelare a functiei fork().
Daca in urma executiei s-a terminat cu eroare, se va afisa "EROARE". Daca suntem in procesul fiu (pid = =0), atunci codul fiului inseamna scrierea a 25 cifre de 0, iar daca suntem in procesul parinte, se vor scrie 25 cifre de 1. Deoarece avem apelul wait in cadrul parintelui, intai se va executa fiul si apoi parintele. Pe ecran se va afisa:
am pornit copilul0000000000000000000000000
am pornit parintele 1111111111111111111111111
Rolul fflush(stdout) este de a scrie pe ecran, imediat ce bufferul stdout are un caracter in el. Fara fflush(stdout), momentul scrierii pe ecran este atunci cand stdout este plin. In acest caz trebuie sa stim cand se afiseaza pe ecran ceva. Daca acest program nu ar contine wait , atunci nu s-ar sti cine se executa primul si cine al doilea, fiul si parintele lucrand in paralel. In programul nostru, cand fiul tipareste numai 0 iar parintele numai 1, ar trebui ca, la o rulare, sa avem la printare o secventa de 0 ,1 amestecata.( Ex: 0011101010110.). Dar, daca rulam de multe ori acest program, constatam ca de fiecare data el va tipari mai intai 25 de 0 si apoi 25 de 1, ca si cum a exista wait-ul. Care este explicatia? Ea trebuie cautata in modul de rulare, in time sharing, in functie de cuanta de timp alocata fiecarui proces. Oricum ea este foarte mare in raport cu duratele proceselor parinte si fiu din exemplul nostru. De aceea, procesele fiu si parinte nu vor fi intrerupte din rularea in procesor, deoarece ele se termina amandoua intr-o singura cuanta.
-Programul b
#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
#include<sys/types.h>
main()
if(pid==0)
else ;
for (i=1;i<=25;i++)
}}
unde sorin1 este un fisier executabil ce se obtine prin compilarea fisierului sursa sorin1c astfel:
gcc -o sorin1 sorin1c
Fisierul sorin 1c are continutul:
#include<stdio.h>
main()
Se observa ca in acest program in codul fiului avem functia execlp care va inlocui codul parintelui prin codul dat de fisierul executabil sorin 1 care, de fapt, realizeaza acelasi lucru ca la programul anterior. Rezultatul rularii va fi:
0000000000000000000000000
am pornit parintele 1111111111111111111111111
Fata de rezultatul de la programul anterior, nu se mai executa printf("am pornit copilul"). Din ce cauza? Pentru ca atunci cand se executa fiul, printarea "am pornit copilul" este trecuta in bufferul stdout, insa cand se excuta execlp, bufferul se goleste si se executa strict numai fisierul sorin1.
Programul c
#unclude<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
main()
else }
In acest program fiul isi termina executia imediat iar parintele este intarziat cu 120 secunde. In acest timp fiul intra in starea zombie, pana cand procesul init(), care preia copiii orfani, va prelua rolul de parinte al fiului si il va scoate din starea zombie, eliberand tabela proceselor. Programul se compileaza cu:
gcc -o zombie numefisier.c
si se executa astfel :
./zombie &
Daca se lanseaza comanda :
ps -a
se poate observa cat timp sta fiul in starea zombie, pana il preia procesul init(),
3) Tema
Sa se scrie un program care sa creeze 10 procese. Fiecare proces va scrie o linie numai cu cifre; procesul 0 va scrie o linie de 0, procesul 1 va scrie o linie numai de 1... procesul 9 va scrie o linie numai de 9. Liniile vor trebui scrise in ordinea cifrelor, deci prima linie de 0 si ultima de 9.
3.3. Comunicare intre procese
3.3.1. Comunicarea intre procese prin
PIPE-uri si FIFO
1) Consideratii teoretice
a) Pipe-ul este un pseudofisier care serveste la comunicarea unidirectionala intre doua procese. Faptul ca este unidirectional a fost considerat ulterior ca una dintre limitele mecanismului si de aceea unele versiuni actuale au inlocuit pipe-ul unidirectional prin cel bidirectional. Astfel la SOLARIS pipe-urile sunt bidirectionale dar in Linux sunt unidirectionale. Aici, deci, vom considera pipe-urile unidirectionale.
O alta caracteristica a pipe-ului este faptul ca procesele care comunica intre ele trebuie sa aiba un grad de rudenie, de exemplu tata-fiu.
Un pipe este creat cu apelul:
int pipe (int file des[2]);
care creeaza in kernel un pipe accesibil in procesul apelant prin doi descriptori:
file des[0] deschis in citire
file des [1] deschis in scriere
In urma apelului pipe exista doua returnari:
0 , in caz de succes,
-1, in caz de eroare.
FILE DES[0] FILE DES[1]
Fig. 1. Schema unui pipe unidirectional.
Marea majoritate a aplicatiilor care utilizeaza pipe-urile inchid, in fiecare dintre procese, capatul de pipe neutilizat in comunicarea unidirectionala. Astfel, daca pipe-ul este utilizat pentru comunicatia parinte-fiu, atunci procesul parinte scrie in pipe iar procesul fiu citeste din pipe.
b)FIFO (pipe-uri cu nume)
Restrictia ca procesele care comunica sa fie inrudite este eliminata la pipe-urile cu nume care sunt fisiere de tip FIFO.
Apelul sistem pentru crearea unui FIFO este:
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char*pathname,mode_t mode);
Cele doua argumente ale apelului sunt:
char*pathname, ( numele FIFO-ului)
mode_t mode, (apare numai la deschidere, are loc si
crearea fisierului).
Dupa ce FIFO a fost creat, i se pot atasa toate operatiile tipice cu fisiere (open, read, write, unlink etc.).
2) Desfasurarea lucrarii
Programul a
Acesta este un program care transmite un text ("text prin pipe") prin pipe de la procesul parinte la procesul fiu.
#include<unistd.h>
#include<sys/tipes.h>
#define MAXLINE 500
main()
if((pid=fork())<0)
else
if(pid>0)
eose
exit(0);}
Program b
Programul foloseste doua pipe-uri, pipe1 si pipe2, incercand sa simuleze un pipe bidirectional. Se va observa ca, de fiecare data cand se executa o operatie la un capat al pipe-ului, celalalt capat este inchis.
#include<stdio.h>
main()
else
Program c
Acest program creaza un proces copil care citeste dintr-un pipe un set de caractere trimis de procesul parinte, convertind orice litera mica intr-o litera mare. Procesul parinte citeste sirul de caractere de la intrarea standard.
#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>
#include<unist.h>
#include<sys/wait.h>
int piped[2]; /*pipe-ul*/
int pid; /*pid-ul fiului*/
int c;/*caracterele citite*/
main()
if((pid=fork())<0)/*creare proces fiu*/
if (pid) /*sunt in procesul
parinte*/
/*se inchide descriptorul
de scriere in pipe*/
close(piped[1]);
if (wait(NULL)<0)
exit(4);}
else
close(piped[0]); /*se inchide descriptorul
de citire*/
exit(0);}}
Program d
In acest program se citesc date dintr-un fisier existent, specificat ca prim argument linie de text, si se trimit prin pipe comenzii sort ce are iesire redirectata intr-un fisier specificat ca argumentul al doilea.
#include<stdio.h>
#include<unistd.h>
int main(int argc,char*argv[])
Programul se compileaza cu
gcc -o pope pope.c
si se lanseaza
pope fisierintrare fisieriesire
Program e
Acest program este asemanator cu programul d numai ca de data aceasta nu mai comunica parintele cu fiul prin pipe ci doua procese prin FIFO.
#include<stdio.h>
#include<stdlib.h>
#define FIFO-FILE"MY FILO"
int main(int argc,char*argv[])
if((fp=fopen(FILO-FILE,"w"))=NULL)
fputs(argv[1],fp)
felse(fp);
return 0;}
Programul se compileaza cu
gcc/o fifoc
si se lanseaza in executie serverul cu
./fifos
Apoi, daca s-a lansat serverul in background, se da umatoarea comanda de la acelas terminal. Daca s-a lansat serverul in foreground, se da comanda de la alt terminal.
./fifoc "sir de curatire afisat la server"
3) Tema
a)Sa se creeza un pipe prin care procesul parinte va trimite procesului fiu numai "numerele rotunde" dintre cele citite de parinte de la tastatura. (Un numar rotund este numarul care, in baza 2, are numarul de cifre 0 egal cu numarul de cifre 1.
b) Sa se creeze un FIFO prin care un proces va trimite altui proces numai numere multiple de 11 dintre cele citite de la tastatura de primul proces.
3.3.2. Comunicarea intre procese prin semnale
1)Consideratii teoretice
Semnalele reprezinta unul dintre primele mecanisme de comunicare intre procese. Ele anunta aparitia unui eveniment. Semnalele pot fi trimise de catre un proces altui proces sau pot fi trimise de catre kernel. Momentul aparitiei unui semnal este neprecizat el aparand asincron.
Un semnal este reprezentat printr-un numar si un nume care se post vedea prin lansarea comenzii
kill -1
Se pot trimite semnale:
-cu comanda kill,
-in program cu apelul sistem kill(),
-cu anumite combinatii de chei de la tastatura,
-cand se indeplinesc anumite conditii: de exemplu eroare de virgula mobila (SIGFPE) sau referirea unei adrese din afara spatiului unui proces (SIGSEGV)
-sau prin kernel care poate semnaliza, de exemplu, prin SIGURG aparitia aut-of-band pe un soclu-socket.
Functia kill()
Trimite un semnal unui proces sau unui grup de procese.
int kill(pid_t pid,int sig);
Pentru ca un proces sa poata trimite un semnalaprocesului identificat prin pid, trebuie ca user ID-ul real sau efectiv al procesului care trimite semnalul sa se potriveasca cu ID-ul real sau set-user-ID salvat al procesului care receptioneaza semnalul.
-Daca pid>0 semnalul se trimite tuturor procesului pid;
-daca pid==0 semnalul se trimite tuturor proceselor care fac parte din acelasi grup de procese cu procesul care trimite semnalul, daca exista permisiunile necesare;
-daca pid==-1 semnalul se trimite tuturor proceselor (cu exceptia unui set nespecificat de procese sistem), daca exista permisiunile necesare;
-daca pid<0&&pid!=-1, semnalul se trimite tuturor proceselor care fac parte din grupul de procese al carui pgid este egal cu modulul valorii primului argument, daca exista permisiunile necesare;
-daca al doilea argument este 0, nu se trimite nici un semnal; se testeaza existenta procesului specificat in primul argument.
Exista doua moduri de lucru cu semnalele:
A) folosind standardul initial (stil vechi, nerecomandat);
B) folosind noul stil.
In ambele situatii, pentru procesul care receptioneaza un semnal, putem seta trei tipuri de actiuni:
-actiunea implicita, reprezentata prin pointerul la functie SIG_DEL
-actiunea de a ignora semnalul receptionat, reprezentata prin pointerul la functie SIG_IGN;
-actiunea precizata printr-o functie, numita handler, reprezentata printr-un pointer la functie (numele functiei este adresa ei.
A)Standardul vechi
In vechiul stil, pentru a seta o actiune corespunzatoare unui semnal foloseam functia signal() al carui prototip era:
void(*signal(int sig,void(*handler)(int)))(int);
glibc foloseste pentru handlere tipul sig_t.
Mai exista extensia GNU:
typedef void(*sighandler_t handler)(int);
sighandler_t signal(int signum,
sighandler_t handler);
B) Standardul Posix
Cele trei cazuri raman si aici valabile.
Putem sa specificam un handler pentru semnal cand actiunea este de tipul captare de semnal:
void handler(int signo);
Doua semnale nu pot fi captate (nu putem scrie handlere pentru ele): SIGKILL si SIGSTOP.
Putem ignora un semnal prin setarea actiunii la SIG_IN. Pentru SIGKILL si SIGSTOP nu se poate acest lucru.
Putem seta o actiune implicita prin folosirea lui SIG_IN. Actiunea implicita inseamna, pentru majoritatea semnalelor, terminarea unui proces. Doua semnale au actiunea implicita sa fie ignorate: SIGCHLD ce este trimis parintelui cand un copil a terminat si SIGURG la sosirea unor date aut-of-band.
Functia sigaction()
Pentru a seta actiunea corespunzatoare unui semnal, in loc de functia signal() vom folosi functia sigaction().Pentru aceasta trebuie sa alocam o structura de tipul sigaction
typedef void(*sighandler_t)(int signo)
struct sigaction
;
Cateva flaguri:
SA_NOCLDSTOP - un semnal SIGCHLD este trimis parintelui unui proces cand un copil de-al sau a terminat sau e oprit. Daca specificam acest flag, semnalulSIGCHLDva fi trimis numai la terminarea unui proces copil.
SA_ONESHOT -imediat ce handlerul pentru acest semnal este rulat, kernelul va reseta actiunea pentru acestsemnal la SIG_DEL.
SA_RESTART - apelurile sistem "lente" care returnau cu eroarea EINTR vor fi restartate automat fara sa mai returneze.
Prototipul functiei sigaction() este
int sigaction(int signum,struct sigaction act,
struct sigaction oact);
Unde signum este semnalul a carui livrare urmeza sa fie setata, prima structura sigaction act contine setarile pe care kernelul le va utiliza cu privire la semnalul signum, iar a doua structura oact memoreaza vechile setari (pentru a fi setate ulterior); se poate specifica NULL pentru ultimul argument daca nu ne intereseaza restaurarea.
Alte functii utilizate
Tipul de date pe care se bazeaza functiile pe care le vom prezenta este sig_set si reprezinta un set de semnale. Cu acest tip putem pasa usor o lista de semnale kernelului.
Un semnal poate apartine sau nu unui set de semnale.
Vom opera asupra unui obiect sig_set numai cu ajutorul urmatoarelor functii:
int sigempzyset(sigset_t*set);
int sigfillset(sigset_t*set);
int sigaddset(sigset_t*set,int signo);
int sigdelset(sigset_t*set,int signo);
int sigismember(const sigset_t*set, int signo)
Observam ca primul argument este un pointer la setul de semnale. sigempzyset() scoate toate semnalele din set, iar sigfillset() adauga toate semnalele setului. Trebuie neaparat sa folosim una din cele doua functii pentru a initializa setul de semnale. sigaddset() adauga un semnal setului iar sigdelset() scoate un semnal din set.
Un concept important referitor la procese il reprezinta masca de semnale corespunzatoare procesului. Aceasta precizeaza care semnale sunt blocate si nu vor fi livrate procesului respectiv; daca un astfel de semnal este trimis, kernelul amana livrarea lui pana cand procesul deblocheaza acel semnal. Pentru a modifica masca de semnale se utilizeaza:
int sigprocmask(int how,const sigset_t*modset,
sigset_t*oldset);
how poate fi:
SIG_BLOCK - semnalele continute in modset vor fi adaugate mastii curente si semnalele respective vor fi si ele blocate.
SIG_UNBLOCK - semnalele continute in modset vor fi scoase din masca curenta de semnale.
SIG_SETMASK - masca de semnale va avea exact acelasi continut cu modset.
Cand un semnal nu poate fi livrat deoarec eeste blocat, spunem ca semnalul respectiv este in asteptare. Un proces poate afla care semnale sunt in asteptare cu:
int sigpending(sigset_t*set);
In variabila set vom avea toate semnalele care asteapta sa fie livrate dar nu sunt, deoarece sunt blocate.
Un proces poate sa-si suspende executia simultan cu schimbarea mastii de semnale pe timpul executiei acestui apel sistem prin utilizarea lui:
int sigsuspend(const segset_t*mask);
Este scos din aceasta stare de oricare semnal a carui actiune este precizata printr-un handler sau a carui actiune este sa termine procesul. In primul caz, dupa executia handlerului se revine la masca de semnale de dinaintea lui sigsuspend() iar in al doilea caz (cand actiunea e sa termine procesul) functia sigsuspend() nu mai returneaza. Daca masca este specificata ca NULL, atunci va fi lasata nemodificata.
Concluzii pentru semnalele Posix:
-un semnal instalat ramane instalat-(vechiul stil dezactiva handlerul);
-in timpul executiei handlerului, semnalul respectiv ramane blocat; in plus, si semnalele specificate in membrul sa_mask al structurii sigaction sunt blocate;
-daca un semnal este transmis de mai multe ori cand semnalul este blocat, atunci va fi livrat numai odata, dupa ce semnalul va fi deblocat;
-semnalele sunt puse intr-o coada.
Semnale de timp real
Modelul de semnale implementat in UNIX in 1978 nu era sigur. In decursul timpului au fost aduse numeroase imbunatatiri si in final s-a ajuns la un model Posix de timp real.
Vom incepe cu definitia structurii sigval
union sigval ;
Semnalele pot fi impartite in doua categorii:
1) semnale realtime ale caror valori sunt cuprinse intre SIGRTMIN si SIRTMAX (vezi cu $kill_1);
2) restul semnalelor.
Pentru a avea certitudinea comportarii corecte a semnalelor de timp real va trebui sa specificam pentru membrul sa_flags al structurii sigaction valoarea SA_SIGINFO si sa folosim unul din semnalele cuprinse intre SIGRTMIN si SIGRTMAX.
Ce inseamna semnale de timp real?
Putem enumera urmatoarele caracteristici:
1)FIFO - semnalele nu se pierd; daca sunt generate de un numar de ori, de acelasi numar de ori vor fi livrate;
2)PRIORITATI - cand avem mai multe semnale neblocate, intre limitele SIGRTMIN si SIRTMAX, cele cu numere mai mici sunt livrate inaintea celor cu numere mari (SIGRTMIN are prioritate mai mare decat SIRTMIN+1;
3)Comunica mai multa informatie - pentru semnalele obisnuite singurul argument pasat era numarul semnalului; cele de tipul real pot comunica mai multa informatie.
Prototipul functiei handler este:
void func(int signo,siginfo_t*info,void*context);
unde signo este numarul semnalului iar structura siginfo_t este definita:
typedef structsiginfo_t;
SI_ASYNCIO insemna ca semnalul a fost trimis la terminarea unei cereri I/O asincrone.
SI_MESGQ inseamna ca semnalul a fost trimis la plasarea unui mesaj intr-o coada de mesaje goale.
SI_QUEUE inseamna ca mesajul a fost trimis cu functia sigqueue().
SI_TIMER semnal generat la expirarea unui timer.
SI_USER semnalul a fost trimis cu functia kill().
In afara de functia kill() mai putem trimite semnale cu functia sigqueue(), functie care ne va permite sa trimitem o union sigval impreuna cu semnalul.
Pentru SI_USER nu mai putem conta pe si_value. Trebuiesc si :
act.sa_sigachon=func;//pointer k functie handler
act.sa_flags=SA-SIGINFO;//rest time
2) Desfasurarea lucrarii
Program a
/*Compilam programul cu
$gcc -o semnal semnal.c
si dupa lansare observam de cate ori a fost chemat handlerul actiune, in doua cazuri:
-lasam programul sa se termine fara sa mai trimitem alte semnale;
-lansam programul in fundal si trimitem mai multe comenzi prin lansarea repetata:
kill -10 pid
Ar trebui sa gasim valoarea:
10000+nr-de-killuri */
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
sig_atomic_t nsigusrl=0;
void actiune(int signal_number
int main()
for(i=0;i<20;i++)sleep(3);
printf("SIGUSR1 a aparut de %d orin",
nsigusfl1);
return 0;
Program b
#include<signal.h>
/*vom compila programul cu
gcc-o ter1 ter1
si vom putea lansa programul in fundal cu
ter1 &
Vom citi pid-ul copilului si-i vom putea trimite un semnal
cu
kill-10pid-copil
si vom vedea starea de iesire (corespunzatoare semnalului trimis). La o alta lansare in foreground, lasam programul sa se termine si observam starea de iesire. Sa se remarce utilizarea handlerului curatare-copil care este instalat sa trateze terminarea unui copil. Aceasta abordare permite ca parintele sa nu fie blocat intr-un wait() in asteptarea starii de iesire a copilului*/
#include<>
#include<>
#include<>
sig_atomic_t copil_stare_exit;
void curatare_copil(int signal_number)
int main()
else
for(i=0;i<10000000;i++);
if(WIFEXISTED(copil_stare_exit))
else
prinf("copilul a iesit anormal cu semnalul
%dn",WTERSIG(copil_stare_exit));
return 0;
Program c
/*Programul vrea sa testeze comportarea real time a semnalelor din intervalul SIGRTMIN-SIGRTMAX.
Dupa fork() copilul blocheaza receptia celor trei semnale. Parintele trimite apoi cate trei salve purtand informatie (pentru a verifica ordinea la receptie), pentru fiecare semnal, incepand cu semnalul cel mai putin prioritar. Apoi copilul deblocheaza cele trei semnale si vom putea vedea cate semnale si in ce ordine au fost primite.
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
typedef void sigfunc_rt(int,siginfo_t*,void*);
static void sig_rt(int,siginfo_t*,void*);
sigfunc_rt*signal_rt(int,sigfunc_rt*,sigset*);
int main()
//in parinte
sleep(3);//lasa copilul sa blocheze semnalele
for(i=SIGRTMIN+2;i>=SIGRTMIN;i--)
}
exit(0);
static void sig_rt(int signo,siginfo_t*info,
void*context)
/*vom seta structura sigaction si apoi vom apela functia sigaction() pentru a comunica kernelului comportamentul dorit*/
sigfunc_rt*signal_rt(int signo,
sigfunc_rt*func,sigset_t*mask)
else
if(sigaction(signo,&act,&oact)<0)
return((sigfunc.rt*)SIG_ERR);
return(oact.sa_sigaction);}
3) Tema
Sa se scrie un program care sterge in 10 minute, toate fisierele temporare, (cu extensie.bak), din directorul curent.
3.3.3. Comunicatie intre procese prin sistem V IPC.
Cozi de mesaje
Mecanismele de comunicare intre procese prin sistem V IPC sunt de trei tipuri:
-cozi de mesaje;
-semafoare;
-memorie partajata.
Pentru a genera cheile necesare obtinerii identificatorilor, care vor fi folositi in functiile de control si operare, se utilizeaza functia ftok()
#include<sys/ipc.h>
key_t ftok(const char*pathname,int id);
unde tipul de data keyt_t este definit in <sys/types.h>
Functia foloseste informatii din sistemul de fisiere pornind de la pathname, numarul i-node si din LSB-ul lui id. Pathname nu trebuie sters si recreat intre folosiri deoarece se poate schimba i-node-ul. Teoretic nu este garantat ca folosind doua pathname-uri diferite si acelasi id vom obtine doua chei de 32 biti diferite. In general, se convine asupra unui pathname unic intre clienti si server si daca sunt necesare mai multe calale se utilizeaza mai multe id-uri.
Structura ipc-perm
O structura insoteste fiecare obiect IPC
struct ipc_perm
uid si gid sunt id-urile proprietar, cuid si cgid sunt id-urile creator ce vor ramane neschimbate, mode contine permisiunile read-write, seq un numar de secventa care ne asigura ca nu vor fi folosite de procese diferite din intamplare si, in final,cheia key.
Pentru a obtine identificatorul care va fi utilizat in functia de control si operare cu ajutorul functiilor _get(), putem folosi ca prim argument o valoare intoarsa de functia ftok() sau valoarea speciala IPC- PRIVATE (in acest caz avem certitudinea ca un obiect IPC nou si unic a fost creat, deoarece nici o combinatie de pathname si id nu va genera valoarea 0, datorita faptului ca numarul de i-nod este mai mare ca zero).
Se pot specifia in oflag IPC_CREAT,cans de va crea o noua intrare, corespunzator cheii specificate daca nu exista sau IPC_CREAT|IPC_EXCL cand,la fel ca mai sus, se va crea o noua cheie daca nu exista sau va returna eroarea EEXIST daca deja exista. Daca serverul a creat obiectul, clientii pot sa nu specifice nimic.
Permisiuni IPC
In momentul creerii unui obiect IPC cu una din functiile _get() urmatoarele informatii sunt salvate in structura ipc_perm
permisiunile dec read sau/si write pentru user, grup si altii:
0400 read de catre user
0200 write de catre user
0040 read de catre grup
0020 write de catre grup
0004 read de catre altii
0002 write de catre altii
-cuid si cgid (id-urile creator) sunt setati la uid-ul efectiv si gid-ul efectiv al procesului chemator (acestia nu se pot schimba
-uid si gid sunt setati la fel ca mai sus; se numesc uid si gid proprietar; se pot schimba prin apelul unei functii de control ctl( ) cu argument IPC_SET.
Verificarea permisiunilor se face atat la deschiderea obiectului cu _get( ) cat si de fiecare data cand un obiect IPC este folosit. Daca un obiect, la deschidere, are precizat pentru membrul mode sa nu aiba drept de read grupul si altii si un client, si un client foloseste un oflag ce include acesti biti, va obtine o eroare chiar la _get( ). Aceasta eroare s-ar putea ocoli prin precizarea unui flag 0, dar de aceea se vor verifica la orice operatie permisiunile.
Iata ordinea testelor-la prima potrivire se acorda accesul:
-superuserului i se permite accesul;
-daca uid-ul efectiv este egal cu uid-ul sau cuid-ul obiectului IPC si daca bitul corespunzator din membrul mode este setat, se permite accesul;
-daca gid-ul efectiv este egal cu cu gid-ul sau cgid-ul obiectului IPC si bitul corespunzator din membrul mode al obiectului este setat, accesul este permis;
-daca bitul corespunzator din membrul mode al obiectului IPC este setat, se permite accesul.
Comenzi de vizualizare IPC
ipcs, pentru a vedea informatii pentru fiecare IPC
Pentru a sterge IPC-uri din sistem folosim:
ipcrm -q msg_id (pentru cozi)
ipcrm -m shm_id (pentru memorie partajata)
ipcrm -s sem_id (pentru semafoare)
Exista si o sintaxa cu aceleasi optiuni dar cu litere mari unde se specifica ca ultim argument cheia.
Cozi de mesaje
Un proces cu privilegiile corespunzatoare si folosind identificatorul cozii de mesaje poate plasa mesaje in ea, dupa cum un proces cu privilegiile corespunzatoare poate citi mesajele. Nu este necesar (la fel ca la POSIX) ca un proces sa astepte mesaje inainte ca sa plasam mesaje in coada.
Kernelul pastreaza informatiile pentru o coada intr-o structura definita in <sys/msg.h> ce contine :
struct msgid_ds ;
Primul membru reprezinta tipul mesajului receptionat. mtext este textul mesajului. Argumentul msgsz specifica lungime in bytes a componentei mtext. Mesajul receptionat va fi trunchiat la lungimea msgsz daca in cadrul flagurilor msgflg precizam MSG_NOERROR. Altfel functia msgrcv() va returna eroarea E2BIG.
Argumentul msgtype determina politica la receptie astfel:
-daca = = 0, primul mesaj din coada este solicitat;
-daca = = n>0, primul mesaj de tipul n este solicitat;
-daca = = n<0, primul mesaj al carui tip este mai mic sau egal cu valoarea absoluta a lui msgtyp va fi solicitat.
Argumentul msgflg precizeaza cum sa se procedeze daca tipul dorit nu este in coada:
-daca msgflg contine si IPC_NOWAIT, functia msgrcv() va returna imediat cu eroarea ENOMSG; astfel se intra in sleep pana cand:
-un mesaj de tipul dorit este disponibil in coada;
-coada careia ii solicitam un mesaj este distrusa de altcineva, astfel ca msgrcv() returneaza eroarea EIDRM
-sleepul este intrerupt de un semnal.
Daca receptionarea s-a efectuat cu succes, structura informationala asociata cu msqid este actualizata astfel:
msg_qnum va fi decrementat cu 1;
msg_lrpid va fi setat la pid-ul procesului apelant;
msg_rtime este setat la timpul curent.
La Linux prototipul functiei este:
Ssize_t msgrcv,(int msqid,struct msgbuf*msgp,
Ssize_t msgsz,long msgtyp,int msgflg);
Functia msgctl
Prototipul functiei este;
int msgctl(int msqid,int cmd,struct msqid_ds*buf);
Sunt permise urmatoarele comenzi:
IPC_STA va copia informatiile din structura informationala asociata cu msqid in structura indicata prin pointerul buf, daca avem dreptul de read asupra cozii;
IPC_SET va scrie unii membri din structura indicata prin pointerul buf in structura de date informationala a cozii; membrii care pot fi modificati sunt:
msg_perm.uid
msg_perm.gid
msg_perm.mode //numai LSB 9 biti
msg_qbytes
Aceasta actualizare se va efectua daca procesul apelant are privilegiile necesare: root sau user id-ul efectiv al procesului este cel al msg_perm.cuid sau msg_perm.uid.La Linux pentru a mari msg_qbytes peste valoarea sistem MSGMNB trebuie sa fim root. Dupa o operatie de control se va actualiza si msg_ctime.
IPC_RMID va distruge coada al carei identificator a fost specificat in msgctl() impreuna cu structura de date informationala msqid_ds asociata. Aceasta comanda va putea fi executata de un proces cu user id-ul efectiv egal cu cel msg_perm.cuid sau msg_perm.uid.
Erori:
EINVAL - msqid gresit sau comanda gresita:
EIDRM - coada deja distrusa;
EPERM - comanda IPC_SET sau IPC_RMID dar procesul apelant nu are drepturile necesare;
EACCES - comanda IPC_STAT dar procesul apelant nu are dreptul de read
EFAULT - comanda IPC_SET sau IPC_STAT dar adresa specificata de pointerul buf nu este accesibila.
2) Desfasurarea lucrarii
Program a
*scriere-q.c--scriu mesaj in coada
se compileaza cu
gcc -o scriere - q scriere - q.c
si se lanseaza in executie
scriere_q nr-nivel
se introduc linii de text.
Se porneste citirea cu
gcc citire_q nr_nivel
Sa se incerce citirea cu niveluri diferite*/
#include<stdio.h>
#include<errno.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include <sys/stat.h>
struct my_buf;
int main(int argc,char*argv[])
if((key=ftok("scriere-q.c",'L'))==(key_t)-1)
if((msqid=msgget(key,S_IRUSR|S_IWUSR|S_IRGRP|S
_IROTH|IPC_CREAT))==-1)
printf)"introduceti linii de text,^D pt.
terminare:n");
buf.mtype=atoi(argv[1];/*nu ne intereseaza
acum*/
while(gets(buf.mytext),!feof(stdin))
if(msgctl(msqid,IPC_RMID,NULL)==-1)
return 0;
Program b
citire-q.c- citeste coada
Se lanseaza scrierea:
$./scriere-q nivel
se incepe introducerea liniilor de test.
Se iese cu ^D.
Sa se lanseze citirile cu niveluri aleatoare
$./citire-q nivel
#include<errno.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/stat.h>
struct my_buf ;
int main(int argc,char*argv[])
if((key=ftok("scriere-q.c",'L'))==(key_t)-1
if((msqid=msgget(key,S_IRUSR|S_IWUSR|S_IRGRP|S
IROTH))==-1
printf("citire mesaj:sunt gata pentru receptie
mesaje...n");
while(1)
printf("citire-mesaj:"%s"n",buf.mytext);
return 0;
3)Tema
Sa se creeze doua procese numite "client" si "server" in care clientul va introduce mesaje in sir iar serverul va extrage mesajul de prioritate maxima.
3.3.4. Comunicatie intre procese prin sistem V IPC.
Semafoare
Asa cum am aratat in capitolele anterioare, semaforul a fost inventat de Edsger Dijkstra, ca obiect de sincronizare a proceselor. Implementarea din Linux este bazata pe acest concept dar ofera facilitati mai generale.
Exista o implementare in SVR4, foarte complexa, cu urmatoarele caracteristici:
-semafoarele nu exista individual ci numai in seturi, numarul semafoarelor dintr-un set fiind definit la crearea setului;
-crearea si initializarea sunt doua operatii separate si distincte; crearea se face prin apelul semget iar initializarea prin apelul semet1;
-deoarece semafoarele raman in sistemul de operare dupa terminarea proceselor care le utilizeaza, ca si la celelate structuri IPC, trebuie gasita o solutie de tratare a situatiilor in care un program se termina fara a elibera semafoarele alocate.
Forma structurilor semafoarelor este:
struct semid_ds
La randul sau, campul sem_base are urmatoarele structuri:
struct sem
Apelul sistem de creare a semafoarelor este:
#include<sys/tipes.h>
#include<sys/ipc.h>
#include<sys/sem.h>
int semget(key_t key,int nsems, int flag);
/*unde nsems reprezinta numarul de semafoare din set; valoarea lui se pune la crearea semaforului*/
Apelul sistem pentru initializare este:
int semcti(int semid,int semnum,intcmd,union semnum arg);
unde parametrii reprezinta:
semnum indica un semafor din set pentru functiile cmd
union semnum arg are urmatoarea structura:
union sem num
cmd specifica 10 functii de executat asupra setului identificat de semid.
2) Desfasurarea lucrarii
Program a Este un program care implementeaza primele semafoare binare, inventate de Dijkstra, pentru niste procese care intra in sectiunea critica.
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<errno.h>
#define SEMPERM 0100
#define TRUE 1
#define FALSE 0
typedef union_semun
semun;
int init_sem(key_t semkey)
else
if(semid==-1|| status==-1)
return semid;}
/*implementarea operatiei p(s)*/
int p(int semid)
return 0;}
/*implementarea operatiei v(s)*/
int v(int semid)
return 0;}
/*procese concurente ce folosesc semaforul*/
int semid;
pid_t pid=getpid();
if((semid=init_sem(skey))<0) exit(1);
printf("proces %d inaintea sectiunii
criticen",pid);
p(semid);printf("procesul %d in sectiune
critican", pid);
sleep(5);/*se desfasoara operatiile critice*/
printf("procesul %d paraseste sectiunea
critican",pid);
v(semid); printf("procesul %d iesen",pid);
exit(0);}
/*programul principal*/
main()
3)Tema
Sa se implementeze problema producator-consumator folosind semafoarele.
3.3.5.Comunicatia intre procese prin sistem V IPC.
Memorie partajata
1) Consideratii teoretice
Mecanismul care permite comunicarea intre doua sau mai multe procese folosind o zona comuna de memorie este folosit frecvent de multiprocesoare. Pentru a utiliza memoria partajata este nevoie de sincronizarea proceselor, deci de utilizarea excluderii mutuale. Implementarea excluderii mutuale se poate face cu obiecte de sincronizare, cel mai adesea cu semafoare.
Structura pentru evidenta segmentelor de memorie este:
struct shmid_ds
Pentru a obtine un identificator de memorie partajata se utilizeaza apelul shmget
#include<sys/tipes.h>
#include<sys/ipc.h>
#include<sys/shm.h>
int shmget(key-t key,int size,int flag);
2) Desfasurarea lucrarii
Program a
#include<stdio.h>
#include<signal.h>
#include<sys/tipes.h>
#include<sys/ipc>
#include<sys/sem.h>
#define SHMKEY1(key_t)0x100 /*cheia primului
segment*/
#define SHMKEY2 (key_t) 0x1AA /*cheia pentru al
doilea segment*/
#define SEMKEY (key_t) 0x100 /*cheie pentru
semafoare*/
#define SIZ 5*BUFSIZ/*dimensiunea segmentului
BUFSIZ*/
struct databuf
typedef union_semun
semun;
/*rutinele de initializare*/
#define IFLAGS(IPC_CREAT|IPC_EVCL)
#define ERR ((struct databuf*)-1)
static int shmid1,shmid2,semid;
/*se creeaza segmentele de memorie partajata*/
void getseg(struct databuf**p1,struct databuf p2)
/*ataseaza segmentele de memorie*/
if((*p1=(struct databuf*)shmat(shmid1,0,0))==ERR)
if((*p2=(struct databuf*)shmat(shmid2,0,0))==ERR)
}
int getsem(void)
/*se initializeaza valorile semafoarelor*/
if(semct1(semid,0,SETVAL,x)==-1)
return semid;}
/*rutina pentru stergerea identificatorilor
memoriei partajate si semafoarelor*/
void remobj(void)
{if(shmct1(shmid1,IPC-RMID,NULL)==-1
/*definitie pentru operatiile p() si v() pe
cele doua semafoare*/
struct sembuf p1=,p2=;
struct sembuf v1=, v2=;
/*rutina de citire*/
void reader(int semid,struct databuf*buf1,
struct databuf*buf2)
/*program principal*/
main()
exit(0);}
3.3.6. Comunicatia intre fire de executie
1) Consideratii teoretice
In capitolul 3 am definit firele de executie si motivele din care au fost introduse. Firele de executie (thread-urile) pot fi considerate ca niste subunitati ale proceselor.
Crearea unui fir de executie se face prin comanda:
#include<pthreads.h>
int pthread_create(pthread_t*thread,const pthread_
attrt*attr,void*(start_routine)(void*),void arg);
Firul de executie nou creat va executa codul din start_routine caruia i se transmit argumentele arg.
Noul fir de executie are atributele transmise prin attr, iar daca ele sunt implicite se utilizeaza NULL. Daca functia se executa cu succes ea va returna 0 si in thread se va pune identificatorul nou creat.
Terminarea executiei unui fir de asteptare se specifica prin apelul functiei pthread.
#include<pthread.h>
void pthread_exit(void*status);
O alta proprietate a unui fir de executie este detasarea. Firele de executie detasate elibereaza, in momentul terminarii lor, memoria pe care au detinut-o, astfel ca alte fire nu se pot sincroniza cu fire detasate. Implicit, firele sunt create cu atributul joinable, ceea ce face ca alte fire sa poata specifica ca asteapta terminarea unui astfel de fir.
#include<pthread.h>
int pthread_join(pthread_t thread,void status);
2) Desfasurarea lucrarii
Program a
#include<stdio.h>
#include<pthread.h>
int global=5;
void*copilfunctie(void*p)
main()
pthread_t copil;
pthread_create(&copil,NULL,copilfunctie,NULL);
printf("parinte,pid=%d,global=%dn",getpid(),
global);
global=10;
pthread_join(copil,NULL);
printf("nucopil,global=%dn",global);}
Programul se va compila astfel
$gcc -D-REENTRANT -o sorin sorin.c -epthread
unde sorin.c este fisierul sursa iar constanta REENTRANT specifica executia in paralel a fiilor.
Un posibil raspuns ar fi:
copil aici,pid=3680,global=5
copil,global acum 15
parinte,pid=3680,global=15
nu copil,global=10
3) Tema
Sa se creeze trei fire de executie in care:
-primul fir calculeaza media aritmetica a n numere citite,
-al doilea fir calculeaza media geometrica a n numere citite,
-al treilea fir calculeaza media armonica a n numere citite.
(n si numerele se citesc de la tastatura) . Apoi sa se comparerezultatele.
3.3.7. Interfata SOCKET
1) Consideratii teoretice
Interfata SOCKET reprezinta o facilitate generala de comunicare a proceselor aflata, in general, pe masini diferite.
Un SOCKET poate avea tipuri diferite si poate fi asociat cu unul sau mai multe procese, existand in cadrul unui domeniu de comunicatie. Datele pot fi schimbate numai intre SOCKET-uri apartinand aceluiasi domeniu de comunicatie.
Exista doua primitive pentru SOCKET-uri.
Prima primitiva
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,intprotocol)
int domain este un parametru ce stabileste formatu adreselor masinilor implicate in transferul de date. Uzual aceste domenii sunt:
AF-UNIX, care stabileste domeniile de comunicare locala (UNIX);
AF-INET, care foloseste protocolul TCP/IP si este utilizat in INTERNET.
int type se refera la modalitatile de realizare a comunicarii. Cele mai utilizate tipuri sunt:
SOCK-STREAM, in care un flux de date se transmite intr-o comunicare de tip full-duplex;
SOCK-DGRAM, in care se stabileste o comunicare fara conexiune cu utilizarea datagramelor.
int protocol specifica protocolul particular utilizat pentru transmisia datelor. De obicei se utilizeaza valoarea 0(zero).
A doua primitiva este SOCKETPAIR()
Aceasta se utilizeaza pentru crearea unei perechi de SOCKET-uri conectate.
#include<sys/types.h>
#include<sys/socket>
int socketpair(int domain,int type,int protocol,
int SV[2];
Primele trei argumente sunt la fel ca la socket iar cel de-al patrulea argument SV[2] este la fel ca la pipe.
2)Desfasurarea lucrarii
Program a
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdio.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
main()
/*crearea fiului*/
switch(fork())
prinf("procesul cu pid-ul%d(fiu)a primit%sn",
getpid, tampon);
/*scriem mesajul copilului*/
if(write(sd[1],c,100)<0)
/*trimitem EoF*/
else sd[1];
exit(0);
default :/*parinte*/
/*trimitem mesajulparintelui*/
if(writw(sd[0],p,100)<0)
/*citim mesajul pornind de la copil*/
if(read(sd[0],tampon,100)<0)
printf("procesul cu pid %d(parinte)
a pornit'%s'n",getpid(),tampon);
/*sa asteptam terminarea copilului*/
if(wait(NULL)<0)
close(sd[0]);
return 0}}
Dupa rulare se va afisa:
procesul cu pidul 10896(fiul) |sunt parintele|
procesul cu pidul 10897(parintele)|sunt fiul|
eroare: No child proces
3) Tema
Sa se creeze o pereche de socket-uri in care primul socket va trimite celui de-al doilea socket un sir de caractere, iar cel de-al doilea va returna primului socket caracterele ordonate dupa alfabetul ASCII.
3.3.8. Modelul client/server-TCP
1) Considerente teoretice
In modelul client /server o masina numita server ofera anumite servicii altor masini numite clienti.
TCP (Transmission Control Protocol) este un protocol de comunicatie care realizeaza legatura intre client si server prin intermediul socketurilor, utilizand streamuri.
Schema generala de functionare client/server - TCP, in Linux, este data in fig. 2.
Server TCP
Se executa urmatoarele apeluri sistem:
socket() - se creaza un socket care va trata conexiunile cu clientii.
bind() - se ataseaza socketul creat anterior la un port de comunicatie
listen() - se instaleaza socketul in vederea ascultarii portului pentrustabilirea conexiunii cu clientii
acept() - se asteapta realizarea unei conexiuni cu un client si apoi acest apel blocheaza programul pana cand vine o cerere de conectare de la alt client
read(),write() - primitive pentru schimbul de mesaje client/server
close() - se inchide conexiunea cu clientul
SERVER TCP CLIENT TCP
Fig. 2. Schema de functionare client/server-TCP.
Client TCP
Se utilizeaza aceleasi apeluri sistem ca si la server, cu exceptia primitivei accept() care trebuie sa contina adresa IP si portul serverului la care se conecteaza clientul.
La apelul read() din server va corespunde un apel write() la client iar la write() din server va corespunde un apel read() in client.
Primitivele utilizate in acest protocol sunt:
a) bind()
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockd,struct sockaddr*addr,
socklen_t addrlen);
int sock d este descriptorul serverului.
struct sockaddr*addr este o structura care retine informatia de adresa pentru orice tip de socket-uri.Este definita astfel:
struct sockaddr
In cazul INTERNET-ului structura utilizata este:
struct sockaddr_in
Trebuie testat ca sin_zero este nul si acest lucru se realizeaza prin functiile bzero() sau manset().
Adresa Internet este stocata in structura in_addr :
struct in_addr
/*adresa IP*/
b) listen()
#include<sys/socket.h>
int listen(int sockd,int backlog);
-backlog arata numarul de conexiuni permise in coada de asteptare a conexiunilor clienti, uzual fiind 5.
c) accept()
Se ruleaza asteptarea de catre master.
#include<sys/types.h>
#include<sys/socket.h>
in accept(int socd,struct sockaddr*addr,
soclen_t*addrlen)
2) Desfasurarea lucrarii
Acest program creeaza un server si un client; serverul primeste un sir de caractere de la client si il trimite inapoi in ecou. Clientul citeste un sir de caractere de la intrarea standard, il trimite serverului, apoi asteapta ca serverul sa il returneze.
Server C
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<errno.h>
#include<unist.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define PORT 8081 /*se defineste portul*/
extern int errno; /*codul de eroare*/
main()
/*se pregatesc structurile de date*/
bzero(&server,sizeof/server));
bzero(&from,sizeof(from));
server.sin_family=AF-INET;/*familia de socketuri*/
server.sin_addr.s_addr=htonl(INADOR-ANY);
server.sin_port=htons(PORT);
/*se ataseaza socketul*/
if(bind(sd,(struct sockaddr*)&server,
sizeof((struct sockaddr))==-1)
/*serverul asculta linia daca vin clienti*/
if(listen(sd,5)==-1)
/*se servesc clientii*/
while(1)
bzero(tampon,100);
/*s-a realizat conexiunea,
se asteapta mesajul*/
printf("asteptam mesajuln");fflush(sdout);
/*se citeste mesajul*/
if(read(client,tampon,100)<=0)
/*s-a inchis conexiunea cu clientul*/
printf("mesaj receptionat, trimitem mesaj inapoi");
/*se returneaza mesajul clientului*/
if(write(client,tampon,100)<=0)
else printf("transmitere cu succes"n");
/*am terminat cu acest client,
se inchide conexiunea*/
close(client)}}
Client C
/*retransmite serverului mesajul primit de la acesta*/
#include<sys/types.h>
#include<sys/socket.h>
#include<etinet/in.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<stoing.h>
extern int errno;/*codul de eroare*/
int port; /*portulde conectare la server*/
int main(int argc,char*argv[])
port=atoi(argv[2]);
/*se creeaza socketul*/
if((sd=socket(AF_INET,SOCK_STREAM,0))==-1)
server.sin_family=AF_INET;/*familia socketului*/
server.sin_addr.s_addr=inet_addr(argv[1];)
/*adresa IP a serverului*/
server.sin_port=htons(port);/*portul de
conectare*/
if(connect(sd,(struct sockaddr))==-1
/*citirea mesajului si transmiterea catre server*/
bzero(tampon,100);
printf("introduceti mesajul")fflush(stdout);
read)0,buffer,100);
if(writw(sd,tampon,100)<=0)
/*afisarea mesajului primit*/
printf("mesajul primit este%sn",tampon);
close(sd);}
Pentru compilarea clientului si a serverului vom folosi comenzile:
gcc -o server server.c
gcc -o client client.c
Pentru executia programelor:
./server
/client 127.0.0.1 8081
3)Tema. Sa se creeze doua fisiere, server si client, in care clientul trimite serverului numere intregi iar serverul va returna clientului numai numerele pare dintre cele transmise (in TCP).
3.3.9. Modelul client/server-UDP
(User Datagrama Protocol)
In acest protocol de transmisie a datelor nu se realizeaza o conexiune intre client si server pentru ca apoi sa se citeasca si sa se scrie date. In UDP transmisia este asincrona, in sensul ca clientul si serverul isi trimit mesaje unul altuia prin intermediul primitivelor SEND si RECEIVE. Structura de date transmisa se numeste datagrama. Organizarea UDP este data in fig.3.
SERVER UDP CLIENT UDP
CERERE
RASPUNS
Fig.3. Organigrama UDP
Apelurile folosite sunt:
recfrom() cu sintaxa:
#include<sys/types.h>
#include<sys/socket.h>
int recvfrom(int sockd.void*buf,size_t len,
int flags,struct sockaddr*from,socklen.t*fromlen);
sendto() cu sintaxa:
#include<sys/types.h>
#include<sys/socket.h>
int sendto(intsockd,const void*msg,size_t len,
int flags, const struct sockadd*to,socklen_t
tolen);
2) Desfasurarea lucrarii
Server c
/*server UDP iterativ (echo)
Asteapta un mesaj de la clienti;
Mesajul primit este trimis inapoi.*/
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<netinet/in.h>
#include<errno.h>ss
#include<unistd.h>
/*portul folosit*/
#define PORT 8081
/*codul de eroare returnat d anumite apeluri*/
extern int errno;
/*programul*/
ini
main()
/*cream un socket*/
if ((sd=socket(AF_INET,SOCK_DGRAM,0))==-1)
/*sa pregatim structura folosita de server*/
adresa.sin-family=AF-INET;
/*stabilirea familei de socket-uri*/
adresa.sin_addr.s_addr=htonl)INADDR_ANY);
/*acceptam orice adresa*/
adresa.sin_port=htons(PORT);
/*utilizam un port utilizator*/
/*atasam socketul*/
int (bind(sd,struct sockaddr*)&adresa,
sizeof(struct sockaddr))==-1)
/*servim in mod iterativ clientii*/
while(1)
/*..dupa care il trimitem inapoi*/
if(sendto(sd,buffer,bytes,0,&client,length)<0)
} /*while*/
}/*main*/
Client UDP(echo)
/*Client UDP (echo)
Trimite un mesaj unui server;
Mesajul este receptionat de la server.*/
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<netinet/in.h>
#include<errno.h>
#include<netdb.h>
#include<string.h>
/*codul de eroare returnat de anumite apeluri*/
extern int errno;
/*portul de conectarela server*/
int port;
/*programul*/int
main(int argc,char*argv[])
/*stabilim portul*/
port=atoi(argv[2];
/*cream socketul*/
id((sd=socket(AF_INET,SOCK_DGRAM,0))==-1)
/*umplem structura folosita pentru realizarea dialogului cu serverul*/
server.sin_family=AF_INET;
/*familia socketului*/
server.sin_addr.s_addr=inet.addr(argv[1];
/*adresa IP a serverului*/
server.sin_port=htons(port);
/*portul de conectare*/
/*citirea mesakului de la intrareastandard*/
bzero(buffer,100);
printf("introduceti mesajul:");
fflush(stdout);
read(0,buffer,100;
length=sizeof(server);
/*trimiterea mesajului catre server*/
if(sendto(sd,buffer,strlen(buffer),0,
&server,length)<0)
printf("mesajul primit este:´%s´.n",buffer);
/*inchidem socketul, am terminat*/
close(sd)
Clientul va necesita doua argumente in linia de comanda, semnificand adresa IP a serverului si portul de conectare la serverul UDP. Daca ambele programe ruleaza pe aceeasi masina, atunci vom putea introduce:
server -udp
client -udp 127.0.0-18081
Serverul va rula automat in fundal (adoptand postura de daemon ) .
3) Tema.Sa se scrie un program in care un client va trimite un sir de numere intregi serverului iar acesta va returna catre client numerele primite in ordine inversa.
Bibliografie
1.D. Cramer, Interworking with TCP-IP, vol.1, Prentice Hall, New -Jersey,1991.
2.Andrew S. Tanenbaum, Modern Operating Systems, Prentice Hall,1992.
3.Iosif Ignat, Emil Muntean, Kalman Pustzai,Microinformatica, 1992.
4.B. Chapman, E.D. Zwicky, Building Internet Firewalls, O'Reilly&Associates, 1995.
5.Traian Ionescu, Daniela Saru, John Floroiu, Sisteme de operare-principii si functionare, Editura tehnica, Bucuresti, 1997.
6.R. Stevens, UNIX Network Programming, vol. 1, Networking, Prentice Hall, 1998
7.Felicia Ionescu, Principiile calculului paralel,Editura Tehnica, Bucuresti, 1999.
8.-Interprocess Communications, Prentice Hall, N.J. 1999.
9.A. Silberschatz, P.B. Galvin, G.Gagne, Applied Operating System Concepts, Wiley, New-York,200.
Liviu Miclea, Notiuni de sisteme de operare si reetele de calculatoare (LINUX), Universitatea Tehnica Cluj-Napoca, 2001.
11.Dan Cosma, UNIX. Aplicatii, Ed. de Vest, Timisoara, 2001.
12.Ioan Jurca, Sisteme de operare, Editura de Vest, Timisoara, 2001.
13.Sabin Buraga, Gabriel Ciobanu, Atelier de programare in retele de calculatoare, Editura Polirom, Iasi, 2001.
14.Dragos Acostachioaie, Securitatea sistemelor LINUX, Editura Polirom, Iasi, 2001.
15. Andrew Tanenbaum, Sisteme de Operare Moderne, Editura Byblos, Bucuresti, 2004.
17. Cristian Vidrascu, http://www.infoiasi.ro/~vidrascu.
18. Mihai Budiu, Alocarea memoriei in nucleul sistemului de operare, http://www.cs.cmu.edu/mihaib, 1998.
17. http://www.oreilly.com/catalog/opensources/book/
linus. html