Examen CA/CC 2015-2016

Urmăriți precizările din pagina de reguli.

Examen final

Puteți participa la un singur examen final.

Datele de examen de SO pentru sesiunea iunie 2016 sunt:

  • sâmbătă, 4 iunie 2016, ora 08:00, sala EC101, refacere materie anul 4 seria CB
  • marți, 7 iunie 2016, ora 11:00, sala EC101, 331CB, 333CB
  • miercuri, 8 iunie 2016, ora 08:00, sala EC002, 332CB
  • miercuri, 8 iunie 2016, ora 14:00, sala EC004, refacere materie anul 4 seriile CA+CC
  • vineri, 10 iunie 2016, ora 09:00, sala EC002, refacere materie anul 4 seriile CA+CC
  • joi, 16 iunie 2016, ora 08:00, sala A02, 3CC
  • vineri, 17 iunie 2016, ora 08:00, sala PR001, 3CA
  • vineri, 17 iunie 2016, ora 08:00, sala EC004, 334CB, 335CB

Datele de examen de SO pentru sesiunea septembrie 2016 sunt:

  • marți, 30 august 2016
  • luni, 5 septembrie 2016

Detalii despre examen găsiți în secțiunea aferentă din pagina de reguli.

Foi de examen

Lucrări

  • Dacă nu puteți participa la seria fiecăruia, puteți veni la cealaltă serie. Pentru aceasta trimiteți un e-mail catre Răzvan cu subiectul [SO][Lucrare X] Transfer Prenume Nume, Grupa unde:
    • X este indexul lucrării (1, 2, 3 sau 4)
    • Prenume este prenumele.
    • Nume este numa.
    • Grupa este grupa.
  • Nu există sesiune de contestații pentru lucrările de curs. În cazul în care considerați că au fost lipsuri la corectarea lucrării, trimiteți un e-mail catre Răzvan.
    • Folosiți subiectul [SO][Lucrare X] Prenume Nume, Grupa; de exemplu [SO][Lucrare 1] Andreea Popescu, 332CA.
  • Pentru a fi punctat, răspunsul la o întrebare trebuie să fie justificat.

Lucrare 1

  • La începutul cursului 4:
    • marți, 15 martie 2016, 08:00-08:15, EC004, seria CA
    • miercuri, 16 martie 2016, 17:00-17:15, EC004, seria CC

3CA, varianta 1

  1. De ce este importantă separația user mode - kernel mode?
    • Răspuns: Separația kernel mode - user mode este importantă pentru că asigură un mod privilegiat de execuție (kernel mode) pentru operații critice. Un mod privilegiat în care rulează sistemul de operare înseamnă că operațiile critice (IPC, lucrul cu I/O, lucrul cu memoria) vor fi validate de sistemul de operare și un proces (aplicație user space) nu poate face pagube sistemului. Pentru operații privilegiate va fi necesară trecerea în kernel mode prin intermediul unui apel de sistem, și astfel invocarea sistemului de operare care acționează ca un gardian al operațiilor, garantând securitatea și integritatea sistemului.
  2. fd1 este un descriptor de fișier, iar fd2 = dup(fd1). Precizați ce se întamplă cu cursorul de fișier al fd1 atunci când scriem în fd2.
    • Răspuns: Apelul dup() duplică un descriptor de fișier în alt descriptor de fișier, astfel încât ambii descriptori referă aceeași structură de fișier deschis. Referind aceeași structură de fișier deschis, descriptorii partajează cursorul de fișier (aflat în structura de fișier deschis). Atunci când scriem folosind descriptorul de fișier fd2, modificăm (creștem) cursorul de fișier lucru vizibil și în cadrul descriptorului fd1. Este, practic, vorba de același cursor de fișier.
  3. În Bash, executăm comanda sleep 10. Precizați pașii pe care îi va realiza procesul Bash pentru a rula această comandă.
    • Răspuns: Procesul Bash folosește fork() pentru a crea un proces copil identic sieși (clonă a sa) și apoi exec("sleep") în procesul copil pentru rularea codului din executabilul sleep. În procesul părinte se apelează wait() pentru a aștepta încheierea execuției procesului copil (sleep), procesul Bash apărând blocat utilizatorului până la încheierea comenzii (nu se pot introduce comenzi noi în Bash).

