De ce este necesar? La urma urmei, se pare că funcționează pentru sine și că funcționează, de ce o atinge? Acest lucru este necesar din mai multe motive. Mai întâi, nu analizați niciodată analiza codului altcuiva, mai ales dacă este un cod competent. Este o oportunitate de a învăța ceva nou și, de asemenea, de a primi plăcere estetică. În al doilea rând, nu de prisos pentru a înțelege mai profund ceva, asigurați-vă că acele lucruri în care nu au fost asigurate sau, dimpotrivă, pentru a găsi puncte slabe, care anterior nu știa că, în viitor, pentru a scrie cod mai eficient .
Pentru a înțelege sarcina managerului de memorie Delphi, veți avea nevoie de cunoașterea principiilor de memorie virtuală în Windows, precum și o înțelegere a principiilor celor patru funcții: VirtualAlloc, VirtualFree, LocalAlloc și LocalFree. Aceste informații depășesc sfera de aplicare a acestui articol și pot fi obținute fie din cartea lui Jeffrey Richter "Windows for Professionals, ediția a IV-a", fie din MSDN.
Mai întâi de toate, să acceptăm înțelesul termenilor utilizați. Confuzia principală rezultă din faptul că managerul de memorie Delphi utilizează serviciile managerului de memorie virtuală Windows. Un bloc de memorie, liber de la punctul de vedere al programului, poate fi considerat dedicat din punct de vedere al Windows. Deci, un fragment de memorie va fi numit gratuit, dacă este format în întregime din pagini de memorie liberă în sensul Windows. Un fragment de memorie va fi numit rezervat dacă este format în întregime din paginile de memorie rezervate de aplicație. Un fragment de memorie va fi numit dedicat dacă este format în întregime din paginile de memorie, sub care este alocată memoria fizică. În consecință, fragmentul de memorie va fi numit nealocat, dacă constă din paginile rezervate de aplicație, dar pentru care nu este alocată memoria fizică. Un fragment de memorie va fi numit folosit dacă a fost trecut la programul Delphi. În cele din urmă, fragmentul de memorie va fi numit neutilizat dacă există o memorie fizică sub ea, dar în prezent nu este utilizată de aplicația Delphi. Întreaga ierarhie a memoriei este prezentată în Fig. 1.
Structura managerului de memorie Delphi
Managerul de memorie Delphi este alcătuit din patru administratori, precum și funcții de depanare și diagnosticare. Schema de dependență a administratorilor este prezentată în figura 2. Vom descrie pe scurt fiecare dintre ele.
Primul dintre aceștia este administratorul descriptor de bloc. Sarcina sa este de a oferi celorlalți administratori funcții pentru a lucra cu liste de apeluri bidirecționale. Așa cum vom vedea mai târziu, toate informațiile despre memoria alocată și rezervată sunt stocate în astfel de liste. Aceasta ridică o problemă, care constă în faptul că elementele unor astfel de liste trebuie să fie create dinamic. Administratorul descriptor de bloc rezolvă această problemă prin trimiterea la mormântul principal al procesului.
Managerul rezervat de memorie este responsabil pentru memoria rezervată de administratorul de memorie Delphi. Datorită implementării Managerului de memorie virtuală Windows, acest administrator procesează, de asemenea, cereri de transmisie și de returnare a memoriei fizice în regiunea specificată.
Funcția principală a administratorului de memorie alocată este de a gestiona cererile de alocare și de eliberare a memoriei. De asemenea, stochează informații despre toată memoria rezervată, dar care nu a fost încă alocată.
Administratorul memoriei utilizate este administratorul de nivel inferior. Comunică direct cu aplicația Delphi în sine, satisfăcând cererile sale de alocare și eliberare a memoriei. Stochează informații despre toată memoria alocată.
Una dintre trăsăturile activității tuturor administratorilor (cu excepția administratorului descriptorilor de bloc) este faptul că lucrează pe principiul "puteți face mai mult, nu puteți mai puțin". La cerere, administratorul poate aloca mai mult decât suma solicitată de memorie, iar administratorul unui nivel inferior ar trebui să decidă cum să utilizeze restul. Astfel, de exemplu, atunci când încearcă să rezerve o zonă de memorie de 100 bytes (memorie de administrator apel alocate administratorului de memorie este rezervata), va fi în continuare rezervat regiunea în 1 MB de memorie dedicată și administrator va trebui să decidă cum să se ocupe cu reziduu. În consecință, atunci când încercați să eliberați memoria, administratorul poate elibera un fragment mai mic.
De asemenea, vom face o serie de comentarii cu privire la schema de gestionare a erorilor administratorului de memorie Delphi. Dacă funcția returnează un bloc de memorie selectat, în caz de eroare, este returnat un bloc cu valoare addr egal cu zero. În cazul unei erori la eliberarea memoriei, este setată variabila globală heapErrorCode. În plus, un număr de funcții care returnează o valoare booleană semnalează o eroare, returnând valoarea False.
Administrator de descriptori de bloc
Aproape toate informațiile despre memoria rezervată, alocată și nealocată sunt stocate sub formă de liste bidirecționale. Principalul lor avantaj este că pentru a șterge un element dintr-o astfel de listă este suficient să cunoaștem numai indicatorul acestui element (a se vedea, de exemplu, funcția DeleteBlock). Administratorul descriptorului de bloc oferă altor administratori funcții pentru a lucra cu astfel de liste, precum și pentru alocarea dinamică a elementelor din aceste liste în memorie.
O dată vom observa că listele bidirecționale ale inelului, folosite de managerul memoriei Delphi, se întâmplă în două tipuri. Principala diferență între tipuri este modul în care blocul este introdus în listă și blocul este eliminat din listă. Când adăugați un bloc în lista primului tip, restul blocurilor din listă rămân neschimbate. În consecință, putem elimina din listă numai blocul care a fost inserat mai devreme în el. Când se adaugă un bloc la lista de tip 2, se verifică dacă blocul adăugat este adiacent blocurilor rămase din listă. Dacă se întâmplă acest lucru, apare "îmbinarea" blocurilor, adică toate blocurile adiacente sunt înlocuite cu unul. În consecință, este posibil să se elimine din această listă orice bloc de memorie, dacă este conținut doar în unul dintre blocurile prezente în listă. În acest caz, se poate produce "împărțirea" blocului în două. Utilizarea listelor de tip al doilea evită fragmentarea memoriei.
Luați în considerare acum alocarea dinamică a memoriei sub descriptori. Toate datele referitoare la administratorul descriptorului de blocuri sunt stocate în două liste unidirecționale. Primul (blockDescBlockList) stochează descriptorii în grupuri de câte o sută. A doua listă (blockDescFreeList) stochează descriptorii disponibili în prezent, iar următorul câmp al mânerului este utilizat ca legătură între ele. Fiecare dintre mânere este într-unul dintre tipurile de structuri TBlockDescBlock enumerați blockDescBlockList, iar dacă descriptorul este liber, de asemenea, în lista de blockDescFreeList. O stare tipică de administrator pentru descriptorii de bloc este prezentată în Figura 3.
Descrieți pe scurt funcțiile de administrator ale descriptorilor de bloc.
Această funcție adaugă blocul de memorie specificat în lista de apeluri bidirecționale din cel de-al doilea tip (a se vedea mai sus). Valoarea returnată este blocul de memorie format ca rezultat al fuziunii blocului adăugat cu cele deja existente. În cazul unei erori, valoarea addr în blocul returnat este setată la zero.
Această funcție șterge blocul de memorie din lista bidirecțională tip inel a celui de-al doilea tip. Dacă este reușită, funcția returnează True.
Administrator al memoriei rezervate
Astfel, managerul de memorie rezervată conține o singură listă circulară spațială bidirecțională, care stochează o descriere a tuturor apelurilor către funcția VirtualAlloc pentru a rezerva memorie. Inițial, această listă nu conține elemente. Administratorul folosește constantele:
- cSpaceAlign, indicând limita pe care trebuie să o alinieze fragmentele de memorie;
- cSpaceMin, indicând dimensiunea minimă a blocului de memorie care trebuie rezervat;
- cPageAlign, care specifică dimensiunea paginii.
Descrieți pe scurt scopul și caracteristicile fiecăreia dintre funcțiile care fac parte din administrator.
Regiunea de memorie rezervată a cel puțin minusimii octeți. Returnează un bloc de memorie care a fost rezervat. În cazul unei erori, aceasta returnează zero în câmpul addr al blocului de memorie. Memoria rezervată are atributul de acces PAGE_NOACCESS.
Această funcție are o caracteristică interesantă, adică poate rezerva un fragment de memorie mai mic decât cel solicitat. Această caracteristică va fi discutată în detaliu în descrierea funcției GetCommittedAt.
Administrator de memorie dedicată
Managerul de memorie alocat stochează informații despre memoria rezervată, dar nu alocată. Acordați atenție jocului cu cuvinte: administratorul se ocupă de memoria alocată, dar stochează informații despre memoria care nu a fost alocată. Acest administrator procesează cererile de alocare a memoriei de la administratorul memoriei utilizate. Prin urmare, stochează informații despre memoria nealocată, dar rezervată, pentru a satisface nevoile administratorului memoriei utilizate din aceste stocuri. Dacă stocul de memorie nealocat este insuficient, administratorul se aplică administratorului memoriei rezervate.
Managerul de memorie alocat conține o listă bidirecțională de decommittedRoot care conține informații despre toată memoria rezervată, dar nu alocată. Inițial, lista nu conține elemente. Memorie adăugată și eliminată din lista de apeluri folosind decommittedRoot MergeBlockAfter și RemoveBlock, deci în această listă nu poate conține două fragmente de memorie adiacente (de ex. E. Această listă de al doilea tip). În plus, administratorul utilizează o singură constantă - cCommitAlign, care arată ce limită vor fi aliniate cu cererile de alocare a memoriei.
Să vorbim puțin despre interacțiunea dintre administratorii de memorie rezervată și memoria alocată. Toate fragmentele de memorie, care sunt incluse în lista de decommittedRoot, administratorul de memorie dedicată transmite memorie fizică, folosind Commit funcția de administrator memorie rezervate. Dacă, pentru a satisface cererea, administratorul memoriei rezervate nu are suficientă memorie rezervată, dar nu este alocată, ea numește GetSpace. În consecință, administratorul memoriei alocate returnează memoria fizică apelând funcția de dezangajare a managerului de memorie rezervată. După aceea, fragmentul specificat este adăugat la lista de decommittedRoot și se face o verificare pentru a elimina rezervarea dintr-un fragment de memorie nou format prin îmbinarea blocului de memorie eliberat cu cei deja aflați în lista dezangajatăRoot.
Ar trebui să acordați atenție unei caracteristici a comportamentului funcției GetCommittedAt a administratorului de memorie dedicată. Această funcție determină un ciclu GetSpaceAt funcționează atâta timp cât dimensiunea spațiului rezervat nu depășește dimensiunea solicitată (cm. GetSpaceAt funcție particulară). Dar, în același timp, mai mulți descriptori din lista spațiuRoot vor corespunde acestui fragment de memorie. Pentru ce sa făcut asta? Amintiți-vă că apelarea funcției VirtualAlloc pentru a elibera pagina (pavilionul MEM_RELEASE) trebuie să se potrivească exact cu apelul către VirtualAlloc, care a rezervat memoria. Astfel, cu următoarea reducere a mărimii blocului (sau atunci când este eliberată), managerul de memorie va putea să revină la sistem cel puțin o parte din fragmentele rezervate în care a fost localizat acest bloc de memorie.
Descrieți pe scurt fiecare dintre funcțiile administratorului de memorie dedicată.
Selectează o zonă de memorie de cel puțin minSize octeți. Returnează blocul de memorie care a fost efectiv alocat. În cazul unei erori, acesta returnează zero în câmpul addr al blocului de memorie.