Examen

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 2019 sunt:

  • miercuri, 29 mai 2019: 3CC și anul 4
  • joi, 30 mai 2019: 3CB și anul 4
  • vineri, 14 iunie 2019: 3CA

Datele de examen de SO pentru sesiunea septembrie 2019 sunt:

  • TODO

Puteți veni o singură dată la examen. Studenții care refac materia pot veni doar în primele două date de examen (TODO și TODO). Pentru ei sesiunea este mai scurtă (TODO - TODO).

Studenții de anul 3 pot veni în alte zile față de cea aferentă grupei/seriei lor. Studenții în alte situații (de exemplu transferați de la IS) pot veni oricând. Cei care doresc să vină în altă zi față de cele alocate, să trimită un e-mail către Elena cu subiectul [SO][Examen] Transfer: Prenume NUME, grupă, de exemplu [SO][Examen] Transfer: Ana POPESCU, 332CB.

Informații despre desfășurătorul și conținutul examenului găsiți în secțiunea aferentă din pagina de notare. În pregătirea examenului recomandăm să parcurgeți subiectele de examen anterioare.

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

Foi de examen

  • TODO ( TODO )

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 Elena 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 propriu-zisă; dacă sunteți de părere că nu a fost corectată corespunzător lucrarea, trimiteți un e-mail (solicitare de recorectare) către Costin Carabaș. Cererile de recorectare se vor trimite după publicarea soluțiilor pe această pagină, până la data limită anunțată pe lista de discuții.
    • 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:
    • 11.03.2019, seria CA
    • 13.03.2019, seria CB
    • 13.03.2019, seria CC

3CA, varianta 1

  1. Explicaţi de ce apelul printf(”aici”) înainte de o instrucţiune care accesează o zonă de memorie invalidă nu afişează nimic la consolă.
    • Răspuns: Biblioteca va stoca local mesajul aici pentru a-l trimite la consolă atunci când bufferul se umple sau utilizatorul scrie EOL (i.e. \n). Atunci când procesul este întrerupt în urma accesului invalid la memorie, bufferul nu mai este scris la consolă.
  2. Explicați semnificația stării RUNNING în viața unui proces și tranzițiile posibile din această stare.
    • Răspuns: Procesul se află în execuție pe procesor. Dacă îi expiră cuanta va merge în starea READY; dacă face un apel blocant va tranziționa în starea WAITING. Dacă își încheie executța va ajunge în starea TERMINATED.
  3. Ce valori va afișa execuția codului următor atunci când fork reușește?
      int i = 0;
      if (fork()==0)
          i++;
      else sleep(1);
     
      printf(%d\n”,i); 
    • Răspuns: Va afișa 1 (scris de copil) urmat de 0 (scris de părinte).

3CA, varianta 2

  1. Explicați de ce apelul fwrite(..) este mai rapid decât write(..) atunci când facem multe scrieri.
    • Răspuns: fwrite scrie într-un buffer în memoria procesului și execută write cu mai multe date, reducând numărul de apeluri de sistem. Apelurile de sistem sunt costisitoare, iar reducerea lor creste performanța.
  2. Explicați semnificația stării READY în viața unui proces și tranzițiile către această stare.
    • Răspuns: Procesul este gata să ruleze însa așteaptă să fie planificat. Un proces ajunge în READY după ce este pornit, dacă este RUNNING si este preemptat, sau dacă este WAITING și operația pe care o asteptă se termină.
  3. Câte procese noi vor fi create la execuția codului următor atunci când fork reușește?
    int i = 0;
    for (i = 0;i<5;i++)
      if (fork()>0)
         break; 
    • Răspuns: Vor fi create 5 procese noi; de fiecare dată părintele își încheie execuția și copilul va continua bucla pâna când i devine 5.

3CB, varianta 1

  1. Numiți un rol al bibliotecilor în sistemul de operare și exemplificați.
    • Răspuns: Permit refolosirea codului de mai multe procese; o singură bibliotecă poate să fie mapată în memoria mai multor procese ocupând mai puțină memorie fizică. De asemenea, o bibliotecă poate fi o interfață între nucleu și procese.
  2. Fie secvența int fd2 = dup(fd). Execuția apelului de sistem write pe fd2 va influența în vreun fel apelurile ce folosesc ca parametru fd? Explicați.
    • Răspuns: Da, deoarece file offset-ul din structura alocată în kernel se va modifica prin apelul write pe fd2.
  3. Care este diferența între un proces zombie și unul orfan?
    • Răspuns: Un proces orfan este un proces al cărui părinte a murit, iar unul zombie este un proces care a murit, dar la care părintele nu a făcut wait.

3CB, varianta 2

  1. Care este motivul pentru care există noțiunea de “apel de sistem” într-un sistem de operare? De ce nu se fac pur și simplu apeluri simple de funcție?
    • Răspuns: Pentru izolarea proceselor, anumite funcționalități care pot influența alte procese sau integritatea sistemului au nevoie de un schimb de context către kernel unde se verifică dacă procesul are permisiunile necesare pentru efectuarea operației.
  2. Fie secvența int fd2 = dup(fd). Execuția apelului de sistem write pe fd va influența în vreun fel apelurile ce folosesc ca parametru fd2? Explicați.
    • Răspuns: Da, deoarece file offset-ul din structura alocată în kernel se va modifica prin apelul write pe fd.
  3. Numiți un avantaj și un dezavantaj al structurării ierarhice al proceselor într-un sistem de operare (în comparație de exemplu cu o structură liniară). Explicați.
    • Răspuns: Reprezentarea proceselor într-o ierarhie ajută la partajarea de date între procesul părinte și procesul copil, însă implementarea este mai dificilă, deoarece trebuie păstrate mereu legături cu procesul părinte (ex. Procesele orfane sunt adoptate de init).

