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