Laboratoare:Memoria virtuala

Memoria virtuală
Mecanismul de memorie virtuală este folosit de către nucleul sistemului de operare pentru a implementa o politică eficientă de gestiune a memoriei. Astfel, cu toate că aplicațiile folosesc în mod curent memoria virtuală, ele nu fac acest lucru în mod explicit. Există însă câteva cazuri în care aplicațiile folosesc memoria virtuală în mod explicit.

Sistemul de operare oferă primitive de mapare a fișierelor, a memoriei sau a dispozitivelor în spațiul de adresă al unui proces.
 * Maparea fișierelor în memorie este folosită în unele sisteme de operare pentru a implementa mecanisme de memorie partajată. Acest mecanism face de asemenea posibilă implementarea paginării la cerere și a bibliotecilor partajate.
 * Maparea memoriei în spațiul de adresă este folositoare atunci când un proces dorește să aloce o cantitate mare de memorie.
 * Maparea dispozitivelor este folositoare atunci când un proces dorește să folosească direct memoria unui dispozitiv cum ar fi placa video.

Linux
Funcțiile cu ajutorul cărora se pot face cereri explicite asupra memoriei virtuale sunt funcțiile din familia mmap(2). Funcțiile folosesc ca unitate minimă de alocare pagina (adică se poate aloca numai un număr întreg de pagini, iar adresele trebuie să fie aliniate corespunzător).

Maparea fișierelor
În urma mapării unui fișier în spațiul de adresă al unui proces, accesul la acest fișier se poate face similar cu accesarea datelor dintr-un vector. Eficiența metodei vine din faptul că zona de memorie este gestionată similar cu memoria virtuală, supunându-se regulilor de evacuare pe disc atunci când memoria devine insuficientă (în felul acesta se poate lucra cu mapări care depășesc dimensiunea efectivă a memoriei fizice).
 * Observație
 * Nu orice descriptor de fișier poate fi mapat în memorie. Socket-urile, pipe-urile, majoritatea dispozitivelor nu permit decât accesul secvențial și sunt incompatibile din această cauză cu conceptele de mapare. Există cazuri în care fișiere obișnuite nu pot fi mapate (spre exemplu, dacă nu au fost deschise pentru a putea fi citite; pentru mai multe informații: man mmap).

mmap
Prototipul funcției ce permite maparea unui fișier în spațiul de adresă al unui proces este următorul:

Funcția va întoarce un pointer spre o zonă de memorie din spațiul de adresă al procesului, zonă în care a fost mapat fișierul descris de descriptorul fd, începând cu offset-ul offset. Folosirea parametrului start permite propunerea unei anumite zone de memorie la care să se facă maparea; Folosirea valorii NULL pentru parametrul start indică lipsa vreunei preferințe în ceea ce privește zona în care se va face alocarea. Adresa precizată prin parametrul start trebuie să fie multiplu de dimensiunea unei pagini. Dacă sistemul de operare nu poate să mapeze fișierul la adresa cerută, atunci îl va mapa la altă adresă. Adresa la care se mapează fișierul este întoarsă de funcție

Parametrul prot specifică tipul de acces care se dorește; poate fi PROT_READ, PROT_WRITE, PROT_EXEC sau PROT_NONE; dacă zona e folosită altfel decât s-a declarat se va genera un semnal SIGSEGV

Parametrul flags permite stabilirea tipului de mapare ce se dorește; poate lua următoarele valori (combinate prin SAU pe biți; trebuie să existe cel puțin una: fie MAP_PRIVATE, fie MAP_SHARED</tt>):
 * MAP_PRIVATE</tt> - se folosește o politică de tip copy-on-write; zona va conține inițial o copie a fișierului, dar scrierile nu sunt făcute în fișier; modificările nu vor fi vizibile în alte procese dacă există mai multe procese care au făcut mmap</tt> pe aceeași zonă din același fișier
 * MAP_SHARED</tt> - scrierile sunt actualizate imediat în toate mapările existente (în acest fel toate procesele care au realizat mapări vor vedea modificările); pentru ca modificările să fie vizibile și pentru un proces ce utilizează read/write se poate folosi msync</tt>; altfel actualizarea va avea loc la un moment de timp nespecificat
 * MAP_FIXED</tt> - dacă nu se poate face alocarea la adresa specificată de start apelul va eșua
 * MAP_LOCKED</tt> - se va bloca paginarea pe această zonă
 * MAP_ANONYMOUS</tt> - se mapează memorie (argumentele fd și offset sunt ignorate)