3CC, varianta 1

  1. De ce preferăm să folosim un apel de bibliotecă care NU realizează în spate un apel de sistem în locul unui apel de bibliotecă ce face apel de sistem? De exemplu fwrite() în loc de write().
    • Răspuns: Un apel de sistem are un overhead mai mare decât un apel simplu de funcție întrucât se face schimbarea nivelului de privilegiu la apel și revenirea din apel, care consumă timp. Din acest motiv, dacă avem de ales, optăm pentru funcții de bibliotecă ce nu fac apel de sistem ca să reducem overhead-ul.
  2. Fie apelul close(fd) executat cu succes. Observăm că structura de fișier deschis referită de descriptorul fd (acum închis) nu este eliberată din memorie. De ce?
    • Răspuns: pelul close(fd) invalidează descriptorul fd și decrementează contorul de referință din structura de fișier deschis. Structura este dezalocată în momentul în care contorul de referință este 0. Dacă după close(fd) structura de fișier deschis nu a fost dezalocată înseamnă că mai există un descriptor care o referă, descriptor care a fost obținut printr-un apel dup() sau dup2(). Un apel open() nu este un răspuns corect, pentru că acela creează o structură nouă de fișier deschis.
  3. Un proces zombie devine și proces orfan: procesul său părinte își încheie execuția. Ce se întâmplă acum cu procesul zombie rămas orfan?
    • Răspuns: Un proces orfan, indiferent dacă este zombie sau nu este adoptat de init. Apoi init face “reaping” în procesele zombie, așteptându-le și eliminându-le astfel complet din sistemul de operare.

3CC, varianta 2

  1. Dați exemplu de acțiune care poate fi executată doar în modul privilegiat (kernel mode). Justificați de ce acea acțiune nu poate fi executată în modul neprivilegiat (user mode).
    • Răspuns: Acțiuni care pot fi executate doar în kernel mode sunt: lucrul cu I/O, forme de IPC (Inter-Process Communication), alocarea memorie. Acestea sunt privilegiate pentru a garanta integritatea sistemului și izolarea proceselor. Dacă ar fi executate în mod neprivilegiat, un proces ar avea acces la datele altui proces, i-ar putea suprascrie și corupe informații. Cu atât mai mult cu cât dorim separație între procese privilegiate (ce aparțin utilizatorului root) și procese neprivilegiate.
  2. Ce câmpuri din structura de fișier deschis și din structura de fișier de pe disc (FCB: File Control Block) modifică apelul write()? Argumentați răspunsul.
    • Răspuns: Apelul write() modifică pointer-ul/cursorul de fișier, îl crește cu numărul de octeți scriși în fișier; cursorul de fișier este reținut în structura de fișier deschis. În plus, dacă în urma scrierii octeților se trece de limita fișierului, atunci se modifică și dimensiunea fișierului; dimensiunea fișierului este reținută în structura de fișier de pe disc (FCB).
  3. Ce efect are, la nivel de implementare, folosirea operatorului & în shell?
    • Răspuns: Atunci când folosim operatorul & în shell, shell-ul nu mai așteaptă încheierea procesului nou creat. Adică shell-ul nu apelează wait() și nu se blochează în așteptarea încheierii execuției procesului. Din acest motiv shell-ul poate primi noi comenzi de la terminal.

Greșeli frecvente

  • Confuzie între procesele zombie și procesele orfan. Un proces zombie este un proces care și-a terminat execuția, dar pe care nu l-a “așteptat” părintele, iar un proces orfan este un proces al cărui părinte și-a încheiat execuția.
  • Confuzie între kernel mode și utilizator privilegiat. Chiar dacă utilizatorul root este privilegiat, este doar un utilizator. În continuare se face diferențierea între user mode și kernel mode.
  • Greșeli legate de utilizarea & în shell. În shell, & are rolul de a duce un proces în background, nu de a face o dereferențiere (ca de exemplu în C) sau de operator logic AND între comenzi (precum &&).
  • Confuzie între procesele trimise în background și daemoni. Unui proces trimis în background NU i se închid stdin, stdout și stderr.

Lucrări foarte bune

  • APOSTOL Teodor-Petruț, 336CA
  • ŞENDRE Mihai-Alin, 332CC
  • RADU Valentin-Gabriel, 333CC
  • BĂLĂNICĂ Darius, 334CC
  • COCOŞ Vlad, 334CC
  • DUMITRU Philip, 334CC
  • VIŞAN Anamaria, 334CC
  • FOLEA Rareş, 336CC
  • ILIE Vlad-Florin 331CB
  • POPESCU Teodor-Constantin 333CB
  • SMĂDU Răzvan-Alexandru 335CB
  • FIRUŢI Bogdan-Cristian, 331CA
  • IVAN Vlad, 331CA
  • VASILE Cristian-Ştefan, 331CA
  • ANDREI Rareş, 332CA
  • DRAGOMIR Horia-Alexandru, 333CA
  • POPA Bogdan, 333CA
  • BUCUR Adrian-Cătălin, 336CA
  • MARICA Andreea-Mădălina, 336CA

