Urmăriți precizările din pagina de reguli.
Puteți participa la un singur examen final.
Datele de examen de SO pentru sesiunea iunie 2016 sunt:
Datele de examen de SO pentru sesiunea septembrie 2016 sunt:
Detalii despre examen găsiți în secțiunea aferentă din pagina de reguli.
[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.[SO][Lucrare X] Prenume Nume, Grupa
; de exemplu [SO][Lucrare 1] Andreea Popescu, 332CA
.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
.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.sleep 10
. Precizați pașii pe care îi va realiza procesul Bash pentru a rula această comandă.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).printf(...)
și write(1,...)
pot fi folosite pentru a afișa text la consolă. Care este diferența principală dintre ele?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.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
?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.sleep 10 &
. Precizați pașii pe care îi va realiza procesul Bash pentru a rula această comandă.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.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
.READY
, RUNNING
și WAITING
?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
.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.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.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.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.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ă.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.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.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.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?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
.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.3GB
cauzează terminarea/omorârea procesului pe un sistem Linux pe 32 de biți?[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.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
?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ă.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?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./* a is initially 0 */ atomic_inc(&a); /* increment a atomically */ atomic_cmp_xchg(&a, 16, 0); /* atomically do: if (a == 16) a = 0; */
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.system("/bin/bash")
.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.
buf
la dispozitivul de I/O în cazul apelului write(fd, buf, 100)
.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?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.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?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.10Gbit
).accept()
în cadrul 3-way handshake-ul TCP? Justificați.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.send(s,buf,100,0)
pe un socket se blochează?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
.write()
în general NU se blochează?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).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()
.send()
se blochează atunci când buffer-ul de receive
este plin.send()
se blochează dacă nu există 100 de octeți liberi în buffer-ul de send
.