API/Speicherarten

für den Programmierer stellt NT für das Speichermanagement mehrere sogenannte APIs (Advanced Programming Interface) zur Verfügung; als da wären:

Die verschiedenen APIs sind in unterschiedlichen Ebenen angeordnet, Grundlage ist Virtual API.

api.jpg (97439 Byte)

Noch ein paar Ergänzungen zum Paging:

der Auslagerungsbereich besteht aus dem system paging file und aus Codedateien (read-only) für die Programme

jede MEM_COMMIT-Seite ist entweder im RAM oder im Auslagerbereich

mit GlobalMemoryStatus kann man Informationen über die NT-Speicherverwaltung erhalten:

void GlobalMemoryStatus(LPMEMORYSTATUS stat)

Member von Struktur MEMORYSTATUS  
dwLength Strukturgröße
dwMemoryLoad aktuelle Speicherauslastung in % (ungefähr)
dwTotalPhys gesamter physischer Speicher in Bytes
dwAvailPhys freier physischer Speicher in Bytes
dwTotalPageFile maximaler Pagingfile-Speicher in Bytes
dwAvailPageFile davon noch frei
dwTotalVirtual virtueller Speicher im Adreßraum des Aufruferprozesses
dwAvailVirtual noch freier virtueller Speicher im Adreßraum des Aufruferprozesses

eine weitere interessante Funktion ist GetSystemInfo

eine VMM-Komponente beschäftigt sich mit der Seitenverwaltung, d.h. sie stellt 6 Listen zur Verfügung, welche jeweils einen Typ von Seiten verwalten:

wenn eine neue Seite benötigt wird, geschieht folgendes:

es wird eine ausgenullte Seite belegt oder eine freie Seite belegt / ausgenullt und/oder mit Code geladen

wenn beide Liste (free bzw zeroed pages) leer sind, ist als nächstes die standby-Liste unter Beschlag: die Idee ist lazy evaluation ("faule Auswertung") -> die standby-Seiten werden nicht gleich als free page erklärt und ausgenullt, sondern in einer Art Schwebezustand gehalten, d.h. sie können evtl. doch noch vom alten Prozeß wiederverwendet werden (alte Daten sind noch der Seite) oder aber von einem neuen Prozeß angefordert und evtl. mit 0 gefüllt werden

als nächstes werden die modified pages durchsucht, das Prinzip entspricht den standby pages -> hier wird spätestens ein Paging-Festplattenzugriff erspart

bei knappen Hauptspeicher positioniert der VMM länger nicht benutzte Seiten in der standby/modified-Liste

paging2.jpg (106797 Byte)

Global/Local API

Die Global / Local API ist noch ein Relikt aus Win3.x-Zeiten, diese Funktionen sind mit den C-Standardbibliotheksfunktionen a là malloc/realloc/free zu vergleichen. Diese Funktionen können durchaus noch verwendet werden, allerdings ist es nicht empfehlenswert.

HGLOBAL GlobalAlloc( UINT uFlags,DWORD dwBytes ) - allokiert einen Speicherbereich mit best. Attributen

Parameter

uFlags spezifiziert, wie der Speicher zu allokieren ist

ausgewählte Flags
GMEM_FIXED allokiert Speicher, der nicht verschoben werden darf, Rückgabewert ist ein Pointer
GMEM_MOVEABLE allokiert Speicher, der verschoben werden darf, Rückgabewert ist ein Handle, das mit GlobalLock in einen Zeiger umgewandelt werden kann
GMEM_DISCARDABLE allokiert auslagerbaren Speicher
GMEM_NODISCARD allokiert nicht auslagerbaren Speicher
GMEM_ZEROINIT füllt Speicher mit 0

dwBytes Anzahl der zu allokierenden Bytes

Bemerkungen

NULL-Zeiger als Rückgabe = bei Allokieren nicht möglich, allokierter Speicher wird an 8 Byte-Grenzen ausgerichtet, siehe auch GlobalFree, GlobalLock, GlobalReAlloc, GlobalSize

 

HLOCAL LocalAlloc( UINT uFlags,UINT uBytes )

Parameter

uFlags spezifiziert wieder, wie Speicher zu allokieren ist

ausgewählte Flags
LMEM_FIXED allokiert Speicher, der nicht verschoben werden darf, Rückgabewert ist ein Pointer
LMEM_MOVEABLE allokiert Speicher, der verschoben werden darf, Rückgabewert ist ein Handle, das mit LocalLock in einen Zeiger umgewandelt werden kann
LMEM_DISCARDABLE allokiert auslagerbaren Speicher
LMEM_NODISCARD allokiert nicht auslagerbaren Speicher
LMEM_ZEROINIT füllt Speicher mit 0

uBytes Anzahl der zu allokierenden Bytes

Bemerkungen