Lucrare 2

  • La începutul cursului 7:
    • 01.04.2019, seria CA
    • 03.04.2019, seria CB
    • 03.04.2019, seria CC

3CA, varianta 1

  1. Indicați trei surse de overhead care apar la schimbarea de context între două procese.
    • Răspuns: Context save/restore; TLB flushes; cache misses for app data, scheduler overheads.
  2. Cum poate un proces să își modifice propria tabelă de pagini?
    • Răspuns: Doar invocând apeluri de sistem; procesul nu își poate scrie singur tabela de pagini pentru că ar permite breșe de securitate.
  3. Explicați cum funcționează mecanismul copy-on-write pentru memoria virtuală.
    • Răspuns: Se mapează diferite secțiuni din executabil în memorie, inclusiv sectiunea text. Această mapare presupune adăugarea unor intrări în tabela de pagini, acestea sunt marcate ca fiind invalide, dar încărcabile de pe disk. Atunci când se accesează o adresă dintr-o astfel de pagină, MMU va genera un fault care va fi prins de SO; atunci SO va citi datele de pe disk într-un frame liber și va updata intrarea din tabela de pagini; instrucțiunea care a generat fault va fi repornită.

3CA, varianta 2

  1. Enumerați pașii efectuați de SO pentru schimbarea de context între două procese.
    • Răspuns: Save context; flush TLB; change PTBR; select proces to run from READY queue; load context.
  2. Descrieți pașii prin care o adresă virtuală este translată la o adresa fizică într-un sistem cu paginare.
    • Răspuns: MMU identifică pagina dorita prin inspectarea unui număr predefinit (e.g. 20) din biții cei mai semnificativi din adresa virtuală. Acest număr este căutat în TLB; dacă există o intrare, se înlocuiește pagina cu frame-ul asociat și se obține adresa fizică. Altfel MMU folosește PTBR pentru a localiza tabela de pagini activă și găsește frame-ul asociat la adresa PTBR + page * page_entry_size.
  3. Explicați mecanismul prin care SO încarcă în memorie codul unui executabil exact atunci când acel cod urmează să fie folosit.
    • Răspuns: Mai multe pagini (din mai multe procese) vor fi mapate la același frame, însă intrările respective vor fi marcate R/O. Atunci când se accesează una din aceste pagini, MMU generează trap care este rezolvat de SO prin duplicarea frameului, maparea paginii faulty la frame-ul nou cu permisiuni R/W și reluarea instrucțiunii faulty.

3CB, varianta 1

  1. Ce parametri ai planificatorului trebuie să modificăm și cum pentru a avea un sistem cu productivitate mai mare?
    • Răspuns: Pentru a avea un sistem cu productivitate mai mare putem mări cuanta de timp alocată unui proces din starea running. O cuantă de timp mai mare înseamnă un număr mai mic de context switch-uri, deci overhead mai mic, ceea ce duce la productivitate mai mare a sistemului per total.
  2. Menționați un avantaj și un dezavantaj al paginării față de segmentare. Explicați avantajul, respectiv dezavantajul.
    • Răspuns: Un avantaj al paginării față de segmentare este acela că paginarea nu duce la fragmentare externă. Deoarece paginile au dimensiuni fixe, nu vor exista situații în care să avem goluri ce nu pot fi refolosite/sunt greu de refolosit între două pagini alocate, față de cazul segmentării.
      Un dezavantaj al paginării față de segmentare este acela că paginarea duce la fragmentare internă. Deoarece paginile au dimensiuni fixe, în momentul în care avem de alocat o secțiune cu dimensiune mică relativă la dimensiunea paginii, se va aloca o pagină întreagă, iar memoria rămasă liberă în cadrul paginii nu poate fi refolosită de alt proces.
  3. La execuția unei instrucțiuni se generează un page-fault dar procesul care a rulat acea instrucțiune continuă să ruleze fără eroare. Motivați/descrieți de ce nu s-a încheiat execuția procesului.
    • Răspuns: Apariția unui page-fault nu generează neapărat un eveniment care să ducă la încheierea forțată a execuției programului. De exemplu, dacă instrucțiunea care a generat page fault-ul încercă să citească memorie dintr-o pagină care se află în swap, se generează un page-fault pentru aducerea paginii din swap. De asemenea, dacă instrucțiunea respectivă încearcă să scrie într-o pagină marcată ca fiind copy-on-write, se generează un page-fault pentru duplicarea paginii.