În caz de succes, funcția întoarce un pointer către zona din spațiul de adresă al procesului unde a fost mapat fișierul. În caz de insucces se întoarce MAP_FAILED</tt>, eroarea fiind semnalată în errno</tt>. De exemplu, pentru alocare de memorie se poate utiliza MAP_PRIVATE | MAP_ANONYMOUS</tt>.


 * Observație
 * Operația mmap</tt> incrementează contorul de utilizări ale fișierului deschis în fd. La închiderea lui fd maparea va supraviețui. Acest lucru are ca efect menținerea unui fișier chiar după ce el este șters, dacă fișierul este mapat. Fiind asociată spațiului de adresă al unui proces, maparea va fi distrusă atunci când se termină procesul.
 * Adresa întoarsă este multiplu de dimensiunea unei pagini.

msync</tt>
Pentru a declanșa în mod explicit sincronizarea fișierului cu maparea din memorie este disponibilă următoarea funcție:

unde flags poate fi
 * MS_SYNC</tt>
 * datele vor fi scrise în fișier și abia apoi funcția se va termina
 * MS_ASYNC</tt>
 * este inițiată secvența de salvare dar nu se așteaptă terminarea ei
 * MS_INVALIDATE</tt>
 * se invalidează mapările zonei din alte procese, pentru a forța recitirea paginii în toate celelalte procese la următorul acces.

Dacă operația are succes se întoarce 0 și se actualizează porțiunea din fișier care corespunde zonei de memorie <tt>start</tt> de lungime <tt>length</tt>. Dacă <tt>msync</tt> eșuează se întoarce valoarea <tt>-1</tt>. Erorile sunt specificate în <tt>errno</tt>.

Alocare de memorie în spațiul de adresă al procesului
În UNIX, tradițional, pentru alocarea memoriei dinamice se folosește apelul de sistem <tt>brk</tt>. Acest apel crește sau descrește zona de heap asociată procesului. Odată cu oferirea către aplicații a unor apeluri de sistem de gestiune a memoriei virtuale (<tt>mmap</tt>), a existat posibilitatea ca procesele să aloce memorie folosind aceste noi apeluri de sistem. Practic, procesele pot mapa în spațiul de adresă... memorie, nu fișiere.

Procesele pot cere alocarea unei zone de memorie de la o anumită adresă din spațiul de adresare, chiar și cu o anumită politică de acces (citire, scriere sau execuție). În UNIX acest lucru se face tot prin intermediul funcției <tt>mmap</tt>. Pentru acest lucru parametrul de flag-uri trebuie să conțină flag-ul <tt>MAP_ANONYMOUS</tt>. În acest caz, descriptorul de fișier și offset-ul sunt ignorate. În rest, funcția primește argumente cu aceeași semnificație ca cele discutate în secțiunea anterioară.

Maparea dispozitivelor
Există chiar și posibilitatea ca aplicațiile să mapeze în spațiul de adresă al unui proces un dispozitiv de intrare-ieșire. Acest lucru este util de exemplu pentru plăcile video: o aplicație poate mapa în spațiul de adresă memoria plăcii video. În UNIX, dispozitivele fiind reprezentate prin fișiere, pentru a realiza acest lucru nu trebuie decât să deschidem fișierul asociat dispozitivului și să-l folosim într-un apel <tt>mmap</tt>. Atenție însă, nu toate dispozitivele pot fi mapate în memorie, iar atunci când pot fi mapate, ce înseamnă acest lucru depinde de dispozitiv.

Un alt exemplu de dispozitiv care poate fi mapat este chiar... memoria. În Linux se poate folosi fișierul /dev/zero pentru a face mapări de memorie, ca și când s-ar folosi flag-ul <tt>MAP_ANONYMOUS</tt>.

Demaparea unei zone din spațiul de adresă
Dacă se dorește demaparea unei zone din spațiul de adresă al procesului se poate folosi funcția <tt>munmap</tt>:

start este adresa primei pagini ce va fi demapate (trebuie sa fie multiplu de dimensiunea unei pagini). Dacă length nu este o dimensiune care reprezintă un număr întreg de pagini, va fi rotunjit superior. Zona poate să conțina bucăți deja demapate. Se pot astfel demapa mai multe zone în același timp.

Redimensionarea unei zone mapate
Pentru a executa operații de redimensionare a zonei mapate se poate utiliza următoarea funcție:

Zona pe care old_adress și old_size o descriu trebuie să aparțină unei singure mapări. O singură opțiune este disponibilă pentru flags: <tt>MREMAP_MAYMOVE</tt> care arată că este în regulă ca pentru obținerea noii mapări să se realizeze o noua mapare într-o altă zonă de memorie (vechea zona fiind dezalocată). Dacă totul se termină cu bine atunci funcția întoarce un pointer spre noua zonă. In caz de eroare se întoarce <tt>MAP_FAILED</tt>.

Schimbarea protecției unei zone mapate
Uneori este nevoie ca modul (drepturile de acces) în care a fost mapată o zonă să fie schimbat. Pentru acest lucru se poate folosi funcția <tt>mprotect</tt>:

Funcția primește ca parametri intervalul de adrese [addr, addr + len - 1] și noile drepturi de access (<tt>PROT_READ</tt>, <tt>PROT_WRITE</tt>, <tt>PROT_EXEC</tt>, <tt>PROT_NONE</tt>). Ca și la <tt>munmap</tt> addr trebuie sa fie multipla de dimensiunea unei pagini. Funcția va schimba protecția pentru toate paginile care conțin cel puțin un byte în intervalul specificat. Funcția întoarce 0 dacă operația s-a încheiat cu succes sau -1 în caz contrar, erorile fiind descrise de <tt>errno</tt>.

Optimizări
Pentru ca sistemul de operare să poată implementa cât mai eficient accesele la o zona de memorie mapată, programatorul poate să informeze kernel-ul (prin apelul de sistem <tt>madvise(2)</tt>) despre modul în care zona va fi folosită. <tt>madvise(2)</tt> e utilă mai ales la când în spatele memoriei virtuale se află un dispozitiv fizic (de ex. când se mapează fișiere de pe hard-disk, kernelul poate citi în avans pagini de pe disk, reducând latența datorată poziționării capului de citire). Prototipul funcției este următorul:

unde valorile acceptate pentru advice sunt:
 * <tt>MADV_NORMAL</tt>
 * regiunea este una obișnuită și care nu are nevoie de un tratament special
 * <tt>MADV_RANDOM</tt>
 * regiunea va fi accesată în mod aleator; sistemul de operare nu va citi în avans pagini
 * <tt>MADV_SEQUENTIAL</tt>
 * regiunea va fi accesată în mod secvențial; sistemul de operare ar putea citi în avans pagini
 * <tt>MADV_WILLNEED</tt>
 * regiunea va fi utilizată undeva în viitorul apropiat (nucleul poate decide să preîncarce paginile în memorie)
 * <tt>MADV_DONTNEED</tt>
 * regiunea nu va mai fi utilizată; nucleul poate să elibereze zona alocată din memorie, dar zona nu este demapată; nu se garantează păstrarea datelor la accesări ulterioare

Dacă operația are loc cu succes se întoarce 0. Dacă apare o eroare se întoarce -1, erorile fiind descrise de <tt>errno</tt>.

Blocarea paginării
Există o categorie de procese care trebuie să execute anumite acțiuni la momente de timp bine determinate, pentru a se păstra calitatea execuției. Pentru exemplificare putem considera un player audio sau video. Sau un program ce controlează mersul unui robot biped. Problema cu acest gen de procese este dată de faptul că dacă o anumită pagină nu este prezentă în memorie, va dura un timp până ce ea va fi adusă. Pentru a contracara aceste probleme, sistemele UNIX pun la dispoziție apelurile <tt>mlock</tt> și <tt>mlockall</tt>.

