Examen CA/CC 2014-2015
Examen final
Puteți participa la o singură sesiune de examen final.
Foi de examen
Lucrări
Lucrare 1
La începutul cursului 4:
marți, 17 martie 2015, 09:05-09:15, EC004, seria CA
miercuri, 18 martie 2015, 17:05-17:15, EC004, seria CC
3CA, varianta 1
În ce situație practică este folosit apelul dup()
?
Ce conține tabela de descriptori de fișier a unui proces?
Răspuns: Tabela de descriptori de fișier a unui proces conține pointeri; ca structură de date este un vector de pointeri. Acești pointeri referă structuri de fișier deschis de proces. Când un proces deschide un fișier, se alocă o structură de fișier deschis, iar adresa acestei structuri este stocată într-un loc liber (indicat de descriptorul de fișier) din tabela de descriptori de fișier.
Apelul wait()
este un apel blocant. Când are loc deblocarea procesului blocat în wait()
?
3CA, varianta 2
De ce nucleul sistemului de operare rulează, în general, într-un spațiu dedicat, numit kernel space?
Răspuns: Pentru că în kernel space au loc operații privilegiate. Spațiul kernel este un spațiu privilegiat la care doar nucleul sistemului de operare are acces. În felul acesta se păstrează securitatea sistemului, orice operație privilegiată necesitând trecerea în spațiul kernel și acordul nucleului sistemului de operare pentru execuție.
Care este un avantaj al apelurilor de tipul buffered I/O (precum fread
, fwrite
) și care este un avantaj al celor de tipul system I/O (precum read
, write
)?
Răspuns: Apelurile de tipul buffered I/O fac mai puține apeluri de sistem, deci overhead mai redus, întrucât informația este ținută în buffere până la nevoia de flush. Sunt, de asemenea, portabile. Apelurile de tipul system I/O au o latența mai redusă, informațiile ajung repede pe dispozitiv. De asemenea, apelurile de tipul system I/O
nu alocă memorie suplimentară pentru buffering, sunt mai economice din acest punct de vedere.
De ce spunem despre apelul fork()
că este invocat o dată dar se întoarce de două ori?
3CC, varianta 1
Un descriptor de fișier gestionează/referă, în general, un fișier obișnuit (regular file). Ce altceva mai poate referi?
Răspuns: Un descriptor de fișier mai poate referi un director, un link simbolic, un pipe, un socket, un dispozitiv bloc sau caracter. Toate aceste entități sunt gestionate de un proces prin intermediul unui descriptor de fișier.
Dați exemplu de apel care modifică dimensiunea unui fișier.
Ce este un proces zombie?
3CC, varianta 2
Câte tabele de descriptori de fișier există la nivelul sistemului de operare?
Răspuns: Fiecare proces are o tabelă de descriptori de fișier, deci vor exista, la nivelul sistemului de operare, atâtea tabele de descriptori de fișier câte procese există în acel moment în sistem.
Dați un exemplu de informație care se găsește în structura de fișier deschis și un exemplu de informație care se găsește în structura de fișier pe disc (inode).
Răspuns: În structura de fișier deschis se găsesc cursorul de fișier, permisiunile de deschidere a fișierului, pointer către structura de fișier pe disc. În structura de fișier pe disc se găsesc permisiuni de acces, informații despre utilizatorul deținător, grupul deținător, dimensiunea fișierului, timpi de acces, tipul fișierului, pointeri către blocurile de date.
Dați exemplu de situație care duce la trecerea unui proces din starea RUNNING
în starea READY
.
Lucrare 2
La începutul cursului 7:
marți, 7 aprilie 2015, 08:05-08:15, EC004, seria CA
miercuri, 8 aprilie 2015, 17:05-17:15, EC004, seria CC
3CA, varianta 1
Numiți o sursă de overhead care apare atunci când sistemul de operare schimbă contextul de execuție între două procese.
Răspuns: Surse de overhead pentru schimbarea de context între procese sunt schimbarea tabelei de pagini, care conduce la flush la TLB, algoritmul de alegere a următorului proces și schimbarea efectivă de context, cu salvarea registrelor procesului curent și restaurarea procesului ales.
De ce este uzual și avantajos ca spațiul virtual de adrese al proceselor să cuprindă o zonă dedicată pentru kernel?
Răspuns: Prezența zonei dedicate pentru kernel în spațiul de adresă al fiecărui proces înseamnă că la fiecare apel de sistem, adică la trecerea din user space în kernel space, tabela de pagini rămâne aceeași și nu se face flush la TLB. În cazul în care kernel-ul ar avea o zonă dedicată, atunci ar avea și o tabelă de pagini dedicată și ar trebui schimbată tabela de pagini la fiecare apel de sistem și la fiecare revenire din apel de sistem.
Procesul P1 folosește 100MB
de memorie fizică (RAM) rezidentă. P1 execută fork()
și rezultă procesul P2. Câtă memorie fizică (RAM) rezidentă folosesc împreună P1 și P2 imediat dupa fork()
? De ce?
Răspuns: Apelul fork()
folosește copy-on-write ceea ce înseamnă că nu se alocă memorie rezidentă nouă pentru noul proces. Se alocă, într-adevăr, o nouă tabelă de pagini, dar spațiul rezident al procesului P1 este acum partajat cu procesul P2 până la prima operație de scriere, când pagina aferentă va fi duplicată.
3CA, varianta 2
Descrieți o problemă posibilă care poate apărea dacă un sistem de operare implementează un algoritm de planificare de tipul Shortest Job First.
Răspuns: În cazul unei planificări Shortest Job First, dacă sunt adăugate în sistem, în mod constant, procese noi și de durată scurtă, procesele de durată mai lungă nu vor apuca să ruleze. Va rezulta într-un timp de așteptare foarte mare pentru procesele de lungă durată sau chiar în starvation
(așteptare nedefinită pentru ca un proces să poată rula pe procesor).
Care este utilitatea conceptului de demand paging?
Răspuns: Atunci când sistemul de operare folosește demand paging alocarea de memorie fizică este amânată până în momentul în care nevoie (adică la primul acces). Sistemul de operare doar rezervă memorie virtuală și nu alocă memorie fizică în spate, economisind memorie fizică. La primul acces se alocă și memorie fizică, la cerere (adică on demand) și se face maparea acesteia la spațiul virtual (paging).
De ce zonele .text
și .rodata
din cadrul bibiliotecilor partajate (shared libraries
) pot fi partajate între mai multe procese?
Răspuns: Zonele .text
și .rodata
sunt zone read only. Acest lucru înseamna că pot fi partajate în siguranță pentru că nici un proces care accesează zona nu o va putea modifica. Zonele conțin permanent aceleași informații indiferent de numărul de procese care le folosesc și pot fi, deci, partajate.
3CC, varianta 1
La ce se referă noțiunea de timp de așteptare (waiting time) în contextul planificării proceselor (process scheduling)?
De ce este utilă paginarea ierarhică?
Răspuns: Dacă nu am folosi paginare ierarhică tabelele de pagini ar ocupa foarte mult spațiu; ar fi neovie de o intrare pentru fiecare pagină virtuală a unui proces. Paginarea ierarhică conduce la reducerea spațiului ocupat de tabela de pagini, profitând de faptul că o bună parte din spațiul virtual de adrese al procesului nu este folosit.
Care este o cauză sursă pentru evacuarea unei pagini din memoria fizică (RAM) pe disc (swap out)?
3CC, varianta 2
Care este un avantaj și un dezavantaj al folosirii unei cuante de timp scurte în planificarea proceselor (process scheduling)?
Ce conține tabela de pagini a unui proces?
Răspuns: Tabela de pagini a unui proces conține pointeri de pagini fizice. Indexul în tabelă este pagina virtuală. În general tabela de pagini mai conține și informații legate de permisiuni, validatate, dacă pagina a fost sau nu modificată.
Dați exemplu de situație care cauzează page fault fără a rezulta în trimiterea unei excepții de acces la memorie (de tipul SIGSEGV
, Segmentation fault) către procesul care a generat page fault-ul.
Răspuns: Dacă un proces are rezervat un spațiu virtual în modul demand paging atunci accesarea unei pagini virtuale din acel spațiu va conduce la page fault. În urma page fault-ului, se va aloca și mapa o pagină fizică, iar procesul își va continua execuția. Nu va fi generată excepție de acces la memorie. La fel se întâmplă și în cazul copy-on-write.
Lucrare 3
3CA, varianta 1
De ce este relevant, în contextul securității memoriei, faptul că adresa de retur a unei funcții se reține pe stivă?
Răspuns: Adresa de retur stocată în memorie oferă unui atacator posibilitatea suprascrierii acesteia și alterarea fluxului normal de execuție al programului. Pentru aceasta este nevoie de o vulnerabilitate într-un buffer la nivelul stivei. În general folosirea de adrese pe stive oferă această posibilitate si e de evitat, dar nu putem face asta în privința adresei de retur; este nevoie de stocarea pe stivă pentru a putea reveni în stack frame-ul anterior.
Care este un avantaj și un dezavantaj al folosirii unei implementări de thread-uri în user space (user-level threads)?
Răspuns:
Avantaje pot fi:
timp de creare mai mic decât thread-urile kernel level
schimbări de context mai rapide
control mai bun asupra aspectelor de planificare (totul se întâmplă în user space, sub controlul programatorului)
Dezavantaje pot fi:
De ce este importantă o instrucțiune de tip TSL
(test and set lock) la nivelul procesorului?
3CA, varianta 2
De ce folosirea DEP (Data Execution Prevention) nu previne atacurile de tipul return-to-libc?
Răspuns: DEP previne existența simultană a permisiunilor de scriere și execuție. Adică nu se poate scrie într-o zonă un shellcode (sau ceva similar) care apoi să se execute. Un atac de tipul return-to-libc presupune suprascrierea unei adrese (de retur, pointer de funcție) ca să pointeze către o funcție din biblioteca standard C. Întrucât un atac de tipul return-to-libc nu presupune scriere și execuție a aceleiași zone, nu poate fi prevenit de DEP.
Care este un avantaj și un dezavantaj al folosirii unei implementări de thread-uri cu suport în kernel (kernel-level threads)?
Răspuns:
Avantaje pot fi:
dacă un thread se blochează celelalte thread-uri pot rula
se folosește suportul multiprocesor al sistemului
planificator robust asigurat de sistemul de operare, preemptiv
Dezavantaje pot fi:
Precizați un dezavantaj al folosirii primitivelor de sincronizare.
3CC, varianta 1
De ce trebuie avut grijă la construcțiile precum cea de mai jos în cadrul unei funcții?
int (*fn_ptr)(int, int); /* fn_ptr is a function pointer */
char buffer[128]; /* buffer for storing strings */
Răspuns: În construcția din exercițiu dacă nu se ține cont de dimensiunea buffer-ului se poate obține un buffer overflow. În urma overflow-ului, se suprascrie pointer-ul de funcție fn_ptr
. Probabil acest pointer va fi folosit la un moment dat rezultând în execuția arbitrară și alterând fluxul normal de execuție al programului.
De ce dimensiunea spațiului virtual de adresă al unui proces crește în momentul creării unui thread (chiar dacă thread-ul nu ajuns încă să se execute)?
Răspuns: În momentul creării unui thread se alocă o stivă nouă acelui thread. În mod implicit, pe sistemele Linux, dimensiunea stivei este de 8
MB de memorie, observând o creștere semnificativă a spațiului virtual de adresă al procesului.
Când este recomandat să folosim un spinlock în locul unui mutex?
Răspuns: Spinlock-ul folosește busy-waiting și are operații de lock()
și unlock()
ieftine prin comparație cu mutex-ul. Operațiilor de lock()
și unlock()
pe mutex sunt de obicei costisitoare întrucât pot ajunge să invoce planificatorul. Având operații rapide, spinlock-ul este potrivit pe secțiuni critice de mici dimensiuni în care nu se fac operații blocante; în aceste cazuri faptul că face busy-waiting nu contează așa de mult pentru că va intra rapid în regiunea critică. Dacă am folosi un mutex pentru o regiune critică mică, atunci overhead-ul cauzat de operațiile pe mutex ar fi relativ semnificativ față de timpul scurt petrecut în regiunea critică, rezultând în ineficiența folosirii timpului pe procesor.
3CC, varianta 2
De ce, în general, un shellcode se încheie cu invocarea apelului de sistem execve
?
Răspuns: Un shellcode încearcă, în general, rularea unui program nou, de exemplu a unui shell în forma echivalentă a unui apel execve(”/bin/sh”)
. Întrucât un apel de bibliotecă este mai dificil de realizat, se preferă o instrucțiune simplă de apel de sistem (precum int 0x80
. Se face un apel de sistem execve
cu un argument de forma unui șir /bin/sh
într-un registru rezultând în crearea unui shell nou.
De ce schimbarea de context între două thread-uri ale aceluiași proces este, în general, mai rapidă decât schimbarea de context între două procese?
Care sunt cele două tipuri de operații aferente mecanismelor de sincronizare prin secvențiere/ordonare?
Lucrare 4
3CA, varianta 1
O aplicație execută un apel send()
cu 1024
de octeți de date, iar apelul send
întoarce 1024
. Alegeți varianta corectă de mai jos și argumentați: În acest moment, aplicația sender poate fi sigură că datele au fost livrate cu succes către
nucleul SO de pe sistemul destinație
aplicația destinație
datele au fost salvate în send-buffer-ul de pe sistemul transmițătorului
Răspuns: Datele au fost salvate în buffer-ul de send al transmițătorului. În momentul în care datele au fost scrise acolo, apelul se întoarce. Este posibil ca datele să nu fi părăsit sistemul, dar apelul se va întoarce. Stiva TCP se va ocupa de transmiterea datelor din buffer-ul de send către destinație.
Indicați două obiective ale algoritmilor de planificare a cererilor pentru hard disk, și dați un exemplu de algoritm care le îndeplinește.
Răspuns: Un obiectiv este performanță ridicată. Un alt obiectiv este fairness: asigurarea că toate procesele au acces echitabil la resurse și că un proces nu așteaptă mai mult ca altul accesul la disc. Un algoritm care colectează mai multe cereri și apoi le sortează și agregă, independent de procesul care le cauzează va atinge obiectivele. Algoritmi precum C-SCAN sau C-LOOK sau altele satisfac aceste obiective.
Precizați două diferențe între un symbolic link și un hard link.
Răspuns: Un symbolic link are un inode al său, pe când un hard link este un dentry (un nume și un index de inode). Un symbolic link poate referi directoare în timp ce un hard link nu; un symbolic link poate fereri un fișier de pe altă partiție/alt sistem de fișiere, în timp ce un hard link nu.
3CA, varianta 2
Explicați motivul pentru care este indicat să folosim pentru apelurile send()
și recv()
buffere de dimensiuni mari (de exemplu mai mari decât 100KB
).
Ce limitează performanța hard disk-ului în cazul accesului aleator la date din diverse zone ale discului?
Un director conține N
subdirectoare. Câte hard link-uri pointează la acest director?
3CC, varianta 1
De ce la plăcile de rețea de mare viteză are sens folosirea polling în locul întreruperilor pentru partea de intrare/ieșire?
Răspuns: Având viteze mari, vor veni pachete foarte des și vor fi generate întreruperi foarte des. În această situație, procesorul va fi ocupat foarte mult timp rulând rutine de tratare a întreruperilor. Prin trecere la polling, procesorul interoghează placa de rețea și, dacă are date, le citesțe repede, fără întreruperi. În restul timpului face și alte lucruri, fără a mai consuma timp în rutina de tratare a întreruperilor.
În ce situație operația send()
pe un socket se blochează?
Răspuns: Apelul send()
pe socket se blochează în situația în care buffer-ul din kernel (send buffer) nu dispune de loc pentru copierea datelor din buffer-ul de user space. Sau, în anumite cazuri, precum în cazul sockeților non-blocanți, dacă nu există nici măcar 1 octet liber în buffer-ul de kernel (send buffer).
Care este un avantaj și un dezavantaj al alocării indexate (cu i-node) pentru blocuri de date pentru fișiere?
Răspuns: Principalul dezavantaj al alocării indexate este limitarea dimensiunii fișierului la numărul de intrări din lista de indecși (pointeri către blocuri). Avantajele este accesul rapid la blocuri (se citește indexul) și absența fragmentării externe: blocurile se pot găsi oriunde și pot fi referite din lista de indecși. Dezavantajul este compensat prin folosirea indirectării (simple, duble, triple) ducând la o mai mare dimensiune a fișierului, dar introducând un alt dezavantaj: timp mai mare de acces pentru blocurile din partea finală a fișierului; întrucât se trece prin blocurile de indirectare. Un dezavantaj aici poate fi și ocuparea de blocuri doar cu indecși, în loc să conțină date efective.
3CC, varianta 2
De ce operația lseek()
nu are sens pe dispozitive de tip caracter, ci doar pe dispozitive de tip bloc?
Răspuns: Pentru că pe dispozitivele de tip caracter datele vin și sunt citite/scrise octet cu octet, ca într-o țeavă. Nu putem anticipa date și ne putem plasa mai sus sau mai jos pe banda de date. În cazul dispozitivelor de tip bloc însă, datele se găsesc pe un spațiu de stocare pe care ne putem plimba/glisa; putem "căuta" date prin plasarea pe un sector/bloc al dispozitivului de stocare și atunci operația lseek()
are sens.
În cazul unui apel recv()
comandat pentru citirea a 789
de octeți, se citesc 123
de octeți. Cum se explică citirea unui număr mai mic de octeți decât cel comandat?
Ce conțin blocurile de date aferente unui inode de tip director?
Răspuns: Conțin un vector de dentry-uri. Un dentry este o structură ce conține numele fișierului și indexul inode-ului aferent. Fiecare intrare din director (indiferent de tipul acesteia: fișier, director, link symbolic) are un dentry.
Examene anterioare