3CB, varianta 2

  1. Ce parametri trebuie să modificăm și cum pentru a avea un sistem cât mai responsive?
    • Răspuns: Pentru a avea un sistem cu responsivitate mai mare putem micșora cuanta de timp alocată unui proces din starea running. O cuantă de timp mai mică înseamnă un număr mai mare de context switch-uri, adică procesele ajung mai des și mai repede să ruleze pe procesor oferind un grad mai ridicat de responsivitate.
  2. Ce avantaj aduce noțiunea de ierarhie în paginare?
    • Răspuns: Un avantaj al paginării ierarhice este reducerea spațiului ocupat de tabela/tabelele de pagini.
  3. Fie următoarele 2 secvențe de cod:
    a) a = mmap(...., n * 1024 * sizeof(int),....);
       for(i = 0; i < n * 1024; i = i + 1024)
          a[i] = i;
    b) b = mmap(...., n * 1024 * sizeof(int),....);
       for(j = 0; j < n; j++)
          b[j] = j; 

    Care dintre secvențe este mai rapidă și de ce?

    • Răspuns: Snippet-ul de cod de la punctul b) se va executa mai rapid decât snippet-ul de cod de la punctul a) deoarece vor fi mai puține page-fault-uri. Numarul de iterații este același în ambele cazuri (n), dar în cazul a) se indexează relativ la 1024, generând page-fault la fiecare instrucțiune.

3CC, varianta 1

  1. De ce procesele I/O intensive primesc, în general, o cuantă de timp de rulare mai mare?
    • Răspuns: Procesele I/O intensive accesează des dispozitive I/O și execută des operații blocante. Aceasta înseamnă că un proces I/O intensive proaspăt planificat se va bloca repede și va trece din starea RUNNING în starea BLOCKED/WAITING. Șansele sunt foarte mici ca un astfel de proces să își încheie cuanta de timp în starea RUNNING. De aceea i se oferă o cuantă de timp mai mare, pe care o va folosi în mai multe runde de planificare, fiecare rundă de planificare încheindu-se, în general, cu tranziția sa în starea BLOCKED/WAITING din cauza unei operații blocante.
  2. Un sistem pe 32 de biți are pagini de 4KB. Presupunând că sistemul folosește schemă de paginare simplă, pe un singur nivel (adică nu ierarhică/multi-nivel), care este dimensiunea aproximativă (ordinul de mărime) al unei tabele de pagini?
    • Răspuns: Pe un sistem pe 32 de biți, spațiul virtual de adrese are 2^32 = 4GB. Dacă o pagină are 4KB, avem 4GB / 4 KB = 2^20 pagini. O tabelă de pagini simplă, pe un singur nivel avea o intrare pentru fiecare pagină. Dacă o intrare în tabela de pagini ocupă 4-8 octeți (aproximativ) atunci o tabelă de pagini va ocupa 2^20 * (4/8) octeți, adică undeva la 4MB-8MB de memorie.
  3. Fie instrucțiunea *a = 3; În ce situație executarea acestei instrucțiuni va conduce la evacuarea unei pagini în spațiul de swap (swap out)?
    • Răspuns: Instrucțiuna *a = 3 înseamnă un acces la memorie în pagina virtuală ce conține adresa indicată de pointerul a. Dacă acea pagină virtuală nu are pagina fizică rezidentă (adică în memoria fizică / RAM), pagina fizică nu a fost alocată încă (demand paging) sau se găsește pe spațiul de swap sau a apărut duplicarea ei ca urmare a copy-on-write. Dacă nu aveam nici o pagină disponibilă, atunci este nevoie de evacuarea unei pagini (swap out) și folosirea paginii eliberate.

3CC, varianta 2

  1. De ce este util ca prioritatea proceselor să fie dinamică? Care este neajunsul folosirii priorităților statice?
    • Răspuns: În cazul unui sistem cu priorități statice, un proces care este CPU-intensive și care are o prioritate mai bună va fi planificat foarte des: îi expiră cuanta, este trecut în coada READY cu prioritate foarte bună, apoi este replanificat. În această situație un proces care are o prioritate mai slabă va fi planficat foarte rar, posibil deloc, adică să ajungă la situația de starvation. Pentru a compensa aceste dezavantaj, folosim priorități dinamice, care permit proceselor cu prioritate mai slabă să câștige, în timp, priorități mai bune.
  2. De ce, în general, TLB-ul (Translation Lookaside Buffers) este golit (flushed) la schimbări de context?
    • Răspuns: Intrările din TLB sunt cache-uri din tabela de pagini a procesului curent, intrări folosite recent. La o schimbare de context are loc schimbarea proceselor adică și a tabelelor de pagini. Fiind vorba de o tabelă de pagini nouă, intrările din TLB sunt nevalide și atunci sunt golite (flushed) pentru a fi populate cu intrări din tabela de pagini nouă.
  3. Dați un exemplu de situație în care un page fault conduce la livrarea unei excepții de acces de memorie către un proces (de tipul Segmentation fault) și un exemplu de situație care nu conduce la livrarea unei excepții de acces de memorie.
    • Răspuns: Un page fault este generat de MMU când o pagină a fost marcată ca nevalidă în tabela de pagini. Dacă acea pagină nu a fost rezervată de proces înseamnă că nu este accesibilă din spațiul de adrese al procesului și atunci sistemul de operare va livra o excepție de acces la memorie (Segmentation fault) procesului. Altfel, dacă acea pagină este marcată în informațiile sistemului de operare ca fiind copy-on-write sau rezervată dar nealocată (demand paging) sau evacuată pe disc (swapped out), se va trata page fault-ul fără ca sistemul de operare să livreze excepție de acces la memorie procesului.