Funcția <tt>mlock</tt> va bloca paginarea (nu se va mai face swapout) paginilor incluse în intervalul [addr, addr + len - 1]. Funcția <tt>mlockall</tt> va bloca paginarea tuturor paginilor procesului, în funcție de flag-uri:
 * <tt>MCL_CURRENT</tt>
 * se va bloca paginarea tuturor paginilor mapate în spațiul de adresă al procesului la momentul apelului
 * <tt>MCL_FUTURE</tt>
 * se va bloca paginarea noilor pagini mapate în spațiul de adresă al procesului; aici intră noi mapări realizate cu funcția <tt>mmap</tt> dar și paginile de stivă mapate automat de sistem


 * Notă:
 * Flag-ul <tt>MCL_FUTURE</tt> nu garantează faptul că paginile de stivă vor fi automat mapate în sistem. Dacă procesul depășește limita de memorie impusă de sistem, va primi semnalul SIGSEGV. Pentru a nu se ajunge în astfel de situații, programul trebuie să folosească <tt>mlockall(MCL_CURRENT | MCL_FUTURE)</tt> și apoi să aloce dimensiunea maximă a stivei pe care urmează să o folosească (prin declararea unei variabile locale, un vector de exemplu, și accesarea completă a acesteia).

Există bineînțeles și funcții ce readuc lucrurile la normal:

Astfel funcția <tt>munlock</tt> va reporni mecanismul de paginare al tuturor paginilor din intervalul [addr, addr + len - 1], iar funcția <tt>munlockall</tt> face același lucru pentru toate paginile procesului, atât curente cât și viitoare. Trebuie notat faptul că dacă s-au efectuat mai multe apeluri <tt>mlock</tt> sau <tt>mlockall</tt> este suficient un singur apel <tt>munlock</tt> sau <tt>munlockall</tt> pentru a reactiva paginarea.

Excepții
Atunci când se detectează o încălcare a protecției la accesul la memorie se va trimite semnalul <tt>SIGSEGV</tt> sau <tt>SIGBUS</tt> procesului. După cum am văzut atunci când am discutat despre semnale, semnalul poate fi tratat cu două tipuri de funcții pe care aici o să le denumim <tt>signal</tt> și <tt>sigaction</tt>. Funcția de tip <tt>sigaction</tt> va primi ca parametru o structură <tt>siginfo_t</tt>. În cazul semnalelor ce tratează excepții cauzate de un acces incorect la memorie, următoarele câmpuri din această structură sunt setate:
 * <tt>si_signo</tt>
 * setat la <tt>SIGSEGV</tt> sau <tt>SIGBUS</tt>
 * <tt>si_code</tt>
 * pentru <tt>SIGSEGV</tt> poate fi <tt>SEGV_MAPPER</tt> pentru a arăta că zona accesată nu este mapată în spațiul de adresă al procesului, sau <tt>SEGV_ACCERR</tt> pentru a arăta că zona este mapată dar a fost accesată necorespunzător; pentru <tt>SIGBUS</tt> poate fi <tt>BUS_ADRALN</tt> pentru a arăta că s-a făcut un acces nealiniat la memorie, <tt>BUS_ADRERR</tt> pentru a arăta că s-a încercat accesarea unei adrese fizice inexistente sau <tt>BUS_OBJERR</tt> pentru a indica o eroare hardware
 * <tt>si_addr</tt>
 * adresa care a generat excepția

ElectricFence
ElectricFence este un pachet ce ajută programatorii la depanarea problemelor de tipul buffer overrun. Aceste probleme sunt cauzate de faptul că anumite date sunt suprascrise fiindcă nu se fac verificări când se modifică date adiacente. Soluția folosită de ElectricFence este înlocuirea apelurilor standard malloc și free cu implementări proprii. ElectricFence va plasa zona de memorie alocată în spațiul de adrese al procesului, astfel încât ea să fie mărginită de pagini neaccesibile (protejate la scriere și citire).

Din păcate, sistemul de operare și arhitectura procesorului limiteazã dimensiunea paginii la cel putin 1-4K, astfel încât dacã zona de memorie alocată nu este multiplu de această dimensiune, există posibilitatea ca programul să poată citi sau scrie și în zone în care nu ar trebui, fără ca sistemul de operare să oprească executia programului. Pentru a preveni situații de acestă natură alocarea zonelor de memorie de către ElectricFence se face astfel încât ele sunt la limita superioară a unei pagini, după care urmează o pagină neaccesibilă. Problema cu o astfel de abordare este că nu previne situațiile de buffer underrun, în care datele sunt citite sau scrise peste limita inferioarã.