3CA, varianta 2

  1. Apelurile printf(...) și write(1,...) pot fi folosite pentru a afișa text la consolă. Care este diferența principală dintre ele?
    • Răspuns: Diferența principală este că cele două apeluri este că printf() este un apel de tipul buffered, adică scrie într-un buffer de memorie în user space. Doar când buffer-ul este plin sau când se apelează fflush() sau când se întâlnește newline (\n) se face apel de sistem. write() realizează mereu apel de sistem. printf() oferă potențial avantaj de overhead (nefăcând apel de sistem de fiecare dată) cu nevoia alocării unui buffer de memorie în user space.
  2. Un proces scrie în fiecare secundă PID-ul și timpul curent în fișierul f.txt cu ajutorul descriptorului fd. Procesul execută fork(). După fork() ambele procese scriu PID-ul și timpul curent în fd. Care dintre procese va reuși să scrie în f.txt?
    • Răspuns: După fork() procesul copil moștenește tabela de descriptori de fișiere a procesului părinte astfel încât și acesta are acces la fișierele folosite până atunci de procesul părinte. Ambele procese vor putea scrie în fișierul f.txt. După fork() procesele partajează structura de fișier deschis astfel încât o modificare făcută de un proces (a cursorului de fișier) va fi vizibilă și în celălalt proces.
  3. În Bash, executăm comanda sleep 10 &. Precizați pașii pe care îi va realiza procesul Bash pentru a rula această comandă.
    • Răspuns: Procesul Bash folosește fork() pentru a crea un proces copil identic sieși (clonă a sa) și apoi exec("sleep") în procesul copil pentru rularea codului din executabilul sleep. În procesul părinte nu se apelează wait(), procesul Bash nu așteaptă încheierea procesului sleep astfel încât utilizatorul poate introduce comenzi noi în Bash.

3CC, varianta 1

  1. Indicați un avantaj și un dezavantaj al unui kernel monolitic față de un microkernel.
    • Răspuns: Avantaj pentru un nucleu monolitic sunt viteza de execuție (overhead-ul redus). Dezavantaje pentru un nucleu monolitic sunt securitatea potențal mai slabă (întrucât kernel-ul este mai mare și are suprafață de atac mai mare, spunem că are TCB (Trusted Computing Base) mai mare); este mai puțin modular decât un microkernel însemnând o toleranță la defecte mai redusă și o proiectare mai greu extensibilă.
  2. Indicați două funcții de lucru cu fișiere care alterează cursorul de fișier.
    • Răspuns: Funcții care alterează cursorul de fișier sunt write() și read() (care modifică cursorul de fișier în momentul în care se citește un buffer); modificarea se face în sens pozitiv cu numărul de octeți citiți/scriși. Modificare se poate face la orice punct din fișier folosind funcția lseek(). Funcția open() plasează cursorul de fișier la începutul sau sfârșitul fișierului funcție de prezența opțiunii O_APPEND.
  3. Câte procese se pot găsi la un moment dat în fiecare dintre cele trei stări ale unui proces: READY, RUNNING și WAITING?
    • Răspuns: În starea RUNNING se pot găsi între 0 și N procese unde N este numărul de procesoare. Un proces are nevoie de un procesor pentru a putea rula. În stările READY și WAITING se pot găsi oricâte procese, limitarea fiind dată de resursele sistemului (memorie). Nu există constrângeri puternice pentru stările READY și WAITING.

3CC, varianta 2

  1. Care este un avantaj și un dezavantaj al folosirii apelurilor de sistem?
    • Răspuns: Apelurile de sistem au avantajul că oferă acces la serviciile nucleului (privilegiate) și tot prin intermediul acestuia obține acces la resursele hardware ale sistemului. Au ca dezavantaj overhead-ul; trecerea din user mode în kernel mode induce overhead de execuție.
  2. Indicați două informații din metadatele unui fișier (excluzând numele fișierului).
    • Răspuns: Metadatele fișierului conțin informații despre utilizarea fișierului. Metadate sunt: dimensiunea fișierului, timpii de acces, user, group, permisiuni de acces, indecși către blocurile de date, tipul fișierului.
  3. Ce se întâmplă cu un proces zombie rămas orfan?
    • Răspuns: Un proces rămas orfan este adoptat de init. Și un proces zombie rămas orfan este adoptat de init; doar că, spre deosebire de un proces non-zombie, procesul zombie este apoi "terminat" de init (zombie reaping) și informațiile rezidente legate de modul în care și-a încheiat execuția sunt șterse.