Greșeli frecvente

  • Multe răspunsuri conform cărora unui proces I/O i se dă cuantă mai mare pentru că este mai lent și are nevoie de mai mult timp pentru a termina operația de I/O.
  • Multe răspunsuri conțin doar calcule, fără explicații prin care să prezinte ce reprezintă numerele scrise. Multe răspunsuri în care nu se prezintă unitatea de măsură (ex. dimensiunea tabelei este 4 sau 2^20).
  • Se rotunjesc puterile lui 2 la puterile lui 10 (ex. 1KB == 1000B).
  • Multe răspunsuri conform cărora, prin folosirea priorităților statice, procesele care așteaptă date (ex. input de la utilizator) consumă timp inutil pe procesor.
  • Multe răspunsuri care presupun că malloc nu alocă memorie, doar o rezervă (folosește mmap întotdeauna).

Lucrări foarte bune

  • CEBERE Ioan-Tudor, 331CC
  • ILIESCU Valentina-Florentina, 331CC
  • ION Horia-Paul, 331CC
  • TĂZLĂUANU Bianca, 334CC
  • VIŞAN Anamaria, 334CC
  • FOLEA Rareş, 336CC
  • SULIMAN Anca, 336CC
  • DANCIU Andra-Maria, 335CB
  • MĂRGINEANU Cristian, 336CB
  • ANDREI Rareș, 332CA
  • MARICA Andreea-Mădălina, 336CA
  • MARIN Cristian-Alexandru 333CB
  • TRIFU Andrei-Ștefan 331CB
  • SECUIU Ana 336CB

Lucrare 3

  • La începutul cursului 10:
  • 22.04.2019, seria CA
  • 24.04.2019, seria CB
  • 24.04.2019, seria CC

3CA, varianta 1

  1. Un programator dorește să citească aleator dintr-un fișier de 1GB pe un sistem cu 2GB de memorie RAM. Ajutați-l să aleagă între memory mapped I/O și standard I/O.
    • Răspuns: Memory-mapped I/O este preferabil din cauza accesului aleator și pentru că există destul RAM pentru a ține fișierul în memorie; după încărcarea inițiala programul va merge la viteza memoriei.
  2. Cum poate fi suprascrisă adresa de retur a unei funcții dintr-o aplicație de tip server folosind un stack buffer overflow atunci când stack canaries sunt folosite pe un sistem cu 64 de biți? Pentru fiecare client, serverul se va clona folosind fork(), și va apela o funcție de tratare a cererii.
    • Răspuns: Se poate ataca canarul octet cu octet atunci când există un server care folosește fork.
  3. Scrieți pseudocod care implementează un semafor (up neblocant și down blocant atunci când sem == 0) folosind suportul hardware oferit de funcția compare_and_swap (cas): int compare_and_swap(int* reg, int oldval, int newval);
    • Răspuns:
      int sem = 0;
      void up(){ 
        while (1) { 
          int o = sem;
          if (cas(&sem, o, o+1)) break;
        }
      }
       
      void down() { 
        while (1) {
          int o; 
          while ((o = sem) == 0);
          if (cas(&sem, o, o-1)) break; 
        }
      } 

3CA, varianta 2

  1. Un programator dorește să citească secvențial un fisier de 10GB pe un sistem cu 1GB de memorie RAM. Ajutați-l să aleagă între memory mapped I/O și standard I/O.
    • Răspuns: Standard I/O este preferabil pentru că accesul este secvențial și caching-ul datelor în memorie nu este necesar; de asemenea memoria este de dimensiune mică.
  2. Cum poate fi gasită o adresă validă pe un sistem pe 64 biți cu ASLR atunci când există un stack buffer overflow? Presupunem că programul atacat este un server. Pentru fiecare client, serverul se va clona folosind fork(), și va apela o funcție de tratare a cererii.
    • Răspuns: La un server care folosește fork se poate ataca ASLR încercând octet cu octet adresa de retur a funcției, până când se gasește o adresă validă în zona de text.
  3. Scrieți pseudocod care implementează un spinlock (lock și unlock) folosind suportul hardware oferit de funcția compare_and_swap(cas):

int compare_and_swap(int* reg, int oldval, int newval);

  • Răspuns:
    void unlock(){ 
      lock = 0;
    }
     
    void lock() { 
      while (!cas(&lock,0,1)); 
    } 

3CB, varianta 1

  1. Ați executat comanda pmap pe un proces ce rulează și ați observat că secțiunea .text și secțiunea .rodata se află mapate în aceeași pagină cu atributele read și execute. De ce NU este o problemă de securitate faptul că secțiunea .rodata are aceste permisiuni?
    • Răspuns: Chiar dacă secțiunea .rodata este mapată în aceeași zonă ca secțiunea .text și ambele au permisiunile pentru citire și execuție, acest lucru nu este o problemă de securitate deoarece, în general, în secțiunea .rodata nu vom găsi cod executabil, și zona nefiind mapată cu drepturi de scriere, un posibil atacator nu ar putea adăuga cod executabil în acea secțiune.
  2. Când preferăm folosirea proceselor în detrimentul thread-urilor într-un sistem multi procesor? Oferiți un exemplu.
    • Răspuns: Preferăm să folosim procese în detrimentul thread-urilor atunci când dorim să asigurăm robustețea programului (atunci când în cadrul unui thread se generează o excepție sau o eroare și se încheie execuția întregului program; în cazul proceselor, dacă un proces se termină brusc, nu afectează celelalte procese).De asemenea, preferăm să folosim procese în detrimentul thread-urilor și în cazurile în care dorim să întărim securitatea aplicației (în cazul în care un proces este compromis, nu se comprominte întreaga aplicație). Un astfel de exemplu este un browser web. Dacă pentru fiecare tab se crează un proces nou, în cazul în care unul dintre tab-uri crapă/are o eroare, nu se va închide întreaga aplicație, ci doar tab-ul respectiv.
  3. Într-un sistem multi-procesor, mai multe thread-uri execută
    int a=10, b = 10000; // variabile globale
    thread_func() {
      a--;
      if ( a > 0) {
        b +=  b / a;
      }
    } 

    La un moment dat, execuția programului este întreruptă de o eroare. Identificați/explicați problema și propuneți o soluție.

    • Răspuns: Variabila a este accesată într-un mod care nu este thread-safe. Între intrarea în if și execuția împărțirii din cadrul threadului X, un alt thread Y decrementează a, și aceasta devine 0. Thread-ul X va face împărțire la 0 și primește semnalul SIGFPE care va încheia execuția programului. Trebuie adăugat un mutex la intrarea și ieșirea din funcție.