Pentru a putea verifica și astfel de situații utilizatorul trebuie să definescă variabila de mediu <tt>EF_PROTECT_BELOW</tt> înainte de rula programul. În acest caz, ElectricFence va plasa zona de memorie alocată la începutul unei pagini, pagină care la rândul ei este plasată după o pagină inaccesibilă procesului.

De ce este importantă detectarea situațiilor de buffer overrun? Asa cum am explicat și în secțiunea precedentă, astfel de situații vor produce în cele din urmă erori, dar la un moment de timp ulterior, astfel încât este imposibil să determinăm cauza erorii cu mijloace de depanare obișnuite. În plus, situațiile buffer overrun pot suprascrie nu numai variabile, ci și alte date importante pentru stabilitatea programului cum ar fi datele de control folosite de rutinele malloc și free. Pachetul ElectricFence poate determina erorile de buffer overrun doar dacă acestea apar în memoria alocată dinamic (adică în zona heap) cu rutinele malloc și free. Pentru a folosi pachetul ElectricFence utilizatorul trebuie să folosească la compilarea programului biblioteca <tt>libefence</tt>. Pentru a vedea utilitatea acestui pachet să analizăm programul de mai jos:

'''Exemplu 15. exemplul-8.c'''

Aparent totul pare în regulă. La execuția programului însă obținem următorul output:

[tavi@dhcp-48 intro]$ gcc -Wall -g exemplul-8.c [tavi@dhcp-48 intro]$ ./a.out 0 11 1 10  2  9  3  8  4  7  5  6  6  5  7  4 -8  3  8  2  8  1 -7  0

Ceva este clar în neregulă. Dacă folosim biblioteca efence și GDB eroarea va fi vizibilă imediat :

[tavi@dhcp-48 intro]$ gcc -Wall -g exemplul-8.c -lefence [tavi@dhcp-48 intro]$ gdb a.out (gdb) run Starting program: /home/tavi/cursuri/so/lab/draft/intro/a.out [New Thread 1024 (LWP 20752)] Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com> Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 1024 (LWP 20752)] 0x08048594 in main at exemplul-8.c:13 13                     data_1[i]=i; (gdb) print i $1 = 11 (gdb)

Se observă că eroarea apare în momentul în care încercăm să inițializăm al 12-lea element al vectorului, deși vectorul nu are decât 11 elemente.

Pentru mai multe informatii despre pachetul ElectricFence consultați pagina de manual (man efence).

Windows
În Windows funcțiile de control al memoriei virtuale sau mai bine zis al spațiului de adresă al unui proces nu mai sunt grupate ca în cazul Unix într-o singură primitivă oferită de sistemul de operare. Avem funcții pentru maparea fișierelor în memorie și funcții pentru alocarea de memorie fizică în spațiul de adresă al unui proces.

Maparea fișierelor
Pentru a mapa un fișier în spațiul de adresă al unui proces trebuie mai întâi creat un handle către un obiect de tipul <tt>FileMapping</tt> și apoi realizată efectiv maparea. Funcțiile <tt>CreateFileMapping</tt> și <tt>MapViewOfFile</tt> au mai fost prezentate atunci când s-a discutat despre memoria partajată. Le prezentăm din nou pe scurt aici:

Funcția primește ca parametri handle-ul fișierului care se dorește a fi mapat, atribute de securitate care controlează accesul la hande-ul obiectului <tt>FileMapping</tt> creat, tipul mapării (<tt>PAGE_READONLY</tt>, <tt>PAGE_READWRITE</tt>, <tt>PAGE_WRITECOPY</tt> pentru copy-on-write) și dimensiunea maximă care poate fi mapată cu ajutorul funcției <tt>MapViewOfFile</tt>. Opțional se poate specifica și un șir care să identifice obiectul <tt>FileMapping</tt> creat. Dacă mai există un obiect de acest tip, funcția <tt>CreateFileMapping</tt> nu va crea unul nou, ci îl va folosi pe cel existent. Atenție însă, obiectul trebuie să fi fost creat cu drepturi care să permită procesului apelant să îl deschidă. Pentru deschiderea unui obiect de tip <tt>FileMapping</tt> deja creat se mai poate folosi funcția <tt>OpenFileMapping</tt>, funcție care a fost de asemenea prezentată atunci când s-a discutat despre memoria partajată.