Greșeli frecvente

  • Verbul a crea are formele: a crea, creează, a creat, creare. Vedeți și articolul de la adresa https://diacritica.wordpress.com/tag/a-crea-sau-a-creea/
  • & înseamnă rulare în background, dar când vorbim de internele lucrului cu procese semnificația sa este că procesul părinte nu așteaptă (nu apelează wait() sau WaitForSingleProcess()) după procesul copil. Rularea în background este o denumire de comportament în shell; internele shell-ului (pașii urmați de shell) se referă la faptul că nu se apelează wait() de procesul părinte.
  • Rularea în background-ul shell-ului nu înseamnă proces daemon. Procesul daemon este detașat de terminal; un proces care rulează în background poate fi adus în foreground-ul shell-ului. Spunem că un proces daemon rulează în background-ul sistemului, complet detașat de un terminal sau de un shell. Background-ul shell-ului diferă de noțiunea de background al sistemului pentru procese deamon (decuplate de terminal).
  • Afirmația printf() scrie mereu la stdin, pe când write() poate scrie la un descriptor de fișier ce poate fi redirectat. este falsă. Redirectarea se face la nivelul descriptorului de fișier, nu ține de apelul folosit. Și printf() folosește un descriptor de fișier (descriptorul 1) care poate redirectat, caz în care ce scriem folosind printf() va ajunge în fișierul în care am realizat redirectarea.
  • Afirmația fd1 pointează către fișier nu este complet corectă. Corect este pointează către o structură de fișier deschis; este vorba de o structură în memorie creată după deschiderea fișierului. Fișierul stă pe disc indiferent dacă este deschis sau nu de un proces, structura de fișier deschis stă în memorie și este creată în momentul deschiderii fișierului de pe disc de un proces.
  • Când vorbim despre pașii pe care îi realizează procesul Bash (sau orice alt proces) ne referim la internele operațiilor (API-ului) realizate de Bash. Stările prin care trece procesul Bash (sau orice alt proces) (RUNNING, READY, WAITING) țin de modul în care acest proces este planificat de planificatorul/scheduler-ul sistemului de operare. Nu sunt pași pe care procesul îi realizează.
  • Există o confuzie între modurile de operare și utilizatori; de la exprimări de genul pentru că un utilizator să nu strice kernel-ul, până la afirmația utilizatorul root operează în kernel mode. Cel mai bine este să privim user mode/user space ca fiind application mode; nu are legătură cu utilizatorul/permisiunile utilizatorului ci cu procesele/aplicațiile; se referă la nivelul de privilegiu al proceselor/aplicațiilor. Procesele/Aplicațiile pornite de utilizatorul root rulează, de asemenea, în user space. Diferența este că la trecerea în kernel mode (prin apel de sistem) acestea au privilegii superiorare; un apel kill() de trimitere a unui semnal către un proces va fi tratat de kernel în mod diferit dacă procesul/aplicația care l-a inițiat a fost rulată de root sau de un utilizator obișnuit.
  • Afirmația În starea WAITING există un singur proces. este nevalidă. Starea WAITING este starea în care sunt procese blocate în așteptarea unui eveniment care să le deblocheze. Pot fi oricât de multe procese în starea WAITING în limita resurselor sistemului.
  • Ca avantaj pentru apeluri de sistem a fost precizat Apelurile de sistem sunt mai rapide. Apelurile de sistem au overhead, nu sunt neapărat rapide. Prin comparație cu apeluri de bibliotecă, apelurile de sistem sunt mai lente.
  • Afirmația Cursorul de fișier e metadată a fișierului. este falsă. Fișierul rezidă pe disc, structura de fișier deschis rezidă în memorie și este creată când este deschis un fișier de proces; structura de fișier deschis conține cursorul de fișier. Metadatele fișierului se referă la fișierul de pe disc, iar aceste metadate nu conțin cursorul de fișier.
  • Afimația Cursorul de fișier e afectat de apelul close(). nu este validă. Apelul close() invalidează un cursor de fișier și eventual dezalocă structura de fișier deschis ce conține cursorul de fișier; dar nu schimbă valoarea cursorului de fișier.
  • Afirmația Un proces zombie nu poate fi orfan. este falsă. Un proces devine zombie când își încheie execuția dar nu este încă așteptat de procesul său părinte. Un proces zombie devine orfan când procesul său părinte își încheie execuția, fără să îl aștepte.

Lucrări foarte bune

  • DUMITRU Mihai-Valentin, 335CB
  • MURARU George-Cristian, 333CB
  • IONESCU Bianca-Raluca, 331CC
  • MIHAI Darius, 331CC
  • BĂLAN Alexandru, 332CC
  • DIMOS Alexandros, 332CC
  • POȘTOACĂ Andrei Vlad, 332CC
  • OPREA George Bogdan, 333CC
  • MARINUȘ Alexandru, 334CC
  • RACU Roxana, 335CC
  • ROTARU Alexandru Andrei, 335CC
  • ONOSE Cristian, 342C3
  • PAVEL Nicolae Teodor, 333CA
  • TUFĂ Adriana 333CA
  • COSTEA Dragos Florin 334CA

Lucrare 2

  • La începutul cursului 7:
    • marți, 5 aprilie 2016, 08:00-08:15, EC004, seria CA
    • miercuri, 6 aprilie 2016, 17:00-17:15, EC004, seria CC

