This shows you the differences between two versions of the page.
so:cursuri:curs-06 [2014/03/21 08:30] razvan.deaconescu created |
so:cursuri:curs-06 [2019/03/24 14:10] (current) razvan.deaconescu |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Curs 06 - Memoria virtuală ====== | ====== Curs 06 - Memoria virtuală ====== | ||
- | <html> | + | * [[http://prezi.com/qshrq7oeytj2/?utm_campaign=share&utm_medium=copy&rc=ex0share | Curs 06 - Memoria virtuală (vizualizare Prezi)]] |
- | <iframe src="http://prezi.com/embed/qshrq7oeytj2/?bgcolor=ffffff&lock_to_path=0&autoplay=0&autohide_ctrls=0&features=undefined&disabled_features=undefined" width="550" height="400" frameBorder="0"></iframe> | + | |
- | </html> | + | |
- | + | ||
- | * [[http://prezi.com/qshrq7oeytj2/so-curs-7/?kw=view-qshrq7oeytj2&rc=ref-31844697 | Curs 06 - Memoria virtuală (vizualizare Prezi)]] | + | |
* [[http://elf.cs.pub.ro/so/res/cursuri/SO_Curs-06.pdf | Curs 06 - Memoria virtuală (PDF)]] | * [[http://elf.cs.pub.ro/so/res/cursuri/SO_Curs-06.pdf | Curs 06 - Memoria virtuală (PDF)]] | ||
+ | |||
+ | * [[https://drive.google.com/open?id=1jsFIY2bG2QssLX1X9AWn4_0Ec2x_hK9oOGohmFqKSRM|Notițe de curs]] | ||
* Suport curs | * Suport curs | ||
* Operating Systems Concepts Essentials | * Operating Systems Concepts Essentials | ||
* Capitolul 8 - Virtual Memory | * Capitolul 8 - Virtual Memory | ||
- | * Modern Operating Systems | + | * Modern Operating Systems, 2nd Ed. |
* Capitolul 4 - Memory Management | * Capitolul 4 - Memory Management | ||
* Secțiunile 4.4, 4.5 | * Secțiunile 4.4, 4.5 | ||
+ | * Modern Operating Systems, 3rd Ed. | ||
+ | * Capitolul 3 - Memory Management | ||
+ | * Secțiunile 3.4, 3.5 | ||
+ | * [[http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/|Anatomy of a Program in Memory]] | ||
+ | |||
+ | <html> | ||
+ | <center> | ||
+ | <iframe src="https://prezi.com/embed/qshrq7oeytj2/?bgcolor=ffffff&lock_to_path=0&autoplay=0&autohide_ctrls=0&features=undefined&disabled_features=undefined" width="550" height="400" frameBorder="0"></iframe> | ||
+ | </center> | ||
+ | </html> | ||
===== Demo-uri ===== | ===== Demo-uri ===== | ||
- | Pentru parcurgerea demo-urilor, folosiți [[http://elf.cs.pub.ro/so/res/cursuri/curs-07.zip|arhiva aferentă]]. | + | Pentru parcurgerea demo-urilor, folosim [[http://elf.cs.pub.ro/so/res/cursuri/curs-06-demo.zip|arhiva aferentă]]. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda<code bash> |
+ | wget http://elf.cs.pub.ro/so/res/cursuri/curs-06-demo.zip | ||
+ | </code> și apoi decomprimăm arhiva<code bash> | ||
+ | unzip curs-06-demo.zip | ||
+ | </code> și accesăm directorul rezultat în urma decomprimării<code bash> | ||
+ | cd curs-06-demo/ | ||
+ | </code> | ||
- | - Spațiul de adresă al unui proces | + | Acum putem parcurge secțiunile cu demo-uri de mai jos. |
- | * Intrați în directorul //1-addr-space// și deschideți sursa addr-space.c. | + | |
- | * În alt terminal compilați și rulați programul. | + | ==== Alocarea variabilelor într-un executabil ==== |
- | * Observați zonele de memorie din executabil în care sunt salvate variabilele, folosind comanda:<code bash> | + | |
+ | Dorim să investigăm modul în care se alocă variabilele într-un executabil. Pentru aceasta accesăm subdirectorul ''exec-vars/''; urmărim conținutul fișierului ''exec-vars.c''. În acest fișier definim/alocăm variabile în diverse forme: dinamic, static, global. Vrem să vedem cum sunt acestea alocate în executabil și în proces. | ||
+ | |||
+ | Compilăm programul folosind ''make''. | ||
+ | |||
+ | Investigăm zonele de memorie din executabil în care sunt salvate variabilele, folosind comanda<code bash> | ||
objdump -t addr-space | grep var | objdump -t addr-space | grep var | ||
- | </code> | + | </code> |
- | * Explicați de ce apar doar unele variabile. | + | |
- | * Ce semnfică ''l'' și ''g'' din output-ul obținut? | + | De ce apar doar unele variabile? |
- | * Afișați conținutul zonei ''.rodata'' folosind comanda<code bash> | + | |
+ | <spoiler Răspuns> | ||
+ | Pentru variabilele alocate dinamic (pe stivă, heap) se alocă la runtime, adică în momentul în care procesul rulează. Executabilul nu are legătură cu datele alocate la runtime. | ||
+ | </spoiler> | ||
+ | |||
+ | Ce semnfică ''l'' și ''g'' în output-ul obținut? | ||
+ | |||
+ | <spoiler Răspuns> | ||
+ | Arată că variabilele respective sunt simboluri globale (''g'' - la nivelul întregului modul) sau locale (''l'' - locale unei funcții). | ||
+ | </spoiler> | ||
+ | |||
+ | Pentru a afișa conținutul zonei ''.rodata'' folosind comanda<code bash> | ||
readelf -x .rodata addr-space | readelf -x .rodata addr-space | ||
</code> | </code> | ||
- | - Investigarea mapării folosind [[http://linux.die.net/man/1/pmap | pmap]] | + | Secțiunea ''.rodata'' conține variabile read-only, în cazul de față literalul ''rulz''. Literalii și constantele se stocează în secțiunea ''.rodata'' a unui executabil. |
- | * Intrați în directorul ''2-intro'' și compilați sursa ''intro.c''. | + | |
- | * Rulați programul ''intro''. Folosiți ''ENTER'' pentru a continua programul. | + | ==== Investigarea mapării folosind pmap ==== |
- | * Folosiți comanda <code bash> | + | |
- | watch -d pmap $(pidof intro) | + | Vrem să vizualizăm spațiul virtual de adrese al unui proces. Pentru aceasta, accesăm subdirectorul ''pmap/''; urmărim conținutul fișierului ''pmap.c''; facem mapări (folosind apelul ''mmap'') folosind diverse flag-uri. |
- | </code> pentru a urmări modificările asupra memoriei procesului. | + | |
- | * Urmăriți modificările care apar în urma diferitelor tipuri de mapare din cod. | + | Compilăm programul folosind ''make'' și apoi îl rulăm:<code bash> |
- | * De ce unele biblioteci sunt mapate cu drept de scriere? | + | ./pmap |
- | - Alocarea de memorie virtuală | + | </code> |
- | * Intrați în directorul ''3-allocation/''. | + | |
- | * Consultați fișierul ''allocation.c''. | + | Într-o altă consolă urmărim schimbările din spațiul virtual de adrese al procesului creat folosind comanda<code bash> |
- | * Compilați sursele folosind comanda ''make''. | + | watch -d pmap $(pidof pmap) |
- | * Deschideți o consolă nouă. | + | </code> Comanda de mai sus urmărește spațiul virtual de adrese. Ca să generăm schimbări în spațiul virtual de adrese, apăsăm ''ENTER'' în consola în care rulează programul pentru a continua pașii din cod. |
- | * Într-o consolă rulați executabilul aferent:<code> | + | |
+ | Urmărim modificările care apar în urma diferitelor tipuri de mapare din cod. Observăm că se mapează câte o singură pagină; la fiecare operație de mapare spațiul total crește cu 4K, iar la fiecare operație de demapare spațiul total scade cu 4K. Observăm că în cazul mapării partajate permisiunile sunt ''rw-s''; ''s'' înseamnă shared. Tot în cazul memoriei partajate apare mapat dispozitivul ''/dev/zero'', unul dintre modurile uzuale de a face mapare partajată. | ||
+ | |||
+ | De ce unele biblioteci sunt mapate cu permisiuni de scriere? | ||
+ | |||
+ | <spoiler Răspuns> | ||
+ | Bibliotecile au mai multe secțiuni, similar unui executabil, mapate cu permisiunile corespunzătoare. Secțiunea de cod/text este mapată cu permisiuni de citire și execuție (''r-x''), cea de date este mapată cu permisiuni de citire și scriere (''rw-'') iar cea de date read-only este mapată cu permisiuni doar de citire (''r--''). | ||
+ | </spoiler> | ||
+ | |||
+ | ==== Alocarea de memorie virtuală ==== | ||
+ | |||
+ | Vrem să urmărim modul în care se alocă memorie virtuală în spațiul virtual de adrese al unui proces. Pentru aceasta, accesăm subdirectorul ''allocation/''; urmărim conținutul fișierului ''allocation.c''; în cadrul fișierului se fac alocări de memorie virtuală folosind pe rând apelul ''malloc'' și apelul ''mmap''. | ||
+ | |||
+ | Compilăm fișierul folosind comanda ''make''. | ||
+ | |||
+ | Deschidem o consolă nouă și rulăm în cele două console astfel: | ||
+ | * Într-o consolă rulăm executabilul aferent:<code> | ||
./allocation | ./allocation | ||
</code> | </code> | ||
- | * Într-o altă consolă vizualizați dimensiunea spațiului fizic ocupat și a a spatiului virtual ocupat, folosind comanda:<code bash> | + | * Într-o altă consolă vizualizăm dimensiunea spațiului fizic ocupat și a a spatiului virtual ocupat, folosind comanda<code bash> |
watch -n 1 ps -o pid,rss,vsz,cmd -p $(pidof allocation) | watch -n 1 ps -o pid,rss,vsz,cmd -p $(pidof allocation) | ||
</code> | </code> | ||
- | * Observați cum crește dimensiunea spațiului fizic și a spatiului virtual în cazul folosirii ''malloc'' și **doar** a spațiului virtual folosind ''mmap''. | + | |
- | * Cum explicați acest comportament? | + | Observați cum crește dimensiunea spațiului fizic și a spatiului virtual în cazul folosirii ''malloc'' și **doar** a spațiului virtual folosind ''mmap''. |
- | * Rulați executabilul prin ''strace'':<code> | + | |
+ | Pentru a explica acest comportament, rulăm executabilul prin ''strace'':<code> | ||
strace ./allocation | strace ./allocation | ||
</code> | </code> | ||
- | * Ce apel de sistem invocă funcția ''malloc''? | + | |
- | * Dar funcția ''mmap''? | + | Observăm că în cazul funcției de bibliotecă ''malloc'' se realizează apelul de sistem ''brk'', în timp ce în cazul funcției de bibliotecă ''mmap'' se realizează apelul de sistem ''mmap''. Apelul de sistem ''mmap'' alocă **doar** memorie virtuală. |
- | * De ce, la 1024 de apeluri ale funcției ''malloc'' sunt mai puține apeluri de sistem? | + | |
- | * Actualizați codul astfel încât ''malloc'' să aloce, la fel ca și ''mmap'' calupuri de ''1MB'' de memorie. | + | Observăm de asemenea, că se realizează un număr redus de apeluri de sistem ''brk'' raportat la cele ''1024'' de apeluri de bibliotecă ''malloc''. Un apel ''brk'' alocă un pool mai mare de memorie care va fi apoi folosit la apeluri viitoare ''malloc''; realizează o prealocare. |
- | * Compilați noua sursă folosind comanda ''make''. | + | |
- | * Rulați executabilul într-o consolă și comanda de vizualizare în altă consolă. | + | Pentru a vedea comportamentului funcției de bibliotecă ''malloc'', actualizăm codul astfel încât ''malloc'' să aloce, la fel ca ''mmap'' calupuri de ''1MB'' de memorie. Adică bucla ''for'' aferentă să arate așa:<code c> |
- | * Ce observați? | + | for (cnt = 0; cnt < NUM_ROUNDS; cnt++) { |
- | * Cum vă explicați acest comportament? | + | puts("Using malloc to allocate 1024 sets of 1024 bytes."); |
- | * Rulați executabilul nou prin ''strace''. | + | p = malloc(1024*1024); |
- | * Ce apel de sistem invocă **acum** funcția ''malloc''? | + | DIE(p == NULL, "malloc"); |
- | * Comparați apelul de sistem (și argumentele acestuia) invocat acum de ''malloc'' cu apelul de sistem și argumentele acestuia invocate de ''mmap''. | + | sleep(2); |
- | - Paginare la cerere (demand paging) | + | } |
- | * Intrați în directorul ''4-demand-paging/''. | + | </code> |
- | * Consultați fișierul ''demand-paging.c''. | + | |
- | * Compilați sursele folosind comanda ''make''. | + | Compilăm noua sursă folosind comanda ''make''. La fel ca mai devreme rulăm executabilul într-o consolă și comanda de vizualizare în altă consolă. Observăm că acum atât funcția de bibliotecă ''malloc'', cât și funcția de bibliotecă ''mmap'' alocă **doar** memorie virtuală. |
- | * Deschideți o consolă nouă. | + | |
- | * Într-o consolă rulați executabilul aferent:<code> | + | Folosim în continuare ''strace'' pentru a investiga:<code bash> |
+ | strace ./allocation | ||
+ | </code> | ||
+ | |||
+ | Observăm că acum și funcția de bibliotecă ''malloc'' folosește în spate tot apelul de sistem ''mmap''. Acesta alocă doar memorie virtuală de unde și comportamentul. De la o valoare dată, alocarea cu ''malloc'' folosește apelul de sistem ''mmap''. | ||
+ | |||
+ | <spoiler Despre apelul malloc> | ||
+ | Valoarea de la care funcția de bibliotecă ''malloc'' folosește ''mmap'' este definită de ''MMAP_THRESHOLD'', în mod implicit configurat la ''128KB''. Detalii se găsesc în [[http://man7.org/linux/man-pages/man3/malloc.3.html#NOTES|pagina de manual a malloc]]:<code> | ||
+ | Normally, malloc() allocates memory from the heap, and adjusts the | ||
+ | size of the heap as required, using sbrk(2). When allocating blocks | ||
+ | of memory larger than MMAP_THRESHOLD bytes, the glibc malloc() | ||
+ | implementation allocates the memory as a private anonymous mapping | ||
+ | using mmap(2). MMAP_THRESHOLD is 128 kB by default, but is | ||
+ | adjustable using mallopt(3). Allocations performed using mmap(2) are | ||
+ | unaffected by the RLIMIT_DATA resource limit (see getrlimit(2)). | ||
+ | </code> | ||
+ | </spoiler> | ||
+ | |||
+ | ==== Paginare la cerere (demand paging) ==== | ||
+ | |||
+ | Vrem să urmărim modul în care se alocă pagini de memorie fizică la cerere, proces care se numește ''(on) demand paging''. Pentru aceasta, accesăm subdirectorul ''demand-paging/''; urmărim conținutul fișierului ''demand-paging.c''; în cadrul fișierului alocăm memorie virtuală folosind ''mmap'' și apoi accesăm primul octet al fiecărei pagini alocate. | ||
+ | |||
+ | Compilăm codul sursă folosind comanda ''make''. | ||
+ | |||
+ | Avem nevoie de două console: | ||
+ | * Într-o consolă rulăm executabilul aferent:<code> | ||
./demand-paging | ./demand-paging | ||
</code> | </code> | ||
- | * Într-o altă consolă vizualizați dimensiunea spațiului fizic ocupat și a a spatiului virtual ocupat, folosind comanda:<code bash> | + | * Într-o altă consolă vizualizăm dimensiunea spațiului fizic ocupat și a a spatiului virtual ocupat, folosind comanda:<code bash> |
watch -n 1 ps -o pid,rss,vsz,cmd -p $(pidof demand-paging) | watch -n 1 ps -o pid,rss,vsz,cmd -p $(pidof demand-paging) | ||
</code> | </code> | ||
- | * Observați cum crește dimensiunea spațiului spatiului virtual fără a crește dimensiunea spațiului fizic în prima parte. | + | |
- | * Observați cum crește dimensiunea spațiului fizic în a doua parte (fără a crește dimensiunea spațiului virtual). | + | Observăm cum crește dimensiunea spațiului spatiului virtual (coloana ''VSZ'') fără a crește dimensiunea spațiului fizic în prima parte. Se face alocare de memorie virtuală, fără paginare - adică fără alocare de spațiu fizic aferent. În partea a doua, observați cum crește dimensiunea spațiului fizic (coloana ''RSS'') în a doua parte (fără a crește dimensiunea spațiului virtual). Aceasta este //(on) demand paging//, cu alocarea paginilor fizice necesare la nevoie. |
- | * Într-o consolă rulați executabilul aferent:<code> | + | |
+ | Ca să detaliem, urmărim page fault-urile realizate pe parcursul rulării programului. Folosim, la fel, două console: | ||
+ | * Într-o consolă rulăm executabilul aferent:<code> | ||
./demand-paging | ./demand-paging | ||
</code> | </code> | ||
- | * Într-o altă consolă vizualizați numărul de page fault-uri generate de program (''min_flt'' este coloana de interes, ''maj_flt'' este pentru interacțiuni cu discul -- swapping):<code bash> | + | * Într-o altă consolă vizualizăm numărul de page fault-uri generate de program (''min_flt'' este coloana de interes, ''maj_flt'' este pentru interacțiuni cu discul -- swapping):<code bash> |
watch -n 1 ps -o pid,min_flt,maj_flt,cmd -p $(pidof demand-paging) | watch -n 1 ps -o pid,min_flt,maj_flt,cmd -p $(pidof demand-paging) | ||
</code> | </code> | ||
- | * Observați cum nu există page fault-uri în prima pare a rulării programului. | + | Observați cum nu există page fault-uri în prima pare a rulării programului, în momentul în care facem mapări de memorie. Dar apar page fault-uri în a doua parte a rulării programului. |
- | * Observați cum există page fault-uri în a doua parte a rulării programului. | + | |
- | * Câte page fault-uri sunt generate la o "trecere prin chunk"? De ce? | + | Câte page fault-uri sunt generate la o "trecere prin chunk"? De ce? |
- | - Page-Faulturi | + | |
- | * Intrați în directorul ''5-fork-faults/''. | + | <spoiler Răspuns> |
- | * Consultați fișierului ''fork-faults.c''. | + | Se generează 256 page fault-uri. Asta se întâmplă pentru ca la fiecare "trecere prin chunk" se accesează 256 pagini, fiecare pagină având câte 4KB, pentru un total de 1MB. Un page fault înseamnă alocarea (on demand) a unei pagini fizice. |
- | * **Câte page-fault-uri** credeți că se realizează la rulare? | + | </spoiler> |
- | * Compilați fișierul. | + | |
- | * Rulați programul ''fork-faults''. | + | ==== Page fault-uri la fork (copy-on-write) ==== |
- | * Folosiți ''ENTER'' pentru a continua programul, dar după rularea ''pidstat'' (vedeți mai jos). | + | |
- | * Pe un alt terminal sau tab de terminal folosiți utilitarul ''pidstat'' din pachetul ''sysstat'' care permite monitorizarea page fault-urilor unui proces (prin intermediul argumentului ''-r''). | + | Vrem să urmărim realizarea page fault-urilor în urma unui apel ''fork''; page fault-urilor vor fi cauzate de mecanismul de copy-on-write în momentul în care unul dintre cele două procese (copil sau părinte) scrie în zona respectivă. Pentru aceasta, accesăm subdirectorul ''fork-faults''; urmărim conținutul fișierului ''fork-faults.c''. În cadrul fișierului ''fork-faults.c'' se execută următorii pași: |
- | * Dacă nu merge comanda ''pidstat'' trebuie să instalați pachetul ''sysstat'' folosind comanda:<code bash> | + | - se alocă memorie virtuală folosind ''mmap'' |
+ | - se alocă memorie fizică pentru paginile de mai sus, folosind //(on) demand paging// prin accesarea primului octet al fiecărei pagini | ||
+ | - se creează un proces nou | ||
+ | - procesul copil citește valoarea din prima jumătate a numărului de pagini (**doar** citește) | ||
+ | - procesul copil scrie o valoare în cadrul fiecărei pagini din a două jumătate | ||
+ | - procesul părinte scrie o valoare în toate paginile | ||
+ | |||
+ | Ca să urmărim ce se întâmplă, compilăm fișierul folosind ''make''. Apoi rulăm programul obținut:<code bash> | ||
+ | ./fork-faults | ||
+ | </code> Folosim ''ENTER'' pentru a continua programul, dar după rularea ''pidstat'' (mai jos). | ||
+ | |||
+ | Într-o altă consolă folosim utilitarul ''pidstat'' din pachetul ''sysstat'' care permite monitorizarea page fault-urilor unui proces (prin intermediul argumentului ''-r''). Dacă nu există comanda ''pidstat'' trebuie să instalăm pachetul ''sysstat'' folosind comanda:<code bash> | ||
apt-get install sysstat | apt-get install sysstat | ||
</code> | </code> | ||
- | * Folosiți comanda <code bash> | + | |
+ | Pentru a rula ''pidstat'' și a urmări page fault-urile, folosim, pe a doua consolă, comanda<code bash> | ||
pidstat -r -T ALL -p $(pidof fork-faults) 5 100 | pidstat -r -T ALL -p $(pidof fork-faults) 5 100 | ||
- | </code> pentru a urmări page fault-urile. | + | </code> Comanda de mai sus afișează câte un mesaj la fiecare 5 secunde. În prima consolă apăsăm ''ENTER'' pentru a continua rularea și urmărim informațiile afișate de ''pidstat'', apoi continuăm apăsarea ''ENTER'' etc. |
- | * Comanda de mai sus vă afișează câte un mesaj la fiecare 5 secunde. Sincronizați apăsarea tastei ''ENTER'' cu afișajul comenzii ''pidstat''. | + | |
- | * Urmăriți evoluția numărului de page fault-uri pentru cele două procese: părinte și copil. | + | Urmărim, în outputul comenzii ''pidstat'', evoluția numărului de page fault-uri pentru cele două procese: părinte și copil. Page fault-urile care apar în cazul unui copy-on-write în procesul copil vor fi vizibile ulterior și în procesul părinte. Observăm că procesul copil generează page fault-uri doar pe jumătate din pagini (cele în care scrie), iar procesul părinte generează page fault-uri pe toate paginile. Asta pentru că un proces creează o copie a paginilor inițiale, dar lasă acele pagini ''read-only'', iar alt proces primește page fault dar doar schimbă permisiunile din read-only în read-write. |
- | * Page fault-urile care apar în cazul unui copy-on-write în procesul copil vor fi vizibile ulterior și în procesul părinte. | + |