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.

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

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
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
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 )
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
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
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).
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)
| HEAP_GENERATE_EXCEPTIONS | bei Speichermangel soll eine Ausnahme ausgelöst werden (SEH) |
| HEAP_NO_SERIALIZE | alle Threads haben gleichzeitig Zugang zum Heap |
| HEAP_ZERO_MEMORY | Block wird mit 0 gefüllt |
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.
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.
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.
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
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.

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.
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.