3CA, varianta 1

  1. Precizați un avantaj și un dezavantaj al multitasking-ului preemptiv pe sisteme cu un singur procesor.
    • Răspuns: Multitasking-ul preemptiv pe un singur procesor mărește interactivitatea sistemului, planificând rând pe rând procesele pe procesor, dând șansa fiecăruia să ruleze. Este avantajos pentru că limitează process starvation. Are dezavantajul lipsei de throughput întrucât un proces va fi întrerupt când îi expiră cuanta de timp alocată. De asemenea, e dezavantajos pentru că nu mai avem determinism la nivelul rulării și nu putem estima cu precizie când un proces care știm cât ar trebui să ruleze își va încheia execuția.
  2. Explicați cum folosește sistemul de operare Translation Lookaside Buffer (TLB).
    • Răspuns: În momentul în care este accesată o adresă virtuală, trebuie realizată translatarea acesteia în adresă fizică, informație care se găsește în tabela de pagini, dar cached în TLB. Se caută prima oară in TLB dacă există intrarea pentru pagina virtuală corespunzătoare adresei și se extrage de acolo pagina fizică. Dacă există (TLB hit), se extrage adresa paginii fizice și se calculează adresa fizică și se accesează. În caz contrar (TLB miss) se parcurge tabela de pagini a procesului și, în caz de intrare validă, se populează o intrare în TLB cu valoarea în cauză.
  3. Procesul A folosește 3MB de memorie fizică. A execută fork() și creează procesul B. Înainte ca A sau B să își continue execuția, care va fi consumul total de memorie fizică al celor două procese?
    • Răspuns: Întrucât fork() folosește copy-on-write, imediat după fork() procesul părinte și procesul copil, deși cu spațiii virtuale diferite, vor partaja spațiul fizic de memorie al procesului părinte. Consumul total de memorie va rămâne de 3MB.

3CA, varianta 2

  1. Precizați două dezavantaje ale algoritmului de planificare Shortest Job First (SJF).
    • Răspuns: Un dezavantaj este faptul că poate duce la process starvation. Dacă în sistem apar în continuu procese noi și care durează puțin, procesele care durează mult nu vor fi planificate. Acest dezavantaj poate fi exprimat și prin timp mare de așteptare pentru procesele cu durată mare. Sau poate fi exprimat și prin interactivitate redusă. Un alt dezavantaj este că nu putem estima cu precizie durata unui proces ca să putem face o planificare de tip Shortest Job First. Nu există dezavantaje legate de priorități sau de cuante de timp pentru că este un sistem de planificare nepreemptiv.
  2. Detaliați pașii pe care îi execută Memory Management Unit (MMU) pentru a rezolva o adresă virtuală într-o adresă fizică.
    • Răspuns: MMU primește o adresă virtuală și are un pointer (de exemplu registrul CR3 pe x86) la tabela de pagini a procesului care a făcut acces la acea adresă. Calculează adresa pagini virtuale aferente adresei și, în primă fază, caută existența intrării în TLB. Dacă există, extrage adresa paginii fizice aferente. Dacă nu există, accesează tabela de pagini a procesului în memoria RAM și caută acolo corespondența. Dacă există în TLB sau în tabela de pagini calculează adresa fizică corespunzătoare adresei virtuale inițiale și accesează memoria RAM. Dacă nu există în TLB și nici în tabela de pagini generează un page fault.
  3. Explicați de ce nu este nevoie de TLB flush în cadrul unui apel de sistem.
    • Răspuns: În sistemele de operare moderne, spațiul virtual de adrese al tuturor proceselor are rezervată zonă pentru nucleul sistemului de operare. În acest fel, în momentul unui apel de sistem și al schimbării din user mode în kernel mode, spațiul virtual de adrese nu se schmbă iar intrările din TLB sunt valide. La apelarea și la revenirea din apel de sistem nu are loc schimbare de context și nu este nevoie de TLB flush.

3CC, varianta 1

  1. De ce este problematică folosirea unui planificator care folosește doar priorități statice?
    • Răspuns: Dacă un planificator folosește doar priorități statice atunci procesele care au priorități superioare vor fi preferate în fața celor cu priorități inferioare. Acest lucru rezultă într-un sistem mai puțin interactiv, cu timp mediu de așteptare mare și riscul de process starvation pentru procesele cu priorități inferioare. Singurul moment în care procesele cu priorități inferioare vor putea rula este atunci când procesele cu priorități superioare sunt toate blocate sau când nu mai există astfel de procese în sistem.
  2. Cum se realizează partajarea memoriei între două procese pe un sistem care folosește paginare și memorie virtuală?
    • Răspuns: Pe un sistem cu paginare și memorie virtuală, fiecare proces dispune de o tabelă de pagini care face asocierea între pagini virtuale și pagini fizice. Două procese diferite pot avea astfel corespondențe de corespondențe la aceeași pagină fizică; paginile virtuale sunt diferite dar vor corespunde aceleiași pagini fizice.
  3. De ce dereferențierea unei adrese virtuale peste 3GB cauzează terminarea/omorârea procesului pe un sistem Linux pe 32 de biți?
    • Răspuns: Un sistem Linux pe 32 de biți rezervă pentru spațiul virtual de adrese al fiecărui proces zona [3GB,4GB] pentru sistemul de operare. Această zonă este protejată și accesibilă doar din kernel mode. Dacă un proces accesează o astfel de zonă (prin dereferențierea unei adrese virtuale mai mari de 3GB), va primi un page fault și va fi omorât, nefiind validă accesarea acestei adrese din user mode.

