WriteFile hat Erfolg gemeldet. Wo befinden sich die Daten jetzt eigentlich?
Die Antwort lautet, fast sicher: noch nicht auf der Festplatte. Sie wurden lediglich in einen Cache im Arbeitsspeicher kopiert. Deshalb passiert „gespeichert, aber nach dem Stromausfall war es weg“, deshalb erreichen Benchmarks von Dateikopien physikalisch unmögliche Geschwindigkeiten, und deshalb rufen Datenbanken pflichtbewusst fsync auf.
Teil 4 der Reihe „Die Tiefen von Windows-I/O“ widmet sich dem, was dazwischensteht: dem Cache-Manager. In Teil 2 hieß es, „liegt es bereits im Cache, wird auch asynchrones I/O synchron abgeschlossen“, und in Teil 1 blieb „die Abkürzung, die kein IRP baut (Fast I/O)“ als Hausaufgabe offen. Diese Folge löst beide Fäden auf.
1. Zunächst das Fazit
- Windows’ Dateicache ist ein Write-Back-Cache. Lesevorgänge werden zunächst aus dem System-Dateicache bedient, Schreibvorgänge gehen zunächst ebenfalls dorthin. Die Übertragung auf die Festplatte erledigt das Betriebssystem im Nachhinein.1
- Der Cache ist im Kern eine Dateizuordnung. Der Cache-Manager mappt 256-KB-Abschnitte einer Datei in Slots des System-Adressraums, und cache-fähige Lese- und Schreibvorgänge werden zu Speicherkopien zwischen Slot und Anwendungspuffer (Kapitel 2).1
- Schreibvorgänge holt ein jede Sekunde laufender Lazy Writer nach. Ein Absturz der Anwendung verliert keine Daten, aber ein Stromausfall oder OS-Absturz verliert dirty Cache-Daten, die noch nicht zurückgeschrieben wurden (Kapitel 4).1
- Es gibt drei Werkzeuge für „garantiert geschrieben“.
FlushFileBuffers(entspricht .NETsFlush(true)),FILE_FLAG_WRITE_THROUGHundFILE_FLAG_NO_BUFFERING. Bei jedem Schreibvorgang zu flushen ist ineffizient; die offizielle Dokumentation empfiehlt stattdessen die Kombination aus NO_BUFFERING und WRITE_THROUGH (Kapitel 5).21 - NO_BUFFERING bringt Ausrichtungsanforderungen mit sich. Größe und Versatz müssen ganzzahlige Vielfache der Sektorgröße sein, und auch die Pufferadresse muss an einer physischen Sektorgrenze ausgerichtet sein. Und selbst unter NO_BUFFERING bleiben Metadaten weiterhin gecacht (Abschnitt 5.3).31
- Gemappte Ansicht und Cache teilen dieselben Daten. Speichergemappte Dateien und gewöhnliches cache-fähiges I/O bleiben kohärent, und das dauerhafte Speichern einer Zuordnung erfolgt in zwei Schritten:
FlushViewOfFile, gefolgt vonFlushFileBuffers(Kapitel 6).45 - Synchrone Lese- und Schreibvorgänge, die den Cache treffen, erzeugen möglicherweise nicht einmal ein IRP. Die Abkürzung Fast I/O führt direkt zum Cache-Manager – die Antwort auf die Hausaufgabe aus Teil 1 (Kapitel 7).6
2. Was der Cache wirklich ist — Dateien werden in den Speicher gemappt
2.1. 256-KB-Slots und Speicherkopien
Wer sich Windows’ Dateicache als „Behälter für Festplattenblöcke“ vorstellt, kann sich vieles seines Verhaltens nicht mehr erklären. Das richtige Bild ist dieses: Der Cache-Manager mappt 256-KB-Abschnitte einer Datei in „Slots“ des System-Adressraums, und cache-fähige Lese- und Schreibvorgänge werden als Speicherkopien zwischen diesem Slot und dem Puffer der Anwendung ausgeführt.1
flowchart TB
subgraph U["Anwendung (User-Mode)"]
BUF["Puffer der Anwendung<br/>(der an ReadFile/WriteFile übergebene Bereich)"]
end
subgraph S["System-Adressraum"]
SLOT["System-Dateicache<br/>Slot, der einen 256-KB-Abschnitt der Datei mappt"]
end
DISK[("Datei auf der Festplatte")]
BUF <-->|"ReadFile/WriteFile =<br/>Speicherkopie mit dem Slot"| SLOT
SLOT <-->|"Einlesen beim ersten Zugriff und<br/>nachgeholtes Zurückschreiben erfolgen seitenweise"| DISK
Abbildung 1: Die Realität von cache-fähigem I/O. Was aus Anwendungssicht „Lesen oder Schreiben einer Datei“ ist, ist meistens nur eine Speicherkopie
Ein leicht misszuverstehender Punkt: 256 KB ist die Granularität der Ansicht (der Zuordnung), nicht die Aussage, dass Festplatten-I/O stets in 256-KB-Einheiten erfolgt. Seiten innerhalb eines Slots werden bei Bedarf eingelesen, und die tatsächliche Menge an Festplatten-I/O hängt von der Anfragegröße und dem Zugriffsmuster ab. Wird ein Abschnitt zum ersten Mal gelesen, löst das Festplatten-I/O zur Füllung aus (hier wandert ein IRP aus Teil 1 in den unteren Speicherstack). Liegt er bereits im Cache, wird der Lesevorgang allein durch Kopieren abgeschlossen. Was wir in Teil 2, Kapitel 5 gesehen haben – „bei einem Cache-Treffer wird auch bei asynchronem Ausgeben synchron abgeschlossen“ – ist genau dieses Verhalten „sofort beantworten, wenn es geht“, das sich hier zeigt. Umgekehrt gilt: Ist die Seite bei aktiviertem Cache nicht im Speicher vorhanden, kann ein asynchroner Lesevorgang mangels asynchronem Mechanismus für die Seitenfehlerbehandlung synchron verarbeitet werden – eine Falle, die wir ebenfalls in Teil 2 gesehen haben.7
2.2. Was hinter „der freie Speicher ist gesunken“ wirklich steckt
Ob Caching und Read-Ahead verwendet werden, wird pro Öffnung (pro Dateiobjekt) verwaltet1, aber die gecachten Daten selbst werden pro Datei (pro Stream) geteilt. Öffnet man dieselbe Datei mehrfach, entstehen keine getrennten Caches – jedes Handle sieht denselben gecachten Inhalt (das ist die Grundlage für die in Kapitel 6 behandelte Kohärenz). Der Cache bleibt, solange Windows läuft, unter der Kontrolle des Cache-Managers.1 Kopiert man eine große Datei oder liest und schreibt viel, wird freier physischer Speicher zunehmend in Cache umgewandelt. Auch wenn der freie Speicher im Task-Manager kleiner erscheint, handelt es sich bei einem Großteil davon um „Standby-Speicher, der wertvoll genutzt wird und sofort zurückgegeben wird, sobald eine Anwendung ihn anfordert“.
Das lässt sich am Bildschirm nachprüfen. Öffnen Sie Task-Manager > Leistung > Arbeitsspeicher, dann ist die untere Leiste „Speicherzusammensetzung“ unterteilt in In Verwendung / Geändert / Standby / Frei. Der Großteil des Dateicaches landet in Standby und wird in der Liste rechts als „Zwischengespeichert“ summiert. Für eine feinere Ansicht zeigt Ressourcenmonitor > Registerkarte Arbeitsspeicher dieselbe Aufteilung mit konkreten Werten. Kopieren Sie eine mehrere GB große Datei und schauen Sie danach erneut hin: Standby steigt, Frei sinkt, während „In Verwendung“ sich kaum ändert – der sichtbare Beweis dafür, dass nicht „der Speicher aufgebraucht wurde“, sondern „freier Speicher als Cache genutzt wurde“. Diese Unterscheidung haben wir auch praktisch bei der Diagnose von Speicherengpässen in „GC-Wartezeit und Speicherleck bei .NET unterscheiden“ behandelt.
3. Read-Ahead — Spekulation beim Lesen
Der Cache-Manager lädt anhand vergangener Zugriffsmuster den Abschnitt vor, der voraussichtlich als Nächstes gelesen wird (Read-Ahead). Bei einer der Reihe nach gelesenen Datei liegen die nächsten Daten oft schon im Cache, bevor die Anwendung sie überhaupt anfordert – das ist das Geheimnis hinter der Geschwindigkeit sequenzieller Lesevorgänge. Die Menge des Vorauslesens ist nicht fest, sondern variiert mit dem erkannten Muster und der Anfragegröße.
flowchart LR
A["Historie der Leseanfragen der Anwendung<br/>liest der Reihe nach von vorn"]
D{"Cache-Manager erkennt<br/>ein Muster"}
R["Read-Ahead: liest den folgenden Abschnitt<br/>ein, bevor er angefordert wird<br/>(Menge variiert mit Muster und Anfragegröße)"]
H1["Hinweis FILE_FLAG_SEQUENTIAL_SCAN<br/>= Read-Ahead aktiv nutzen"]
H2["Hinweis FILE_FLAG_RANDOM_ACCESS<br/>= Read-Ahead unterdrücken, da nutzlos"]
A --> D
D --> R
H1 -.-> D
H2 -.-> D
Abbildung 2: Read-Ahead. Zusätzlich zur Erkennung des Zugriffsmusters lassen sich über Flags von CreateFile Hinweise geben
Die in Teil 1 tabellierten FileOptions.SequentialScan / RandomAccess sind Hinweise an diese Read-Ahead-Engine. Ersteres eignet sich für Batch-Verarbeitung, die „alles der Reihe nach“ durchgeht, Letzteres für Zugriffe, die wie beim Verfolgen eines Index umherspringen – man versteht ihren Einsatzort am besten als Flags, mit denen man dem Betriebssystem eine Zukunft mitteilt, die nur die Anwendung selbst kennt.
4. Verzögertes Schreiben — Was der „Erfolg“ von WriteFile bedeutet
4.1. Der Lazy Writer kommt jede Sekunde vorbei
Die Schreibseite ist ein Write-Back-Cache. WriteFile gibt Erfolg zurück, sobald die Daten in einen Slot kopiert wurden, und die Übertragung auf die Festplatte wird zurückgestellt. Diese Strategie des „später schreiben“ ist das verzögerte Schreiben (Lazy Writing).1
Für die Übertragung sorgt der Lazy Writer, den der Cache-Manager jede Sekunde anstößt. Er reiht ein Achtel der zuletzt nicht geflushten Seiten zum Schreiben ein und legt bei Bedarf weitere nach. Mit dem Attribut FILE_ATTRIBUTE_TEMPORARY erstellte temporäre Dateien werden dabei vom Flush-Ziel des Lazy Writers ausgenommen – es wäre verschwendet, etwas zu schreiben, das ohnehin bald gelöscht wird.1 Das ist allerdings nur ein durch das Attribut gegebener Hinweis: Bei Speicherdruck können die Seiten dennoch zurückgeschrieben werden, und für eine Datei, deren Name lediglich „temporär wirkt“, greift das nicht.
sequenceDiagram
participant App as Anwendung
participant C as Systemcache
participant LW as Lazy Writer (jede Sekunde)
participant D as Festplatte
App->>C: WriteFile(Daten)
Note over C: Kopiert in den Slot und<br/>markiert die Seite als dirty (nicht geschrieben)
C-->>App: TRUE wird sofort zurückgegeben
Note over App,C: Von hier bis zum Zurückschreiben besteht das "gefährliche Fenster"<br/>bei Stromausfall/OS-Absturz gehen diese Daten verloren
LW->>C: Wählt 1/8 der dirty Seiten aus
LW->>D: Schreibt sie gebündelt zurück
Note over D: Erst hier erfolgt die dauerhafte Speicherung
Abbildung 3: Verzögertes Schreiben. Der Erfolg von WriteFile bedeutet „an das Betriebssystem übergeben“, nicht „dauerhaft gespeichert“
4.2. Was passiert, wie viel geht verloren
Präzisieren wir die Bedeutung des „gefährlichen Fensters“. Das Schicksal hängt von der Art des Ausfalls ab.
flowchart TB
W["Daten direkt nach erfolgreichem WriteFile<br/>(dirty Seite im Cache)"]
Q{"Was ist passiert"}
A1["Prozess der Anwendung<br/>abgestürzt/erzwungen beendet"]
A2["Gesamtes Betriebssystem gestoppt<br/>(Stromausfall, Bluescreen)"]
S["Daten bleiben erhalten<br/>der Cache gehört dem Betriebssystem, daher<br/>schreibt der Lazy Writer wie geplant zurück"]
L["Dirty Seiten gehen verloren<br/>nur was bereits die Festplatte erreicht hatte, bleibt"]
W --> Q
Q --> A1
Q --> A2
A1 --> S
A2 --> L
Abbildung 4: Die Trennlinie zwischen Ausfallarten. Der Cache gehört „dem Betriebssystem“, nicht „dem Prozess“
- Stirbt die Anwendung, gehen die Daten nicht verloren. Sobald die Kopie in den Cache erfolgt ist, gehört der Besitz der Daten dem Betriebssystem. Dass „die Anwendung direkt nach dem Speichern abstürzte, die Datei aber unversehrt war“, verdankt sich genau diesem Umstand.
- Stirbt das gesamte Betriebssystem, geht der dirty Anteil verloren. Die Häufigkeit des Flushens wird als Kompromiss zwischen Leistung und Zuverlässigkeit abgestimmt, und die Dokumentation hält ausdrücklich fest, dass „bei plötzlichem Stromverlust gecachte Daten verloren gehen“.1
Die eigentliche Frage beim Entwurf einer Geschäftsanwendung lautet also: „Darf diese Daten im Moment eines Stromausfalls verloren gehen?“ Ein paar Sekunden Protokolldaten mag verzeihlich sein. Ein bestätigter Auftragsdatensatz vermutlich nicht. Setzen Sie die Werkzeuge des nächsten Kapitels nur für das ein, was Sie sich nicht zu verlieren leisten können.
5. Ein Werkzeugkasten für „garantiert geschrieben“
5.1. FlushFileBuffers — jetzt sofort komplett schreiben
FlushFileBuffers schreibt die gepufferten Daten der angegebenen Datei vollständig zum Gerät durch. Da Metadaten des Dateisystems stets gecacht bleiben, ist ein Flush (oder WRITE_THROUGH) auch nötig, um Metadaten zuverlässig zu übertragen – ein Punkt, den man im Blick behalten sollte.12 In .NET entspricht dem FileStream.Flush(true) (Flush() allein gibt nur .NETs internen Puffer an das Betriebssystem weiter, der Cache des Betriebssystems bleibt unangetastet).8
Die offizielle Dokumentation weist allerdings deutlich darauf hin: es bei jedem einzelnen Schreibvorgang aufzurufen ist ineffizient. Bei vielen Schreibvorgängen, die jeweils Dauerhaftigkeit benötigen, sollten stattdessen die weiter unten behandelten NO_BUFFERING und WRITE_THROUGH kombiniert werden.2
5.2. FILE_FLAG_WRITE_THROUGH — nur die Verzögerung entfernen
Öffnet man eine Datei mit FILE_FLAG_WRITE_THROUGH, wird bei jedem Schreibvorgang wie gewohnt in den Cache geschrieben, zugleich aber sofort und ohne auf den Lazy Writer zu warten auch auf die Festplatte.1 Entscheidend ist, dass Lesevorgänge weiterhin vom Cache profitieren – eine direkte Antwort auf „Lesevorgänge sollen schnell bleiben, nur die Verzögerung beim Schreiben soll wegfallen“.
5.3. FILE_FLAG_NO_BUFFERING — am Cache vorbei
FILE_FLAG_NO_BUFFERING nimmt bei Lese- und Schreibvorgängen den Systemcache selbst aus dem Bild. Jeder Lese- und Schreibvorgang wird bei jedem Aufruf zu Geräte-I/O ohne dazwischenliegenden Cache.1 Umgangen wird dabei aber nur Windows’ Systemcache – wie Abbildung 5 zeigt, ist der Schreibcache innerhalb des Speichergeräts eine eigene Stufe. Wird auch Resilienz gegen Stromausfälle benötigt, bleiben WRITE_THROUGH zusätzlich oder FlushFileBuffers erforderlich. Es ist ein Werkzeug für den Massentransfer großer Datenmengen oder für Datenbank-Engines, die ihre Pufferung selbst verwalten – kommt aber mit strengen Regeln.3
- Größe und Dateiversatz jedes Lese- oder Schreibvorgangs müssen ein ganzzahliges Vielfaches der Sektorgröße des Volumes sein (bei 512-Byte-Sektoren: 512, 1024, 1536 usw.).
- Auch die Pufferadresse muss an der physischen Sektorgröße ausgerichtet sein (auch „Advanced Format“-Festplatten mit 4096-Byte-Sektoren sind zu berücksichtigen).
- Metadaten bleiben trotzdem weiterhin gecacht, sodass für vollständige Dauerhaftigkeit zusätzlich WRITE_THROUGH oder
FlushFileBuffersnötig ist.12
Über diese „Regeln“ stolpert als Erstes, wer glaubt, das bloße Setzen des Flags reiche aus. Lesen oder Schreiben ohne Einhaltung der Ausrichtung schlägt mit ERROR_INVALID_PARAMETER (87) fehl. Hier die drei einzuhaltenden Punkte im Überblick.3
| Auszurichten | Bedingung | Wie erfüllen |
|---|---|---|
| Größe von Lese-/Schreibvorgang | Ganzzahliges Vielfaches der Sektorgröße des Volumes | lpBytesPerSector über GetDiskFreeSpace abfragen und auf ein Vielfaches davon runden |
| Dateiversatz | Wie oben (gilt auch für Offset in OVERLAPPED) |
In Vielfachen der Sektorgröße vorrücken |
| Pufferadresse | An der physischen Sektorgröße ausgerichtet | Mit VirtualAlloc zuweisen (liefert an Seitengrenzen ausgerichteten Speicher) |
Der dritte Punkt wird besonders häufig übersehen. Adressen, die malloc, new oder ein C#-Array liefern, garantieren keine Ausrichtung an einer Sektorgrenze. Verwendet man VirtualAlloc, das an Seitengrenzen zuweist, erfüllt man dabei zugleich die Anforderungen von „Advanced Format“-Festplatten mit 4096-Byte-Sektoren. Die minimale Form sieht so aus.
// C++ / Win32. Fehlerbehandlung auf das Nötigste reduziert
DWORD sectorsPerCluster = 0, bytesPerSector = 0, freeClusters = 0, totalClusters = 0;
if (!GetDiskFreeSpaceW(L"C:\\", §orsPerCluster, &bytesPerSector,
&freeClusters, &totalClusters))
{
return GetLastError();
}
// Lese-/Schreibeinheit auf ein ganzzahliges Vielfaches der Sektorgröße bringen (hier etwa 1 MiB)
const DWORD chunk = (1024 * 1024 / bytesPerSector) * bytesPerSector;
// Puffer an einer Seitengrenze ausgerichtet anlegen (malloc/new garantieren das nicht)
BYTE* buffer = static_cast<BYTE*>(
VirtualAlloc(nullptr, chunk, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
if (buffer == nullptr) { return GetLastError(); }
HANDLE h = CreateFileW(L"C:\\temp\\big.bin", GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, nullptr);
if (h == INVALID_HANDLE_VALUE)
{
// GetLastError liefert das Ergebnis des "zuletzt aufgerufenen Win32-Aufrufs".
// Würde man zuerst VirtualFree aufrufen, würde der eigentliche Fehlergrund von
// CreateFileW (Zugriff verweigert, Pfad nicht vorhanden usw.) vom Ergebnis
// des Aufräumens überschrieben, und es bliebe nur ein aussageloser Fehlercode
const DWORD err = GetLastError();
VirtualFree(buffer, 0, MEM_RELEASE);
return err;
}
// Da jeweils um chunk Bytes vorgerückt wird, bleiben Größe und Versatz ausgerichtet
DWORD read = 0;
while (ReadFile(h, buffer, chunk, &read, nullptr) && read > 0)
{
// Die ersten read Bytes von buffer verarbeiten
// (am Dateiende gilt read < chunk - das ist normal)
}
CloseHandle(h);
VirtualFree(buffer, 0, MEM_RELEASE);
Zu beachten ist außerdem, dass .NETs FileOptions keinen Wert kennt, der FILE_FLAG_NO_BUFFERING entspricht. Wird es unbedingt benötigt, muss CreateFile direkt aufgerufen werden, wobei die obigen Ausrichtungsanforderungen dann selbst einzuhalten sind. Gehen Sie in der Reihenfolge „NO_BUFFERING, weil ich die Pufferung selbst verwalte“ vor, nicht „NO_BUFFERING, weil es schneller sein soll“.
5.4. Zusammenfassung der Einsatzbereiche
flowchart TB
A["Puffer der Anwendung"]
B["System-Dateicache<br/>(dirty Seiten)"]
C["Cache im Speichergerät"]
D[("Nichtflüchtiges Speichermedium")]
A -->|"Standard-WriteFile: Erfolg wird bereits hier zurückgegeben"| B
B -->|"Lazy Writer (jede Sekunde) / WRITE_THROUGH (sofort)"| C
C -->|"Zeitpunkt des Geräts /<br/>FlushFileBuffers verlangt vollständiges Durchschreiben"| D
A -.->|"NO_BUFFERING überspringt den Cache und geht direkt hierhin"| C
Abbildung 5: Die Schichten, die Daten durchlaufen, und wie weit jedes Werkzeug sie vorantreibt. Nicht die letzte Stufe vergessen, „der Cache im Speichergerät“
| Methode | Was passiert | Wofür geeignet |
|---|---|---|
| Standard (Cache aktiv) | Abschluss mit dem Kopieren in den Cache; die Übertragung übernimmt der Lazy Writer | Die meisten Datei-I/O-Vorgänge |
FlushFileBuffers / Flush(true) |
Schreibt die aktuellen Daten plus Metadaten vollständig durch | Bestätigung an einem Meilenstein (z. B. Commit einer Transaktion) |
FILE_FLAG_WRITE_THROUGH |
Bei jedem Schreibvorgang sofort auf die Festplatte (Lesevorgänge weiter über den Cache) | Ein laufendes Protokoll oder Journal, das nicht verloren gehen darf |
FILE_FLAG_NO_BUFFERING (+WRITE_THROUGH) |
Cache vollständig umgangen; Ausrichtung erforderlich | Selbstverwaltete Pufferung, Massentransfers |
Die Reihenfolge der Wahl umfasst zwei Schritte: erst festlegen, wie viel bei einem Stromausfall verloren gehen darf, dann prüfen, wie viel Verlangsamung dafür in Kauf genommen werden kann. Überfliegen Sie die Tabelle nicht einfach von oben nach unten, sondern folgen Sie dieser Verzweigung.
flowchart TB
S["Diese Daten sollen geschrieben werden"]
Q1{"Ist es hinnehmbar, sie im Moment<br/>eines Stromausfalls oder Bluescreens zu verlieren"}
A0["Beim Standard bleiben (Cache aktiv)<br/>am schnellsten. Die meisten I/O-Vorgänge gehören hierhin"]
Q2{"Was nicht verloren gehen darf:<br/>ein „Meilenstein“ oder „jeder einzelne Datensatz“?"}
A1["An Meilensteinen FlushFileBuffers<br/>in .NET Flush(true)<br/>Kosten: nur das Warten am Meilenstein"]
Q3{"Wird die Pufferung selbst verwaltet<br/>und lassen sich die Ausrichtungsanforderungen aus 5.3 erfüllen"}
A2["FILE_FLAG_WRITE_THROUGH<br/>bei jedem Schreibvorgang sofort auf die Festplatte<br/>Lesevorgänge bleiben über den Cache schnell"]
A3["FILE_FLAG_NO_BUFFERING<br/>+ FILE_FLAG_WRITE_THROUGH<br/>die von den offiziellen Docs genannte Form für "häufige Dauerhaftigkeit""]
S --> Q1
Q1 -->|"hinnehmbar<br/>(z. B. die letzten paar Sekunden eines Protokolls)"| A0
Q1 -->|"nicht hinnehmbar"| Q2
Q2 -->|"Meilenstein<br/>(z. B. bestätigte Transaktion)"| A1
Q2 -->|"jeder einzelne Datensatz"| Q3
Q3 -->|"nein (gewöhnliche Anwendung)"| A2
Q3 -->|"ja (DB-Engine usw.)"| A3
Abbildung 6: Wie man ein Werkzeug wählt. Die erste Verzweigung ist die Zuverlässigkeitsanforderung, die zweite die tragbaren Kosten. Dass es keinen Pfad für „bei jedem einzelnen Datensatz FlushFileBuffers“ gibt, liegt daran, dass die offizielle Dokumentation das, wie in 5.1 gesehen, als ineffizient bezeichnet
Nur drei Praxismuster seien noch genannt.
- „In eine temporäre Datei schreiben → flushen → umbenennen“ ist das Standardrezept, um keine halb geschriebene Datei zu hinterlassen. Erst den Inhalt vollständig schreiben, dann per Namen bestätigen – diese atomare Übergabe wird ausführlich in „Grundlagen der wechselseitigen Sperrung bei Dateiintegration“ behandelt.
- Es einer Datenbank zu überlassen ist ebenfalls ein solides Design. Wie SQLite über WAL und Flushen Dauerhaftigkeit erreicht, ist in „SQLite in C#-Geschäftsanwendungen einsetzen“ nachzulesen. „Keine eigene Flush-Strategie schreiben“ ist immer eine mögliche Wahl.
- Bei Benchmarks den Cache im Verdacht haben. Eine Messung „das Lesen ist verdächtig schnell“ fängt meist Cache-Treffer ab dem zweiten Durchlauf ein. Das richtige Vorgehen bei Messungen ist in „Wie man die Geschwindigkeit verschiedener Programmversionen unter Windows korrekt vergleicht“ zusammengefasst.
Und vergessen Sie nicht die letzte Stufe aus Abbildung 5 – den Cache im Speichergerät selbst. FlushFileBuffers verlangt ein Durchschreiben bis dorthin, doch bei USB-Sticks und externen Festplatten spielt zusätzlich die geräteseitige Schreibcache-Richtlinie („Schnelles Entfernen“ versus „Bessere Leistung“) eine Rolle. Zum Umgang mit Wechseldatenträgern siehe auch „Wie man USB-Geräte aus einer Windows-Anwendung heraus behandelt“.
6. Kohärenz mit speichergemappten Dateien
Wer in Teil 1 gehört hat, „der Cache ist im Kern eine Dateizuordnung“, hat sich vermutlich gefragt: Kommt sich dann eine selbst mit MapViewOfFile erzeugte Ansicht nicht mit dem Cache in die Quere, den ReadFile/WriteFile verwenden?
Tut sie nicht. Weil beide auf demselben Mechanismus aufsetzen. Ein Dateizuordnungsobjekt ist von einer Datei gestützt, und das Verdrängen einer Seite erfolgt als Zurückschreiben in diese Datei. Auch wenn mehrere Prozesse Ansichten derselben lokalen Datei erzeugen, ist der sichtbare Inhalt kohärent.4
flowchart TB
subgraph P1["Adressraum von Prozess A"]
V1["Ansicht von MapViewOfFile"]
end
subgraph SYS["System-Adressraum"]
SC["Ansicht des Cache-Managers<br/>(der von ReadFile/WriteFile genutzte Slot)"]
end
PAGES["Dieselben physischen Seiten<br/>(von der Datei gestützter Speicher)"]
DISK[("Datei auf der Festplatte")]
V1 --> PAGES
SC --> PAGES
PAGES --> DISK
NB["I/O über FILE_FLAG_NO_BUFFERING<br/>liegt außerhalb dieser Kohärenz (direkt zur Festplatte)"]
NB -.-> DISK
Abbildung 7: Sowohl die gemappte Ansicht als auch der Cache blicken auf dieselben “von der Datei gestützten Seiten”. Außerhalb steht nur NO_BUFFERING
Zwei Dinge sind zu beachten.
- I/O über
FILE_FLAG_NO_BUFFERINGliegt außerhalb dieser Kohärenz. Lese- und Schreibvorgänge, die den Cache umgehen, werden nicht mit dem über eine gemappte Ansicht oder den Cache sichtbaren Inhalt abgeglichen. Wer beides mischt, muss die Konsistenz selbst herstellen. - Das dauerhafte Speichern einer gemappten Ansicht erfolgt in zwei Schritten.
FlushViewOfFilestößt das Zurückschreiben der dirty Seiten im angegebenen Bereich an, schreibt dabei aber keine Metadaten und wartet nicht auf den physischen Schreibvorgang aus dem Cache des Speichergeräts. Um es zuverlässig zu übertragen, ist nachFlushViewOfFilezusätzlichFlushFileBuffersaufzurufen.5
Die praktische Seite von Dateizuordnung als gemeinsamem Speicher (benannte Freigabe, Synchronisierung, typische Fehlermuster) wird in „Fallstricke und Best Practices für gemeinsam genutzten Speicher“ behandelt.
7. Fast I/O — die Hausaufgabe aus Teil 1 wird eingelöst
Vorweg in zwei Zeilen. Fast I/O ist eine Abkürzung für synchrone Lese- und Schreibvorgänge auf eine bereits gecachte Datei, die ohne den Aufbau eines IRP (I/O Request Packet – der Behälter, in dem der Kernel eine Anfrage an einen Treiber übergibt) direkt Daten mit dem Cache austauscht. Dass in der Spalte „Operation“ von Procmon IRP_MJ_READ und FASTIO_READ gemischt auftauchen, liegt daran, dass dasselbe „Lesen“ entweder den normalen Pfad oder diese Abkürzung nehmen kann.
In Teil 1, Abschnitt 5.2, stand: „Nicht jedes I/O wird zum IRP.“ Hier die Auflösung.
Bei einer im Cache liegenden Datei ist bekannt, dass Lese- und Schreibvorgänge allein durch eine Speicherkopie mit dem Cache erledigt werden können, ohne den Aufwand, ein IRP aufzubauen und durch den Gerätestack zu schicken. Deshalb stellt Windows für synchrones I/O auf eine gecachte Datei die Abkürzung Fast I/O bereit: Es wird kein IRP erzeugt, stattdessen werden die „Fast-I/O-Einstiegspunkte“ des Dateisystems direkt aufgerufen und direkt vom Cache-Manager kopiert.6 Kann Fast I/O die Anfrage nicht bearbeiten (nicht im Cache, eine Sperre ist beteiligt, ein Filter greift ein usw.), fällt es auf den normalen IRP-Pfad zurück. Zu beachten ist, dass dies ein schneller Pfad speziell für synchrone Anfragen ist – ein Cache-Treffer bedeutet nicht immer Fast I/O. Vorgänge auf einem asynchronen (FILE_FLAG_OVERLAPPED) Handle können weiterhin über den IRP-Pfad laufen, selbst wenn sie sofort aus dem Cache abgeschlossen werden (Teil 2, Kapitel 5).
flowchart TB
REQ["Synchrone Lese-/Schreibvorgänge auf ein cache-fähiges Handle"]
Q{"Kann Fast I/O die Anfrage bearbeiten<br/>(z. B. bereits im Cache)"}
FAST["Fast I/O<br/>kopiert direkt mit dem Cache, baut kein IRP<br/>erscheint in Procmon als FASTIO_"]
IRP["Normaler Pfad<br/>baut ein IRP auf und sendet es durch den Gerätestack<br/>(die Welt aus Abbildung 6 in Teil 1)"]
REQ --> Q
Q -->|kann| FAST
Q -->|kann nicht| IRP
Abbildung 8: Die Verzweigung bei Fast I/O. Deshalb tauchen in Procmon FASTIO_READ und IRP_MJ_READ gemischt auf
Damit lässt sich erklären, warum bei der Procmon-Beobachtung in Teil 1, Kapitel 7 Zeilen mit FASTIO_ gemischt auftauchten. Bei einem Cache-Treffer beim Lesen ist selbst ein IRP schon ein Luxus. Die Existenz dieses Pfades wirkt sich auch auf die in Teil 6 behandelten Filtertreiber aus (Minifilter können sich auch in Fast I/O einklinken).
8. Zusammenfassung
- Windows’ Dateicache ist Write-Back und im Kern eine Zuordnung von 256-KB-Abschnitten der Datei. Cache-fähige Lese- und Schreibvorgänge werden zu Speicherkopien mit einem Slot.1
- Beim Lesen spekuliert Read-Ahead, und
SequentialScan/RandomAccesssind dafür Hinweise.1 - Beim Schreiben holt der jede Sekunde laufende Lazy Writer nach. Stirbt die Anwendung, bleiben Daten erhalten; stirbt das Betriebssystem selbst, geht nur der dirty Anteil verloren. Die Design-Frage lautet: „Darf diese Daten im Moment eines Stromausfalls verloren gehen?“1
- Die Werkzeuge für zuverlässiges Schreiben sind
FlushFileBuffers(Bestätigung an Meilensteinen) /WRITE_THROUGH(bei jedem Schreibvorgang) /NO_BUFFERING(ohne Cache, mit Ausrichtungsanforderungen). Jedes Mal zu flushen ist ineffizient; für häufige Dauerhaftigkeit empfiehlt die offizielle Dokumentation die Kombination aus NO_BUFFERING und WRITE_THROUGH. Zu beachten bleibt, dass Metadaten stets gecacht werden.231 - Gemappte Ansicht und Cache teilen dieselben Seiten und bleiben kohärent. Außerhalb steht nur NO_BUFFERING. Das dauerhafte Speichern einer Zuordnung erfolgt in zwei Schritten:
FlushViewOfFile, gefolgt vonFlushFileBuffers.45 - Synchrone Lese- und Schreibvorgänge, die den Cache treffen, sparen sich über Fast I/O sogar das IRP – das ist die wahre Identität der
FASTIO_-Einträge aus dem Procmon-Trace in Teil 1.6
Weiter geht es mit Teil 5, „Die NTFS-Interna — Das Dateisystem anhand der MFT verstehen“. Bis hierhin haben wir eine Datei als „Versatz und Bytefolge“ behandelt; von hier aus gehen wir tiefer, hinein in die Frage, wie NTFS Daten tatsächlich auf der Festplatte anordnet – die MFT, mehrere Datenströme, das Journal, Hardlinks – die statische Struktur auf der Festplatte selbst.
Verwandte Artikel
- Die Tiefen von Windows-I/O (Teil 1) — Jedes Lesen und Schreiben wird zum IRP: Das Gesamtbild des I/O-Systems
- Die Tiefen von Windows-I/O (Teil 2) — Synchrones und asynchrones I/O: Was OVERLAPPED wirklich bedeutet
- Die Tiefen von Windows-I/O (Teil 3) — I/O-Completion-Ports (IOCP) und der .NET-Thread-Pool: Der Keller unter async/await
- Grundlagen der wechselseitigen Sperrung bei Dateiintegration - Best Practices für Datei-Locks und atomare Übergaben
- Fallstricke und Best Practices für gemeinsam genutzten Speicher
- SQLite in C#-Geschäftsanwendungen einsetzen — WAL-Modus, wechselseitige Sperrung, Schutz vor Beschädigung und die Abgrenzung zu EF Core
- Wie man die Geschwindigkeit verschiedener Programmversionen unter Windows korrekt vergleicht
- Wie man USB-Geräte aus einer Windows-Anwendung heraus behandelt — Wahl zwischen virtuellem COM, HID, WinUSB und Hersteller-SDK
Verwandte Beratungsleistungen
Die KomuraSoft LLC übernimmt Design und Ursachenuntersuchung von Datei-I/O in Windows-Geschäftsanwendungen – Fälle wie „vermeintlich gespeicherte Daten sind verschwunden“ oder „Dateischreibvorgänge sind langsam oder verdächtig schnell“.
- Windows-Anwendungsentwicklung
- Fehleruntersuchung und Ursachenanalyse
- Weiternutzung und Migration von Altbeständen
- Kontakt
Referenzlinks
-
Microsoft Learn, File Caching. Dazu, dass Windows Dateidaten standardmäßig cacht, Lesevorgänge aus dem System-Dateicache bedient werden und auch Schreibvorgänge in den Cache gehen, wodurch es ein Write-Back-Cache ist; dazu, dass Caching pro Dateiobjekt verwaltet wird und unter der Kontrolle des Cache-Managers abläuft; dazu, dass die Strategie, das Schreiben auf die Festplatte zu verzögern und die Daten im Cache zu halten, verzögertes Schreiben (Lazy Writing) genannt wird; dazu, dass beim Lesen einer Datei ein 256-KB-Abschnitt in einen 256-KB-Slot des System-Adressraums eingelesen wird und der Benutzerprozess Daten zwischen sich und diesem Slot kopiert; dazu, dass der Cache-Manager jede Sekunde den Lazy Writer startet, der ein Achtel der zuletzt nicht geflushten Seiten zum Schreiben auf die Festplatte einreiht und bei Bedarf weitere nachlegt; dazu, dass temporäre Dateien nicht geflusht werden; dazu, dass ungeschriebene gecachte Daten bei einem plötzlichen Systemausfall wie Stromverlust verloren gehen; dazu, dass Dateimetadaten auch bei deaktiviertem Caching über FILE_FLAG_NO_BUFFERING weiterhin gecacht sein können; dazu, dass Daten unter FILE_FLAG_WRITE_THROUGH in den Cache geschrieben werden, während sie zugleich ohne die Verzögerung des Lazy Writers sofort auf die Festplatte geschrieben werden; sowie dazu, dass Dateisystem-Metadaten stets gecacht werden, weshalb ihre dauerhafte Speicherung einen Flush oder FILE_FLAG_WRITE_THROUGH erfordert. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19
-
Microsoft Learn, FlushFileBuffers function. Dazu, dass WriteFile normalerweise in einen internen Puffer schreibt, den das Betriebssystem periodisch auf die Festplatte schreibt; dazu, dass FlushFileBuffers alle gepufferten Informationen der angegebenen Datei zum Gerät schreibt; dazu, dass es ineffizient ist, es bei jedem von vielen Schreibvorgängen aufzurufen, und dass Anwendungen, die für wichtige Daten bei häufigen Schreibvorgängen Dauerhaftigkeit benötigen, stattdessen ungepuffertes I/O über FILE_FLAG_NO_BUFFERING und FILE_FLAG_WRITE_THROUGH einsetzen sollten; sowie dazu, dass ein Aufruf auf einem Volume-Handle (mit Administratorrechten) alle offenen Dateien auf diesem Volume flusht. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, File Buffering. Zu den Zugriffsanforderungen für eine mit FILE_FLAG_NO_BUFFERING geöffnete Datei: Lese-/Schreibgrößen und Dateiversätze (einschließlich der über OVERLAPPED angegebenen) müssen ein ganzzahliges Vielfaches der Sektorgröße des Volumes sein; Lese-/Schreibpufferadressen sollten an der physischen Sektorgröße ausgerichtet sein; und Rücksicht ist bei Advanced-Format-Geräten mit 4.096-Byte-physischen-Sektoren nötig. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, File Mapping. Dazu, dass ein Dateizuordnungsobjekt von einer Datei auf der Festplatte gestützt ist, wobei das Auslagern einer Seite als Schreiben des geänderten Inhalts in diese Datei erfolgt; sowie dazu, dass Daten kohärent (identisch mit dem Inhalt der Datei auf der Festplatte) sind, wenn mehrere Prozesse Ansichten derselben lokalen Datei aus demselben Dateizuordnungsobjekt erzeugen. ↩ ↩2 ↩3
-
Microsoft Learn, FlushViewOfFile function. Dazu, dass FlushViewOfFile das Schreiben der dirty Seiten im Bereich der gemappten Ansicht auf die Festplatte anstößt; dazu, dass die Funktion keine Dateimetadaten flusht und nicht auf den physischen Schreibvorgang aus einem Hardware-Festplattencache wartet; sowie dazu, dass FlushFileBuffers nach FlushViewOfFile aufgerufen werden muss, um alle dirty Seiten und Metadaten vollständig und physisch zu schreiben. ↩ ↩2 ↩3
-
Microsoft Learn, IRPs Are Different From Fast I/O. Dazu, dass Fast I/O ein schneller Pfad für synchrones I/O auf eine gecachte Datei ist, der Dateisystem- und Cache-Manager-Einstiegspunkte direkt aufruft, ohne ein IRP zu erzeugen; dazu, dass Daten direkt vom Cache in den Benutzerpuffer (oder umgekehrt) übertragen werden; sowie dazu, dass der normale IRP-basierte Pfad verwendet wird, wenn Fast I/O die Anfrage nicht bearbeiten kann. ↩ ↩2 ↩3
-
Microsoft Learn, Asynchronous disk I/O appears as synchronous on Windows. Dazu, dass eine Anfrage sofort abgeschlossen wird und TRUE zurückgibt, wenn die Daten bereits im Cache sind; sowie dazu, dass Windows’ Cache über eine Dateizuordnung implementiert ist und mangels asynchronem Mechanismus für Seitenfehler ein asynchroner Lesevorgang auf einem cache-fähigen Handle synchron verarbeitet werden kann, wenn die Seite nicht vorhanden ist. ↩
-
Microsoft Learn, FileStream.Flush method (.NET). Dazu, dass Flush() den internen Puffer des Streams an das Betriebssystem schreibt, und Flush(true) zusätzlich auch alle zwischengeschalteten Dateipuffer (die eigenen Puffer des Betriebssystems) flusht. ↩
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Die Tiefen von Windows I/O (Teil 2) — Synchrones und asynchrones I/O: Was OVERLAPPED wirklich bedeutet
Teil 2 einer Serie, die Windows' synchrones und asynchrones I/O (Overlapped I/O) anhand von Diagrammen erklärt. Wir stellen die Bedeutung...
Die Tiefen von Windows I/O (Teil 1) — Jedes Lesen und Schreiben wird zu einem IRP: Das Gesamtbild des I/O-Systems
Teil 1 einer Serie, die das Windows-I/O-System von Grund auf erklärt. Wir stellen den Namensraum des Object Managers, die drei Objektarte...
Die Tiefen von Windows I/O (Teil 5) — NTFS-Interna: Das Dateisystem anhand des MFT verstehen
Teil 5 einer Serie, die die NTFS-Interna anhand von Diagrammen erklärt. Behandelt werden der MFT und Dateidatensätze, mehrere Datenströme...
Die Tiefen von Windows-I/O (Teil 3) — I/O-Completion-Ports (IOCP) und der .NET-Thread-Pool: Der Keller unter async/await
Teil 3 einer Reihe, die I/O-Completion-Ports (IOCP) anhand von Diagrammen erklärt. Behandelt das Design, das Completion-Queue und Thread-...
Die Tiefen von Windows I/O (Teil 6, Finale) ── Filtertreiber und Minifilter: Warum Procmon und Virenscanner sich in die I/O einklinken können
Der Abschluss einer bebilderten Artikelserie über Windows-Filtertreiber und Minifilter. Er behandelt den Filter-Manager und Altitudes, Pr...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Ist die Daten bereits auf der Festplatte, sobald WriteFile Erfolg zurückgibt?
- Standardmäßig nicht. Windows' Dateicache ist ein Write-Back-Cache, und WriteFile gibt Erfolg zurück, sobald die Daten in den System-Dateicache kopiert wurden. Das eigentliche Schreiben auf die Festplatte übernimmt anschließend der Lazy Writer des Cache-Managers, der einmal pro Sekunde läuft. Entscheidend ist der Unterschied zwischen den Arten von Ausfällen. Stürzt der Prozess der Anwendung ab, gehen Daten, die es in den Cache geschafft haben, nicht verloren, weil das Betriebssystem sie später zurückschreibt, solange das Betriebssystem selbst läuft. Fällt dagegen das gesamte Betriebssystem aus (Stromausfall, Bluescreen), gehen dirty Cache-Daten verloren, die noch nicht zurückgeschrieben wurden. Die korrekte Lesart von „WriteFile hat Erfolg gemeldet“ lautet nicht „es wurde dauerhaft gespeichert“, sondern „es wurde an das Betriebssystem übergeben“.
- Wie stelle ich sicher, dass Daten tatsächlich auf der Festplatte ankommen?
- Es gibt drei Werkzeuge. Erstens FlushFileBuffers, das alle gepufferten Daten und Metadaten dieser Datei bis zum Gerät durchschreibt (in .NET entspricht dem FileStream.Flush(true)). Zweitens FILE_FLAG_WRITE_THROUGH, das bei jedem Schreibvorgang in den Cache schreibt und zugleich sofort auf die Festplatte. Drittens FILE_FLAG_NO_BUFFERING, das den Cache vollständig umgeht. Microsofts eigene Dokumentation weist darauf hin, dass es ineffizient ist, FlushFileBuffers bei jedem Schreibvorgang aufzurufen, und dass Anwendungen, die bei häufigen Schreibvorgängen zuverlässige Dauerhaftigkeit benötigen, stattdessen FILE_FLAG_NO_BUFFERING mit FILE_FLAG_WRITE_THROUGH kombinieren sollten. Jedes dieser Werkzeuge gibt einen Teil des Nutzens aus dem Cache zugunsten der Sicherheit auf, sodass es in der Praxis darauf ankommt, sie nicht überall anzuhängen, sondern gezielt für Schreibvorgänge einzusetzen, die Sie sich nicht zu verlieren leisten können.
- Worin unterscheiden sich FILE_FLAG_WRITE_THROUGH und FILE_FLAG_NO_BUFFERING?
- WRITE_THROUGH bedeutet „weiterhin in den Cache schreiben, aber vor dem Abschluss auch auf die Festplatte schreiben“. Lesevorgänge profitieren weiterhin vom Cache; entfernt wird nur die Verzögerung des Lazy Writers beim Schreiben. NO_BUFFERING bedeutet, dass Lese- und Schreibvorgänge den Systemcache vollständig umgehen, sodass beide bei jedem Aufruf zu Geräte-I/O werden (umgangen wird dabei allerdings nur Windows' eigener Cache – ein im Speichergerät selbst sitzender Schreibcache wird dadurch nicht übersprungen). Im Gegenzug gelten strenge Einschränkungen: Größe und Dateiversatz jedes Lese- oder Schreibvorgangs müssen ein ganzzahliges Vielfaches der Sektorgröße des Volumes sein, und auch die Pufferadresse muss an einer physischen Sektorgrenze ausgerichtet sein. Zudem werden auch bei NO_BUFFERING weiterhin die Metadaten des Dateisystems gecacht, sodass für deren zuverlässige Speicherung zusätzlich FlushFileBuffers oder WRITE_THROUGH nötig ist. Typischerweise wird es von Software eingesetzt, die ihre Pufferung selbst verwaltet, etwa Datenbank-Engines; gewöhnliche Anwendungen sollten zunächst WRITE_THROUGH oder FlushFileBuffers in Betracht ziehen.
- Zeigt der Task-Manager wenig freien Speicher, liegt das am Dateicache?
- Häufig ja, und das ist normales Verhalten. Windows nutzt freien physischen Speicher aktiv als Dateicache, sodass das Kopieren einer großen Datei oder viel Lesen und Schreiben den Cache anschwellen lässt und die Speichernutzung höher erscheinen lässt. Die meisten der vom Cache belegten Seiten lassen sich jedoch vergleichsweise schnell freigeben, sobald eine Anwendung Speicher anfordert – das ist von einem echten Speichermangel zu unterscheiden. Bei Verdacht auf tatsächlichen Mangel ist es sinnvoller, auf den committeten Speicher und die Häufigkeit von Hard Faults zu achten als allein auf die scheinbare Menge an freiem Speicher.
- Kommen sich der Inhalt einer speichergemappten Datei und ReadFile/WriteFile bei derselben Datei in die Quere?
- Gegenüber gewöhnlichem, cache-fähigem I/O nicht. Windows' eigener Cache ist selbst als Dateizuordnung implementiert, sodass eine gemappte Ansicht und der Cache derselben lokalen Datei dieselben Daten teilen – eine Änderung über den einen Weg ist über den anderen sichtbar. Auch mehrere Prozesse, die Ansichten aus demselben Dateizuordnungsobjekt erzeugen, sehen kohärente Daten. Lese- und Schreibvorgänge über ein mit FILE_FLAG_NO_BUFFERING geöffnetes Handle umgehen den Cache jedoch und fallen aus dieser Kohärenz heraus. Um Änderungen über eine gemappte Ansicht zuverlässig dauerhaft zu machen, reicht außerdem FlushViewOfFile allein nicht aus – es schreibt keine Metadaten und wartet nicht auf einen Hardware-Cache –, weshalb nach FlushViewOfFile noch FlushFileBuffers aufgerufen werden muss.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.