Änderungsverlauf (Erstfassung, veröffentlicht am 29. Jul 2026)
- Erstveröffentlichung
Diesen Artikel zitieren(DOI (registriertes Archiv): 10.5281/zenodo.22175308)
Die folgenden DOIs verweisen auf bereits archivierte Versionen, die vom aktuellen Text abweichen können. Verwenden Sie die URL dieser Seite, um auf den aktuellen Text zu verweisen.
Go Komura (2026). Die Tiefen von Windows I/O (Teil 4) — Cache-Manager: Wann erreicht Ihr WriteFile den Datenträger?. KomuraSoft LLC. https://comcomponent.com/de/blog/windows-cache-manager-writefile-disk/
- DOI (registriertes Archiv)
- 10.5281/zenodo.22175308
- DOI (zuletzt registrierte Version)
- 10.5281/zenodo.22175309
WriteFile hat Erfolg gemeldet. Wo befinden sich die Daten jetzt eigentlich?
Bei einem gewöhnlichen cache-fähigen Schreibvorgang werden die Daten zuerst an den Cache im Speicher des Betriebssystems übergeben. Ein Erfolg von WriteFile bedeutet „an das Betriebssystem übergeben“, nicht „auf dem Datenträger dauerhaft gespeichert“. Die Übernahme auf den Datenträger erledigt das Betriebssystem später.1
Kennt man diesen Unterschied, lassen sich Phänomene wie „gespeichert, und nach dem Stromausfall war es weg“ oder „die Messwerte einer Dateikopie sind schneller als die Datenträgerleistung“ erklären. Dass Datenbanken mit fsync und Ähnlichem ausdrücklich schreiben, dient genau dazu, Annahme und Dauerhaftigkeit zu trennen.
Dieser Artikel ist Teil 4 der Reihe „Die Tiefen von Windows I/O“. Er nimmt den Cache-Manager zwischen Anwendung und Datenträger auf und ordnet der Reihe nach den Mechanismus des Caches, den Zeitpunkt des Zurückschreibens und die Speicherung von Daten, die man sich nicht zu verlieren leisten kann.
Zugleich erklärt er den Grund aus Teil 2, warum asynchrones I/O bei einem Cache-Treffer synchron abschließt, und den aus Teil 1, Fast I/O als Abkürzung, die kein IRP baut.
1. Zuerst das Fazit
- Beim Standardschreiben sind das Kopieren in den Cache und die Übernahme auf den Datenträger getrennt. Windows verwendet Write-Back, und der Lazy Writer schreibt später. Daten, die den Cache des Betriebssystems erreicht haben, überstehen einen Absturz allein der Anwendung; bei Stromausfall oder OS-Absturz gehen dirty Pages verloren, die noch nicht übernommen wurden.1
- Für Daten, die man nicht verlieren darf, wählt man ein Verfahren, das zur Speichergrenze passt. Zum Festschreiben an einem Einschnitt dient
FlushFileBuffers(in .NETFileStream.Flush(true)), zum Entfernen der Verzögerung je SchreibvorgangFILE_FLAG_WRITE_THROUGH.FILE_FLAG_NO_BUFFERINGist ein anderes Werkzeug, das den Systemcache umgeht, Ausrichtungsanforderungen hat und den geräteinternen Cache sowie Metadaten weiter berücksichtigen muss. Für häufige Dauerhaftigkeit nennt die Dokumentation die Kombination aus NO_BUFFERING und WRITE_THROUGH.231 - Auch die Lesegeschwindigkeit und der I/O-Pfad lassen sich aus dem Cache-Mechanismus verstehen. Der Cache ist in Wirklichkeit eine Dateizuordnung, beim Lesen arbeitet Read-Ahead. Kohärenz mit einer gemappten Ansicht und Dauerhaftigkeit denkt man getrennt, Fast I/O als Abkürzung, die ein IRP weglassen kann.456
Je nach Ziel kann man bei den folgenden Abschnitten einsteigen.
| Was Sie wissen wollen | Leseabschnitt |
|---|---|
| Was der Cache wirklich ist, warum freier Speicher sinkt, Read-Ahead | Abschnitte 2 und 3 |
| Was nach einem Erfolg von WriteFile bei einem Ausfall verloren geht | Abschnitt 4 |
| Die Wahl zwischen Flush, WRITE_THROUGH, NO_BUFFERING und die Ausrichtungsanforderungen | Abschnitt 5 |
| Kohärenz der gemappten Ansicht und das zweistufige Flush | Abschnitt 6 |
| Der Unterschied zwischen Cache-Treffer und Fast I/O | Abschnitt 7 |
In der Abbildung kennzeichnet eine durchgezogene Linie eine stets geltende Beziehung und eine gestrichelte Linie eine bedingte (die Bedingungen stehen bei jeder Beziehung auf der Detailseite). Die vollständige Liste der Beziehungen (32 insgesamt, mit Beleg und Sicherheitsgrad) und die Definitionen der wichtigsten Konzepte sind auf der Detailseite der Wissenskarte (auf Japanisch) zusammengestellt. Daten: JSON-LD / Turtle
2. Was der Cache wirklich ist — Dateien werden in den Speicher gemappt
2.1. 256-KB-Slots und Speicherkopien
Der Cache ist in Wirklichkeit eine Ansicht, die einen Teil einer Datei in den Speicher mappt. Der Cache-Manager mappt 256-KB-Abschnitte einer Datei in „Slots“ des Systemadressraums. Cache-fähige Lese- und Schreibvorgänge laufen als Speicherkopien zwischen diesem Slot und dem Puffer der Anwendung.1
flowchart TB
subgraph U["Anwendung (Benutzermodus)"]
BUF["Puffer der Anwendung<br/>(der an ReadFile/WriteFile übergebene Bereich)"]
end
subgraph S["Systemadressraum"]
SLOT["Systemdateicache<br/>Slot, der einen 256-KB-Abschnitt der Datei mappt"]
end
DISK[("Datei auf dem Datenträger")]
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 Wirklichkeit cache-fähigen I/O. Was aus Anwendungssicht Lesen oder Schreiben einer Datei ist, ist meistens nur eine Speicherkopie
256 KB ist nicht die Einheit, in der auf den Datenträger gelesen und geschrieben wird
256 KB ist die Granularität der Ansicht (des Mappings), nicht die Aussage, Datenträger-I/O erfolge immer in 256-KB-Einheiten. Seiten im Slot werden nach Bedarf eingelesen, und die tatsächliche I/O-Menge hängt von Anforderungsgröße und Zugriffsmuster ab.
Für einen Abschnitt, der zum ersten Mal gelesen wird, entsteht Datenträger-I/O, um den Cache zu füllen, und das in Teil 1 gesehene IRP läuft in den Speicherstapel darunter. Liegt er bereits im Cache, endet das Lesen allein mit einer Speicherkopie.
Auch asynchron ausgegeben kann es an Ort und Stelle verarbeitet werden
„Bei einem Cache-Treffer schließt auch asynchrones I/O synchron ab“ aus Abschnitt 5 von Teil 2 ist das Verhalten, eine Anforderung an Ort und Stelle abzuschließen, wenn sie sich sofort beantworten lässt.
Umgekehrt kann ein asynchrones Lesen auch bei eingeschaltetem Cache synchron verarbeitet werden, wenn die Seite nicht im Speicher liegt, weil die Page-Fault-Verarbeitung keinen asynchronen Mechanismus hat. Sofortigen Abschluss durch einen Cache-Treffer und synchrone Verarbeitung wegen fehlender Seite hält man getrennt.7
2.2. Was „freier Speicher ist gesunken“ wirklich bedeutet
Einstellungen je Öffnungsvorgang und geteilte Daten trennen
Ob der Cache genutzt wird und der Zustand des Read-Ahead werden je Öffnungsvorgang (je Dateiobjekt) verwaltet.1 Die gecachten Daten selbst werden dagegen je Datei (je Stream) geteilt. Mehrfaches Öffnen derselben Datei erzeugt keine getrennten Caches. Dass jedes Handle denselben Cache-Inhalt sieht, ist die Grundlage der Kohärenz in Abschnitt 6.
Der Cache-Manager verwaltet den Cache durchgehend, solange Windows läuft.1 Beim Kopieren einer großen Datei oder bei viel Lesen und Schreiben wird freier physischer Speicher als Cache genutzt. Viel davon ist Standby-Speicher, der sich vergleichsweise schnell umwidmen lässt, sobald eine Anwendung Speicher anfordert.
Gesunkener freier Speicher und Speichermangel sind also nicht dasselbe. Die Beobachtungspraxis mit dieser Unterscheidung behandelt auch „GC-Wartezeit von einem Speicherleck in .NET unterscheiden“.
Am Bildschirm die Veränderung von Standby und Frei beobachten
Im Task-Manager unter Leistung > Speicher zeigt der Balken „Speicherzusammensetzung“ unten In Verwendung / Geändert / Standby / Frei. Der Großteil des Dateicaches landet in Standby, und die Liste rechts summiert ihn als „Zwischengespeichert“. Die Registerkarte Speicher des Ressourcenmonitors zeigt dieselbe Aufteilung mit Kapazitäten.
Kopieren Sie eine Datei von einigen GB und vergleichen Sie vorher und nachher. Standby steigt und Frei sinkt, während In Verwendung kaum wechselt — daran erkennt man, dass frei gewesener Speicher als Cache genutzt wurde.
3. Read-Ahead — Spekulation beim Lesen
Der Cache-Manager liest aus vergangenen Zugriffsmustern den nächsten Abschnitt, der gelesen werden dürfte, vorab ein. Das ist Read-Ahead.
Liest man eine Datei von vorn der Reihe nach, wird das Lesen schneller, wenn die Fortsetzung schon im Cache liegt, bevor die nächste Anforderung kommt. Die Menge des Read-Ahead ist nicht fest; sie ändert sich mit dem erkannten Muster und der Anforderungsgröße.
flowchart LR
A["Verlauf der Leseanforderungen der Anwendung<br/>liest von vorn der Reihe nach"]
D{"Der Cache-Manager<br/>erkennt das Muster"}
R["Read-Ahead: den folgenden Abschnitt<br/>einlesen, bevor er angefordert wird<br/>(Menge je nach Muster und Anforderungsgröße variabel)"]
H1["Hinweis FILE_FLAG_SEQUENTIAL_SCAN<br/>= Read-Ahead aktiv betreiben"]
H2["Hinweis FILE_FLAG_RANDOM_ACCESS<br/>= Read-Ahead wäre verschwendet, deshalb drosseln"]
A --> D
D --> R
H1 -.-> D
H2 -.-> D
Abbildung 2: Read-Ahead. Zusätzlich zur Erkennung des Zugriffsmusters lassen sich Hinweise über CreateFile-Flags geben
FileOptions.SequentialScan / RandomAccess aus der Zuordnungstabelle in Teil 1 sind Hinweise an dieses Read-Ahead. Die Anwendung teilt dem Betriebssystem mit, welchen Zugriff sie plant.
| Geplanter Zugriff | Win32-Flag | Angabe in .NET | Hinweis an das Read-Ahead |
|---|---|---|---|
| Stapelverarbeitung, die die ganze Datei der Reihe nach liest | FILE_FLAG_SEQUENTIAL_SCAN |
FileOptions.SequentialScan |
Read-Ahead aktiv betreiben |
| Unregelmäßiger Zugriff, etwa dem Index nach | FILE_FLAG_RANDOM_ACCESS |
FileOptions.RandomAccess |
Read-Ahead drosseln, das leicht verschwendet wäre |
Wichtig ist, einen Hinweis zu wählen, der zur Verarbeitung passt; keines von beiden legt die Read-Ahead-Menge fest.
4. Verzögertes Schreiben — was der Erfolg von WriteFile bedeutet
4.1. Der Lazy Writer kommt einmal pro Sekunde
Beim Standardschreiben gibt WriteFile Erfolg zurück, sobald die Daten in den Cache-Slot kopiert sind, und die Übernahme auf den Datenträger wird nachgeholt. Diese Write-Back-Cache-Politik heißt verzögertes Schreiben (lazy writing).1
Die Übernahme trägt der Lazy Writer, den der Cache-Manager einmal pro Sekunde startet. Er legt ein Achtel der kürzlich nicht geflushten Seiten in die Warteschlange und legt nach, wenn mehr zu schreiben ist.1
„Einmal pro Sekunde“ ist der Startabstand der Verarbeitung. Es ist keine Garantie, dass jeder einzelne Schreibvorgang innerhalb einer Sekunde dauerhaft wird.
Das Attribut einer temporären Datei ist ein Hinweis, das Zurückschreiben zu drosseln
Temporäre Dateien mit dem Attribut FILE_ATTRIBUTE_TEMPORARY werden aus dem Flush-Ziel des Lazy Writers genommen, weil man davon ausgeht, dass sie bald gelöscht werden.1 Das Attribut ist jedoch nur ein Hinweis. Bei Speichermangel kann trotzdem zurückgeschrieben werden, und ein bloß temporär klingender Name reicht nicht.
sequenceDiagram
participant App as Anwendung
participant C as Systemcache
participant LW as Lazy Writer (startet jede Sekunde)
participant D as Datenträger
App->>C: WriteFile(Daten)
Note over C: In den Slot kopieren und<br/>die Seite dirty (ungeschrieben) markieren
C-->>App: TRUE kommt sofort zurück
Note over App,C: Von hier bis zum Zurückschreiben ist das gefährliche Fenster<br/>bei Stromausfall oder OS-Absturz verschwinden diese Daten
LW->>C: Ein Achtel der dirty Pages wählen
LW->>D: Gesammelt zurückschreiben
Note over D: Hier werden sie erstmals dauerhaft
Abbildung 3: Verzögertes Schreiben. Der Erfolg von WriteFile bedeutet an das Betriebssystem übergeben, nicht dauerhaft gespeichert
4.2. Was verschwindet, je nachdem, was passiert
Zwischen dem Erfolg von WriteFile und dem Ende des Zurückschreibens bleibt ein Zeitraum, in dem die Daten noch im Cache liegen. Was in dieser Zeit aus den Daten wird, teilt sich danach, ob nur die Anwendung stehen geblieben ist oder das Betriebssystem insgesamt.
flowchart TB
W["Daten unmittelbar nach Erfolg von WriteFile<br/>(dirty Pages im Cache)"]
Q{"Was ist passiert"}
A1["Der Prozess der Anwendung<br/>stürzt ab oder wird beendet"]
A2["Das Betriebssystem bleibt stehen<br/>(Stromausfall, Bluescreen)"]
S["Die Daten bleiben<br/>der Cache gehört dem Betriebssystem,<br/>der Lazy Writer schreibt planmäßig zurück"]
L["Dirty Pages gehen verloren<br/>es bleibt nur, was den Datenträger erreicht hatte"]
W --> Q
Q --> A1
Q --> A2
A1 --> S
A2 --> L
Abbildung 4: Die Trennlinie zwischen Art des Ausfalls und Überleben. Der Cache ist nicht Besitz des Prozesses, sondern des Betriebssystems
| Ausfall | Was aus Daten wird, die den Cache des Betriebssystems erreicht haben |
|---|---|
| Absturz oder erzwungenes Beenden der Anwendung | Der Cache gehört dem Betriebssystem, also wird zurückgeschrieben, solange das Betriebssystem läuft |
| Stromausfall oder OS-Absturz | Dirty Pages, die noch nicht zurückgeschrieben wurden, gehen verloren; es bleibt nur, was den Datenträger erreicht hatte |
„Direkt nach dem Speichern ist die Anwendung abgestürzt, die Datei war aber heil“ kommt daher, dass das Betriebssystem die Daten nach dem Kopieren in den Cache hält. Bei plötzlichem Stromverlust gehen nicht übernommene Cache-Daten verloren, das steht auch in der offiziellen Dokumentation. Die Flush-Häufigkeit wird als Abwägung zwischen Leistung und Zuverlässigkeit justiert.1
Was man im Entwurf zuerst festlegt, ist: „Dürfen diese Daten im Moment eines Stromausfalls verloren gehen?“ Die letzten paar Sekunden eines Protokolls mag man hinnehmen, den festgeschriebenen Datensatz eines Auftrags nicht. Für Schreibvorgänge, die man nicht verlieren darf, verwendet man die Verfahren des nächsten Abschnitts.
5. Der Werkzeugkasten für „sicher geschrieben“
In diesem Abschnitt unterscheidet man drei Verfahren: den vorhandenen Puffer jetzt schreiben, die Schreibverzögerung entfernen, den Systemcache umgehen. Abschnitt 5.4 fasst die Reihenfolge zusammen, in der man nach Zuverlässigkeitsanforderung und tragbaren Kosten wählt.
5.1. FlushFileBuffers — jetzt durchschreiben
FlushFileBuffers schreibt die gepufferten Daten der angegebenen Datei auf das Gerät. Metadaten des Dateisystems werden immer gecacht, deshalb braucht man für Metadaten ein Flush oder WRITE_THROUGH.12
In .NET Flush() und Flush(true) unterscheiden
| Aufruf | Was geschrieben wird |
|---|---|
FileStream.Flush() |
Den internen Puffer von .NET an das Betriebssystem übergeben. Ein Flush des OS-Caches wird nicht verlangt |
FileStream.Flush(true) |
Zusätzlich zu .NET intern auch Zwischenpuffer des Betriebssystems und Ähnliches flushen |
Die Angabe, die unter Windows FlushFileBuffers entspricht, ist FileStream.Flush(true). Den Unterschied zu einem bloßen Flush() sollte man im Blick behalten.8
Auch die Kosten eines Flush bei jedem Schreibvorgang bedenken
Die offizielle Dokumentation merkt an, dass FlushFileBuffers bei vielen Schreibvorgängen jedes Mal ineffizient wird. Für häufige Schreibvorgänge, bei denen jede einzelne Dauerhaftigkeit nötig ist, wird die Kombination aus NO_BUFFERING und WRITE_THROUGH genannt, die weiter unten kommt.2
5.2. FILE_FLAG_WRITE_THROUGH — nur die Verzögerung entfernen
Öffnet man mit FILE_FLAG_WRITE_THROUGH, gehen Schreibvorgänge zugleich in den Cache und ohne Warten auf den Lazy Writer auf den Datenträger.1
Der Systemcache selbst wird nicht umgangen, Lesevorgänge nutzen ihn weiter. Das ist das Verfahren für „den Cache beim Lesen behalten, nur die Verzögerung beim Schreiben entfernen“.
5.3. FILE_FLAG_NO_BUFFERING — ohne den Cache
FILE_FLAG_NO_BUFFERING nimmt den Systemcache von Windows aus den Lese- und Schreibvorgängen. Lesen und Schreiben werden zu I/O auf das Datenträgergerät, ohne den Cache.1
Der Schreibcache im Gerät selbst ist jedoch eine andere Stufe. NO_BUFFERING umgeht ihn nicht, deshalb bleiben WRITE_THROUGH in Kombination oder FlushFileBuffers im Blick, wenn Stromausfallfestigkeit nötig ist.
Es eignet sich für den gebündelten Transfer großer Datenmengen und für Datenbank-Engines, die ihre Puffer selbst verwalten, verlangt aber, dass die Anwendung die folgenden Ausrichtungsanforderungen erfüllt.3
- Größe und Dateiversatz von Lesen und Schreiben müssen ganzzahlige Vielfache der Sektorgröße des Volumes sein (bei 512-Byte-Sektoren 512, 1024, 1536 …).
- Auch die Pufferadresse muss an der physischen Sektorgröße ausgerichtet sein (auch mit Blick auf Advanced-Format-Datenträger mit 4096 Byte physischer Sektorgröße).
- Metadaten werden trotzdem weiter gecacht, vollständige Dauerhaftigkeit braucht also WRITE_THROUGH in Kombination oder
FlushFileBuffers.12
Größe, Versatz und Adresse in den drei Punkten angleichen
Ein bloßes Hinzufügen des Flags reicht nicht, um auf NO_BUFFERING umzustellen. Lesen und Schreiben, die die Ausrichtungsanforderungen nicht einhalten, scheitern mit ERROR_INVALID_PARAMETER (87). Zu prüfen sind die folgenden drei Punkte.3
| Was anzugleichen ist | Bedingung | Wie man sie erfüllt |
|---|---|---|
| Größe von Lesen und Schreiben | Ganzzahliges Vielfaches der Sektorgröße des Volumes | lpBytesPerSector von GetDiskFreeSpace holen und auf dieses Vielfache runden |
| Dateiversatz | Dasselbe (auch bei Angabe über Offset in OVERLAPPED) |
In Vielfachen der Sektorgröße fortschreiten |
| Pufferadresse | An der physischen Sektorgröße ausgerichtet | Mit VirtualAlloc belegen (es kommt ein Bereich zurück, der an der Seitengrenze, üblicherweise 4096 Byte, ausgerichtet ist) |
An einem minimalen C++-Beispiel die Ausrichtung des Puffers prüfen
Besonders leicht übersieht man den dritten Punkt, die Pufferadresse. Die Adresse, die malloc, new oder ein C#-Array zurückgibt, ist nicht an einer Sektorgrenze ausgerichtet.
Das folgende Beispiel belegt mit VirtualAlloc einen an der Seitengrenze ausgerichteten Bereich. Eine gewöhnliche 4096-Byte-Seitengrenze erfüllt auch die Anforderung eines Advanced-Format-Datenträgers mit 4096 Byte physischer Sektorgröße. Größe und Versatz schreitet man in Vielfachen der geholten Sektorgröße fort.
// C++ / Win32. Die Fehlerbehandlung ist auf das Minimum beschränkt
DWORD sectorsPerCluster = 0, bytesPerSector = 0, freeClusters = 0, totalClusters = 0;
if (!GetDiskFreeSpaceW(L"C:\\", §orsPerCluster, &bytesPerSector,
&freeClusters, &totalClusters))
{
return GetLastError();
}
// Die Einheit von Lesen und Schreiben auf ein ganzzahliges Vielfaches der Sektorgröße setzen (hier rund 1 MiB)
const DWORD chunk = (1024 * 1024 / bytesPerSector) * bytesPerSector;
// Den Puffer als an der Seitengrenze ausgerichteten Bereich nehmen (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 gibt das Ergebnis des unmittelbar vorherigen Win32-Aufrufs zurück. Ruft man
// zuerst VirtualFree auf, wird der Grund für das Scheitern von CreateFileW (Zugriff verweigert,
// Pfad fehlt usw.) vom Ergebnis der Aufräumarbeit überschrieben, und es kommt nur ein
// undurchsichtiger Code zurück
const DWORD err = GetLastError();
VirtualFree(buffer, 0, MEM_RELEASE);
return err;
}
// Weil jedes Mal chunk Byte fortgeschritten wird, bleiben Größe und Versatz ausgerichtet
DWORD read = 0;
while (ReadFile(h, buffer, chunk, &read, nullptr) && read > 0)
{
// Die ersten read Byte von buffer verarbeiten
// (am Dateiende wird read < chunk. Das ist normal)
}
CloseHandle(h);
VirtualFree(buffer, 0, MEM_RELEASE);
Auch in .NET lässt sich die Verwaltung der Ausrichtung nicht weglassen
In den FileOptions von .NET gibt es keinen Wert, der FILE_FLAG_NO_BUFFERING entspricht. Wenn man ihn braucht, ruft man CreateFile direkt, muss die Ausrichtungsanforderungen dann aber selbst einhalten.
Die Reihenfolge der Überlegung ist nicht „NO_BUFFERING, weil es schneller sein soll“, sondern „NO_BUFFERING, weil wir die Puffer selbst verwalten“.
5.4. Die Wahl ordnen
flowchart TB
A["Puffer der Anwendung"]
B["Systemdateicache<br/>(dirty Pages)"]
C["Cache im Datenträgergerät"]
D[("nichtflüchtiges Medium")]
A -->|"Standard-WriteFile: bis hier Erfolg"| B
B -->|"Lazy Writer (jede Sekunde) / WRITE_THROUGH (sofort)"| C
C -->|"Zeitpunkt des Geräts /<br/>FlushFileBuffers verlangt Durchschreiben"| D
A -.->|"NO_BUFFERING überspringt den Cache und geht direkt"| C
Abbildung 5: Die Schichten der Daten und wie weit jedes Werkzeug schiebt. Auch die letzte Stufe, der Cache im Datenträgergerät, verdient Aufmerksamkeit
| Verfahren | Was geschieht | Geeignet für |
|---|---|---|
| Standard (Cache aktiv) | Abschluss mit Cache-Kopie. Übernahme durch den Lazy Writer | Die allermeiste Datei-I/O |
FlushFileBuffers / Flush(true) |
Daten plus Metadaten dieses Zeitpunkts durchschreiben | Festschreiben an einem Einschnitt (Commit einer Transaktion und Ähnliches) |
FILE_FLAG_WRITE_THROUGH |
Bei jedem Schreibvorgang sofort auf den Datenträger (Lesen bleibt im Cache) | Protokoll und Journal, bei denen Schreibvorgänge nicht verloren gehen dürfen |
FILE_FLAG_NO_BUFFERING (+WRITE_THROUGH) |
Ohne Cache. Mit Ausrichtungsanforderungen | Eigene Pufferverwaltung, große gebündelte I/O |
Zuerst die Zuverlässigkeit festlegen, dann die Kosten prüfen
Zuerst legt man fest, „wie viele Vorgänge bei einem Stromausfall verloren gehen dürfen“. Dann trennt man, ob das, was nicht verloren gehen darf, ein „Einschnitt“ wie das Festschreiben eines Geschäfts ist oder „jeder einzelne Schreibvorgang“.
Dann prüft man, wie viel langsamer das werden darf und ob man eigene Pufferverwaltung und Ausrichtung tragen kann. Man wählt nicht nach dem Namen des Verfahrens, sondern entlang der folgenden Verzweigung.
flowchart TB
S["Diese Daten sollen geschrieben werden"]
Q1{"Darf es im Moment von Stromausfall<br/>oder Bluescreen verloren gehen"}
A0["Standard belassen (Cache aktiv)<br/>am schnellsten. Die allermeiste I/O liegt hier"]
Q2{"Was nicht verloren gehen darf:<br/>Einschnitt oder jeder einzelne Vorgang"}
A1["Am Einschnitt FlushFileBuffers<br/>in .NET Flush(true)<br/>Kosten: nur das Warten am Einschnitt"]
Q3{"Kann man Puffer selbst verwalten<br/>und die Ausrichtung aus 5.3 erfüllen"}
A2["FILE_FLAG_WRITE_THROUGH<br/>bei jedem Schreibvorgang sofort auf den Datenträger<br/>Lesen bleibt schnell über den Cache"]
A3["FILE_FLAG_NO_BUFFERING<br/>+ FILE_FLAG_WRITE_THROUGH<br/>die Form häufiger Dauerhaftigkeit, die die Dokumentation nennt"]
S --> Q1
Q1 -->|"Ja<br/>(Protokoll der letzten Sekunden und Ähnliches)"| A0
Q1 -->|"Nein"| Q2
Q2 -->|"Einschnitt<br/>(Festschreiben eines Geschäfts und Ähnliches)"| A1
Q2 -->|"Jeder einzelne Vorgang"| Q3
Q3 -->|"Nein (gewöhnliche Anwendung)"| A2
Q3 -->|"Ja (DB-Engine und Ähnliches)"| A3
Abbildung 6: Die Wahl der Werkzeuge. Die erste Verzweigung ist die Zuverlässigkeitsanforderung, die zweite die tragbaren Kosten. Dass es keinen Weg gibt, bei jedem Vorgang FlushFileBuffers zu rufen, liegt daran, dass die offizielle Dokumentation das, wie in 5.1 gesehen, als ineffizient einstuft
In Speicherentwurf und Leistungsmessung übersetzen
In der Praxis lässt sich die Wahl leichter treffen, wenn man sie an die folgenden drei Muster bindet.
- „In eine temporäre Datei schreiben, flushen, umbenennen“ ist der klassische Weg, keine halb zerbrochene Datei zurückzulassen. Den Inhalt durchschreiben und dann über den Namen festschreiben — diese atomare Übergabe behandelt „Grundlagen des wechselseitigen Ausschlusses bei der Dateiintegration“ ausführlich.
- Der Datenbank zu überlassen ist ebenfalls ein vollwertiger Entwurf. Wie SQLite Haltbarkeit mit WAL und Flush herstellt, steht in „SQLite aus C# in Business-Apps nutzen“. Die Option, „keine eigene Flush-Strategie zu schreiben“, gibt es immer.
- Benchmarks sollte man dem Cache gegenüber misstrauen. Messungen, die „zu schnell lesen“, messen meist den Cache-Treffer ab dem zweiten Mal. Die Messpraxis steht in „Wie man die Geschwindigkeit verschiedener Programmversionen unter Windows korrekt vergleicht“.
Den Cache im Gerät mitdenken
Am Ende von Abbildung 5 bleibt der Cache im Datenträgergerät. FlushFileBuffers verlangt das Durchschreiben einschließlich dieser Stufe.
Bei USB-Sticks und externen Datenträgern spielen auch die geräteseitigen Richtlinien „Schnelles Entfernen“ und „Bessere Leistung“ mit. Zum Umgang mit Wechseldatenträgern siehe auch „USB-Geräte aus einer Windows-App ansprechen“.
6. Kohärenz mit speicherabgebildeten Dateien
6.1. Gemappte Ansicht und Cache sehen dieselben Daten
Wenn der Cache in Wirklichkeit eine Dateizuordnung ist, was wird aus einer selbst mit MapViewOfFile erzeugten Ansicht und dem Cache, den ReadFile / WriteFile nutzen?
Gewöhnliches cache-fähiges I/O und eine gemappte Ansicht teilen Daten, die von derselben Datei hinterlegt sind. Werden Seiten des Dateizuordnungsobjekts verdrängt, werden Änderungen in die Datei zurückgeschrieben. Auch wenn mehrere Prozesse Ansichten derselben lokalen Datei aus demselben Dateizuordnungsobjekt erzeugen, ist der sichtbare Inhalt kohärent (einheitlich).4
flowchart TB
subgraph P1["Adressraum von Prozess A"]
V1["Ansicht von MapViewOfFile"]
end
subgraph SYS["Systemadressraum"]
SC["Ansicht des Cache-Managers<br/>(Slot, den ReadFile/WriteFile nutzen)"]
end
PAGES["Dieselben physischen Seiten<br/>(von der Datei hinterlegter Speicher)"]
DISK[("Datei auf dem Datenträger")]
V1 --> PAGES
SC --> PAGES
PAGES --> DISK
NB["I/O mit FILE_FLAG_NO_BUFFERING liegt<br/>außerhalb dieser Teilung (direkt auf den Datenträger)"]
NB -.-> DISK
Abbildung 7: Gemappte Ansicht und Cache sehen dieselben von der Datei hinterlegten Seiten. Außerhalb des Rahmens liegt nur NO_BUFFERING
6.2. Kohärenz und Dauerhaftigkeit getrennt prüfen
I/O mit NO_BUFFERING liegt außerhalb dieser Kohärenz. Lesen und Schreiben ohne Cache und der Inhalt über gemappte Ansicht oder Cache werden nicht gegeneinander gehalten. Mischt man sie, muss die Anwendung die Übereinstimmung selbst herstellen.
Außerdem ist sichtbar in der gemappten Ansicht nicht dasselbe wie dauerhaft auf dem Datenträger. Die Dauerhaftigkeit einer gemappten Ansicht stellt man in dieser Reihenfolge her.5
| Reihenfolge | Aufruf | Rolle und Hinweise |
|---|---|---|
| 1 | FlushViewOfFile |
Das Schreiben der dirty Pages im Bereich beginnen. Metadaten werden nicht geschrieben, und auf den Abschluss des physischen Schreibens aus dem Gerätecache wird nicht gewartet |
| 2 | FlushFileBuffers |
Das Durchschreiben einschließlich Dateimetadaten und Gerätecache verlangen |
Die Praxis der Dateizuordnung als Shared Memory (benannte Freigabe, Synchronisierung, Unfallmuster) behandelt „Fallstricke bei Shared Memory und Best Practices für die Praxis“.
7. Fast I/O — die Hausaufgabe aus Teil 1
Fast I/O ist eine Abkürzung für synchrones Lesen und Schreiben auf eine gecachte Datei, die ohne IRP verarbeitet. Ein IRP ist ein „I/O Request Packet“, die Struktur, in der der Kernel eine Anforderung an einen Treiber übergibt.6
7.1. Wann sich ein IRP weglassen lässt und wann der normale Weg zurückkehrt
Die Erklärung in Abschnitt 5.2 von Teil 1, „nicht jedes I/O wird zum IRP“, meint genau diesen Pfad.
Lassen sich Lesen und Schreiben allein mit einer Speicherkopie zum Cache erledigen, muss kein IRP gebaut und durch den Gerätestapel geschickt werden. Fast I/O ruft den Einstiegspunkt des Dateisystems direkt und tauscht Daten mit dem Cache-Manager.6
Lässt sich Fast I/O nicht nutzen — weil es nicht im Cache liegt, Sperren eine Rolle spielen, ein Filter eingreift —, kehrt man auf den gewöhnlichen IRP-Pfad zurück.
Zu beachten ist auch: „Cache-Treffer heißt nicht immer Fast I/O“. Vorgänge auf einem asynchronen Handle (FILE_FLAG_OVERLAPPED) können auch dann über den IRP-Pfad laufen, wenn sie aus dem Cache an Ort und Stelle abschließen. Ob es in Abschnitt 5 von Teil 2 „an Ort und Stelle abschließt“ und ob dieses Kapitel „das IRP weglässt“, sind verschiedene Fragen.
flowchart TB
REQ["Synchrones Lesen und Schreiben auf ein cache-fähiges Handle"]
Q{"Lässt sich mit Fast I/O verarbeiten<br/>(liegt im Cache und Ähnliches)"}
FAST["Fast I/O<br/>ohne IRP direkt mit dem Cache kopieren<br/>in Procmon als FASTIO_ angezeigt"]
IRP["Gewöhnlicher Pfad<br/>IRP bauen und in den Gerätestapel<br/>(die Welt von Abbildung 6 in Teil 1)"]
REQ --> Q
Q -->|ja| FAST
Q -->|nein| IRP
Abbildung 8: Die Verzweigung von Fast I/O. Deshalb sieht man in Procmon FASTIO_READ und IRP_MJ_READ gemischt
7.2. In der Beobachtung denselben Lesepfad unterscheiden
Dass in der Procmon-Beobachtung von Abschnitt 7 in Teil 1 Zeilen mit FASTIO_ gemischt waren, liegt daran, dass derselbe Lesevorgang verschiedene Pfade nimmt. FASTIO_READ und IRP_MJ_READ liest man als Unterschied zwischen Fast I/O und gewöhnlichem IRP-Pfad.
Diese Abkürzung betrifft auch die Filtertreiber in Teil 6. Minifilter können nicht nur in den gewöhnlichen IRP-Pfad, sondern auch in Fast I/O eingreifen.
8. Zusammenfassung
Der Artikel insgesamt in der Reihenfolge Mechanismus, Ausfall, Entwurfsentscheidung.
| Blickwinkel | Was festzuhalten ist |
|---|---|
| Was der Cache ist | Eine Ansicht, die 256-KB-Abschnitte einer Datei mappt. Cache-fähiges Lesen und Schreiben ist eine Speicherkopie mit dem Slot; 256 KB ist keine feste Datenträger-I/O-Größe |
| Lesen | Den nächsten Abschnitt vorab einlesen. SequentialScan / RandomAccess sind Hinweise, die das Zugriffsmuster mitteilen |
| Schreiben und Ausfall | Standardmäßig schreibt der Lazy Writer nach. Daten, die den OS-Cache erreicht haben, überstehen einen Absturz allein der Anwendung; bei Stromausfall oder OS-Absturz gehen dirty Pages verloren, die noch nicht übernommen wurden |
| Wahl des Speicherverfahrens | Festschreiben am Einschnitt mit FlushFileBuffers, Verzögerung je Schreibvorgang entfernen mit WRITE_THROUGH. NO_BUFFERING hat Ausrichtungsanforderungen; bei häufiger Dauerhaftigkeit die Kombination mit WRITE_THROUGH erwägen |
| Kohärenz und Dauerhaftigkeit | Gemappte Ansicht und gewöhnliches Cache-I/O teilen Daten. NO_BUFFERING liegt außerhalb; für die Dauerhaftigkeit einer Zuordnung nach FlushViewOfFile noch FlushFileBuffers |
| I/O-Pfad | Fast I/O ist der Pfad, der bei synchronem I/O auf eine gecachte Datei das IRP weglässt. Ein Cache-Treffer wird jedoch nicht immer zu Fast I/O |
Auf Write-Back, Read-Ahead und Lazy Writer aufbauend legt man zuerst fest, „dürfen diese Daten bei einem Stromausfall verloren gehen?“. Dann wählt man das Speicherverfahren mit Blick auf Flush-Kosten, stets gecachte Metadaten und den Cache im Gerät.123
Kohärenz der gemappten Ansicht und Abschluss des Schreibens sowie Cache-Treffer und Weglassen des IRP sind jeweils eigene Entscheidungen. Diese Unterscheidung ist der Einstieg, wenn Speicherfehler oder unnatürlich schnelle Benchmarks zu untersuchen sind.456
Weiter geht es in Teil 5, „NTFS-Interna — Das Dateisystem anhand des MFT verstehen“. Bis hier haben wir eine Datei als Versatz und Bytefolge behandelt; dahinter, wie NTFS die Daten legt — MFT, mehrere Datenströme, Journale, Hardlinks — steigt die Reihe in die statische Struktur auf dem Datenträger hinab.
Verwandte Artikel
- Die Tiefen von Windows I/O (Teil 1) — Jedes Lesen und Schreiben wird zu einem 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-Threadpool: Der Keller unter async/await
- Grundlagen des wechselseitigen Ausschlusses bei der Dateiintegration - Best Practices für Dateisperren und atomare Claims
- Fallstricke bei Shared Memory und Best Practices für die Praxis
- SQLite aus C# in Business-Apps nutzen — WAL-Modus, exklusive Sperren, Schutz vor Datenbankbeschädigung und wann sich EF Core lohnt
- Wie man die Geschwindigkeit verschiedener Programmversionen unter Windows korrekt vergleicht
- USB-Geräte aus einer Windows-App ansprechen — Die Wahl zwischen virtual COM, HID, WinUSB und Hersteller-SDKs
Verwandte Beratungsleistungen
Die KomuraSoft LLC übernimmt Entwurf und Fehleruntersuchung der Datei-I/O in Windows-Geschäftsanwendungen, etwa „gespeicherte Daten waren weg“ oder „das Dateischreiben ist langsam bzw. verdächtig schnell“.
- Windows-App-Entwicklung
- Fehleruntersuchung und Ursachenanalyse
- Nutzung und Migration bestehender Assets
- Kontakt
Quellen
-
Microsoft Learn, File Caching. Dazu, dass Windows Dateidaten standardmäßig cached, Lesen aus dem Systemdateicache erfolgt und Schreiben ebenfalls in den Cache geht (Write-Back-Cache); dass der Cache je Dateiobjekt verwaltet wird und unter Leitung des Cache-Managers läuft; dass die Politik, Schreiben auf den Datenträger zu verzögern und im Cache zu halten, verzögertes Schreiben (lazy writing) heißt; dass beim Lesen einer Datei ein 256-KB-Abschnitt in einen 256-KB-Slot des Systemadressraums gelesen wird und der Benutzerprozess Daten mit diesem Slot kopiert; dass der Cache-Manager einmal pro Sekunde den Lazy Writer startet, ein Achtel der kürzlich nicht geflushten Seiten in die Datenträger-Schreibwarteschlange legt und bei Bedarf nachlegt; dass temporäre Dateien nicht geflusht werden; dass bei einem plötzlichen Systemausfall wie Stromverlust ungeschriebene Cache-Daten verloren gehen; dass sich Metadaten auch bei deaktiviertem Cache mit FILE_FLAG_NO_BUFFERING weiterhin cachen lassen; dass bei FILE_FLAG_WRITE_THROUGH Daten zugleich in den Cache und ohne Verzögerung des Lazy Writers sofort auf den Datenträger geschrieben werden; und dass Dateisystemmetadaten stets gecacht werden, weshalb ihre Dauerhaftigkeit ein Flush oder FILE_FLAG_WRITE_THROUGH braucht. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15
-
Microsoft Learn, FlushFileBuffers function. Dazu, dass WriteFile gewöhnlich in einen internen Puffer schreibt und das Betriebssystem regelmäßig auf den Datenträger schreibt; dass FlushFileBuffers alle gepufferten Informationen der angegebenen Datei auf das Gerät schreibt; dass ein Aufruf bei jedem von vielen Schreibvorgängen ineffizient ist und Anwendungen, die bei häufigen Schreibvorgängen die Dauerhaftigkeit wichtiger Daten brauchen, ungepuffertes I/O mit FILE_FLAG_NO_BUFFERING und FILE_FLAG_WRITE_THROUGH verwenden sollten; und dass sich der Aufruf auf ein Volume-Handle (mit Administratorrechten) anwenden lässt, um alle offenen Dateien auf dem Volume zu flushen. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, File Buffering. Zu den Zugriffsanforderungen an eine mit FILE_FLAG_NO_BUFFERING geöffnete Datei: dass Größe und Dateiversatz von Lesen und Schreiben (einschließlich Angabe über OVERLAPPED) ganzzahlige Vielfache der Sektorgröße des Volumes sein müssen, dass die Adresse des Lese- und Schreibpuffers an der physischen Sektorgröße ausgerichtet sein sollte, und dass Advanced-Format-Geräte mit 4096 Byte physischer Sektorgröße zu berücksichtigen sind. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, File Mapping. Dazu, dass ein Dateizuordnungsobjekt von einer Datei auf dem Datenträger hinterlegt ist und das Auslagern einer Seite als Schreiben der Änderungen in die Datei erfolgt; und dazu, dass Daten kohärent sind (derselbe Inhalt wie die Datei auf dem Datenträger), wenn mehrere Prozesse Ansichten derselben lokalen Datei aus demselben Dateizuordnungsobjekt erzeugen. ↩ ↩2 ↩3
-
Microsoft Learn, FlushViewOfFile function. Dazu, dass FlushViewOfFile das Schreiben der dirty Pages im Bereich einer gemappten Ansicht auf den Datenträger beginnt; dass diese Funktion Dateimetadaten nicht flusht und den Abschluss des physischen Schreibens aus dem Hardware-Datenträgercache nicht abwartet; und dass man nach FlushViewOfFile FlushFileBuffers aufrufen sollte, um dirty Pages und Metadaten vollständig physisch durchzuschreiben. ↩ ↩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 gecachte Dateien ist, der ohne Erzeugen eines IRP den Einstiegspunkt von Dateisystem oder Cache-Manager direkt aufruft; dass Daten direkt vom Cache in den Benutzerpuffer (oder umgekehrt) übertragen werden; und dass der gewöhnliche IRP-Pfad verwendet wird, wenn Fast I/O nicht verarbeiten kann. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Asynchronous disk I/O appears as synchronous on Windows. Dazu, dass eine Anforderung an Ort und Stelle mit TRUE abschließt, wenn die Daten im Cache liegen; und dazu, dass der Cache von Windows als Dateizuordnung umgesetzt ist und es keinen asynchronen Page-Fault-Mechanismus gibt, wenn die Seite fehlt, weshalb cache-fähiges asynchrones Lesen synchron verarbeitet werden kann. ↩
-
Microsoft Learn, FileStream.Flush method (.NET). Dazu, dass Flush() den internen Puffer des Streams an das Betriebssystem schreibt, und dass Flush(true) zusätzlich alle Zwischen-Dateipuffer (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 der Serie erklärt synchrones und asynchrones I/O (Overlapped I/O) unter Windows anhand von Diagrammen. Behandelt werden FILE_FLAG_...
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 bebilderten Reihe über die NTFS-Interna. Behandelt werden MFT und Dateidatensätze, mehrere Datenströme (Zone.Identifier), Ha...
Die Tiefen von Windows I/O (Teil 3) — I/O-Completion-Ports (IOCP) und der .NET-Threadpool: Der Keller unter async/await
Teil 3 einer bebilderten Reihe über I/O-Completion-Ports (IOCP). Behandelt das Design, das Completion-Warteschlange und Threadanzahl-Steu...
Die Tiefen von Windows I/O (Teil 6, Abschluss) — Wie Minifilter funktionieren und langsame I/O mit Procmon untersuchen
Erklärt, wie Minifilter Datei-I/O überwachen und steuern: FltMgr, Altitudes, Pre-/Post-Callbacks, fltmc, langsame Vorgänge in Procmon fin...
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.
- Sind die Daten bereits auf dem Datenträger, sobald WriteFile Erfolg zurückgibt?
- Standardmäßig nicht. Der Windows-Dateicache ist ein Write-Back-Cache, und WriteFile gibt Erfolg zurück, sobald die Daten in den Systemdateicache kopiert wurden. Das eigentliche Schreiben auf den Datenträger ü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 dem Datenträger 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 den Datenträger. Drittens FILE_FLAG_NO_BUFFERING, das den Cache vollständig umgeht. Die Microsoft-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 den Datenträger 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 der Cache von Windows selbst – ein im Speichergerät 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 speicherabgebildeten Datei und ReadFile/WriteFile bei derselben Datei in die Quere?
- Gegenüber gewöhnlichem, cache-fähigem I/O nicht. Der Cache von Windows ist selbst als Dateizuordnung umgesetzt, 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.