3CC, varianta 2

  1. De ce preferăm să oferim o cuantă de timp de planificare (scheduling time slice) mai mare proceselor I/O intensive (I/O bound)?
    • Răspuns: Preferăm o cuantă de timp mai mare proceselor I/O intensive pentru că acestea au probabilitate mare de a se bloca și de a elibera astfel procesorul, lăsând locul altor procese. Un proces I/O intensiv va părăsi astfel mai rar procesorul din cauză exprirării cuantei, mărind productivitatea (throughput-ul) sistemului (o schimbare de context suplimentară cauzează overhead).
  2. Un sistem x86 poate folosi pagini de 4KB sau de 4MB (numite și huge pages). Care este un avantaj al folosirii paginilor de 4KB și un avantaj al folosirii paginilor de 4MB?
    • Răspuns: Avantajul folosirii unei pagini mai mici, de 4KB, este că vom avea fragmentară internă redusă (la nivelul unei pagini). Alocarea a 10 octeți de date într-o pagină nouă lasă nefolosiți 4KB-10 octeți, relativ puțin față de același lucru întâmplându-se pentru o pagină de 4MB. Un alt avantaj este acela al granularității alocării, în cazul în care acest lucru ne interesează (corelează cu reducerea fragmentării interne). Avantajul folosirii unei pagini mai mare, de 4MB, este dimensiunea redusă a tabelei de pagini. Spațiul ocupat de tabela de pagini va fi de 1024 de ori mai mic decât în cazul folosirii de pagini de 4KB. Viteza de căutare în tabela de pagini va fi, de asemenea, redusă.
  3. Dați exemplu de două situații în care apariția unui page fault nu cauzează terminarea/omorârea procesului care a generat page fault-ul.
    • Răspuns: Situații în care un page fault nu cauzează terminarea procesului sunt:
      • pagina fizică aferentă este în swap și trebuie făcut swap in pentru folosirea acesteia;
      • pagina fizică nu a fost încă alocată și mapată, pentru că folosim mecanismul de demand paging;
      • pagina virtuală este read-only și marcată copy-on-write; rezultă un page fault care conduce la duplicarea paginii fizice aferente și marcarea paginii aferente read-write.

Greșeli frecvente

  • Prioritățile statice au problema că unele procese pot fi etern prioritare și pot să producă starvation pentru alte procese. Orice alte explicații sau precizări riscă să
  • Răspunsuri presupuse sau încercări de a nimeri un răspuns la o întrebare vor conduce de multe ori la un răspuns greșit. Dacă nu știți răspunsul la o întrebare, cel mai bine este să nu răspundeți la acea întrebare. Colecționăm perle și probabil le vom publica (anonime) pentru viitorii studenți.

Lucrări foarte bune

  • FLOREA Cătălin, 334CC
  • POȘTOACĂ Andrei Vlad, 332CC

Lucrare 3

  • La începutul cursului 10:
    • marți, 26 aprilie 2016, 08:00-08:15, EC004, seria CA
    • miercuri, 27 aprilie 2016, 17:00-17:15, EC004, seria CC

3CA, varianta 1

  1. Compilatorul folosește canary values pentru a proteja împotriva vulnerabilităților de tip stack buffer overflow, iar toate celelalte mecanisme de protecție sunt dezactivate. Explicați cum poate fi exploatat acest sistem.
    • Răspuns: Cu ajutorul canary values putem proteja spațiul dintre un buffer și adresa de retur. Dacă însă în urma unui stack buffer overflow ajungem să suprascriem un pointer de funcție și sărim astfel la acea funcție, sau dacă suprascriem o variabilă care este apoi folosită într-o comparație și alterăm astfel fluxul de execuție, atunci vom reuși să exploatăm programul. Alternativ, printr-o anumită formă de atac de tip information leak putem extrage valoarea canary value și apoi să suprascriem zona de canary value cu valoarea extrasă (adică valoarea inițială) și să suprascriem și adresa de retur a funcției și să executăm cod din altă zonă din spațiul de adresă. Întrucât canary value nu apare modificat, nu se va detecta o problemă.
  2. Precizați un avantaj și un dezavantaj al folosirii unei implementări de thread-uri user level față de o implementare de thread-uri kernel level.
    • Răspuns: O implementare de thread-uri user level are avantajul unor timpi de creare, schimbare de context și încheiere mult mai rapide. De asemenea, o implementare de thread-uri user level are avantajul că nu necesită suport la nivelul sistemului de operare. Totodată, implementatorul decide funcționarea planificatorului de thread-uri. Pe partea de dezavantaje o bibliotecă cu implementare de thread-uri user level blochează întreg procesul atunci când un thread se blochează, nu poate rula în format multicore (un thread pe un cor) dacă sistemul are așa ceva (sistemul de operare nu este conștient de existența mai multor thread-uri), iar implementaraea planificatorului este în general una colaborativă (nepreemptivă).
  3. Care este diferența dintre un deadlock și o condiție de cursă (race condition)?
    • Răspuns: Un deadlock blochează instanțele de execuție care sunt blocate în deadlock (thread-uri sau procese). O condiție de cursă înseamnă o ordonare necorespunzătoare a instanțelor de execuție astfel încât o instanță folosește date incoerente/corupte, ducând la un comportament arbitrar al programului.

