Capitol 05 - Gestiunea memoriei

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-05-demo.zip

și apoi decomprimăm arhiva

unzip curs-05-demo.zip

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

cd curs-05-demo/

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

Informații despre memoria sistemului

Ca să aflăm informații despre memoria RAM (fizică) disponibilă pe sistem folosim comanda

free -m

Rezultatul afișat este în megaocteți.

Altă variantă este consultarea fișierului /proc/meminfo:

cat /proc/meminfo

Spațiul de adresare disponibil îl putem afla prin consultarea fișierului /proc/cpuinfo:

cat /proc/cpuinfo | grep 'address sizes'

Ni se vor afișa informații despre spațiul adresabil fizic și cel virtual. În general, la sistemele cu arhitectură x86_64, deși registrele sunt pe 64 de biți, spațiul adresabil virtual este de 48 de biți.

Pentru a afla informații desre memoria cache a sistemului, folosim comanda

lscpu

și urmărim liniile care conțin cuvântul cache. Sau folosim comanda

getconf -a | grep 'CACHE'

De obicei avem mai multe niveluri de memorie cache. Primul nivel conține în general un cache pentru date și unul pentru instrucțiuni.

Pentru a afla dimensiunea paginii sistemului folosim comanda

getconf PAGE_SIZE

Programatic, astfel de informații pot fi determinate prin intermediul apelului sysconf. O implementare succintă se găsește în subdirectorul system-memory/. În fișierul system-memory.c folosim apelul sysconf pentru a afla dimensiunea paginii sistemului, spațiul de memorie, informații despre memoria cache. Pentru a folosi programul, îl compilăm și îl rulăm:

make
./system-memory

Informații despre spațiul de adresă al unui proces

Spațiul de adresă al unui proces poate fi vizualizat folosind comanda pmap. De exemplu, dacă dorim să vizualizăm spațiul de adresă al procesului curent folosim comanda

pmap $$

Spațiul de adresă al unui proces cuprinde zona de cod/text (marcată r-x), zone de date (marcate r-- și rw-), biblioteci partajate, heap, stivă.

Spațiul de adresă al procesului este modificat dinamic (la runtime) prin alocare și dezalocare de memorie. Pentru a urmări modul în care este alterat spațiul de adresă al procesului la alocare și dezalocare folosim programul din subdirectorul address-space/.

În fișierul address-space.c alocăm și dezalocăm memorie folosind stiva (la un apel de funcție), apelurile malloc și free, respectiv apelurile mmap și munmap. Înaintea executării unui pas programul așteaptă 5 secunde, timp în care putem urmări evoluția spațiului de adresă.

Pentru a urmări evoluția programului avem nevoie de două console. Într-o consolă compilăm și rulăm programul, iar în alta verificăm funcționalitatea sa folosind pmap.

Pentru acesta în prima consolă compilăm și rulăm:

make
./address-space

Apoi, în cealaltă consolă rulăm periodic comanda:

pmap $(pidof address-space)

Observăm cum se modifică dimensiunile diverselor regiuni după alocare. Stiva nu se modifică; stiva este deja alocată iar spațiul ocupat de buffer nu conduce la alterarea stivei. Pentru malloc/free și mmap/munmap se alocă/eliberează dimensiunea cerută în conformitate cu apelul.

Granularitatea alocării de memorie

Deși un apel de genul malloc permite alocare fină de memorie (de nivelul octeților), în spate sistemul de operare și hardware-ul alocă memorie la nivel de pagină. Un apel de alocare a memoriei va aloca mai mult spațiu pentru a permite viitoare alocări.

Pentru a verifica acest lucru folosim subdirectorul allocation-granularity/. Fișierul allocation-granularity.c este un program care primește ca argument dimensiunea care să fie transmisă apelului malloc și apoi așpteaptă 5 secunde înainte și după pentru a putea urmări efectul acestei alocări asupra spațiului de adresă, după care procesul este închis.

Vom folosi două console: pe o consolă vom rula programul iar pe alta vom investiga spațiul de adresă aferent. Pentru aceasta pe prima consolă vom compila și vom rula procesul:

make
./allocation-granularity 1

în vreme ce pe a doua consolă vom consulta spațiul de adresă al procesului:

pmap $(pidof allocation-granularity)