Funcția primește ca parametri un handle către un obiect de tip <tt>FileMapping</tt>, modul de acces la zona mapată (<tt>FILE_MAP_READ</tt>, <tt>FILE_MAP_WRITE</tt>, <tt>FILE_MAP_COPY</tt> pentru copy-on-write), offset-ul în fișier de unde începe maparea și numărul de octeți de mapat. Funcția va întoarce un pointer în spațiul de adresă al procesului, la zona mapată.

Alocare de memorie în spațiul de adresă al procesului
Pentru alocarea de memorie în spațiul de adresă al procesului se pot folosi funcțiile <tt>VirtualAlloc</tt> sau <tt>VirtualAllocEx</tt>:

Cu funcția <tt>VirtualAllocEx</tt> se poate aloca memorie în spațiul de adresă al unui proces arbitrar, specificat în parametrul hProcess. Procesul curent trebuie să aibă drepturi corespunzătoare asupra procesului pe care se încearcă operația (PROCESS_VM_OPERATION). Funcțiile întorc un pointer către adresa de start, iar parametrii așteptați de funcții sunt descriși mai jos:
 * fAllocationType
 * specifică tipul operației: rezervare (<tt>MEM_RESERVE</tt>), alocare (<tt>MEM_COMMIT</tt>) sau renunțare la zonă (<tt>MEM_RESET</tt>); rezervarea unei zone înseamnă de fapt "punerea deoparte" a unui interval din spațiul de adrese virtuale al procesului, fără a se aloca însă memorie fizică; dacă se folosește MEM_COMMIT, se alocă efectiv memorie (dar doar dacă în prealabil zona vizată a fost rezervată); atunci când se renunță la zonă nucleul poate face discard la paginile din zonă, fără a face însă dezalocarea lor; după această operație datele nu se păstrează
 * lpAddress
 * adresa din spațiul de adresă de unde începe alocarea; trebuie să fie multiplu de 4KB pentru alocare și 64KB pentru rezervare; dacă este NULL sistemul va furniza automat o adresă
 * dwSize
 * dimensiunea zonei
 * flProtect
 * specifică modul de acces permis la zona alocată: <tt>PAGE_EXECUTE</tt>, <tt>PAGE_EXECUTE_READ</tt>, <tt>PAGE_EXECUTE_READWRITE</tt>, <tt>PAGE_EXECUTE_WRITECOPY</tt>, <tt>PAGE_READONLY</tt>, <tt>PAGE_READWRITE</tt>, <tt>PAGE_WRITECOPY</tt>, <tt>PAGE_NOACCESS</tt>, <tt>PAGE_GUARD</tt>, <tt>PAGE_NOCACHE</tt>. Modurile <tt>_WRITECOPY</tt> arată că se va folosi mecanismul copy-on-write. Modul <tt>PAGE_GUARD</tt> specifică faptul că la primul acces la o astfel de zonă se va genera o excepție <tt>STATUS_GUARD_PAGE</tt>. <tt>PAGE_GUARD</tt> și <tt>PAGE_NOCACHE</tt> se pot folosi împreună cu celelalte moduri

Demaparea unei zone din spațiul de adresă
Pentru demaparea unei fișier mapat în memorie se folosește funcția <tt>UnmapViewOfFile</tt>:

Funcția primește adresa de început a zonei, și întoarce TRUE dacă operația s-a încheiat cu succes sau FALSE în caz contrar, caz în care eroarea poate fi aflată cu funcția <tt>GetLastError</tt>.

Pentru demaparea unei zone de memorie din spațiul de adresă se folosesc funcțiile <tt>VirtualFree</tt> și <tt>VirtualFreeEx</tt>:

Funcția <tt>VirtualFreeEx</tt> va dezaloca o zonă de memorie din spațiul de adresă al unui proces arbitrar, specificat în parametrul hProcess. Procesul curent trebuie să aibă drepturi corespunzătoare asupra procesului pe care se încearcă operația (<tt>PROCESS_VM_OPERATION</tt>).

Parametrii lpAddress și dwSize identifică zona de dezalocat. dwFreeType specifică tipul operației: <tt>MEM_DECOMMIT</tt>, <tt>MEM_RELEASE</tt>. Prima operație va demapa paginile din spațiul de adresă, dar ele vor rămâne rezervate. Cea de-a doua operație va anula rezervarea întregii zone "puse deoparte" anterior, astfel încât adresa de start trebuie să coincidă cu adresa de start a zonei rezervate, iar dimensiunea trebuie să fie setată pe 0.