3CA, varianta 2

  1. Un sistem are activat DEP (Data Execution Prevention) și ASLR (Address Space Layout Randomization). Într-un program care rulează pe acest sistem un hacker a descoperit o vulnerabilitate de tip stack buffer overflow. Ce poate face atacatorul ca să altereze execuția normală a programului?
    • Răspuns: Un atacator poate ajunge să suprascrie o variabilă care este apoi folosită într-o comparație pentru a altera astfel fluxul de execuție. Dacă un atacator suprascrie o adresă de funcție (pointer de funcție sau adresa de retur) atunci acesta poate sări undeva în codul programului (codul existent al programului, nu o bibliotecă al cărei cod este randomizat - ASLR); dacă în acest cod se găsesc elemente care pot fi folosite de atacator (probabil da), acesta va altera execuția programului pentru a îl exploata.
  2. Pe un sistem pe 32 de biți, un programator testează care este limita de procese pe care le poate crea (folosind fork() într-o buclă for într-un program) și care este limita de thread-uri pe care le poate crea (folosind pthread_create() într-o buclă for într-un program). De ce va putea crea mai multe procese decât thread-uri?
    • Răspuns: Când creăm thread-uri fiecare thread ocupă în spațiul virtual de adrese al procesului spațiu pentru stivă (în mod tipic 8MB pe Linux). Limita de thread-uri care poate fi creată este dată de limita de 4GB (pe 32 de biți, de fapt mai puțin pentru că există spațiu ocupat de kernel) pentru spațiul virtual de adrese al procesului. În cazul proceselor, se partajează memoria procesului, doar se creează structurile pentru noul proces. Limita este dată de spațiul fizic de memorie pe care îl are sistemul, în mod tipic suficient pentru a crea mai multe procese în sistem decât thread-uri într-un proces.
  3. Precizați două diferențe între spinlock-uri și mutex-uri.
    • Răspuns: Spinlock-urile folosesc busy waiting în timp ce mutex-urile folosesc blocare. Un spinlock nu reține informații despre thread-urile/procesele care îl folosesc, pe când un mutex menține coada de thread-uri/procese blocate. Un spinlock este util de folosit în regiuni critice de mici dimensiuni în vreme ce un mutex este folosit în regiuni critice de dimensiuni mai mari, eventual și în care thread-urile/procesele fac acțiuni blocante.

3CC, varianta 1

  1. De ce este ASLR (Address Space Layout Randomization) un mecanism de protecție mai puternic pe un sistem x86_64 (64 de biți) decât pe un sistem i386 (32 de biți)?
    • Răspuns: ASLR este un sistem care plasează la adrese aleatoare zone din spațiul de adresă al procesului (precum heap, stivă, biblioteci partajate). Întrucât este mai mult spațiu disponibili pentru de randomizare pe 64 de biți (de fapt 48 de biți de adrese virtuale) atunci va fi mult mai dificil de “șuntat” ASLR printr-un atac de tipul brute force (încercări repetate). Adică ASLR este mai puternic pe 64 de biți decât pe 32 de biți.
  2. Precizați două avantaje ale folosirii unei implementări de thread-uri kernel level față de o implementare de thread-uri user level.
    • Răspuns: Thread-urile cu implementare kernel level au avantajul că pot folosi suportul de multicore al sistemului fizic, putând fi planificate câte un thread per core și mărind nivelul de paralelism; thread-urile cu implementare user level nu pot folosi suportul de multicore. De asemenea, un într-o implementare kernel level, un thread care se blochează nu blochează întreg procesul, spre deosebire de thread-urile user level.
  3. Pentru sincronizarea accesului la o variabilă, un programator folosește într-un program multithreaded secvența de cod de mai jos. De ce este problematică această secvență în contextul sincronizării accesului?
    /* a is initially 0 */
    atomic_inc(&a);  /* increment a atomically */
    atomic_cmp_xchg(&a, 16, 0); /* atomically do: if (a == 16) a = 0; */
    • Răspuns: În secvența de mai sus este posibilă preemptarea unui thread (sau rularea simultană a unui alt thread pe un alt procesor) între cele două instrucțiuni. În acel caz, dacă valoarea variabilei este 15, va ajunge la 17 (două incrementări) iar comparația va eșua, contorul nu va fi resetat la 0. Operațiile în sine sunt atomice, dar ansamblul nu este atomic.