Prima comandă alocă un singur octet în mod dinamic în cadrul spațiului de adresă. Ultima comandă o vom rula de mai multe ori pentru a verifica modul în care se modifică spațiul de adresă al procesului. Vedem că la o simplă alocare de 1 octet se alocă o zonă mai mare în spațiul de adresă al procesului, conform modului intern de lucru al apelului de bibliotecă malloc.

Informații despre TLB

Pentru a afla informații despre TLB instalăm pachetul cpuid. Pe un sistem Debian-based folosim comanda

apt-get install cpuid

Ca să determinăm informații despre TLB folosim comanda

cpuid | grep TLB

Dacă vrem să urmărim numărul de miss-uri pentru TLB putem folosi perf. De exemplu, pentru a urmări TLB miss-urile într-un interval de 10 secunde la nivelul sistemului folosim comanda

sudo perf stat -e iTLB-load-misses -a sleep 10

TLB miss-urile cresc în momentul schimbării spațiului de adresă, adică la planificarea proceselor. Dacă avem multe schimbări de spațiu de adresă între procese atunci numărul de TLB miss-uri va crește. Pentru a verifica acest lucru, din arhiva laboratorului 4, din subdirectorul nice/ rulăm scriptul start-all (după ce am compilat în prealabil executabilul cpu). După ce am rulat scriptul folosim o altă consolă pentru a măsura numărul de TLB miss-uri, folosind aceeași comandă ca mai sus:

sudo perf stat -e iTLB-load-misses -a sleep 10

Observăm un număr semnificativ mai mare de TLB miss-uri datorat numeroaselor schimbări de context ce au loc între procesele pornite de scriptul start-all.

Permisiuni de access

În tabela de pagini a unui proces, fiecare intrare are asociate permisiuni de access: citire (read), scriere (write) și, mai recent, execuție (execute). În general zonele din spațiul de adrese al procesului au permisiuni corespunzătoare rolului lor: zona de date (.data), în care stocăm variabilele globale, are permisiuni de citire și scriere, iar zona de cod (.text), în care se găsesc funcțiile (codul executabil), are permisiuni de citire și execuție.

În subdirectorul access-permissions/ demonstrăm funcționarea acestor permisiuni. În fișierul cod sursă access-permissions.c, variabila data este o variabilă globală stocată în zona de date, iar exec_do_nothing este o funcție aflată în zona de cod. Pe acest două variabile realizăm cele trei tipuri de acțiuni: citire, scriere, execuție.

Ca să verificăm, compilăm programul folosind comanda make și apoi rulăm executabilul generat folosind comanda ./access-permissions. Programul va rula cu succes:

$ ./access-permissions 
reading from .data section
writing to .data section
reading from .text section
executing .text section

Acest lucru se întâmplă pentru că liniile de cod problematice (cu accese nevalide) sunt comentate:

        /* These won't work due to permission issues. */
        //do_exec("executing .data section", &data[0]);
        //do_write("writing to .text section", exec_do_nothing, 77);

Dacă vom decomenta câte o linie dintre cele de mai sus și vom rula executabilul, vom primi excepție de access la memorie (Segmentation fault). Pentru că facem acces nevalid: de execuție la variabila globală data aflată într-o zonă care nu poate fi executată; de scriere la funcția exec_do_nothing aflată într-o zonă care nu poate fi scrisă.

Dacă rulăm comanda dmesg aflăm informații despre excepția de access la memorie:

[2556682.592368] access-permissi[24582]: segfault at 56337d586010 ip 000056337d586010 sp 00007ffc899aede8 error 15 in access-permissions[56337d586000+1000]
[2556762.153281] access-permissi[24638]: segfault at 558684f0763a ip 0000558684f07667 sp 00007ffffe4b8d70 error 7 in access-permissions[558684f07000+1000]

În rezultatul de mai sus, codul erorii ne oferă informații despre ce a cauzat excepția. Conform documentației, codurile de eroare înseamnă:

  • 15 este acces de execuție la o zonă validă în spațiul de adrese dar neexecutabilă, adică accesul de execuție la variabila globală data
  • 7 este acces de scriere la o zonă validă în spațiul de adrese dar care nu poate fi scrisă, adică accesul de scriere la funcția exec_do_nothing
so/curs/memory.txt · Last modified: 2020/04/04 07:31 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