NULL-Zeiger als Rückgabe = bei Allokieren nicht möglich, allokierter Speicher wird an 8 Byte-Grenzen ausgerichtet, siehe auch LocalFree, LocalLock, LocalReAlloc, LocalSize

 

HGLOBAL GlobalFree( HGLOBAL handle )

HLOCAL LocalFree( HLOCAL handle )

HGLOBAL GlobalReAlloc( HGLOBAL handle,DWORD newByteSize, UINT uFlags )

HLOCAL LocalReAlloc( HLOCAL handle,DWORD newByteSize, UINT uFlags )

mit Global-/LocalFree wird der Speicherbereich freigegeben

Virtual API

Pageattribute

um die Funktionsweise der Virtual-API-Funktionen zu verstehen, muß man die Attribute kennen, die einer Seite zugewiesen werden können:

MEM_COMMIT physischen Speicher zur Verfügung stellen, Pageeintrag wird initialisiert
MEM_RESERVE Page wird als reserviert markiert (in Pagetable), aber noch kein physischer Speicher zur Verfügung gestellt
MEM_FREE Pagetable-Eintrag und Speicher freigegeben
MEM_DECOMMIT eine voll allokierte Seite wird physisch freigegeben, außer dem Pagetable-Eintrag
MEM_RELEASE Seite vollständig freigeben
MEM_TOP_DOWN Speicher wird an der höchtmöglichen Adresse allokiert

weitere Attribute sind:

PAGE_NOACCESS Lesen, Schreiben, Code ausführen löst General Protection Fault (Prozeßabbruch), für Bereiche über 2 GB betriebssystemintern
PAGE_READONLY Code ausführen und Lesen bei Intel erlaubt, andere CPUs nur Lesen
PAGE_READWRITE Lesen, Schreiben
PAGE_WRITECOPY Lesen, Schreiben erlaubt

erster Schreibzugriff löst Page Fault aus, den der VMM abfängt, um die betreffende Seite zu kopieren, in neuer Seite wird Schreibzugriff ausgeführt

Copy-On-Write-Prinzip

gemeinsame Nutzung von gleichen Daten

PAGE_EXECUTE

PAGE_EXECUTE_READ

PAGE_EXECUTE_READWRITE

PAGE_EXECUTE_WRITECOPY

analog vorhergehenden Attributen, nur daß zusätzlich Codeausführung möglich ist
PAGE_GUARD solche markierten Seiten sind zwar voll allokiert, lösen beim ersten Zugriff eine Ausnahme aus, die vom VMM aufgefangen wird

letzte Seite des Stacks wird mit diesem markiert, um vom Interrupt-Handler des VMM den Stackbereich zu erweitern

PAGE_NOCACHE Seite darf nicht im Systemcache gespeichert werden

zur Kommunikation mit externen Geräten

Es gibt folgend Virtual-API-Funktionen:

LPVOID VirtualAlloc(LPVOID lpvAddress,DWORD cbSize,DWORD fdwAllocationType,DWORD fdwProtect)

 

DWORD VirtualQueryEx(LPCVOID lpAddress,PMEMORY_BASIC_INFORMATION lpmbiBuffer,DWORD dwLength)

PVOID BaseAddress Basisadresse der Region
PVOID AllocationBase Basisadresse im Prinzip wie BaseAddress
DWORD AllocationProtected liefert den Zugriffsschutz (PAGE_READONLY...)
DWORD RegionSize Regiongröße
DWORD State Allokationsstatus (MEM_RESERVED...)
DWORD Protect wie AllocationProtect
DWORD Type Typ der Seiten (MEM_PRIVATE...)

 

VirtualLock/VirtualUnlock dienen dazu, um Seiten nicht auslagerbar/auslagerbar zu machen (lock) -> gefährlich

 

BOOL VirtualProtect(HANDLE hProcess,LPVOID lpvAddress,DWORD cbSize,DWORD fdwNewProtect,PDWORD pfdwOldProtect) dient zur Veränderung des Zugriffsschutzes (MEM_READONYLY...) und darf nur verwendet werden, wenn der gesamte reservierte Bereich gültig ist (physisch allokiert).

 

Heap-API

Die Heap-API ist als Alternative für die Global/Local-API gedacht. Jeder Prozeß hat einen sogenannten Win32-Heap, den das System automatisch beim Start erzeugt (default heap). Dessen Handle kann man mittels GetDefaultHeap() ermitteln und in verschiedenen Heapfunktionen einsetzen. Auf jedem Heap können einzelne Speicherbereiche allokiert werden. Jeder Prozeß kann mehrere Heaps besitzen.

Grundfunktion ist LPVOID HeapAlloc(HANDLE hHeap,DWORD dwFlags,DWORD dwBytes)

die Größe eines Blockes kann man mit LPVOID HeapReAlloc(HANDLE hHeap,DWORD dwFlags,LPVOID lpMem,DWORD dwBytes) verändern

 

