Verwenden von MDLs
Ein E/A-Puffer, der einen Bereich von zusammenhängenden virtuellen Speicheradressen umfasst, kann auf mehrere physische Seiten verteilt werden, und diese Seiten können unauffällig sein. Das Betriebssystem verwendet eine Speicherdeskriptorliste (Memory Descriptor List , MDL), um das layout der physischen Seite für einen virtuellen Speicherpuffer zu beschreiben.
Eine MDL besteht aus einer MDL-Struktur , auf die ein Datenarray folgt, das den physischen Speicher beschreibt, in dem sich der E/A-Puffer befindet. Die Größe einer MDL variiert je nach den Merkmalen des E/A-Puffers, den die MDL beschreibt. Es stehen Systemroutinen zur Berechnung der erforderlichen Größe einer MDL sowie zum Zuordnen und Freigeben der MDL zur Verfügung.
Eine MDL-Struktur ist teilweise undurchsichtig. Ihr Treiber sollte direkt auf die Member Next und MdlFlags dieser Struktur zugreifen. Ein Codebeispiel, das diese beiden Member verwendet, finden Sie im folgenden Beispielabschnitt.
Die verbleibenden Member einer MDL sind undurchsichtig. Greifen Sie nicht direkt auf die undurchsichtigen Member einer MDL zu. Verwenden Sie stattdessen die folgenden Makros, die das Betriebssystem bereitstellt, um grundlegende Vorgänge für die Struktur auszuführen:
MmGetMdlVirtualAddress gibt die virtuelle Speicheradresse des E/A-Puffers zurück, der von der MDL beschrieben wird.
MmGetMdlByteCount gibt die Größe des E/A-Puffers in Bytes zurück.
MmGetMdlByteOffset gibt den Offset innerhalb einer physischen Seite am Anfang des E/A-Puffers zurück.
Sie können eine MDL mit der IoAllocateMdl-Routine zuordnen. Verwenden Sie zum Freigeben der MDL die IoFreeMdl-Routine . Alternativ können Sie einen Block des nicht auslagerten Arbeitsspeichers zuordnen und diesen Speicherblock dann als MDL formatieren, indem Sie die MmInitializeMdl-Routine aufrufen.
Weder IoAllocateMdl noch MmInitializeMdl initialisieren das Datenarray, das unmittelbar der MDL-Struktur folgt. Verwenden Sie mmBuildMdlForNonPagedPool für eine MDL, die sich in einem vom Treiber zugewiesenen Block ohne Auslagerspeicher befindet, um dieses Array zu initialisieren, um den physischen Arbeitsspeicher zu beschreiben, in dem sich der E/A-Puffer befindet.
Bei ausgelagertem Arbeitsspeicher ist die Korrespondenz zwischen virtuellem und physischem Speicher temporär, sodass das Datenarray, das der MDL-Struktur folgt, nur unter bestimmten Umständen gültig ist. Rufen Sie MmProbeAndLockPages auf, um den auslagerungsfähigen Speicher zu sperren und dieses Datenarray für das aktuelle Layout zu initialisieren. Der Speicher wird erst ausgelagert, wenn der Aufrufer die MmUnlockPages-Routine verwendet, sodass der Inhalt des Datenarrays nicht mehr gültig ist.
Die MmGetSystemAddressForMdlSafe-Routine ordnet die physischen Seiten, die von der angegebenen MDL beschrieben werden, einer virtuellen Adresse im Systemadressraum zu, sofern sie nicht bereits dem Systemadressraum zugeordnet sind. Diese virtuelle Adresse ist nützlich für Treiber, die möglicherweise die Seiten anzeigen müssen, um E/A auszuführen, da die ursprüngliche virtuelle Adresse eine Benutzeradresse sein kann, die nur im ursprünglichen Kontext verwendet werden kann und jederzeit gelöscht werden kann.
Beachten Sie, dass MmGetMdlVirtualAddress beim Erstellen einer partiellen MDL mithilfe der IoBuildPartialMdl-Routine die ursprüngliche Startadresse für die partielle MDL zurückgibt. Diese Adresse ist eine Adresse im Benutzermodus, wenn die MDL ursprünglich als Ergebnis einer Benutzermodusanforderung erstellt wurde. Daher ist die Adresse außerhalb des Kontexts des Prozesses, aus dem die Anforderung stammt, nicht relevant.
In der Regel erstellt ein Treiber stattdessen eine Systemmodusadresse , indem er das Makro MmGetSystemAddressForMdlSafe aufruft, um die partielle MDL zuzuordnen. Dadurch wird sichergestellt, dass der Treiber unabhängig vom Prozesskontext weiterhin sicher auf die Seiten zugreifen kann.
Wenn ein Treiber IoAllocateMdl aufruft, kann er ein IRP der neu zugeordneten MDL zuordnen, indem er einen Zeiger auf das IRP als Irp-Parameter von IoAllocateMdl angibt. Einem IRP kann eine oder mehrere MDLs zugeordnet sein. Wenn dem IRP eine einzelne MDL zugeordnet ist, verweist das MdlAddress-Element des IRP auf diese MDL. Wenn dem IRP mehrere MDLs zugeordnet sind, verweist MdlAddress auf die erste MDL in einer verknüpften Liste von MDLs, die dem IRP zugeordnet sind, die als MDL-Kette bezeichnet wird. Die MDLs werden von ihren Next-Membern verknüpft. Der nächste Member der letzten MDL in der Kette ist auf NULL festgelegt.
Wenn der Treiber Beim Aufrufen von IoAllocateMdlfalse für den SecondaryBuffer-Parameter angibt, wird der MdlAddress-Member des IRP so festgelegt, dass er auf die neue MDL verweist. Wenn SecondaryBufferauf TRUE festgelegt ist, fügt die Routine die neue MDL am Ende der MDL-Kette ein.
Wenn die IRP abgeschlossen ist, entsperrt das System alle MDLs, die dem IRP zugeordnet sind, und gibt sie frei. Das System entsperrt die MDLs, bevor es die E/A-Abschlussroutine in die Warteschlange stellt, und gibt sie frei, nachdem die E/A-Abschlussroutine ausgeführt wurde.
Treiber können die MDL-Kette durchlaufen, indem sie das Nächste Element jeder MDL verwenden, um auf die nächste MDL in der Kette zuzugreifen. Treiber können MDLs manuell in die Kette einfügen, indem sie die Next-Member aktualisieren.
MDL-Ketten werden in der Regel verwendet, um ein Array von Puffern zu verwalten, die einer einzelnen E/A-Anforderung zugeordnet sind. (Ein Netzwerktreiber könnte beispielsweise einen Puffer für jedes IP-Paket in einem Netzwerkvorgang verwenden.) Jeder Puffer im Array verfügt über eine eigene MDL in der Kette. Wenn der Treiber die Anforderung abgeschlossen hat, kombiniert er die Puffer in einem einzelnen großen Puffer. Das System bereinigt dann automatisch alle zugeordneten MDLs für die Anforderung.
Der E/A-Manager ist eine häufige Quelle für E/A-Anforderungen. Wenn der E/A-Manager eine E/A-Anforderung abschließt, gibt der E/A-Manager die IRP frei und gibt alle MDLs frei, die an das IRP angefügt sind. Einige dieser MDLs wurden möglicherweise durch Treiber, die sich unter dem E/A-Manager im Gerätestapel befinden, an den IRP angefügt. Wenn Ihr Treiber die Quelle einer E/A-Anforderung ist, muss Ihr Treiber die IRP und alle MDLs, die an den IRP angefügt sind, nach Abschluss der E/A-Anforderung sauber.
Beispiel
Das folgende Codebeispiel ist eine vom Treiber implementierte Funktion, die eine MDL-Kette von einem IRP freigibt:
VOID MyFreeMdl(PMDL Mdl)
{
PMDL currentMdl, nextMdl;
for (currentMdl = Mdl; currentMdl != NULL; currentMdl = nextMdl)
{
nextMdl = currentMdl->Next;
if (currentMdl->MdlFlags & MDL_PAGES_LOCKED)
{
MmUnlockPages(currentMdl);
}
IoFreeMdl(currentMdl);
}
}
Wenn die physischen Seiten, die durch eine MDL in der Kette beschrieben werden, gesperrt sind, ruft die Beispielfunktion die MmUnlockPages-Routine auf, um die Seiten zu entsperren, bevor IoFreeMdl aufgerufen wird, um die MDL freizugeben. Die Beispielfunktion muss die Zuordnung der Seiten jedoch nicht explizit aufheben, bevor IoFreeMdl aufgerufen wird. Stattdessen hebt IoFreeMdl die Zuordnung der Seiten automatisch auf, wenn die MDL freigegeben wird.