3CC, varianta 2

  1. Pentru exploatarea unei vulnerabilități de tip stack buffer overflow urmărim suprascrierea adresei de retur a unei funcții. Cu ce vom suprascrie adresa de retur în cazul unui atac de tipul return-to-libc?
    • Răspuns: În cazul unui atac de tipul return-to-libc vom suprascrie adresa de retur cu adresa unei funcții din biblioteca standard C. De exemplu, adresa funcției system() urmărind apelul system("/bin/bash").
  2. Un programator implementează o aplicație de tip server astfel încât la fiecare conexiune către server se creează un thread nou care se ocupă de gestiunea conexiunii. Care este dezavantajul acestei abordări și care este o alternativă?
    • Răspuns: Dezavantajul acestei abordări este că se creează un thread nou la fiecare conexiune (și care apoi este închis la sfârșitul tratării conexiunii). Se consumă foarte multe resurse și timp de creare/închidere a thread-ului. Alternativa este să folosim un pool de thread-uri care să servească la nevoie cereri sosite, fără nevoia de creare a unui thread la fiecare pas. Pornind de la ideea că thread-urile au dezavantajul că pot omorî întreg serverul la o eroare, putem considera și o alternativă de creare a unui proces, dar este mult prea “heavy” această abordare pentru o situație practică.
  3. Pentru comunicarea și pentru sincronizarea accesului într-un mediu multithreaded, un programator are de ales între folosirea unui vector sau a unei liste. Precizați un avantaj al folosirii vectorului în locul listei din punct de vedere al sincronizării.
    • Răspuns: Un vector este o structură de date ușor partiționabilă; dacă un thread dorește să modifice anumite elemente din vector va folosi câte un lock pentru o anumită secțiune/partiție a vectorului. În cazul unei liste este problematică partiționarea întrucât listele își pot modifica pointerii între elemente și duce la date incoerente; soluția la liste este un singur lock pe întreaga listă, rezultând în cod puternic serial.

Greșeli frecvente

Răspunsuri cu definiții despre buffer overflow, DEP, ASLR și nu răspuns la întrebare. Învârtit în jurul întrebării, scris ca să fie scris, dar fără răspuns la întrebare. Un soi de învârtit în jurul cozii.

Nu s-a înțeles că dacă ASLR este activat nu se pot face atacuri pe stivă sau în codul din biblioteci. Pentru că stiva și codul din alte biblioteci sunt plasate la adrese aleatoare.

Suprascrierea unei adrese nu este un atac. Trebuie să fie suprascrisă adresa cu o adresă validă care pointează către o zonă de cod executabil; suprascrierea cu o valoare neadecvată va duce la Segmentation fault dar nu exploatarea programului.

Într-un sistem cu DEP și ASLR, nu se poate sări la un cod injectat de atacator. Codul nu poate fi injectat pe un sistem cu DEP, pentru că nu se poate executa ceea ce a scris cineva.

Exprimarea “Limita de thread-uri este dată de numărul de core-uri” (sau alte exprimări echivalente) este nevalidă. Thread-urile care rulează sunt limitate de numărul de core-uri, dar nu cele care există la nivelul sistemului.

“Avantaj kernel level threads față de user level threads: omorârea unui thread nu omoară întreg procesul”. Și variante pe această temă: “Dacă se blochează un thread nu moare întreg procesul.” În ambele cazuri omorârea unui thread duce la încheierea procesului.

Legat de kernel level threads: “Dacă moare un thread în kernel, celelalte încă rulează. În user space dacă moare un thread, acesta generează o excepție care duce la oprirea întregului proces.” În ambele cazuri omorârea unui thread duce la încheierea procesului.

Lucrări foarte bune

  • CIOCÎRLAN Ștefan-Dan, 331CA
  • ȘTEFU Teodor, 332CC
  • CRUCERU Călin, 335CB
  • COMAN Tudor Emil, 333CC
  • RIȚĂ Cristian, 335CC
  • DINU Andrei Mario, 335CC
  • MURARU George, 333CB
  • POPA Maria Cătălina, 331CC
  • MATEȘICĂ Iulian-Răzvan, 331CC

Lucrare 4

  • La începutul cursului 13:
    • marți, 24 mai 2016, 08:00-08:15, EC004, seria CA
    • miercuri, 25 mai 2016, 17:00-17:15, EC004, seria CC

3CA, varianta 1

  1. Descrieți cum ajung datele din buffer-ul buf la dispozitivul de I/O în cazul apelului write(fd, buf, 100).
    • Răspuns: Datele din buffer sunt pentru început copiate în memorie în kernel space în urma unui apel de sistem. Apoi datele ajung în registrele dispozitivului cu ajutorul driverului de dispozitiv din kernel folosind, în general, întreruperi.
  2. Un transmițător efectuează un apel send(s,buf,10000,0) pe un socket TCP conectat. La receptor se apelează recv(s,buf,10000,0). Câți octeți se vor primi la receptor?
    • Răspuns: Datele pot ajunge în partiții (chunk-uri de date) diferite între transmitățor și receptor din cauza buffer-ului transmițătorului, modului de funcționare a rețelei și buffer-ul receptorului. Receptorul poate primi de la 1 octet până la toți cei 10000 de octeți și aceasta este valoarea întoarsă de apelul recv(): câți octeți sunt disponibili atunci în buffer-ul receptorului.
  3. Comanda rm f.txt rulează cu succes. În ce situație rularea acestei comenzi va duce la ștergerea fișierului f.txt pe un sistem de fișiere cu inode-uri?
    • Răspuns: Comanda rm desface un link (un hard link, un nume) de la un inode. Dacă numele/link-ul f.txt este singurul link la inode-ul aferent fișierului, atunci fișierul va fi șters de pe disc. Dacă există mai multe nume/link-uri la fișier, atunci desfacerea link-ului f.txt nu va conduce la ștergerea fișierului de pe disc.