HeapFree(hHeap,dwFlags,lpMemory) gibt einen Block frei

HANDLE HeapCreate(DWORD flOptions,DWORD dwInitialSize,DWORD dwMaximumSize) erstellt einen neuen (prozessprivaten) Heap

HeapDestroy(hHeap) zerstört einen Heap

HeapLock(hHeap) und HeapUnlock(hHeap) sperren bzw. entsperren einen Heap für andere Threads

HeapCompact(hHeap,dwFlags) gibt mit HeapFree befreite Blöcke an NT zurück (nur noch reservierte Seiten)

malloc, realloc, free werden übrigens auf die Heap-Funktionen abgebildet.

Stack

Jeder Thread besitzt unter NT seinen eigenen Stack. Stacks können mit einer bestimmten Größe voreingestellt werden. Erreicht der Stackpointer die vorletzte Seite des Stacks (mit PAGE_GUARD gekennzeichnet) des Stacks, wird eine Ausnahme ausgelöst und der VMM reserviert mehr Stackspeicher. Theoretisch kann der Stack bis 2 GB groß sein.

Shared Memory/Memory Mapped Files

Das Wesen von Shared Memory ist, daß 2 oder mehrere Prozesse einen gemeinsamen Speicherbereich nutzen. Dieser Speicherbereich wird jeweils in Adreßraum eines jeden beteiligten Prozesses eingeblendet.

Es gibt zwei Arten: statischen und dynamischen. Ich konzentriere mich auf den dynamischen.

Dynamischer Shared Memory=Memory Mapped Files (MMF)

Der Mechanismus funktioniert wie das Codesharing bei mehreren Anwendungsinstanzen. Man kann quasi über Zeiger auf eine Datei zugreifen. Alle MMF-Seiten gehören 2 oder mehreren Prozessen.

Der Zugriff auf MMF geschieht in folgenden groben Schritten:

HANDLE hMMF=CreateFileMapping(hFile,...,); //hFile ist ein Handle auf eine Datei, die mit OpenFile oder CreateFile erzeugt wurde

int* lpv=MapViewOfFile(hMMF,...,offset,...,anzahl_bytes); //x Byte großen Dateibereich ab bestimmten Dateioffset in Adreßraum mappen

lpv[i]=x; //auf Speicherbereich zugreifen

UnMapViewOfFile(lpv); //"entmappen"

CloseHandle(hMMF); //Handle freigeben

Threadlokaler Speicher

NT stellt für jeden Thread auf Anforderung 64 x 4byte Speicher bereit, welcher nur vom anfordernden Thread im darauffolgenden zugriffsfähig ist

Der Zugriff auf threadlokalen Speicher:

Übrigens arbeitet die MFC häufig mit solchen Slots, so daß der Zugriff von Threads auf Variablen (meist Klassenobjekte) eines anderen Threads problematisch ist.

threadlo.jpg (119134 Byte)

DLLs

DLLs werden auf Anforderung von einem Prozeß, z.B. mittels LoadLibrary (dynamisch), in dessen Adreßraum gemappt, d.h. sie wird nur einmal in Speicher geladen. Wie vorher schon erwähnt, erfolgt das nach dem lazy evaluation Prinzip (Code erst bei Bedarf in RAM geladen). Die Codeseiten sind wieder read-only. Mit DLLs ist es möglich, neben Code auch Ressourcen zu bunkern.

Eine geladene DLL benutzt den Standardheap und das "Datensegment" des ladenden Prozesses, im Gegensatz zu Win3.x, wo eine DLL ein eigenes Datensegment besitzt. Das heißt, daß alle Speicherallokationen der DLL im Adreßraum des Clientprozesses erfolgen.

DLLs werden standardmäßig an Adresse 0x10000000 geladen.

Mit jeder DLL wird eine Tabelle geladen, welche die Adresse der exportierten DLL-Funktionen und Jmp-Anweisungen enthält, d.h. ruft das Programm eine DLL-Funktion auf, springt die CPU in den enstsprechenden Fkt.tabelleneintrag und mit der dort eingetragenen Adresse springt sie schließlich zu der eigentlichen Funktion.

Validierung von Speicherzugriffen

Zugriffe auf ungültige oder geschützte Seiten lösen Interrupts aus, die im allgemeinen Fall den Verursacherprozeß beenden.

Um solche Fehler zu vermeiden, gibt es verschiedene Möglichkeiten:

NT bietet zu diesem Zweck das sogenannte Structured Exception Handling an (SEH).

das Programm validiert (Gültigkeitsprüfung) seine Zeiger vor dem Zugriff:

Letztere Methode ist allerdings nicht so gut, weil in der Zwischenzeit ein anderer Thread den jeweiligen Bereich modifizieren kann und Rechenzeit kostet.