3CB, varianta 2

  1. Un server este compilat cu flag-ul -fstack-protector. Implementarea se bazează pe un pool de procese care va deservi clienții. Workerii sunt creați printr-un apel fork(), urmat de un apel execve() ce va încărca un binar (compilat la fel ca serverul) care tratează cererile clienților. Când un worker moare, altul va fi creat în locul său. Funcția de prelucrare a cererilor conține următoarele 2 linii:
    char a[10];
    read(socket, a, 100); 

    Poate fi acesta exploatat printr-un atac de tipul buffer overflow? Motivați

    • Răspuns: Workerii sunt creați prin fork() urmat de execve(). Prin urmare, valoarea de canary de pe stivă va fi aleatoare pentru fiecare worker. Prin urmare, NU putem ghici canary-ul octet cu octet, deci șansele sunt de 1 la 4 miliarde. Atacul nu este fezabil.
  2. De ce este mai rapid un context switch între două thread-uri ale aceluiași proces față de context switch-ul între două procese diferite?
    • Răspuns: Față de procese, thread-urile împart același spațiul de adresă. Astfel, un context switch între două thread-uri este mai rapid decât un context switch între două procese deoarece în primul caz nu se face flush la TLB.
  3. Descrieți o situație când a = a + 5 nu este atomică.
    • Răspuns: Declarăm variabila a global, și o modificăm intermitent în alt thread. În cazul în care arhitectura e x86, sistemul trebuie să NU fie single-core.

3CC, varianta 1

  1. De ce Address Space Sanitizer (ASAN) se folosește doar în faza de dezvoltare, nu și în producție?
    • Răspuns: ASAN are un overhead foarte mare (200%). Așa ceva face nepractică folosirea ASAN în producție. Poate fi însă folosit în dezvoltare pentru a detecta atunci probleme în cod.
  2. De ce este preferat ca la o schimbare de context de execuție să schimbăm un thread al unui proces cu un alt thread al aceluiași proces?
    • Răspuns: La schimbarea contextului de execuție între două thread-uri ale aceluiași proces nu schimbăm și spațiul de adresă. Întrucât nu îl schimbăm, nu este nevoie de schimbarea tabelei de pagini și de flush de TLB. Sistemul devine în felul acesta mai productiv.
  3. De ce implementarea internă a unui mutex are nevoie de un spinlock?
    • Răspuns: În implementarea sa internă un mutex are o variabilă internă ce reține starea (locked / unlocked) și o coadă în care sunt menținute thread-urile care așteaptă eliberarea mutexului. Protejarea accesului la variabila internă și la coadă necesită o primitivă de sincronizare, adică un spinlock.

3CC, varianta 2

  1. Ce tip de mecanism protejează împotriva atacurilor de tip code injection, dar nu și împotriva atacurilor de tip code reuse?
    • Răspuns: Data Execution Prevention (DEP) nu permite executarea zonelor care au fost scrise. Adică atunci când injectăm cod, nu-l putem executa. Adică DEP oferă protecție la atacurile de tip code injection. Atacurile de tip code reuse nu sunt afectate pentru că aceste atacuri se referă doar la zona read-executable, nu la zone writable.
  2. De ce thread-urile de tip user level nu pot fi folosite pentru paralelizarea unui program?
    • Răspuns: Implementarea de thread-uri în user space nu este vizibilă la nivelul sistemului de operare (kernel-ului). Întrucât planificatorul rulează în kernel mode, acesta nu poate planifica un thread per procesor pentru paralelizare. Toate thread-urile de tip user level ajung să ruleze pe un singur procesor.
  3. De ce nu este suficientă folosirea instrucțiunilor hardware de tipul compare-and-swap (precum cmpxchg pe x86 ) pentru implementarea unui spinlock pe sistemele multicore?
    • Răspuns: Operațiile de tip compare-and-swap sunt atomice la nivelul procesorului curent. Întrucât magistrala este partajată, aceste instrucțiuni se pot intercala și duce la date necoerente. De aceea, în implementarea de spinlock pentru sistemele multicore se folosește lock pe magistrală.

