This is an old revision of the document!
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'.
==== 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:
<code>
$ ./access-permissions
reading from .data section
writing to .data section
reading from .text section
executing .text section
</code>
Acest lucru se întâmplă pentru că liniile de cod problematice (cu accese nevalide) sunt comentate:
<code>
do_exec(“executing .data section”, &data[0]);
do_write(“writing to .text section”, exec_do_nothing, 77);
</code>
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:
<code>
[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]
</code>
Î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''