Examen CA/CB/CC 2016-2017
Examen final
Puteți participa la un singur examen final.
Datele de examen de SO pentru sesiunea iunie 2017 sunt:
luni, 29 mai 2017, ora 14:00, sala EC105, studenți în anul 4 care refac disciplina
marți, 6 iunie 2017, ora 14:00, sala PR001, studenți în anul 4 care refac disciplina
sâmbătă, 3 iunie 2017, ora 08:00, sala A02, 3CC
luni, 12 iunie 2017, ora 14:00, sala PR 001 (3CA), EC105 (331CB, 332CB, 333CB), EC004 (334CB, 335CB)
joi, 15 iunie 2017, ora 08:00, sala EC105 (3CA)
Datele de examen de SO pentru sesiunea septembrie 2017 sunt:
vineri, 1 septembrie 2017, de la ora 09:00, sala PR 001
vineri, 8 septembrie 2017, de la ora 09:00, sala PR 001
Detalii despre examen găsiți în secțiunea aferentă din pagina de reguli.
Foi de examen
Lucrare 1
La începutul cursului 4:
luni, 13 martie 2017, 17:00-17:15, EC105, seria CA
miercuri, 15 martie 2017, 14:00-14:15, PR001, seriile CB/CC
3CA, varianta 1
Ce se întâmplă cu shell-ul după executarea comenzii exec sleep 10
? Dar după executarea comenzii sleep 10
?
Răspuns: La execuția comenzii exec sleep 10
, imaginea shell-ului este înlocuită cu imaginea executabilului sleep
. Adică shell-ul nu mai există ca proces. La încheierea procesului sleep
, acesta își încheie execuția, iar shell-ul nu mai există. În cazul execuției comenzii sleep 10
se creează un nou proces care folosește imaginea executabilului sleep
. Procesul shell așteaptă încheierea procesului sleep
și apoi își continuă execuția.
Dați două exemple de evenimente care pot determina un proces să iasă din starea RUNNING
.
Cu ce diferă sistemele Windows de cele Unix la crearea de procese?
Răspuns: Pe sistemele UNIX, un proces este creat prin apelul fork()
care creează o clonă / un duplicat al procesului părinte. Pentru un proces cu imagine de executabil nouă se folosește apelul exec()
după apelul fork()
. În Windows procesele sunt create cu apelul CreateProcess
care primește imaginea de executabil, ca o unificare a apelurilor fork()
și exec()
din UNIX.
3CA, varianta 2
Explicați cum lansează shell-ul în execuție comanda ls > a.txt
.
Care este diferența dintre fork
și fork + exec
?
Răspuns: În cazul apelului fork()
se creează un proces clonă, copie aproape identică a procesului părinte. Procesul copil și procesul părinte execută același cod. În urma fork() + exec()
imaginea (codul executabil) aferentă procesului copil este înlocuită cu imaginea de executabil transmisă ca argument funcției exec()
rezultând într-un cod diferit pentru procesul copil.
Care este diferența dintre funcțiile fwrite
și write
atunci când scriem într-un fișier?
Răspuns: Funcția write()
este un apel de sistem unbuffered, în vreme ce funcția fwrite()
este un apel de bibliotecă buffered. Atunci când apelăm write()
se execută apel de sistem care va transfera datele din user space în kernel space. În cazul apelului fwrite()
datele sunt transferate într-un buffer intern al bibliotecii standard C urmând ca apelul de sistem efectiv (și flush-ul datelor) să aibă loc la un moment ulterior (de exemplu la newline sau când se umple buffer-ul intern). Apelul fwrite()
consumă mai multă memorie pentru bufer-ul intern cu avantajul reducerii overhead-ului cauzat de apeluri de sistem.
3CB/CC, varianta 1
Dați un exemplu de efect negativ care s-ar întâmpla dacă nu ar exista separația kernel mode / user mode.
Răspuns: Dacă nu ar exista kernel mode, o aplicație (un proces) ar avea acces la memoria altui proces și ar putea corupe buna funcționare a acestuia. De asemenea, un proces are acces nemediat la resursele hardware putând citi datele altor procese sau putând face un atac de tipul denial-of-service pe acele resurse.
Dați exemplu de operație care modifică cursorul unui fișier fără a modifica dimensiunea fișierului.
Răspuns: Operația read()
modifică cursorul de fișier. Dimensiunea nu este modificată pentru că nu ajunge să se scrie mai mult sau mai puțin. Operația write()
modifică cursorul de fișier și nu modifică dimensiunea dacă nu scrie peste dimensiunea curentă a fișierului. Operația lseek()
este definiția enunțului: doar modifică cursorul de fișier, fără alte acțiuni.
Câte procese se pot găsi în starea RUNNING
la un moment dat în sistemul de operare?
3CC, varianta 2
Dați exemplu de situație în care o aplicație oarecare are nevoie să execute un apel de sistem.
Răspuns: O aplicație are nevoie să execute un apel de sistem în momentul în care dorește să interacționeze cu I/O: rețea, hard disc, terminal, tastatură. De asemenea, atunci când dorește să comunice cu alte procese (în orice formă posibilă). Nu în ultimul rând, atunci când face operații de lucru cu memoria.
Ce conține o intrare din tabela de descriptori a unui proces?
Răspuns: Tabela de descriptori a unui proces conține pointeri către structurile de fișier deschis. Când un fișier este deschis se creează o structură de fișier deschis, iar în tabela de descriptori, în primul slot liber, se plasează un pointer la această structură.
Dați două exemple de situații în care are loc o schimbare de context între două procese.
Greșeli frecvente
Procese in starea RUNNING: cate PID-uri avem in sistem.
Tabela de descriptori conține PID-uri (proces curent, copii, părinte, etc.).
In starea RUNNING se poate afla un singur proces.
Separatia kernel mode/user mode: teorie generala, fara a da exemplu propriu-zis.
Context switch - se înțelege prin context de fapt environment/context general (se modifică o variabilă de mediu, se modifică memorie partajată, 2 procese deschid același fișier și își mută independent cursoarele).
Context switch (perlă frecventă): atunci când 2 procese accesează aceeași zonă de memorie.
Este frecventă confuzia dintre PCB și intrarea din tabela de descriptori.
Lucrări foarte bune
RADU Toma, 333CC
BELINGHIR Grigore Petrut, 332CC
MARDALE Andrei, 334CB
DORNEANU Anda-Alexandra, 333CC
MARINICĂ Elena, 335CA
POPA Ștefan-Adrian, 332CA
ILIE Andra Teodora, 332CA
PICIU Laurențiu-Gheorghe, 333CA
VATAMANU Alexandra, 331CA
STOICAN THEODOR, 333CA
CURCUDEL Ioan-Răzvan, 335CA
BUȘILĂ Ștefan, 334CA
DURBALĂ Cătălina, 332CA
DIACONU Cristian, 333CA
STAN Cristiana-Ștefania, 335CC
MUNTEAN Dan Iulian, 335CB
UNGUREANU Remus-Dan, 335CB
PÎRTOACĂ George Sebastian, 335CB
PANDELEA Alexandru, 335CB
MACARIE Roxana, 333CB
STERPU Paul, 334CB
JITEA Ștefan, 335CC
BULGARU Mihaela, 332CB
OLTEANU Robert, 331CC
POPA Crina Elena, 333CB
CALOIANU George, 332CC
Lucrare 2
La începutul cursului 7:
luni, 3 aprilie 2017, 17:00-17:15, EC105, seria CA
miercuri, 5 aprilie 2017, 14:00-14:15, PR001, seriile CB/CC
3CA, varianta 1
Explicați în ce situație două procese pot fi planificate inechitabil pe un procesor (adică ocupă timp diferit pe procesor) de către un planificator round-robin.
Răspuns: Planificatorul round-robin planifică procesele rând pe rând. Dacă un proces este CPU intensiv iar altul I/O intensiv, atunci cel CPU intensiv va consuma mai mult timp pe procesor. Fie până când îi expiră cuanta (round-robin preemptiv) fie până când își încheie execuția sau, în final, se blochează (round-robin cooperativ). Procesul I/O intensiv se va bloca mult mai repede și astfel va ocupa mai puțin timp pe procesor.
Într-un sistem cu paginare, cum este transformată o adresă virtuală într-o adresă fizică?
Dați un avantaj al folosirii demand paging în sistemele cu memorie virtuală.
Răspuns: Avantajul demand paging este timpul scurt folosit pentru “alocarea” memoriei și reducerea consumului de memorie fizică. Întrucât nu se consumă memorie fizică, “alocarea” va fi mai rapidă și nu va consuma din memoria RAM, lăsând-o disponiilă pentru acțiuni urgente. Memoria RAM va fi alocată și consumată on-demand (la nevoie).
3CA, varianta 2
Un programator apelează pipe(..). Cum poate fi transmis descriptorul rezultat pentru citire unui alt proces?
Cum rezolvă MMU adresa fizică a tabelei de pagini a procesului curent?
Explicați modul în care apelul fork() inițializează memoria procesului nou creat.
Răspuns: Procesul nou creat partajează spațiul de memorie fizică al procesului părinte. Fiecare proces are spațiu virtual propriu și tabelă de pagini proprie; pentru procesul copil se creează o tabelă de pagini nouă și se copiază conținutul tabelei de pagini a procesului părinte. Spațiile virtuale de adrese ale celor două procese sunt mapate peste același spațiu fizic, care est marcat read-only. La accesul de scriere la o pagină din partea unuia dintre cele două procese, are loc page fault și copy-on-write: pagina fizică în cauză este duplicată, este marcată read-write și apoi peste ea este mapată pagina virtuală în care a avut loc page fault-ul.
3CB/CC, varianta 1
De ce un planificator de procese de pe un sistem de operare modern oferă, în general, o cuantă de timp mai mare proceselor I/O intensive?
Răspuns: Un proces I/O intensiv este așteptat să se blocheze repede și astfel, să cedeze procesorul. I se poate aloca o cuantă de timp mai mare (și o prioritate) mai mare pentru a rula cât mai rapid, pentru că oricum apoi va lăsa loc altor procese (CPU intensive sau I/O intensive).
Care este un avantaj al paginării memoriei față de segmentare?
Răspuns: Paginarea conduce la eliminarea fragmentării externe: fragmentarea în afara blocurilor alocate. Spațiile libere sunt multiplu de pagină și pot fi alocate individual. În cazul segmentării ar trebui să găsim o zonă liberă suficientă pentru segmnetul care se alocă. Un alt avantaj este managementul mai ușor al memoriei din partea sistemului de operare, întreaga memorie fiind acum administrată la nivelul unei pagini (bloc de dimensiune fixă).
Furnizați două exemple de situații în care un page fault nu conduce la trimiterea unei excepții de memorie (de tipul segmentation fault) procesului.
3CB/CC, varianta 2
De ce este util să avem priorități dinamice pentru planificarea proceselor, nu priorități statice?
Răspuns: Dacă am folosi doar priorități statice, care nu se modifică pe durata execuției procesului, atunci procesul cu prioritatea statică cea mai bună ar acapara pentru o perioadă nedeterminată procesorul. Și alte procese ar ajunge să folosească puțin (spre deloc) procesorul, rezultând în starvation. Folosirea priorităților dinamice duce la alterararea la rulare a priorității proceselor și, în acest fel, la creșterea prioriății proceselor care nu au rulat și scăderea priorității celor care au rulat, ducând la o folosire mai echitabilă a procesorului și, în acest fel, la evitarea fenomenului de starvation.
De câtă memorie RAM poate dispune un sistem cu procese având spațiu virtual de adrese de o dimensiune dată (de exemplu 4GB)?
Răspuns: Nu există o legătură directă între dimensiunea spațiului virtual de adrese al procesului. Dimensiunea spațiului virtual de adrese este dată de capacitatea de adresare virtuală, iar spațiul fizic (RAM) de capacitatea de adresare fizică (dimensiunea magistralei de adrese dintre procesor și memoria RAM). În teorie, sistemul poate dispune de oricâtă memorie RAM (cât îi permite magistrala de adrese) independent de capacitatea spațiului virtual de adrese al procesului. De exemplu, un output al comenzii cat /proc/cpuinfo: address sizes : 36 bits physical, 48 bits virtual
Când apare fenomenul de “memory thrashing”?
Răspuns: Fenomenul de memory thrashing apare în momentul în care set size-ul (spațiul fizic folosit) al proceselor sistemului depășește capacitatea memoriei RAM: fie multe procese, fie procese cu consum mare de memorie RAM (set size mare). În acest caz, un proces care rulează are nevoie de multe pagini în RAM și le aduce de pe swap (swap in), evacuând pagini ale altui proces (swap out); apoi acel proces este planificat și face operația similară. Rezultă așadar un transfer constant între RAM și disc care afectează performanța.
Greșeli frecvente
Lucrări foarte bune
POPA Ștefan-Adrian, 331CA
SAVA Iulia, 331CA
EFTIMIE Denis, 332CA
DIACONU Cristian, 333CA
MARINICĂ Elena, 335CA
PĂNĂTĂU Liana-Maria, 335CA
COTEȚ Teodor-Mihai 333CA
CURCUDEL Ioan-Răzvan 335CA
ROTSCHING Cristofor 343C1
PRIPOAE Silvia 331CA
ȘTEFĂNESCU Andrei 333CA
SANDU Denisa 332CA
ELISEI Alexandru 333CC
IONESCU-TĂUTU Mihai Andrei, 331CA
FUSU Elian, 332CB
Lucrare 3
3CA, varianta 1
Enumerați o caracteristică a limbajelor C/C++ care permite apariția vulnerabilităților de tip buffer overflow.
Dați un exemplu de cod care folosește instrucțiunea cmpxchg pentru a implementa un mutex.
Răspuns:
void lock (struct mutex* m){
while(atomic_cmpxchg(s->val,1,0)==0){
add_proc_to_mutex_waiting_list;
scheduler();
}
}
void unlock(struct mutex* m){
atomic_set(s->val,1);
mark_waiting_processes_as_ready;
}
Explicați cum pot fi lansate atacuri de tip buffer overflow împotriva unui server care execută fork() pentru a procesa cererile fiecărui client. Se folosesc stack canaries pe 32 biți.
3CA, varianta 2
De ce nu sunt posibile buffer overflows în Java?
Dați un exemplu de cod care folosește instrucțiunea cmpxchg pentru a implementa un semafor.
Răspuns:
void sem_up (struct sem* s){
while(atomic_cmpxchg(s->mutex,1,0)==0);
m->semval++;
atomic_set(s->mutex,1)
wake_up_waiting_processes;
}
void sem_down(struct sem* s){
while (1){
while(atomic_cmpxchg(s->mutex,1,0)==0);
if (m->semval>0){
m->semval--;
atomic_set(s->mutex,1);
return;
}
else {
Add_process_to_waiting_list_for_s;
atomic_set(s->mutex,1);
}
}
}
Explicați cum putem afla adresa aproximativă a segmentului de cod pe un sistem de 64 biți cu ASLR care are o vulnerabilitate de tip buffer overflow. Procesul vulnerabil execută fork() pentru a procesa fiecare cerere a clienților.
Răspuns: Adresa de intoarcere de pe stiva poate fi leaked catre atacator astfel; atacatorul poate incerca sa ghiceasca primul octet executand overflow cu o valoare aleasa de el (e.g. 0); daca clientul continua sa functioneze, adresa a fost buna; altfel conexiunea se va inchide pentru ca client a fost terminat de SO; atacatorul incearca apoi urmatoarea valoare (adresa de retur este aceeasi pentru ca serverul face fork la fiecare client), si asa mai departe pana gaseste un octet bun. Se continua la fel cu urmatorii octeti pana cand se gaseste o adresa completa valida in segementul de cod de la server.
3CB/CC, varianta 1
Un program are o vulnerabilitate de tipul buffer overflow. Sistemul pe care rulează are suport DEP (Data Execution Prevention). Cum puteți obține un shell din exploatarea programului?
Răspuns: Dacă sistemul are suport DEP, nu putem să injectăm cod pe care să-l executăm (în forma unui shellcode). Va trebui să apelăm cod deja existent. Pentru a obține un shell cel mai bine este să apelăm system(“/bin/bash”) adică să generăm un payload care să apeleze cod din biblioteca standard C (return-to-libc attack); facem acest lucru prin suprascrierea adresei de retur cu adresa funcției system() și a transmiterii corespunzătoare a adresei șirului “/bin/bash”.
Numiți un avantaj și un dezavantaj al folosirii thread-urilor în locul proceselor pentru o aplicație care folosește paralelism în execuție.
Răspuns: În momentul în care avem o aplicației cu paralelism în execuție, folosirea thread-urilor are avantajul productivității: fiecare thread va rula pe un core și vom paraleliza astfel programul și vom obține speedup. Este de asemenea, foarte facil să partajăm informațiile între thread-uri. Dezavantajul este că thread-urile pot să acceseze în mod incoerent datele partajate (întreg spațiul de adrese al procesului) rezultând în erori în execuție sau rezultate nedeterministe. De asemenea, dacă un thread execută o operație nevalidă, întreg procesul își va încheia execuția.
Mai multe thread-uri folosesc o listă simplu înlănțuită ca structură comună. Parcurg, adaugă și șterg elemente din listă. Ce se întâmplă dacă nu asigurăm accesul exclusiv la listă?
3CB/CC, varianta 2
Descrieți o situație în care un apel fgets() are o vulnerabilitate de tipul buffer overflow. Semnătura funcției fgets() este char *fgets(char *s, int size, FILE *stream);
Răspuns: Funcția fgets() are o vulnerabilitate în cazul în care se citesc mai mulți octeți decât este dimensiunea buffer-ului. Astfel dacă avem o definiție de forma char buffer[32]; și apelăm fgets(stdin, 64, buffer), astfel încâte 64 > 32, vom avea buffer overflow și se va suprascrie dincolo de limitele buffer-ului.
Pe un sistem Unix modern, crearea unui proces folosind fork() este foarte rapidă. Totuși, durează de câteva ori mai mult decât crearea unui thread. Care e cauza principală pentru această diferență?
Răspuns: Atunci când un thread se creează în afara creării unei structurii de tipul TCB (Thread Control Block) nu se execută multe operații. În cazul unui proces, pe un sistem Unix modern, se creează un spațiu virtual de adresă nou, dar cu aceleași informații fizice ale procesului vechi. Adică se creează o tabelă de pagini nouă care este populată cu tabela de pagini a vechiului proces, rezultând într-un timp mai lung de creare a unui proces față de un thread.
Când este recomandat să folosim spinlock-uri în loc de mutex-uri?
Răspuns: Folosim spinlock-uri în loc de mutex-uri în cazul în care regiunea critică pe care o vom proteja este de mici dimensiuni. În această situație, overhead-ul de lock pe mutex durează prea mult față de timpul petrecut în regiunea critică și atunci folosim spinlock-uri. Regiunea critică de mici dimensiuni și fără operații blocante este un candidat pentru folosirea de spinlock-uri în loc de mutex-uri.
Greșeli frecvente
Lucrări foarte bune
Lucrare 4
3CA, varianta 1
Cum putem transfera datele de la un dispozitiv de intrare-ieșire utilizând cât mai puțin procesorul?
Un programator folosește două apeluri send() pentru a transmite cu TCP dimensiunea mesajului și conținutul său, respectiv, după care așteaptă răspunsul apelând recv(). Ce probleme de performanță pot aparea?
Explicați diferența dintre un hard link și symlink într-un sistem de fișiere UNIX.
3CA, varianta 2
Dați un exemplu în care este mai eficient să folosim polling decât întreruperi pentru a aștepta date de la un dispozitiv de intrare-ieșire.
Ce ni se garantează atunci când apelul send(socket, …) întoarce N > 0?
De ce nu este corect să măsurăm performanța unui dispozitiv de stocare folosind doar apelul write()?
3CC, varianta 1
Precizați un avantaj și un dezavantaj în folosirea operațiilor I/O asincrone.
Răspuns: Avantaj este că nu se blochează programul și putem rula mai multe acțiuni (asincrone) în paralel. Dezavantajul este că trebuie să avem mecanisme de așteptare a operațiilor asincrone care fac programarea mai dificilă; în momentul în care o operație se încheie ne va notifica de acest lucru (care va întrerupe fluxul normal de execuție) sau va trebui să apelăm o funcție dedicată (de obicei blocantă) de așteptare. Uzual este nevoie de un automat de stări care să mențină starea operațiilor asincrone, lucru de asemenea dezavantajos.
De ce este necesar suportul de TCP Offload Engine pentru legături de rețea de mare viteză (precum 10Gbit)?
Un director conține 100 de intrări identificate ca fișiere obișnuite (regular files). Care este numărul minim de inode-uri referite de acele intrări?
3CC, varianta 2
De ce este avantajoasă folosirea DMA (Direct Memory Access) în cazul operațiilor de intrare/ieșire?
Răspuns: Folosirea DMA duce la eliminarea procesorului din fluxul de prelucrare a unor date. Datele ajung de la dispozitiv la memoria RAM și invers fără intervenția procesorului. În felul acesta procesorul poate fi folosit pentru activități precum rularea codului proceselor, mărind productivitatea sistemului.
Un apel send se blochează pentru că buffer-ul de send din kernel al socket-ului este plin. Care este o cauză posibilă pentru acest lucru?
Răspuns: Buffer-ul de send al unui socket se poate bloca din cauză că a apărut o congestie la un middlebox pe traseul pachetului. Până la eliberarea congestiei bufferul de send rămâne plin și blochează apelul send(). Pe lângă aceasta, buffer-ul de receive al destinatarului/receiver-ului se poate să fie umplut și nu poate primi noi date, blocând și buffer-ul de send. Deblocarea se va face în momentul în care se realizează un apel de tip recv() la receiver care va goli parte din buffer-ul de receive și astfel și parte din buffer-ul de send.
Ce conțin blocurile de date indicate de inode-ul unui director?
Răspuns: Blocurile de date indicate de inode-ul unui director conțin un vector de dentry-uri (intrări de tip directory) pentru intrările conține de director. Acestea conțin, în general, numele intrărilor și indexul inode-ului.
Greșeli frecvente
Lucrări foarte bune
Examene anterioare