Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sde:laboratoare:07_ro_python [2020/03/30 13:59]
ioana_maria.culic created
— (current)
Line 1: Line 1:
-====== Laborator 7 - Memoria virtuală ====== 
- 
- 
-===== Materiale ajutătoare ===== 
- 
-  *[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab06-slides.pdf | lab06-slides.pdf]] 
-  *[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab06-refcard.pdf | lab06-refcard.pdf]] 
- 
-==== Nice to read ==== 
- 
-  * TLPI - Chapter 49, Memory mappings ​ 
-  * TLPI - Chapter 50, Virtual memory operations 
- 
-===== Link-uri către secțiuni utile ===== 
- 
-==== Linux ==== 
-  * [[#​maparea_fisierelor|Maparea fișierelor]] 
-  * [[#​alocare_de_memorie_in_spatiul_de_adresa_al_procesului|Alocare de memorie în spațiul de adresă al procesului]] 
-  * [[#​maparea_dispozitivelor|Maparea dispozitivelor]] 
-  * [[#​demaparea_unei_zone_din_spatiul_de_adresa|Demaparea unei zone din spațiul de adresă]] 
-  * [[#​redimensionarea_unei_zone_mapate|Redimensionarea unei zone mapate]] 
-  * [[#​schimbarea_protectiei_unei_zone_mapate|Schimbarea protecției unei zone mapate]] 
-  * [[#​blocarea_paginarii|Blocarea paginării]] 
-  * [[#​electricfence|ElectricFence]] 
- 
-===== 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ă. De asemenea, acest mecanism face 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 (precum placa video). 
- 
-==== Concepte teoretice ====  ​ 
- 
-Dimensiunea spațiului de adresă virtual al unui proces depinde de dimensiunea registrelor procesorului. Astfel, pe un sistem de 32 biți un proces va putea accesa 2^32 = 4GB spațiu de memorie (pe de altă parte, pe un sistem de 64 biți va accesa teoretic 2^64 B). Spațiul de memorie al procesului este împărțit în spațiu rezervat pentru adresele virtuale de kernel - acest spațiu este comun tuturor proceselor - și spațiul virtual (propriu) de adrese al procesului. De cele mai multe ori, împărțirea între cele două este de 3/1 (3GB user space vs 1GB kernel space). 
- 
-Memoria fizică (RAM) este împărțită între procesele active în momentul respectiv și sistemul de operare. Astfel că, în funcție de câtă memorie avem pe mașina fizică, este posibil să epuizăm toate resursele și să nu mai putem porni un proces nou. Pentru a evita acest scenariu s-a introdus mecanismul de memorie virtuală. În felul acesta, chiar dacă spațiul virtual (compus din segmentul de text, data, heap, stivă) al unui proces este mai mare decât memoria fizică disponibilă pe sistem, procesul va putea rula încărcându-și în memorie doar paginile de care are nevoie în timpul execuției (on demand paging). 
- 
-Spațiul virtual de adrese este împărțit în //pagini virtuale// (page). Corespondentul pentru memoria fizică este //pagina fizică// (frame). Dimensiunea unei pagini virtuale este egală cu cea a unei pagini fizice. Dimensiunea este dată de hardware (în majoritatea cazurilor o pagină are 4KB pe un sistem de 32 biți sau 64 biți). 
- 
-Atât timp cât un proces în timpul rulării accesează numai pagini rezidente în memorie, se execută ca și când ar avea tot spațiul mapat în memoria fizică. În momentul în care un proces va dori să acceseze o anumită pagină virtuală, care nu este mapată în memorie, se va genera un //page fault//, iar în urma acestui page fault pagina virtuală va fi mapată la o pagină fizică. Două procese diferite au spațiu virtual diferit, însă anumite pagini virtuale din aceste procese se pot mapa la aceeași pagină fizică. Astfel că, două procese diferite pot partaja o aceeași pagină fizică, dar nu partajează pagini virtuale. 
- 
-=== malloc === 
- 
-Așa cum am aflat la laboratorul de [[http://​ocw.cs.pub.ro/​courses/​so/​laboratoare/​laborator-05#​alocareadealocarea_memoriei| gestiunea memoriei]], ''​malloc''​ alocă memorie pe heap, deci în spațiul virtual al procesului. Funcția ''​malloc''​ poate fi implementată fie folosind apeluri de sistem ''​brk'',​ fie apeluri ''​mmap''​ (mai multe detalii găsiți [[http://​ocw.cs.pub.ro/​courses/​so/​cursuri/​curs-06#​alocarea_de_memorie_virtuala|aici]]). Despre funcția ''​mmap''​ vom vorbi în următoarele paragrafe din laboratorul curent. ​ 
- 
-Alocarea memoriei virtuale se face la nivel de pagină, astfel că ''​malloc''​ va aloca de fapt cel mai mic număr de pagini virtuale ce cuprinde spațiul de memorie cerut. Fie următorul cod:<​code c> 
-char *p = malloc(4150);​ 
-DIE(p == NULL, "​malloc failed"​);​ 
-</​code>​ 
- 
-Considerând că o pagină virtuală are 4KB = 4096 octeți, atunci apelul ''​malloc''​ va aloca 4096 octeți + 54 octeți = 4KB + 54 octeți, spațiu care nu este cuprins într-o singură pagină virtuală, astfel că se vor aloca 2 pagini virtuale. În momentul alocării cu ''​malloc''​ nu se vor aloca (tot timpul) și pagini fizice; acestea vor fi alocate doar atunci când sunt accesate datele din zona de memorie alocată cu ''​malloc''​. De exemplu, în momentul accesării unui element din p se va genera un //page fault//, iar pagina virtuală ce cuprinde acel element va fi mapată la o pagină fizică. 
- 
-<​note>​ 
-În general, la apelul malloc de dimensiuni mici (când se apelează în spate apelul de sistem ''​brk''​) biblioteca standard C parcurge paginile alocate, se generează page fault-uri, iar la revenirea din apel paginile fizice vor fi deja alocate. Putem spune că pentru dimensiuni mici, apelul ''​malloc'',​ așa cum este văzut el din aplicație (din afara bibliotecii standard C), alocă și pagini fizice și pagini virtuale. 
- 
-Mai mult, alocarea efectivă de pagini virtuale și fizice are loc în momentul apelului de sistem ''​brk''​. Acesta prealocă un spațiu mai mare, iar viitoarele apeluri ''​malloc''​ vor folosi acest spațiu. În acest fel, următoarele apeluri ''​malloc''​ vor fi eficiente: nu vor face apel de sistem, nu vor face alocare efectivă de spațiu virtual sau fizic, nu vor genera //page fault-uri//​. 
- 
-Apelul ''​malloc''​ este mai eficient decât apelul ''​calloc''​ pentru că nu parcurge spațiul alocat pentru a-l umple cu zero-uri. Acest lucru înseamnă că ''​malloc''​ va întoarce zona alocată cu informațiile de acolo; în anumite situații, acest lucru poate fi un risc de securitate - dacă datele de acolo sunt private. 
-</​note>​ 
- 
-==== 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). Mai mult, partea de ''​I/​O''​ este realizată de către kernel, programatorul scriind cod care doar preia/​stochează valori din/în regiunea mapată. Astfel nu se mai apelează ''​read'',​ ''​write'',​ ''​lseek''​ - ceea ce adesea simplifică scrierea codului. 
- 
-<note important>​ Nu orice descriptor de fișier poate fi mapat în memorie. Socket-urile,​ pipe-urile, dispozitivele care nu permit decât accesul secvențial (ex. char device) sunt incompatibile 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**). </​note>​ 
- 
-=== mmap === 
- 
-Prototipul funcției [[http://​linux.die.net/​man/​2/​mmap|mmap]] ce permite maparea unui fișier în spațiul de adresă al unui proces este următorul: 
- 
-<code cpp> 
-void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 
-</​code>​ 
- 
-Funcția va întoarce în caz de eroare ''​MAP_FAILED''​. Dacă maparea s-a făcut cu succes, 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 o adresă apropiată și multiplu de dimensiunea unei pagini. Cea mai corespunzatoare adresa este si intoarsa. ​ 
- 
-Parametrul ''​prot''​ specifică tipul de acces care se dorește: ​ 
-  *''​PROT_READ''​ (citire) 
-  *''​PROT_WRITE''​ (scriere) ​ 
-  *''​PROT_EXEC''​ (execuție) 
-  *''​PROT_NONE''​. ​ 
-Cand zona e folosită altfel decât s-a declarat, este generat 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 ''​MAP_PRIVATE''​ sau ''​MAP_SHARED''​):​ 
-  *''​MAP_PRIVATE'' ​  - 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''​ pe aceeași zonă din același fișier. 
-  *''​MAP_SHARED'' ​   - Scrierile sunt actualizate imediat în toate mapările existente. In acest fel toate procesele care au realizat mapări vor vedea modificările. Lucru datorat faptului că mapările ''​MAP_SHARED''​ se fac peste paginile fizice din page cache, iar apelurile r/w folosesc paginile fizice din page cache pentru a reduce numărul de citiri/​scrieri de pe disc. Actualizările pe disc vor avea loc la un moment de timp ulterior, nespecificat. 
-  *''​MAP_FIXED'' ​    - Dacă nu se poate face alocarea la adresa specificată de ''​start'',​ apelul va eșua. 
-  *''​MAP_LOCKED'' ​   - Se va bloca paginarea pe această zonă, în maniera [[http://​linux.die.net/​man/​2/​mlock|mlock]]. 
-  *''​MAP_ANONYMOUS''​ - Se mapează memorie RAM (argumentele ''​fd''​ și ''​offset''​ sunt ignorate). 
- 
- Este de remarcat faptul că folosirea ''​MAP_SHARED''​ permite partajarea memoriei între procese care nu sunt înrudite. În acest caz, conținutul fișierului devine conținutul inițial al memoriei partajate și orice modificare făcută de procese în această zonă este copiată apoi în fișier, asigurând persistență prin sistemul de fișiere.  ​ 
- 
-=== msync === 
- 
-Pentru a declanșa în mod explicit sincronizarea fișierului cu maparea din memorie este disponibilă funcția [[http://​linux.die.net/​man/​2/​msync|msync]]:​ 
- 
-<code c> 
-int msync(void *start, size_t length, int flags); 
-</​code>​ 
- 
-unde ''​flags''​ poate fi: 
-  *''​MS_SYNC''​ - Datele vor fi scrise în fișier și se așteaptă până se termină. 
-  *''​MS_ASYNC''​ - Este inițiată secvența de salvare, dar nu se așteaptă terminarea ei. 
-  *''​MS_INVALIDATE''​ - Se invalidează mapările zonei din alte procese, astfel incât procesele își vor face update cu datele noi înscrise. 
- 
-Apelul msync este util pentru a face scrierea paginilor modificate din page cache pe disc, cu scopul de a evita pierderea modificărilor în cazul unei căderi a sistemului. 
-==== Alocare de memorie în spațiul de adresă al procesului ==== 
- 
-În UNIX, tradițional,​ pentru alocarea //memoriei dinamice//, se folosește apelul de sistem [[http://​linux.die.net/​man/​2/​brk|brk]]. 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 ([[http://​linux.die.net/​man/​2/​mmap|mmap]]),​ a existat posibilitatea ca procesele să aloce memorie folosind aceste noi apeluri de sistem. Practic, procesele pot mapa memorie în spațiul de adresă, 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 [[http://​linux.die.net/​man/​2/​mmap|mmap]]. Pentru acest lucru parametrul ''​flags''​ trebuie să conțină flag-ul ''​MAP_ANONYMOUS''​. 
- 
-==== 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 fizica a 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 ''​mmap''​. 
- 
-<note important>​ Nu toate dispozitivele pot fi mapate în memorie, însă atunci când pot fi mapate, semnificația acestei mapări depinde strict de dispozitiv. </​note>​ 
- 
-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 ''​MAP_ANONYMOUS''​. 
- 
-==== 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 [[http://​linux.die.net/​man/​3/​munmap|munmap]]:​ 
- 
-<code c> 
- int munmap(void *start, size_t length); 
-</​code>​ 
- 
-''​start''​ reprezintă adresa primei pagini ce va fi demapată (trebuie să 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țină 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 funcția [[http://​linux.die.net/​man/​2/​mremap|mremap]]:​ 
- 
-<code c> 
-void *mremap(void *old_address,​ size_t old_size, size_t new_size, unsigned long flags); 
-</​code>​ 
- 
-Zona pe care ''​old_address''​ și ''​old_size''​ o descriu trebuie să aparțină unei singure mapări. O singură opțiune este disponibilă pentru ''​flags'':​ ''​MREMAP_MAYMOVE''​ care arată că este în regulă ca pentru obținerea noii mapări să se realizeze o nouă mapare într-o altă zonă de memorie (vechea zona fiind dealocată). 
-==== 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 [[http://​linux.die.net/​man/​2/​mprotect|mprotect]]:​ 
- 
-<code c> 
-int mprotect(const void *addr, size_t len, int prot); 
-</​code>​ 
- 
-Funcția primește ca parametri intervalul de adrese [''​addr'',​ ''​addr''​ + ''​len''​ - 1] și noile drepturi de access (''​PROT_READ'',​ ''​PROT_WRITE'',​ ''​PROT_EXEC'',​ ''​PROT_NONE''​). Ca și  la [[http://​linux.die.net/​man/​2/​munmap|munmap]],​ ''​addr''​ trebuie să fie multiplu de //​dimensiunea unei pagini//. Funcția va schimba protecția pentru toate paginile care conțin cel puțin un octet în intervalul specificat. 
- 
-==== Exemplu ==== 
-<code cpp> 
-int fd = open("​fisier",​ O_RDWR); 
-void *p = mmap(NULL, 2*getpagesize(),​ PROT_NONE, MAP_SHARED, fd, 0); 
-// *(char*)p = '​a';​ // segv fault 
-mprotect(p, 2*getpagesize(),​ PROT_WRITE);​ 
-*(char*)p = '​a';​ 
-munmap(p, 2*getpagesize());​ 
-</​code>​ 
- 
-Apelul ''​getpagesize''​ va returna dimensiunea unei pagini in bytes. 
- 
-==== 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 [[http://​linux.die.net/​man/​2/​madvise|madvise]]) despre modul în care zona va fi folosită. ​ 
- 
-<spoiler Detalii despre madvise> 
-[[http://​linux.die.net/​man/​2/​madvise|madvise]] e utilă mai ales atunci când în spatele memoriei virtuale se află un dispozitiv fizic (de ex., când se mapează fișiere de pe hard-disk, kernel-ul poate citi în avans pagini de pe disc, reducând latența datorată poziționării capului de citire). Prototipul funcției este următorul: 
- 
-<code c> 
-int madvise(void *start, size_t length, int advice); 
-</​code>​ 
- 
-unde valorile acceptate pentru ''​advice''​ sunt: 
-  *''​MADV_NORMAL''​ - regiunea este una obișnuită și nu are nevoie de un tratament special. 
-  *''​MADV_RANDOM''​ - regiunea va fi accesată în mod aleator; sistemul de operare nu va citi în avans pagini. 
-  *''​MADV_SEQUENTIAL''​ - regiunea va fi accesată în mod secvențial;​ sistemul de operare ar putea citi în avans pagini. 
-  *''​MADV_WILLNEED''​ - regiunea va fi utilizată undeva în viitorul apropiat (nucleul poate decide să preîncarce paginile în memorie). 
-  *''​MADV_DONTNEED''​ - 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. 
-</​spoiler>​ 
-\\  
-==== Blocarea paginării ==== 
- 
-Paginarea se referă la evacuarea paginilor pe disc (swap out) si restaurarea lor (swap in) atunci când sunt folosite. 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/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ă de pe disc. Pentru a contracara aceste probleme, sistemele UNIX pun la dispoziție apelurile [[http://​linux.die.net/​man/​2/​mlock|mlock]] și [[http://​linux.die.net/​man/​2/​mlockall|mlockall]]. 
- 
-<code c> 
-int mlock(const void *addr, size_t len); 
-int mlockall(int flags); 
-</​code>​ 
- 
-<spoiler Detalii despre mlock şi mlockall>​ 
- 
-Funcția [[http://​linux.die.net/​man/​2/​mlock|mlock]] va bloca paginarea (nu se va mai face swap out) paginilor incluse în intervalul [''​addr'',​ ''​addr''​ + ''​len''​ - 1].  
-Funcția [[http://​linux.die.net/​man/​2/​mlockall|mlockall]] va bloca paginarea tuturor paginilor procesului, în funcție de flag-uri: 
-  *''​MCL_CURRENT''​ - se va bloca paginarea tuturor paginilor mapate în spațiul de adresă al procesului la momentul apelului 
-  *''​MCL_FUTURE''​ - se va bloca paginarea noilor pagini mapate în spațiul de adresă al procesului (noi mapări realizate cu funcția ''​mmap'',​ dar și paginile de stivă mapate automat de sistem) 
- 
-** Notă: ** 
- 
-Flag-ul ''​MCL_FUTURE''​ 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ă ''​mlockall(MCL_CURRENT | MCL_FUTURE)''​ ș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). 
-</​spoiler>​ 
- 
-\\  
-Există, bineînțeles,​ și funcții ce readuc lucrurile la normal: 
- 
-<code c> 
-int munlock(const void *addr, size_t len); 
-int munlockall(void);​ 
-</​code>​ 
- 
-Astfel, funcția [[http://​linux.die.net/​man/​2/​munlock|munlock]] va reporni mecanismul de paginare al tuturor paginilor din intervalul [''​addr'',​ ''​addr''​ + ''​len''​ - 1], iar funcția [[http://​linux.die.net/​man/​2/​munlockall|munlockall]] 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 [[http://​linux.die.net/​man/​2/​mlock|mlock]] sau [[http://​linux.die.net/​man/​2/​mlockall|mlockall]],​ este suficient un singur apel [[http://​linux.die.net/​man/​2/​munlock|munlock]] sau [[http://​linux.die.net/​man/​2/​munlockall|munlockall]] 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 ''​SIGSEGV''​ sau ''​SIGBUS''​ procesului. După cum am văzut atunci când am discutat despre semnale, semnalul poate fi tratat cu două tipuri de funcții: ''​sa_handler''​ și ''​sa_sigaction''​. Funcția de tip ''​sa_sigaction''​ va primi ca parametru o structură ''​siginfo_t''​. În cazul semnalelor ce tratează excepții cauzate de un acces incorect la memorie, următoarele câmpuri din această structură sunt setate: 
-  *''​si_signo''​ - setat la ''​SIGSEGV''​ sau ''​SIGBUS''​ 
-  *''​si_code''​ - pentru ''​SIGSEGV''​ poate fi ''​SEGV_MAPPER''​ pentru a arăta că zona accesată nu este mapată în spațiul de adresă al procesului, sau ''​SEGV_ACCERR''​ pentru a arăta că zona este mapată dar a fost accesată necorespunzător;​ pentru ''​SIGBUS''​ poate fi ''​BUS_ADRALN''​ pentru a arăta că s-a făcut un acces nealiniat la memorie, ''​BUS_ADRERR''​ pentru a arăta că s-a încercat accesarea unei adrese fizice inexistente sau ''​BUS_OBJERR''​ pentru a indica o eroare hardware 
-  *''​si_addr''​ - adresa care a generat excepția 
-==== ElectricFence ==== 
- 
-[[http://​linux.die.net/​man/​3/​efence|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 [[http://​linux.die.net/​man/​3/​efence|Electric Fence]] este înlocuirea apelurilor standard ''​malloc''​ și ''​free''​ cu implementări proprii. [[http://​linux.die.net/​man/​3/​efence|Electric Fence]] 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 puțin 1-4KB, 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ă, [[http://​linux.die.net/​man/​3/​efence|Electric Fence]] alocă zonele de memorie la limita superioară a unei pagini, mapând o pagină neaccesibilă după aceasta. Această abordare nu previne //buffer underrun//​-ul,​ în care datele sunt citite sau scrise sub limita inferioară. 
- 
-Pentru a putea verifica și astfel de situații, utilizatorul trebuie să definescă variabila de mediu ''​EF_PROTECT_BELOW''​ înainte de rula programul. În acest caz, [[http://​linux.die.net/​man/​3/​efence|Electric Fence]] 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//? Așa cum am explicat și în secțiunea precedentă,​ astfel de situații vor produce în cele din urmă erori, dar la momente de timp ulterioare, când va fi mai greu să se determine cauza erorilor cu mijloace de depanare obișnuite. În plus, în situațiile de //buffer overrun// se 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''​. Biblioteca [[http://​linux.die.net/​man/​3/​efence|Electric Fence]] 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 [[http://​linux.die.net/​man/​3/​efence|Electric Fence]] utilizatorul trebuie să folosească la link-editare biblioteca ''​libefence''​. Pentru a vedea utilitatea acestui pachet, să analizăm programul de mai jos: 
- 
-<code c ef_example.c>​ 
-#include <​stdio.h>​ 
-#include <​malloc.h>​ 
- 
-int main(void) 
-{ 
- int i; 
- int *data_1, *data_2; 
- 
- data_1 = malloc(11 * sizeof(int));​ 
- 
- for (i = 0; i <= 11; i++) 
- data_1[i] = i; 
- 
- data_2 = malloc(11 * sizeof(int));​ 
- 
- for (i = 0; i <= 11; i++) 
- data_2[i] = 11 - i; 
- 
- for (i = 0; i <= 11; i++) 
- printf("​%d %d\n", data_1[i], data_2[i]); 
- 
- free(data_1); ​ 
- free(data_2);​ 
- 
- return 0; 
-} 
-</​code>​ 
- 
-Aparent totul pare în regulă. La execuția programului însă obținem următorul output: 
- 
-<code bash> 
- ​so@spook$ gcc -Wall -g ef_example.c 
- ​so@spook$ ./a.out 
-ff: malloc.c:​3074:​ sYSMALLOc: Assertion `(old_top == (((mbinptr) (((char *)  
-&​((av)->​bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk,​ fd)))) ​ 
-&& old_size == 0) || ((unsigned long)(old_size) >= (unsigned long) 
-((((__builtin_offsetof (struct malloc_chunk,​ fd_nextsize))+((2 * (sizeof(size_t))) 
- - 1)) & ~((2 * (sizeof(size_t))) - 1))) && ((old_top)->​size & 0x1) && ​ 
-((unsigned long)old_end & pagemask) == 0)' failed. 
-</​code>​ 
- 
-Ceva este clar în neregulă. Dacă folosim biblioteca libefence și GDB eroarea va fi vizibilă imediat: 
- 
-<code bash> 
- ​so@spook$ gcc -Wall -g ef_example.c -lefence 
- ​so@spook$ gdb ./​a.out ​ 
- ​Reading symbols from /​home/​so/​a.out...done. 
- (gdb) run 
- ​Starting program: /​home/​so/​a.out ​ 
- ​[Thread debugging using libthread_db enabled] 
- 
-   ​Electric Fence 2.1 Copyright (C) 1987-1998 Bruce Perens. 
- 
- ​Program received signal SIGSEGV, Segmentation fault. 
- ​0x08048536 in main () at ef.c:12 
- ​12 data_1[i] = i; 
- (gdb) print i 
- $1 = 11 
- (gdb) 
-</​code>​ 
- 
-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 informații despre [[http://​linux.die.net/​man/​3/​efence|Electric Fence]] consultați pagina de manual (**man efence**). 
- 
- 
-===== Exerciții ===== 
- 
-Pentru rezolvarea laboratorului,​ va rugam sa clonati [[https://​www.github.com/​upb-fils/​sde|repository-ul]]. daca il aveti deja, va rugam sa rulati ''​git pull''​. ​ 
- 
-==== Exercițiul 1 - Investigarea mapărilor folosind pmap (2p) ==== 
- 
-Intrați în directorul ''​1-intro''​ și compilați sursa ''​intro.c''​. Rulați programul ''​intro'':<​code>​ 
-./intro 
-</​code>​ 
- 
-Într-o altă consolă, folosiți comanda [[http://​linux.die.net/​man/​1/​pmap | pmap]].:<​code bash> watch -d pmap $(pidof intro) 
-</​code>​ pentru a urmări modificările asupra memoriei procesului. ​ 
- 
-În prima consolă, folosiți ''​ENTER''​ pentru a continua programul. În cea de-a doua consolă urmăriți modificările care apar în urma diferitelor tipuri de mapare din cod. 
- 
-Analizați mapările făcute de procesul ''​init''​ folosind comanda:<​code bash>​sudo pmap 1 
-</​code>​ 
- 
-<note tip> 
-Puteți observa că pentru bibliotecile partajate (de exemplu, ''​libc''​) sunt mapate trei zone: zona de cod (read-execute),​ zona .rodata (read-only) și zona .data (read-write). 
-</​note>​ 
-==== Exercițiul 2 - Scrierea în fișier - write vs. mmap (1p) ==== 
- 
-Intrați în directorul ''​2-compare''​ și inspectați sursele ''​write.c''​ și ''​mmap.c'',​ apoi compilați. Obțineți timpul de execuție al celor două programe folosind comanda ''​time'':​ <code bash> ​ 
-time ./write; time ./mmap 
-</​code>​ 
- 
-Observăm că varianta cu ''​mmap''​ este mai rapidă decât varianta cu ''​write''​. Vom folosi [[http://​linux.die.net/​man/​1/​strace|strace]] pentru a vedea ce apeluri de sistem se realizează pentru rularea fiecărui program: 
-<code bash> 
-strace -c ./write 
-strace -c ./mmap 
-</​code>​ 
- 
-Din output-ul ''​strace''​ observăm că programul ''​write''​ face foarte multe (100000) de apeluri ''​write''​ și din această cauză este mai lent decât programul ''​mmap''​. 
- 
-În continuare vom analiza cele două moduri de mapare a fișierelor:​ ''​MAP_SHARED''​ și ''​MAP_PRIVATE''​. Observați că fișierul ''​test_mmap''​ (creat de programul ''​mmap''​ cu ''​MAP_SHARED''​) conține 100000 de linii: 
-<code bash> 
-cat test_mmap | wc -l 
-</​code>​ 
- 
-În programul ''​mmap.c''​ schimbați flagul de creare al memoriei partajate din ''​MAP_SHARED''​ în ''​MAP_PRIVATE'',​ compilați și rulați din nou: 
-<code bash> 
-./mmap 
-cat test_mmap | wc -l 
-</​code>​ 
- 
-<note tip>​Modificările aduse unei zone de memorie mapată cu ''​MAP_PRIVATE''​ nu vor fi vizible nici altor procese și nici nu vor ajunge în fișierul mapat de pe disc.</​note>​ 
- 
-==== Exercițiul 3 - Detectare '​buffer underrun'​ folosind ElectricFence (2p) ==== 
- 
-Intrați în directorul ''​3-efence''​ și urmăriți sursa ''​bug.c''​. Compilați și rulați executabilul ''​bug'':<​code bash> 
-make 
-./bug 
-</​code>​ 
- 
-Folosiți [[#​electricfence|ElectricFence]] pentru a prinde situația de '​buffer underrun'​ urmărind pașii: 
-  * Instalați pachetul ''​electric-fence''​ în cazul in care biblioteca ''​libefence.so''​ nu se găsește pe sistem. 
-  * Setați în bash variabila de mediu ''​EF_PROTECT_BELOW''​ la ''​1'':​ <code bash> 
-export EF_PROTECT_BELOW=1 
-</​code>​ 
-  * Creați și rulați programul ''​ef_bug''​ utilizând makefile-ul ''​Makefile_efence'':<​code bash> 
-make -f Makefile_efence 
-./ef_bug 
-</​code>​ 
- 
-<note important>​ 
-Dacă întâmpinați probleme în instalarea pachetului ''​electric-fence'',​ descărcați-l de [[http://​ro.archive.ubuntu.com/​ubuntu/​pool/​main/​e/​electric-fence/​electric-fence_2.2.4_amd64.deb| aici pentru x86_64]] și de [[http://​ro.archive.ubuntu.com/​ubuntu/​pool/​main/​e/​electric-fence/​electric-fence_2.2.4_i386.deb | aici pentru i386]] și instalați-l folosind comanda dpkg.  
- 
-<code bash> 
-$ wget http://​ro.archive.ubuntu.com/​ubuntu/​pool/​main/​e/​electric-fence/​electric-fence_2.2.4_i386.deb 
-$ sudo dpkg -i electric-fence_2.2.4_i386.deb 
-</​code>​ 
-</​note>​ 
-==== Exercițiul 4 - Copierea fișierelor folosind mmap (1p) ==== 
- 
-Intrați în directorul ''​4-cp''​ și completați sursa ''​mycp.c''​ astfel încât să realizeze copierea unui fișier primit ca argument. Pentru aceasta, mapați ambele fișiere în memorie și realizați copierea folosind ''​memcpy''​. Urmăriți comentariile cu ''​TODO''​ din sursă și următoarele hint-uri: 
-   * Înainte de mapare, aflați dimensiunea fișierului sursă folosind [[http://​linux.die.net/​man/​2/​fstat | fstat]]. 
-   * Trunchiați fișierul destinație la dimensiunea fișierului sursă folosing [[https://​linux.die.net/​man/​2/​ftruncate | ftruncate]]. 
-   * Folosiți ''​MAP_SHARED''​ pentru mapare pentru a fi transmise schimbările în fișier: rețineți faptul că apelul mmap folosește una dintre opțiunile ''​MAP_SHARED''​ sau ''​MAP_PRIVATE''​ (una singură) 
-   * Pentru fișierul de intrare protecția trebuie să fie ''​PROT_READ'':​ fișierul a fost deschis read-only. 
-   * Pentru fișierul de ieșire protecția trebuie să fie ''​PROT_READ | PROT_WRITE'';​ anumite arhitecturi/​implementări se pot plânge dacă folosiți **doar** ''​PROT_WRITE''​. 
-   * Argumentele funcției [[http://​man7.org/​linux/​man-pages/​man3/​memcpy.3.html|memcpy]] sunt, în ordine: destinația,​ sursa, numărul de octeți care să fie copiați. 
-   * Revedeți secțiunea [[#​maparea_fișierelor|maparea fișierelor]]. 
-   * Asigurați persistența datelor pe sistemul de fișiere printr-un apel explicit [[https://​ocw.cs.pub.ro/​courses/​so/​laboratoare/​laborator-06#​msync|msync]] 
- 
-Puteți testa în felul următor: <code bash> 
-./mycp Makefile /​tmp/​Makefile 
-diff Makefile /​tmp/​Makefile 
-</​code>​ 
- 
-Verificați cum realizează utilitarul [[http://​linux.die.net/​man/​1/​cp | cp]] copierea de fișiere (folosind mmap sau read/write) folosind [[http://​linux.die.net/​man/​1/​strace | strace]]. 
- 
-<note tip>​Utilitarul ''​cp''​ folosește ''​read/​write''​ pentru a copia fișiere, în special pentru a limita consumul de memorie în cazul copierii unor fișiere de dimensiuni mari. De asemenea, în cazul mapării fișierului în memorie cu ''​mmap'',​ scrierea efectivă a datelor pe disc se va face într-un timp mai îndelungat,​ lucru care de cele mai multe ori nu este dorit (urmăriți ​ [[http://​stackoverflow.com/​a/​27987994|acest link]]). </​note>​ 
- 
-==== Exercițiul 5 - Tipuri de acces pentru pagini (1p) ==== 
- 
-Intrați în directorul ''​5-prot''​ și inspectați sursa ''​prot.c''​. 
- 
-Creați o zonă de memorie în spațiul de adresă, formată din trei pagini virtuale (folosiți un singur apel ''​mmap''​). Prima pagină nu va avea vreun drept, a doua va avea drepturi de citire, iar a treia va avea drepturi de scriere (folosiți ''​mprotect''​ pentru a configura drepturile fiecărei pagini). Testați comportamentul programului când se fac accese de citire și scriere în aceste zone. Completați comentariile cu ''​TODO 1''​. 
- 
-Adăugați un handler de tratare a excepțiilor care să remapeze incremental zonele cu protecție de citire și scriere la generarea excepțiilor. Astfel, dacă pagina nu are vreun drept, la page fault se va remapa cu drepturi de citire. Dacă pagina are drepturi de citire, la page fault se va remapa cu drepturi de citire + drepturi de scriere. Completați comentariile cu ''​TODO 2''​. 
- 
-<note important>​ Trebuie să ștergeți prima linie ''​old_action.sa_sigaction(signum,​ info, context);''​ pentru a putea rezolva a doua parte a exercițiului.</​note>​ 
-==== Exercițiul 6 - Page fault-uri (1p) ==== 
- 
-Intrați în directorul ''​6-faults''​ și urmăriți conținutul fișierului ''​fork-faults.c''​. 
- 
-Vom folosi utilitarul ''​pidstat''​ ([[http://​www.cyberciti.biz/​open-source/​command-line-hacks/​linux-monitor-process-using-pidstat| tutorial pidstat]]) din pachetul ''​sysstat''​ pentru a monitoriza page fault-urile făcute de un proces. 
- 
-<note important>​Dacă întâmpinați probleme în instalarea pachetului ''​sysstat'',​ descărcați-l de [[http://​ro.archive.ubuntu.com/​ubuntu/​pool/​main/​s/​sysstat/​sysstat_11.2.0-1_i386.deb|aici]] și instalați-l folosind comanda ''​dpkg''​. 
-<​code>​ 
-student@spook:​~$ wget http://​ro.archive.ubuntu.com/​ubuntu/​pool/​main/​s/​sysstat/​sysstat_11.2.0-1_i386.deb 
-student@spook:​~$ sudo dpkg -i sysstat_11.2.0-1_i386.deb ​ 
-</​code>​ 
-</​note>​ 
- 
-Rulați programul ''​fork-faults''​. Într-o altă consolă executați comanda ​ 
- 
-<code bash>​pidstat -r -T ALL -p $(pidof fork-faults) 5</​code>​ 
- 
-pentru a urmări page fault-urile. Comanda de mai sus vă afișează câte un mesaj la fiecare 5 secunde; ne interesează valorile ''​minflt-nr''​. 
- 
-Pe rând, apăsați tasta ENTER în consola unde ați rulat programul ''​fork-faults''​ și observați output-ul comenzii ''​pidstat''​. Urmăriți 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 (după ce procesul copil își încheie execuția). 
- 
-<​note>​Pachetul ''​sysstat''​ mai conține și utilitarul ''​sar''​ prin care puteți colecta și realiza rapoarte despre activitatea sistemului. Pentru a activa salvarea datelor, trebuie setat flag-ul ''​ENABLED''​ din ''/​etc/​default/​sysstat''​. ​ 
-Cu ajutorul utilitarului ''​sar''​ puteți monitoriza informații precum încărcarea CPU-ului, utilizarea memoriei și a paginilor, operațiile de I/O, activitatea proceselor. Detalii puteți afla din [[http://​www.cyberciti.biz/​tips/​identifying-linux-bottlenecks-sar-graphs-with-ksar.html| tutorial sar]]. </​note>​ 
-==== Exercițiul 7 - Blocarea paginării (1p) ==== 
- 
-Vă aflați într-o situație în care trebuie să procesați în timp real datele dintr-un buffer și vreți să evitați swaparea paginilor. Intrați în directorul ''​7-paging''​ și completați ''​TODO-urile''​ astfel încât paginarea va fi blocată pentru variabila //data// pe parcursul lucrului cu aceasta, iar la final va fi deblocată. Deși pe Linux adresa va fi aliniată automat la dimensiunea unei pagini, acest lucru nu se întâmplă pe toate sistemele POSIX compliant, prin urmare este o practică bună să o aliniem manual. 
- 
-Deoarece variabila //data// este o variabilă locală a funcției ''​main'',​ aceasta va fi alocată pe stivă. Rulați programul ''​paging''​ și folosiți, într-o altă consolă, comanda 
-<code bash> 
-pmap -X -p $(pidof paging) 
-</​code>​ 
-după fiecare apăsare a tastei ENTER. Veți observa blocarea/​deblocarea paginării pentru paginile mapate pe stivă ce conțin cel puțin un byte al variabilei ''​data''​. 
- 
-<note tip>​Limita maximă pentru care se poate executa cu succes ''​mlock''​ este dată de ''​RLIMIT_MEMLOCK''​ (max locked memory). Aceasta are de obicei valoarea ''​64KB''​ și poate fi configurată folosind ''​ulimit''​.</​note>​ 
- 
-==== Exercițiul 8 - Schimbarea tipului de acces pentru pagini din segmentul de cod (1p) ==== 
- 
-Intrați în directorul ''​8-hack''​. Programul apelează funcția //foo()//. Având determinată pagina în care se află funcția în spațiul de adresă al procesului, i se schimbă drepturile de acces în ''​PROT_READ|PROT_WRITE|PROT_EXEC''​ și se modifică valoarea de retur a funcției (se scrie în segmentul de cod). 
- 
-Analizați cu atenție programul. Analizați comportamentul cu ''​gdb''​. Având //pid-ul// procesului afișat la ''​stdout'',​ folosiți [[http://​linux.die.net/​man/​1/​pmap|pmap]] pentru a observa pagina cu drepturile schimbate. Observați tipul de acces pentru celelalte pagini din spațiul de adresă al procesului. 
- 
-Modificați drepturile de acces în ''​PROT_READ|PROT_EXEC'',​ compilați și rulați din nou. Observați că fără drepturi de scriere execuția programului este încheiată de un semnal SIGSEGV. ​ 
- 
-<​hidden> ​   
-===== Soluții ===== 
- 
- ​[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab06-sol.zip | Soluții exerciții laborator 6]] 
-</​hidden>​ 
- 
-===== Resurse Utile ===== 
-  *[[http://​en.wikipedia.org/​wiki/​Memory_management| Wikipedia: Memory Management]] 
-  *[[http://​tldp.org/​LDP/​tlk/​mm/​memory.html| Memory Management in Linux]] 
-  *[[http://​www.opengroup.org/​onlinepubs/​009695399/​functions/​mmap.html| Opengroup - mmap]] 
  
sde/laboratoare/07_ro_python.1585565986.txt.gz · Last modified: 2020/03/30 13:59 by ioana_maria.culic
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