Greșeli frecvente

  • Multe răspunsuri care spun că ASAN este vulnerabil/introduce vulnerabilități.
  • Multe răspunsuri care compară spinlock cu mutex și când e bine să folosim spinlock-ul (secțiune critică mică) și când e bine să folosim mutex-ul (secțiune critică mare), în loc să spună efectiv de ce este necesar să fie folosit un spinlock în implementarea unui mutex.
  • Confuzie între kernel-level threads și user-level threads.
  • Confuzii legate de ce face execve().

Lucrări foarte bune

  • LUCHIAN Alina-Elena, 331CC
  • NIŢU Ioan-Florin-Cătălin, 333CC
  • MOGA Mihaela-Mădălina, 334CC
  • ŞTEFAN Laurenţiu, 334CC
  • SULIMAN Anca, 336CC
  • MANOLE Aida-Ştefania, 331CB
  • DANCIU Andra-Maria, 335CB
  • MĂRGINEANU Cristian, 336CB
  • HINŢIU Diana-Florina, 341C5
  • CREȚU Diana, 336CA
  • OLARU Paul-Stelian, 332CA
  • POPA Adrian, 332CA
  • MITOCARU Irina, 334CB
  • ALEXANDRU Ioana, 334CB
  • TIŢA Matei-Dimitrie, 332CB
  • BRÎNZOI Iuliana, 336CB
  • NACA Andrei, 335CB
  • LIURCĂ Daniel, 335CB

Lucrare 4

  • La începutul cursului 13:
    • 20.05.2019, seria CA
    • 22.05.2019, seria CB
    • 22.05.2019, seria CC

3CA, varianta 1

  1. Ce mecanisme putem folosi pentru a reduce ciclii folosiți de procesor pentru I/O pentru dispozitivele care sunt accesate rar?
    • Răspuns: Întreruperi și DMA.
  2. Cum balansează plăcile de rețea moderne întreruperile către mai multe procesoare?
    • Răspuns: Au mai multe perechi de ring-uri; fiecare ring RX poate genera întreruperi și trimite către oricare procesor. Pachetele sunt demultiplexate în aceste ring-uri folosind o funcție de hash sau alte filtre specificate de utilizator.
  3. De ce au i-node-urile dimensiune fixă?
    • Răspuns: Pentru a le putea stoca într-un vector a cărui adresă de început este cunoscută și pentru a le accesa printr-o singură citire atunci când este nevoie.

3CA, varianta 2

  1. Dați un exemplu când polling este preferat intreruperilor pentru sincronizarea cu dispozitivele de I/O.
    • Răspuns: Plăci de rețea de viteză mare (10Gbps sau mai mult), atunci când primesc multe pachete mici (e.g. TCP ACKs la 10Gbps = 14.88Mpps).
  2. Care sunt cele două mecanisme care permit stivei de TCP să obțină performanță mai bună decât stiva UDP atunci cand bottleneck-ul este sistemul gazdă, nu rețeaua?
    • Răspuns: Batching la apelul funcției send (se trimit mai multe date per syscall) și batching în stivă atunci când se lucrează cu segmente mai mari decat MTU; acestea sunt sparte înainte de a fi puse pe fir folosind GSO sau TSO.
  3. Un utilizator a creat 10 linkuri hard și 10 symlink-uri către un fișier existent care avea ințial 1 singur hardlink. Câte inode-uri sunt folosite în total? Care este reference count-ul inode-ului fișierului a.txt după această operație?
    • Răspuns: 11 inode-uri - 1 pentru fisier si 10 pentru symlink. Ref count 11 (10 hard links noi + 1 existent).

3CB, varianta 1

  1. Ce informații oferă intrările din fișierul /proc/interrupts?
    • Răspuns: Avem mai multe coloane: prima coloană reprezintă numărul întreruperii, ce are corespondent un pin pe procesor; N coloane, una pentru fiecare procesor, ce indică câte întreruperi au fost declanșate; tipul de întrerupere; numele modulului/modulelor de kernel ce îl gestionează.
  2. Sunt suficiente buffer-ele prezente pe placa de rețea (NIC) pentru a realiza o transmisie/recepție a datelor? Explicați.
    • Răspuns: Pentru a realiza o transmie/recepție avem nevoie și de memoria principală. Ring bufferele placii vor referi memoria principală pentru ca datele să ajungă în aceasta.
  3. La nivel de implementare, sub ce formă diferă linkurile simbolice și hard în cadrul sistemului de fișiere?
    • Răspuns: Un link simbolic este un fișier în cadrul sistemului de fișiere, ce conține o cale (un șir de caractere), care poate fi folosită pentru a traversa ierarhia de fișiere, și a ajunge la destinație. Un link hard este reprezentat de un dentry în cadrul unui director (o listă de structuri dentry), care conține un număr inode care corespunde inode-ului fișierului destinație.

