Capitol 06 - Memoria virtuală

Demo-uri

Pentru parcurgerea demo-urilor, folosim arhiva aferentă. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda

wget http://elf.cs.pub.ro/so/res/cursuri/curs-06-demo.zip

și apoi decomprimăm arhiva

unzip curs-06-demo.zip

și accesăm directorul rezultat în urma decomprimării

cd curs-06-demo/

Acum putem parcurge secțiunile cu demo-uri de mai jos.

Alocarea variabilelor într-un executabil

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

objdump -t addr-space | grep var

De ce apar doar unele variabile?

Răspuns

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.

Ce semnfică l și g în output-ul obținut?

Răspuns

Răspuns

Arată că variabilele respective sunt simboluri globale (g - la nivelul întregului modul) sau locale (l - locale unei funcții).

Pentru a afișa conținutul zonei .rodata folosind comanda

readelf -x .rodata addr-space

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.

Investigarea mapării folosind pmap

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.

Compilăm programul folosind make și apoi îl rulăm:

./pmap

Într-o altă consolă urmărim schimbările din spațiul virtual de adrese al procesului creat folosind comanda

watch -d pmap $(pidof pmap)

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.

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?

Răspuns

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–).

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:
    ./allocation
  • Într-o altă consolă vizualizăm dimensiunea spațiului fizic ocupat și a a spatiului virtual ocupat, folosind comanda
    watch -n 1 ps -o pid,rss,vsz,cmd -p $(pidof allocation)

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.

Pentru a explica acest comportament, rulăm executabilul prin strace:

strace ./allocation

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ă.

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.

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:

	for (cnt = 0; cnt < NUM_ROUNDS; cnt++) {
		puts("Using malloc to allocate 1024 sets of 1024 bytes.");
		p = malloc(1024*1024);
		DIE(p == NULL, "malloc");
		sleep(2);
	}

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ă.

Folosim în continuare strace pentru a investiga:

strace ./allocation

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.

Despre apelul malloc

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 pagina de manual a malloc:

       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)).

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:
    ./demand-paging
  • Într-o altă consolă vizualizăm dimensiunea spațiului fizic ocupat și a a spatiului virtual ocupat, folosind comanda:
    watch -n 1 ps -o pid,rss,vsz,cmd -p $(pidof demand-paging)

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.

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:
    ./demand-paging
  • Î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):
    watch -n 1 ps -o pid,min_flt,maj_flt,cmd -p $(pidof demand-paging)

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.

Câte page fault-uri sunt generate la o “trecere prin chunk”? De ce?

Răspuns

Răspuns

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.

Page fault-uri la fork (copy-on-write)

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:

  1. se alocă memorie virtuală folosind mmap
  2. se alocă memorie fizică pentru paginile de mai sus, folosind (on) demand paging prin accesarea primului octet al fiecărei pagini
  3. se creează un proces nou
  4. procesul copil citește valoarea din prima jumătate a numărului de pagini (doar citește)
  5. procesul copil scrie o valoare în cadrul fiecărei pagini din a două jumătate
  6. 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:

./fork-faults

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:

apt-get install sysstat

Pentru a rula pidstat și a urmări page fault-urile, folosim, pe a doua consolă, comanda

pidstat -r -T ALL -p $(pidof fork-faults) 5 100

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.

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.

so/curs/virt-mem.txt · Last modified: 2020/03/26 13:57 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