3CA, varianta 2

  1. Dați un exemplu de situație în care folosirea polling este de preferat folosirii întreruperilor pentru comunicarea cu un dispozitiv I/O.
    • Răspuns: Polling este preferat întreruperilor dacă ar veni foarte multe întreruperi, adică dacă procesorul ar sta ocupat în întreruperi în loc să facă acțiuni de procesare. Astfel în cazul dispozitivelor care efectuează transferuri rapide de date și care, astfel, ar genera foarte multe întreruperi, este de preferat să folosim polling. Este cazul plăcii de rețea, care atinge viteze foarte mari (de exemplu plăci de rețea de 10Gbit).
  2. Când se întoarce cu succes apelul accept() în cadrul 3-way handshake-ul TCP? Justificați.
    1. după primirea primului pachet (conținând flag-ul SYN)
    2. după primirea celui de-al treilea pachet (conținând flag-ul ACK)
    • Răspuns: Apelul accept() se întoarce în momentul în care conexiunea a fost realizată și socket-ul TCP întors poate fi folosit pentru transmitere și recepție de date. Pentru aceasta, trebuie ca întreg handshake-ul să aibă loc pentru a confirma că receptorul și transmitățorul sunt conectați și că numerele de secvență inițiale (ISN: Initial Sequence Number) au fost negociate.
  3. Precizați un avantaj și un dezavantaj al folosirii FAT (file allocation table) pentru gestiunea spațiului într-un sistem de fișiere.
    • Răspuns: FAT are ca avantaje simplitatea (implementare ușoară) și eficiența stocării, având doar pointeri către indecși. Dezavantajul este că folosim un spațiu proporțional cu dimensiunea partiției care poate ajunge destul de mare pentru partiții mari. De asemenea, nu există suport implicit pentru metadate legate de securitate.

3CC, varianta 1

  1. Care este un avantaj și un dezavantaj al folosirii operațiilor I/O asincrone?
    • Răspuns: Operațiile asincrone au avantajul că nu sunt blocante și că se pot planifica mai multe acțiuni asincrone simultan fără a trebui să așteptăm încheierea acestora. Dezavantajele țin de dificultatea în implementare: e nevoie de o structură care să rețină starea operațiile asincrone realizate și de un mecanism de notificare și sincronizare în cazul încheierii operațiilor.
  2. În ce situație apelul send(s,buf,100,0) pe un socket se blochează?
    • Răspuns: Apelul send() se blochează în cazul în care buffer-ul de send al socketului TCP este plin. În acel moment kernel-ul nu poate copia nici măcar un octet din datele din user space în kernel space și blochează apelul. Acest lucru poate fi cauzat de o congestie în rețea care a prevenit ca datele să fie trimise prin rețea din buffer-ul de send.
  3. Cu ce diferă un director de un fișier obișnuit (regular file) din punctul de vedere al implementării sistemului de fișiere?
    • Răspuns: Un fișier obișnuit conține date nestructurate (byte stream). Un director conține date structurate, adică un vector de directory entries (dentry-uri): nume de fișier și index de inode.

3CC, varianta 2

  1. De ce, în lucrul cu discul, apelul write() în general NU se blochează?
    • Răspuns: În urma unui apel write() datele sunt, în cazul discului, copiate din user space în buffer cache de unde vor fi la un moment dat scrise pe disc. Apelul de sistem write() nu interacționează efectiv cu discul, ci doar copiază datele în buffer cache, operație care nu este blocantă (nu interacționează cu un dispozitiv de I/O).
  2. Ce apel de funcție pe socket declanșează handshake-ul TCP de inițiere de conexiune?
    • Răspuns: Funcția connect() este cea care, apelată din client conduce la realizarea conexiunii TCP (se creează 3-way handshake). Funcția creează un socket pe partea clientului care este unul dintre capetele conexiunii. În partea de server celălalt capăt este creat cu ajutorul apelului accept().
  3. De ce numele unui fișier NU este parte a inode-ului?
    • Răspuns: Numele unui fișier nu este parte a inode-ului din rațiuni de eficiență (multe acțiuni se fac doar pe nume și nu dorim să citim tot inode-ul pentru acest lucru) și pentru a permite hard link-uri, adică mai multe nume care să refere același fișier.

Greșeli frecvente

  • Apelul send() se blochează atunci când buffer-ul de receive este plin.
  • Apelul send() se blochează dacă nu există 100 de octeți liberi în buffer-ul de send.

Lucrări foarte bune

  • GHEORGHE Liviu-Adrian, 335CC

Examene anterioare

so/meta/examen/2015-2016.txt · Last modified: 2018/06/05 16:32 by razvan.deaconescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0