3CB, varianta 2

  1. Se dă un sistem cu 4 core-uri (procesoare). Dorim să verificăm cum sunt distribuite întreruperile pe fiecare din core-uri pentru dispozitivul de rețea. Cum am putea proceda?
    • Răspuns: Consultăm fișierul /proc/interrupts. Ne uităm pe ultima coloană (coloana cu modulele de kernel atașate), și căutăm linia unde apare modulul care corespunde plăcii noastre de rețea (e.g. ath9k (WI-FI atheros) sau en*(ethernet)). Pe coloanele CPU0-CPU3 găsim numărul de întreruperi declanșate pe fiecare nucleu.
  2. Ce tipuri de buffere există în NIC-uri și care este rolul lor?
    • Răspuns: Într-un NIC în general există 2 buffere circulare: de recepție și de transmisie. În cazul unui spike în activitatea pe rețea, aceste 2 buffere vor acționa ca un tampon în care se pot acumula date: pentru recepție, procesorul va fi întrerupt mai puțin, iar pentru transmisie, kernelul va termina mai rapid și predictibil transmisia pachetelor. Rezultatul este că se reduce utilizarea procesorului în I/O.
  3. Numiți 2 mecanisme de gestionare a spațiului liber care pot fi folosite în implementarea unui sistem de fișiere și menționați un avantaj pentru fiecare din ele.
    • Răspuns: Alocare contiguă și alocare cu liste. La alocarea contiguă nu e nevoie să facem management/tracking la blocurile unui fișier, iar la alocarea cu liste avem flexibilitate în modificarea dimensiunii unui fișier (adăugare de noi blocuri.

3CC, varianta 1

  1. Numiți un avantaj al folosirii memory-mapped I/O față de port-mapped I/O.
    • Răspuns: Memory-mapped I/O folosește un spațiu comun de adrese cu al memoriei. Aceasta înseamnă că folosește instrucțiunile de lucru cu memoria (de tipul mov, load, store) în loc de instrucțiuni dedicate precum in sau out. Rezultă un set de instrucțiuni (ISA) mai simplu al procesorului.
  2. După apelul connect() pe un socket TCP de tip listener se creează un nou socket TCP de tip conexiune care este folosit pentru comunicare. Ambii sockeți (și alții sockeți creați cu apelul connect()) au aceeași adresă IP și port. Cum demultipleaxează sistemul pachetele pentru a le livra socket-ului corespunzător?
    • Răspuns: Sistemul de operare păstrează un 5-tuplu pentru fiecare conexiune cuprinzând adresa IP sursă, portul sursă, adresa IP destinație, portul destinație, protocolul de nivel transport. Atunci când vine un pachet nou cu aceeași adresă destinație și port destinație, pe baza adresei sursă și a portului sursă are loc demultiplexarea și identificarea socketului care va prelua pachetul.
  3. Care este diferența dintre un fișier și un director în implementarea sistemului de fișiere?
    • Răspuns: Blocurile de date ale unui fișier conțin date de orice fel. Blocurile de date ale unui director conțin un vector de dentry-uri (intrări de director).

3CC, varianta 2

  1. De ce spunem că, de obicei, operațiile de citire/scriere de pe/pe disc NU sunt blocante?
    • Răspuns: Operațiile de citire și de scriere pe disc folosesc, de obicei, cache-ul subsistemului de block I/O. Atunci când are loc o scriere, se realizează apel de sistem, se transferă datele din user space în kernel space și apoi apelul de sistem se întoarce (fără să se blocheze) urmând ca datele să fie scrise (flushed) la un moment ulterior. La fel, la citire, datele sunt de obicei prezente în cache (din prefetch sau read-ahead) și sunt transferate de acolo în user space.
  2. De ce este utilă folosirea TSO (TCP Segmentation Offload) în plăcile de rețea moderne?
    • Răspuns: TSO (TCP Segmentation Offload) permite transmiterea unui pachet de nivel transport (segment) cu dimensiune mai mare decât MTU (Maximul Transmission Unit) al plăcii de rețea. Devine responsabilitatea plăcii de rețea (NIC) segmentarea pachetelor în frame-uri de dimensiunea MTU, eliberând procesorul de această prelucrare și crescând performața.
  3. Sistemul de fișiere ext4 are dimensiunea maximă a unui fișier de 16TB. Ce cauzează această limitare?
    • Răspuns: Limitarea este cauzată de numărul de pointeri pe care îi are inode-ul către blocuri sau blocuri cu indirectare simplă sau dublă sau triplă. Fiecare pointer poate referi un bloc, numărul total de pointeri ducând la limitarea numărului total de blocuri accesibile și, astfel, la limitarea dimensiunii maxime a fișierului.

Greșeli frecvente

  • Confuzie între memory-mapped I/O și maparea fișierelor în memorie.
  • Confuzie între apelurile de sistem și instrucțiuni speciale precum in sau out.
  • Foarte multe răspunsuri care afirmă că operațiile de read și write nu sunt blocante deoarece se folosește DMA.
  • Foarte multe răspunsuri care afirmă că operațiile blocante blochează procesorul.
  • Confuzie între operații non-blocante și asincrone.
  • Confuzie între indexare și indirectare.
  • Foarte multe răspunsuri incomplete și nejustificate.

Lucrări foarte bune

  • BOROGHINĂ Gabriel, 333CB
  • UŞURELU Dragoş-Mihai, 332CB
  • TRIFU Andrei-Ştefan, 331CB
  • ILIE Vlad-Florin, 331CB
  • RĂDULESCU Dan, 333CB
  • TUDOR Anca-Gabriela, 333CC
  • COCOȘ Vlad, 334CC
  • DUMITRU Philip, 334CC
  • MANOLE Aida-Ștefania, 333CB

Examene anterioare

so/meta/examen.txt · Last modified: 2019/05/27 07:55 by maria.mihailescu
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