Schimbarea protecției unei zone mapate
În Windows, schimbarea drepturilor de access a unei zone mapate se poate face cu ajutorul funcțiilor <tt>VirtualProtect</tt> și <tt>VirtualProtectEx</tt>:

Funcțiile vor schimba protecția paginilor care au măcar un octet în intervalul [lpAddress, lpAddress + dwSize - 1] la cea specificată în flNewProtect. Vechile drepturi de acces sunt salvate în lpfOldProtect.

Interogarea zonelor mapate
Pentru a afla informații despre o zonă mapată în spațiul de adresă al unui proces se pot folosi funcțiile <tt>VirtualQuery</tt> și <tt>VirtualQueryEx</tt>. Ele vor oferi informații apelantului despre adresa de start a zonei, protecție, dimensiune etc.

Funcțiile primesc ca parametri o adresă din cadrul zonei ce se dorește a fi interogată, un pointer către un buffer alocat ce va primi informații despre zonă și întorc numărul de octeți scriși în buffer. Dacă funcția întoarce 0 înseamnă că nici o informație nu a fost furnizată. Acest lucru se întâmplă dacă funcției îi este pasată o adresă din spațiul kernel.

Informațiile primite vor descrie două zone: zona alocată (cu <tt>VirtualAlloc</tt>) în care este inclusă adresa dată, și zona care conține pagini de același fel (cu aceeași protecție și stare) în care este inclusă adresa dată:

Câmpurile <tt>AllocationBase</tt> și <tt>AllocationProtect</tt> se referă la zona alocată, iar <tt>BaseAddress</tt>, <tt>RegionSize</tt>, <tt>Type</tt> și <tt>Protect</tt> la zona ce conține pagini de același fel. <tt>State</tt> indică starea paginilor din zonă: MEM_COMMIT pentru zonă alocată, MEM_RESERVED pentru zonă rezervată și MEM_FREE pentru zonă nealocată. <tt>Type</tt> indică dacă în zonă este mapat un fișier (MEM_IMAGE sau MEM_MAPPED) sau nu, și indică de asemenea dacă zona este partajată sau nu (MEM_PRIVATE).

Blocarea paginării
Pentru blocarea paginării pentru un set de pagini (nu se va mai face swapout - în consecință apelurile ulterioare nu mai produc page fault), sistemul de operare Windows pune la dispoziția utilizatorilor funcțiile <tt>VirtualLock</tt> și <tt>VirtualLockEx</tt>:

Funcțiile primesc prin parametri un interval de pagini (alcătuit din paginile care au măcar un octet în intervalul [lpAddress, lpAddess + dwSize]) pentru care se vrea blocarea paginării. Dacă reușesc, vor întoare <tt>TRUE</tt>, altfel <tt>FALSE</tt> și eroarea se poate afla cu <tt>GetLastError</tt>.

Funcțiile pentru repornirea paginării sunt:

Excepții
Atunci când sistemul de operare detectează accese incorecte la memorie, va genera o excepție către procesul care a efectuat accesul. Pentru tratarea excepției se pot folosi construcții <tt>__try</tt> și <tt>__except</tt>, pentru care este necesar suport din partea compilatorului, sau se poate folosi funcția <tt>AddVectoredExceptionHandler</tt>. Vom discuta în continuare ultima variantă.

Funcția <tt>AddVectoredExceptionHandler</tt> va adăuga pe lista funcțiilor de executat atunci când se generează o excepție, pe cea primită ca parametru în <tt>VectoredHandler</tt>. Parametrul <tt>FirstHandler</tt> indică dacă funcția dorește sa fie adăugată la începutul listei sau la sfârșit. Funcția de tratare a excepțiilor trebuie să aibă următoarea semnătură:

În cazul unor excepții cauzate de un acces invalid la memorie, <tt>ExceptionCode</tt> va fi setat la <tt>EXCEPTION_ACCESS_VIOLATION</tt> sau <tt>EXCEPTION_DATATYPE_MISALIGNMENT</tt>, iar <tt>ExceptionAddress</tt> la adresa instrucțiunii care a cauzat excepția; <tt>NumberParameters</tt> va fi setat pe 2, iar prima intrare în <tt>ExceptionInformation</tt> va fi 0 dacă s-a efectuat o operație de citire sau 1 dacă s-a efectuat o operație de scriere. A doua intrare din <tt>ExceptionInformation</tt> va conține adresa virtuală la care s-a încercat accesarea fără drepturi, fapt care a dus la generarea excepției. Așadar, corespondentul câmpului <tt>si_addr</tt> din structura <tt>siginfo_t</tt> de pe Linux este <tt>ExceptionInformation[1]</tt> pe Windows, NU <tt>ExceptionAddress</tt>.

Funcția de tratare a excepției înregistrată cu <tt>AddVectoredExceptionHandler</tt> trebuie să întoarcă <tt>EXCEPTION_CONTINUE_EXECUTION</tt>, dacă excepția a fost tratată și se dorește continuarea execuției, sau <tt>EXCEPTION_CONTINUE_SEARCH</tt> pentru a continua parcurgerea listei de funcții de tratare a excepțiilor, în caz că au fost înregistrate mai multe astfel de funcții.

Quiz
Pentru auto-evaluare răspundeți la întrebările din acest quiz.

Prezentare
Pentru a urmări mai ușor noțiunile expuse la începutul laboratorului folosiți această prezentare (pdf) (odp).

Exerciții de laborator
Folosiți arhiva de sarcini a laboratorului.


 * 1) Linux (1p) În directorul <tt>efence</tt> din arhivă se găsește un program care conține un bug. Folosiți <tt>efence</tt> pentru detectarea bug-ului și corectați-l. Explicați de ce bug-ul nu s-a manifestat anterior.
 * 2) * nu modificați char mtext[0] din structura msgbuf - motivul folosirii acestui vector de dimensiune zero în structură.
 * 3) * Hint: nu uitați de <tt>ipcrm(1)</tt>.
 * 4) Linux (2p)/Windows (2p): Să se scrie un program care copiază un fișier. Programul primește ca argumente numele fișierului sursă, numele fișierului desținatie, mapează în memorie cele două fișiere și copiază conținutul primului fișier folosind <tt>memcpy(3)</tt>.
 * 5) * Hint: pentru aflarea lungimii unui fișier
 * 6) ** în Linux: <tt>stat(2)</tt>
 * 7) ** în Windows: <tt>GetFileAttributesEx</tt>
 * 8) * Atenție: fișierul destinație trebuie trunchiat la dimensiunea fișierului sursă
 * 9) ** în Linux: <tt>ftruncate(2)</tt>
 * 10) ** în Windows: <tt>SetFilePointer și SetEndOfFile</tt>
 * 11) * Atenţie: în Linux trebuie folosit <tt>MAP_SHARED</tt>, altfel nu se va scrie nimic în fișierul final.
 * 12) Linux (1p)/Windows (1p): Alocați cu <tt>mmap</tt>/<tt>VirtualAlloc</tt> memorie de dimensiune dublul mărimii unei pagini (folosiți <tt>NULL</tt> ca adresă de start). Acordați drepturi de scriere pentru primul byte de la adresa intoarsă. Verificați dacă ceilalți bytes pot fi scriși/citiți.
 * 13) Linux (2p)/Windows (2p): Să se creeze trei zone de memorie în spațiul de adresă, cu drepturi de citire, scriere, respectiv nici un drept. Să se testeze comportamentul programului când se fac accese de citire și scriere în aceste zone.
 * 14) * Atenţie: în Windows se poate apela <tt>VirtualProtect</tt> doar pentru o zonă de memorie alocată cu <tt>VirtualAlloc</tt>.

 Pentru acasa 

(5.) Linux/Windows: Pentru exercițiul anterior adăugați un handler de tratare a excepțiilor care să remapeze zonele cu protecție de citire și scriere la generarea excepțiilor.

Soluții

 * Soluții exerciții laborator 7

Link-uri

 * 1) Wikipedia: Memory Management
 * 2) Memory Management in Linux
 * 3) Opengroup - mmap
 * 4) MSDN: Managing Virtual Memory in Win32
 * 5) MSDN: Managing Memory-Mapped Files in Win32
 * 6) MSDN: Structured Exception Handling