Las profundidades de la E/S de Windows (parte 4) — El administrador de caché: ¿cuándo llega su WriteFile al disco?

· Actualizado el: · · Windows, Win32, I/O, Caché, Núcleo, Sistema de archivos, .NET, CSharp

Historial de revisiones (primera versión, publicada el 29 Jul 2026)
Primera publicación
Citar este artículo(DOI (archivo registrado): 10.5281/zenodo.22175310)

Los DOI siguientes remiten a versiones ya archivadas y pueden diferir del texto actual. Para citar el texto actual, utilice la URL de esta página.

Go Komura (2026). Las profundidades de la E/S de Windows (parte 4) — El administrador de caché: ¿cuándo llega su WriteFile al disco?. KomuraSoft LLC. https://comcomponent.com/es/blog/windows-cache-manager-writefile-disk/

DOI (archivo registrado)
10.5281/zenodo.22175310
DOI (última versión registrada)
10.5281/zenodo.22175311

WriteFile ha devuelto éxito. Entonces, ¿dónde están ahora los datos?

En una escritura ordinaria con la caché habilitada, los datos se entregan primero a una caché en la memoria del sistema operativo. El éxito de WriteFile significa «se entregó al sistema operativo», no «se hizo duradero en el disco». El reflejo en el disco lo hace el sistema operativo después.1

Conocer esa diferencia permite explicar fenómenos como «lo guardé y, aun así, un corte de energía lo borró» o «la velocidad medida de la copia de archivos es más alta que la del disco». Que una base de datos escriba de forma explícita con fsync y similares también sirve para separar la aceptación de la escritura de la persistencia.

Este artículo es la parte 4 de la serie «Las profundidades de la E/S de Windows». Toma el administrador de caché que se sitúa entre las aplicaciones y el disco, y organiza el mecanismo de la caché, el momento de la escritura y cómo guardar los datos que no se pueden perder.

De paso, explica «por qué la E/S asíncrona también se completa de forma síncrona si los datos están en la caché» de la parte 2 y «la E/S rápida, el atajo que no crea un IRP» de la parte 1.

1. Primero, la conclusión

  • En la escritura predeterminada, la copia a la caché y el reflejo en el disco son cosas distintas. Windows usa el método write-back y el lazy writer escribe después. Los datos que llegaron a la caché del sistema operativo sobreviven a un bloqueo solo de la aplicación, pero un corte de energía o un bloqueo del sistema operativo pierde las páginas sucias que no se habían reflejado.1
  • Para los datos que no se pueden perder, se elige el método que encaja con el ritmo de las guardas. Si se confirma en un hito, FlushFileBuffers (FileStream.Flush(true) en .NET) es lo básico; si se quiere quitar el retraso de cada escritura, FILE_FLAG_WRITE_THROUGH. FILE_FLAG_NO_BUFFERING es otra herramienta que evita la caché del sistema, tiene requisitos de alineación y sigue exigiendo tener en cuenta la caché interna del dispositivo y los metadatos. Para una persistencia frecuente, la documentación oficial cita la combinación de NO_BUFFERING y WRITE_THROUGH.231
  • La rapidez de las lecturas y las rutas de E/S también se entienden a partir del mecanismo de la caché. La caché es realmente una asignación de archivos, y en las lecturas actúa la lectura anticipada. La coherencia con una vista mapeada y la persistencia se piensan por separado, y la E/S rápida se entiende como un atajo cuando se puede omitir el IRP.456

Según el propósito, se puede empezar por el apartado siguiente.

Lo que quiere saber Apartado que leer
Qué es realmente la caché, por qué baja la memoria libre, la lectura anticipada Capítulos 2 y 3
Tras el éxito de WriteFile, qué se pierde en un fallo Capítulo 4
Cómo elegir entre Flush, WRITE_THROUGH y NO_BUFFERING, y los requisitos de alineación Capítulo 5
La coherencia de la vista mapeada y el vaciado en dos etapas Capítulo 6
La diferencia entre un acierto de caché y la E/S rápida Capítulo 7

En el diagrama, una línea continua marca una relación que siempre se cumple y una línea discontinua una relación condicional (las condiciones están en la explicación de cada relación en la página de detalle). La lista completa de relaciones (32 en total, con evidencia y grado de certeza) y las definiciones de los conceptos principales están reunidas en la página de detalle del mapa de conocimiento (en japonés). Datos: JSON-LD / Turtle

2. Qué es realmente la caché — los archivos se asignan en memoria

2.1. Ranuras de 256 KB y copias en memoria

La caché es realmente una vista que asigna en memoria una parte del archivo. El administrador de caché asigna un tramo de 256 KB del archivo a una «ranura» (slot) del espacio de direcciones del sistema. Las lecturas y escrituras con la caché habilitada se ejecutan como una copia en memoria entre esa ranura y el búfer de la aplicación.1

Espacio de direcciones del sistemaAplicación (modo usuario)ReadFile/WriteFile =una copia en memoria con la ranuraLa lectura en el primer acceso yla reescritura posterior son por páginaCaché de archivos del sistemaRanura que asigna un tramo de 256 KB del archivoBúfer de la aplicación(la región pasada a ReadFile/WriteFile)Archivo en el disco

Figura 1: La imagen real de la E/S con caché habilitada. La «lectura y escritura de archivos» que ve la aplicación es, en muchos casos, solo una copia en memoria

256 KB no es la unidad con la que se lee y se escribe en el disco

256 KB es la granularidad de la vista (de la asignación), no significa que la E/S de disco sea siempre de 256 KB. Las páginas de la ranura se cargan según hace falta, y la cantidad real de E/S cambia con el tamaño de la petición y el patrón de acceso.

Si el tramo se lee por primera vez, se produce E/S de disco para llenar la caché y el IRP visto en la parte 1 avanza hacia la pila de almacenamiento inferior. Si ya está en la caché, la lectura se completa solo con una copia en memoria.

Aunque se emita de forma asíncrona, a veces se procesa en el acto

«Si hay un acierto de caché, incluso una emisión asíncrona se completa de forma síncrona» del capítulo 5 de la parte 2 es el movimiento de completar en el acto una petición a la que se puede responder de inmediato.

A la inversa, aunque la caché esté habilitada, si la página no está en memoria, a veces una lectura asíncrona se procesa de forma síncrona porque el tratamiento del error de página no tiene un mecanismo asíncrono. La finalización inmediata por acierto de caché y el procesamiento síncrono por falta de página se entienden por separado.7

2.2. La verdadera identidad de «ha bajado la memoria libre»

Separar la configuración de cada forma de abrir y los datos que se comparten

Si se usa la caché y el estado de la lectura anticipada se gestionan por forma de abrir (por objeto de archivo).1 En cambio, el contenido de los datos en caché se comparte por archivo (por flujo). Abrir el mismo archivo varias veces no crea cachés distintas. Que cada identificador vea el mismo contenido de caché es la base de la coherencia que se trata en el capítulo 6.

El administrador de caché gestiona la caché de forma continua mientras Windows está en marcha.1 En una copia de un archivo grande o en muchas lecturas y escrituras, la memoria física libre se usa como caché. Gran parte de ella es memoria en espera que se puede reasignar con relativa rapidez si una aplicación pide memoria.

Por tanto, que baje la memoria libre y que falte memoria no son lo mismo. La práctica de observación que parte de esta distinción también se trata en «Distinguir en .NET la espera del GC de una fuga de memoria».

En pantalla, mire los cambios de «en espera» y «libre»

En «Rendimiento > Memoria» del Administrador de tareas, la barra inferior de «composición de la memoria» muestra en uso / modificado / en espera / libre. La mayor parte de la caché de archivos entra en espera y, en la lista de la derecha, se suma como «en caché». En la pestaña «Memoria» del Monitor de recursos se puede comprobar la misma distinción con las cantidades.

Copie un archivo de varios GB y compare el antes y el después. El movimiento «en espera aumenta y libre baja, mientras que “en uso” casi no cambia» confirma que la memoria que estaba libre se ha usado como caché.

3. Lectura anticipada — la especulación de las lecturas

El administrador de caché lee por adelantado, a partir del patrón de acceso pasado, el tramo que parece que se va a leer a continuación. Eso es la lectura anticipada (read-ahead).

Si se lee un archivo en orden desde el principio, y los datos siguientes ya han entrado en la caché antes de emitir la siguiente petición, la lectura se acelera en esa medida. La cantidad de lectura anticipada no es fija: cambia según el patrón detectado y el tamaño de la petición.

Historial de peticiones de lectura de la aplicaciónse está leyendo en orden desde el principioEl administrador de cachédetecta el patrónLectura anticipada: el tramo siguiente selee antes de que se pida(la cantidad varía según el patrón y el tamaño de la petición)Pista FILE_FLAG_SEQUENTIAL_SCAN= lectura anticipada más agresivaPista FILE_FLAG_RANDOM_ACCESS= la lectura anticipada se desperdicia, así que se reduce

Figura 2: Lectura anticipada. Además de detectar el patrón de acceso, se pueden dar pistas con las marcas de CreateFile

FileOptions.SequentialScan / RandomAccess, que aparecían en la tabla de correspondencia de la parte 1, son pistas hacia esta lectura anticipada. La aplicación transmite al sistema operativo el plan de acceso que ya conoce.

Plan de acceso Marca Win32 Especificación .NET Pista hacia la lectura anticipada
Procesamiento por lotes que lee el archivo entero en orden FILE_FLAG_SEQUENTIAL_SCAN FileOptions.SequentialScan Realizar la lectura anticipada de forma agresiva
Acceso irregular, como seguir un índice FILE_FLAG_RANDOM_ACCESS FileOptions.RandomAccess Reducir la lectura anticipada, que tiende a desperdiciarse

Lo importante es elegir la pista que encaja con el contenido del procesamiento; ninguna de las dos fija la cantidad de lectura anticipada.

4. Escritura diferida — el significado del «éxito» de WriteFile

4.1. El lazy writer llega cada segundo

En la escritura predeterminada, WriteFile devuelve éxito en el momento en que copia los datos a la ranura de la caché, y el reflejo en el disco se deja para después. Esta política de caché write-back se llama escritura diferida (lazy writing).1

Quien se encarga del reflejo es el lazy writer que el administrador de caché pone en marcha cada segundo. Encola un octavo de las páginas que no se han vaciado recientemente y, si hay muchos datos que escribir, apila más.1

El «cada segundo» de aquí es el intervalo de arranque del procesamiento. No se lea como una garantía de que cada escritura individual se hace duradera en menos de un segundo.

El atributo de archivo temporal es una pista para reducir la reescritura

Un archivo temporal con el atributo FILE_ATTRIBUTE_TEMPORARY se excluye de los destinos de vaciado del lazy writer, porque se asume que se eliminará de inmediato.1 Sin embargo, el atributo no es más que una pista. Si la memoria se aprieta, puede reescribirse, y no se aplica solo porque el nombre parezca de archivo temporal.

Discolazy writer (arranca cada segundo)Caché del sistemaAplicaciónDiscolazy writer (arranca cada segundo)Caché del sistemaAplicaciónSe copia a la ranura yla página queda sucia (sin escribir)Desde aquí hasta la reescritura es la «ventana peligrosa»un corte de energía o un bloqueo del SO borra estos datosAquí es la primera vez que se hacen duraderosWriteFile(datos)TRUE vuelve enseguidaElige 1/8 de las páginas suciasLas reescribe de golpe

Figura 3: Escritura diferida. El éxito de WriteFile es «se entregó al sistema operativo», no «se hizo duradero»

4.2. Si ocurre algo, ¿hasta dónde desaparece?

Entre el éxito de WriteFile y el final de la reescritura queda un periodo en el que los datos siguen en la caché. Qué ocurre con esos datos se divide según si se detuvo solo la aplicación o se detuvo todo el sistema operativo.

Datos justo después del éxito de WriteFile(páginas sucias en la caché)¿Qué ha ocurrido?El proceso de la aplicaciónse bloquea o se fuerza a terminarSe detiene todo el SO(corte de energía, pantalla azul)Los datos permanecenla caché es del SO, así queel lazy writer reescribe según lo previstoLas páginas sucias se pierdensolo queda lo que ya había llegado al disco

Figura 4: El tipo de fallo y la línea que separa lo que sobrevive. La caché no es «propiedad del proceso», sino «propiedad del sistema operativo»

Fallo Qué ocurre con los datos que llegaron a la caché del SO
Bloqueo o terminación forzada de la aplicación La caché es del SO, así que, si el SO sigue en marcha, se reescribe después
Corte de energía o bloqueo del SO Se pierden las páginas sucias que aún no se habían reescrito y solo queda lo que ya había llegado al disco

«La aplicación se cayó justo después de guardar y, aun así, el archivo estaba bien» ocurre porque, una vez copiados a la caché, el sistema operativo retiene los datos. En cambio, en una pérdida súbita de energía, la documentación oficial deja claro que se pierden los datos de caché no reflejados. La frecuencia de vaciado se ajusta como un equilibrio entre rendimiento y fiabilidad.1

Lo que hay que decidir primero en el diseño es «¿este dato puede perderse en el instante de un corte de energía?». Unos pocos segundos de registro reciente pueden ser aceptables; el registro confirmado de un pedido, no. Para las escrituras que no se pueden perder se usan los métodos del capítulo siguiente.

5. La caja de herramientas para «haber escrito de verdad»

En este capítulo se distinguen tres métodos: vaciar ahora el búfer que hay, quitar el retraso de la escritura y evitar la caché del sistema. El último apartado, 5.4, resume el orden de elección a partir del requisito de fiabilidad y del coste que se puede pagar.

5.1. FlushFileBuffers — escribirlo todo ahora

FlushFileBuffers escribe en el dispositivo los datos en búfer del archivo indicado. Los metadatos del sistema de archivos se almacenan siempre en caché, así que para hacerlos llegar hace falta un vaciado o WRITE_THROUGH.12

En .NET se distingue Flush() de Flush(true)

Llamada Alcance que se escribe
FileStream.Flush() Entrega el búfer interno de .NET al SO. No pide vaciar la caché del SO
FileStream.Flush(true) Además del interno de .NET, también vacía los búferes de archivo intermedios, como los del SO

En Windows, la especificación equivalente a FlushFileBuffers es FileStream.Flush(true). Tenga presente la diferencia respecto a llamar simplemente a Flush().8

También hay que pensar el coste de vaciar cada vez

La documentación oficial señala que llamar a FlushFileBuffers en cada una de muchas escrituras resulta ineficiente. Cuando hace falta persistencia en cada escritura frecuente, se cita la combinación de NO_BUFFERING y WRITE_THROUGH que se verá más adelante.2

5.2. FILE_FLAG_WRITE_THROUGH — quitar solo el retraso

Si se abre con FILE_FLAG_WRITE_THROUGH, la escritura va también a la caché y, al mismo tiempo, se refleja en el disco sin esperar al lazy writer.1

No se saca la caché del sistema en sí, así que las lecturas siguen usando la caché. Es el método para «dejar la caché de lectura y quitar solo el retraso de la escritura».

5.3. FILE_FLAG_NO_BUFFERING — no pasar por la caché

FILE_FLAG_NO_BUFFERING es la especificación que saca la caché del sistema de Windows de las lecturas y escrituras. Tanto la lectura como la escritura se convierten en E/S al dispositivo de disco, sin pasar por la caché.1

Sin embargo, la caché de escritura interna del dispositivo es otro escalón. NO_BUFFERING no llega a evitarla, así que si hace falta resistencia a un corte de energía se sigue considerando la combinación con WRITE_THROUGH o FlushFileBuffers.

Encaja en la transferencia masiva de muchos datos y en los motores de bases de datos que gestionan su propio búfer, pero a cambio la aplicación tiene que cumplir los siguientes requisitos de alineación.3

  • El tamaño y el desplazamiento de archivo de la lectura y la escritura son un múltiplo entero del tamaño de sector del volumen (si el sector es de 512 bytes, 512, 1024, 1536…).
  • La dirección del búfer también está alineada al tamaño de sector físico (hace falta tener en cuenta los discos Advanced Format de 4096 bytes de sector físico).
  • Aun así, los metadatos siguen en caché, así que para una persistencia completa hace falta combinar WRITE_THROUGH o usar FlushFileBuffers.12

Alinear los tres puntos: tamaño, desplazamiento y dirección

No basta con añadir la marca para pasar a NO_BUFFERING. Una lectura o escritura que no cumple los requisitos de alineación falla con ERROR_INVALID_PARAMETER (87). Hay que comprobar estos tres puntos.3

Qué alinear Condición Cómo cumplirla
Tamaño de la lectura o escritura Múltiplo entero del tamaño de sector del volumen Obtener lpBytesPerSector de GetDiskFreeSpace y redondear a ese múltiplo
Desplazamiento de archivo Lo mismo (también si se especifica con Offset de OVERLAPPED) Avanzar de múltiplo en múltiplo del tamaño de sector
Dirección del búfer Alineada al tamaño de sector físico Reservar con VirtualAlloc (devuelve una región alineada al límite de página, normalmente 4096 bytes)

Un ejemplo mínimo de C++ para comprobar la alineación del búfer

Lo más fácil de pasar por alto es el tercero, la dirección del búfer. La dirección que devuelven malloc, new o una matriz de C# no tiene garantía de alinearse a un límite de sector.

El ejemplo siguiente reserva con VirtualAlloc una región alineada al límite de página. Un límite de página habitual de 4096 bytes también cumple el requisito de un disco Advanced Format de 4096 bytes de sector físico. El tamaño y el desplazamiento avanzan en múltiplos del tamaño de sector obtenido.

// C++ / Win32. El tratamiento de errores está reducido al mínimo
DWORD sectorsPerCluster = 0, bytesPerSector = 0, freeClusters = 0, totalClusters = 0;
if (!GetDiskFreeSpaceW(L"C:\\", &sectorsPerCluster, &bytesPerSector,
                       &freeClusters, &totalClusters))
{
    return GetLastError();
}

// Hacer que la unidad de lectura y escritura sea un múltiplo entero del tamaño de sector (aquí, el equivalente a 1 MiB)
const DWORD chunk = (1024 * 1024 / bytesPerSector) * bytesPerSector;

// El búfer se toma de una región alineada al límite de página (malloc/new no lo garantizan)
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 devuelve el resultado de «la llamada Win32 inmediatamente anterior».
    // Si se llama primero a VirtualFree, el motivo del fallo de CreateFileW
    // (acceso denegado, la ruta no existe, etc.) se sobrescribe con el
    // resultado de la limpieza y solo vuelve un código del que no se entiende la causa
    const DWORD err = GetLastError();
    VirtualFree(buffer, 0, MEM_RELEASE);
    return err;
}

// Como se avanza siempre en unidades de chunk bytes, el tamaño y el desplazamiento siguen alineados
DWORD read = 0;
while (ReadFile(h, buffer, chunk, &read, nullptr) && read > 0)
{
    // Procesar los primeros read bytes de buffer
    // (al final del archivo, read < chunk. Eso es normal)
}

CloseHandle(h);
VirtualFree(buffer, 0, MEM_RELEASE);

En .NET tampoco se puede omitir la gestión de la alineación

FileOptions de .NET no tiene un valor que corresponda a FILE_FLAG_NO_BUFFERING. Si hace falta, se llama a CreateFile directamente, pero también entonces hay que cumplir los requisitos de alineación uno mismo.

El orden de consideración no es «NO_BUFFERING porque quiero ir más rápido», sino «NO_BUFFERING porque gestiono el búfer por mi cuenta».

5.4. Cómo elegir

WriteFile predeterminado: el éxito vuelve hasta aquílazy writer (cada segundo) / WRITE_THROUGH (inmediato)el momento del dispositivo /FlushFileBuffers pide escribir hasta el finalNO_BUFFERING salta la caché y va directoBúfer de la aplicaciónCaché de archivos del sistema(páginas sucias)Caché interna del dispositivo de discoMedio de registro no volátil

Figura 5: Las capas de los datos y hasta dónde empuja cada herramienta. Atención también al último escalón, la «caché interna del dispositivo de disco»

Método Qué ocurre Dónde encaja
Predeterminado (caché habilitada) Termina con la copia a la caché. El reflejo lo hace el lazy writer La mayor parte de la E/S de archivos
FlushFileBuffers / Flush(true) Escribe hasta el final los datos de ese momento más los metadatos Confirmación en un hito (el commit de una transacción, etc.)
FILE_FLAG_WRITE_THROUGH En cada escritura, de inmediato al disco (las lecturas usan la caché) Un registro o un diario de escrituras que no se pueden perder
FILE_FLAG_NO_BUFFERING (+WRITE_THROUGH) Sin pasar por la caché. Hay requisitos de alineación Gestión propia del búfer y E/S masiva de golpe

Primero se decide la fiabilidad; después se comprueba el coste

Primero se decide «hasta cuántos elementos se pueden perder en un corte de energía». A partir de ahí se separa si lo que no se puede perder es un «hito», como la confirmación de una transacción, o «cada escritura».

Después se comprueba cuánto más lento se puede ir por ello, y si se puede responder a la gestión propia del búfer y a la alineación. No se elige por el nombre del método, sino siguiendo esta bifurcación.

se puede(unos segundos de registro reciente, etc.)no se puedehito(confirmación de una transacción, etc.)cada unano (una aplicación habitual)sí (motor de BD, etc.)Se va a escribir este datoEn el instante de un corte de energíao de una pantalla azul, ¿se puede perder?Dejarlo predeterminado (caché habilitada)es lo más rápido. La mayor parte de la E/S está aquí¿Lo que no se puede perder esun «hito» o «cada una»?En el hito, FlushFileBuffersen .NET, Flush(true)coste: solo la espera del hito¿Se gestiona el búfer por cuenta propiay se cumplen los requisitos de alineación de 5.3?FILE_FLAG_WRITE_THROUGHen cada escritura, de inmediato al discolas lecturas siguen rápidas con la cachéFILE_FLAG_NO_BUFFERING+ FILE_FLAG_WRITE_THROUGHla forma de «persistencia frecuente» que cita la documentación oficial

Figura 6: Cómo elegir la herramienta. La primera bifurcación es el requisito de fiabilidad; la segunda, el coste que se puede pagar. Que no haya un camino «FlushFileBuffers en cada una» se debe, como se vio en 5.1, a que la documentación oficial lo considera ineficiente

Llevarlo al diseño de cómo se guarda y a la medición de rendimiento

En la práctica, resulta más fácil elegir si se relaciona con estos tres patrones.

  1. «Escribir en un archivo temporal → vaciar → cambiar el nombre» es la receta para no dejar un archivo a medio romper. Escribir el contenido hasta el final y confirmarlo con el nombre: esta entrega atómica se trató con detalle en «Conocimientos básicos de exclusión mutua en la integración de archivos».
  2. Dejarlo en manos de la base de datos también es un diseño decente. Cómo SQLite construye la durabilidad con WAL y vaciado se puede consultar en «Usar SQLite desde C# en una aplicación de negocio». La opción de «no escribir uno mismo la estrategia de vaciado» está siempre ahí.
  3. En un banco de pruebas, sospeche de la caché. Una medición de «la lectura es demasiado rápida» suele estar midiendo el acierto de caché de la segunda vez en adelante. La forma de medir está reunida en «Cómo comparar correctamente la velocidad de distintas versiones de un programa en Windows».

Pensarlo incluyendo la caché interna del dispositivo

Al final de la figura 5 queda la caché interna del dispositivo de disco. FlushFileBuffers pide escribir hasta el final, incluida esa.

En una memoria USB o un disco externo también intervienen las directivas de caché de escritura del lado del dispositivo, «extracción rápida» y «alto rendimiento». El trato de los dispositivos extraíbles también se puede consultar en «Cómo tratar un dispositivo USB desde una aplicación de Windows».

6. Coherencia con los archivos asignados en memoria

6.1. La vista mapeada y la caché ven los mismos datos

Si la caché es realmente una asignación de archivos, ¿qué ocurre con el contenido de una vista que uno mismo ha hecho con MapViewOfFile y el de la caché que usan ReadFile / WriteFile?

La E/S ordinaria con caché habilitada y la vista mapeada comparten los datos respaldados por el mismo archivo. Cuando se expulsa una página del objeto de asignación de archivos, el cambio se reescribe en el archivo. También cuando varios procesos crean una vista del mismo archivo local a partir del mismo objeto de asignación de archivos, el contenido que se ve es coherente.4

Espacio de direcciones del sistemaEspacio de direcciones del proceso AVista del administrador de caché(la ranura que usan ReadFile/WriteFile)Vista de MapViewOfFileEl mismo conjunto de páginas físicas(memoria respaldada por el archivo)Archivo en el discoLa E/S de FILE_FLAG_NO_BUFFERING quedafuera de este marco compartido (directo al disco)

Figura 7: Tanto la vista mapeada como la caché ven las mismas «páginas respaldadas por el archivo». Quien queda fuera del marco es solo NO_BUFFERING

6.2. La coherencia y la persistencia se comprueban por separado

La E/S de NO_BUFFERING queda fuera de esta coherencia. Una lectura o escritura que no pasa por la caché no se coteja con el contenido de la vista mapeada ni con el de la caché. Si se mezclan, la aplicación tiene que tomar la coherencia por su cuenta.

Además, que el cambio se vea en la vista mapeada y que ese cambio se haya hecho duradero en el disco son cosas distintas. La persistencia de una vista mapeada se hace en este orden.5

Orden Llamada Papel y precauciones
1 FlushViewOfFile Inicia la escritura de las páginas sucias del intervalo. No escribe metadatos y tampoco espera a que termine la escritura física desde la caché interna del dispositivo
2 FlushFileBuffers Pide escribir hasta el final, incluidos los metadatos del archivo y la caché interna del dispositivo

La práctica de la asignación de archivos como memoria compartida (compartición con nombre, sincronización, patrones de accidente) se trata en «Trampas de la memoria compartida y prácticas recomendadas».

7. E/S rápida — se recoge el deber de la parte 1

La E/S rápida es un atajo que, en lecturas y escrituras síncronas a un archivo en caché, procesa sin crear un IRP. IRP es «paquete de solicitud de E/S»: la estructura en la que el núcleo mete la petición que entrega al controlador.6

7.1. Cuando se puede omitir el IRP y cuando se vuelve a la ruta habitual

La explicación de «no toda la E/S se convierte en un IRP» del apartado 5.2 de la parte 1 apunta a esta ruta.

Si una lectura o escritura se puede procesar solo con una copia en memoria con la caché, no hace falta armar un IRP y enviarlo por la pila de dispositivos. En la E/S rápida se llama directamente al punto de entrada del sistema de archivos y se intercambian datos con el administrador de caché.6

Sin embargo, si la E/S rápida no se puede usar —porque no está en caché, interviene un bloqueo, se mete un filtro, etc.—, se vuelve a la ruta habitual de IRP.

También hay que tener en cuenta que «un acierto de caché no es siempre E/S rápida». Una operación sobre un identificador asíncrono (FILE_FLAG_OVERLAPPED) a veces se procesa por la ruta de IRP aunque se complete en el acto desde la caché. «Si se completa en el acto» del capítulo 5 de la parte 2 y «si se omite el IRP» de este capítulo son asuntos distintos.

se puedeno se puedeLectura o escritura síncrona a un identificador con caché habilitada¿Se puede procesar con E/S rápida(está en caché, etc.)?E/S rápidacopia directa con la caché, sin crear un IRPen Procmon se muestra como FASTIO_Ruta habitualse arma un IRP y se envía a la pila de dispositivos(el mundo de la figura 6 de la parte 1)

Figura 8: La bifurcación de la E/S rápida. Por eso en Procmon se ven mezclados FASTIO_READ e IRP_MJ_READ

7.2. En la observación, se distinguen las rutas de una misma lectura

Que en la observación con Procmon del capítulo 7 de la parte 1 se mezclaran líneas FASTIO_ se debe a que, incluso en la misma lectura, la ruta que se recorre es distinta. FASTIO_READ e IRP_MJ_READ se leen como la diferencia entre la E/S rápida y la ruta habitual de IRP.

Este atajo también atañe a los controladores de filtro que se tratan en la parte 6. Un minifiltro puede interponerse no solo en la ruta habitual de IRP, sino también en la E/S rápida.

8. Resumen

Se recorre el artículo entero en el orden mecanismo, fallo y juicio de diseño.

Aspecto Qué retener
Qué es realmente la caché Una vista que asigna un tramo de 256 KB del archivo. Las lecturas y escrituras con caché habilitada son una copia en memoria con la ranura; 256 KB no es un tamaño fijo de E/S de disco
Lectura Se lee por adelantado el tramo siguiente. SequentialScan / RandomAccess son pistas que transmiten el patrón de acceso
Escritura y fallos Por defecto, el lazy writer escribe después. Los datos que llegaron a la caché del SO sobreviven a un bloqueo solo de la aplicación, pero un corte de energía o un bloqueo del SO pierde las páginas sucias no reflejadas
Elección del método de persistencia La confirmación en un hito es FlushFileBuffers; para quitar el retraso de cada escritura, WRITE_THROUGH. NO_BUFFERING tiene requisitos de alineación y, en una persistencia frecuente, se considera la combinación con WRITE_THROUGH
Coherencia y persistencia La vista mapeada y la E/S de caché habitual comparten los datos. NO_BUFFERING queda fuera del marco y, para persistir un mapa, se usa FlushFileBuffers después de FlushViewOfFile
Ruta de E/S La E/S rápida es la ruta que omite el IRP en la E/S síncrona a un archivo en caché. Sin embargo, un acierto de caché no se convierte siempre en E/S rápida

Con write-back, lectura anticipada y lazy writer en mente, decida primero «¿este dato puede perderse en un corte de energía?». A partir de ahí, elija el método de persistencia pensando también el coste del vaciado, los metadatos que siempre van a caché y la caché interna del dispositivo.123

La coherencia de la vista mapeada y el fin de la escritura, el acierto de caché y la omisión del IRP, son también juicios distintos. Esa distinción es la pista para investigar un fallo al guardar o un banco de pruebas extrañamente rápido.456

Sigue la parte 5, «Estructura interna de NTFS — comprender el sistema de archivos a partir de la MFT». Hasta ahora el archivo se ha tratado como «un desplazamiento y una secuencia de bytes»; a continuación se baja a la estructura estática sobre el disco: cómo NTFS coloca los datos por detrás —la MFT, los flujos de datos múltiples, los diarios, los enlaces físicos—.

Artículos relacionados

Ámbitos de consulta relacionados

En KomuraSoft LLC nos ocupamos del diseño y de la investigación de fallos de la E/S de archivos de aplicaciones de negocio para Windows, como «desaparecieron datos que se suponía guardados» o «la escritura de archivos es lenta / demasiado rápida y resulta sospechosa».

Referencias

  1. Microsoft Learn, File Caching. Sobre que Windows almacena en caché los datos de archivo de forma predeterminada, que las lecturas se hacen desde la caché de archivos del sistema y que las escrituras también van a la caché, en una caché write-back; que la caché se gestiona por objeto de archivo y funciona bajo la dirección del administrador de caché; que la política de retrasar la escritura al disco y retenerla en la caché se llama escritura diferida (lazy writing); que, al leer un archivo, un tramo de 256 KB se carga en una ranura de 256 KB del espacio de direcciones del sistema y el proceso de usuario copia datos con esa ranura; que el administrador de caché pone en marcha el lazy writer cada segundo, encola un octavo de las páginas que no se han vaciado recientemente y, si hace falta, apila más; que un archivo temporal no se vacía; que, si ocurre un fallo súbito del sistema como una pérdida de energía, se pierden los datos de caché no escritos; que, aunque se desactive la caché con FILE_FLAG_NO_BUFFERING, los metadatos del archivo pueden seguir en caché; que, con FILE_FLAG_WRITE_THROUGH, los datos se escriben también en la caché y, al mismo tiempo, se escriben de inmediato en el disco sin el retraso del lazy writer; y que los metadatos del sistema de archivos se almacenan siempre en caché, así que para persistirlos hace falta un vaciado o FILE_FLAG_WRITE_THROUGH. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15

  2. Microsoft Learn, FlushFileBuffers function. Sobre que WriteFile escribe normalmente en un búfer interno y el sistema operativo escribe periódicamente en el disco; que FlushFileBuffers escribe en el dispositivo toda la información en búfer del archivo indicado; que llamarlo en cada una de muchas escrituras es ineficiente y que una aplicación que necesita persistir datos importantes con escrituras frecuentes debe usar E/S sin búfer con FILE_FLAG_NO_BUFFERING y FILE_FLAG_WRITE_THROUGH; y que, si se llama sobre un identificador de volumen (con privilegios de administrador), se pueden vaciar todos los archivos abiertos del volumen. ↩ ↩2 ↩3 ↩4 ↩5

  3. Microsoft Learn, File Buffering. Sobre los requisitos de acceso a un archivo abierto con FILE_FLAG_NO_BUFFERING: que el tamaño de la lectura o escritura y el desplazamiento de archivo (incluido el caso en que se especifica con OVERLAPPED) deben ser un múltiplo entero del tamaño de sector del volumen; que la dirección del búfer de lectura o escritura debe estar alineada al tamaño de sector físico; y que hay que tener en cuenta los dispositivos Advanced Format de 4.096 bytes de sector físico. ↩ ↩2 ↩3 ↩4

  4. Microsoft Learn, File Mapping. Sobre que un objeto de asignación de archivos está respaldado por un archivo en el disco y que la expulsión de una página se realiza como escritura del contenido modificado en el archivo; y que, cuando varios procesos crean una vista de un archivo local a partir del mismo objeto de asignación de archivos, los datos son coherentes (el mismo contenido que el archivo en el disco). ↩ ↩2 ↩3

  5. Microsoft Learn, FlushViewOfFile function. Sobre que FlushViewOfFile inicia la escritura en disco de las páginas sucias del intervalo de la vista mapeada; que esta función no vacía los metadatos del archivo y tampoco espera a que termine la escritura física desde la caché de disco de hardware; y que, para escribir físicamente hasta el final todas las páginas sucias y los metadatos, hay que llamar a FlushFileBuffers después de FlushViewOfFile. ↩ ↩2 ↩3

  6. Microsoft Learn, IRPs Are Different From Fast I/O. Sobre que la E/S rápida es una ruta rápida de E/S síncrona para archivos en caché que llama directamente a los puntos de entrada del sistema de archivos o del administrador de caché sin generar un IRP; que los datos se transfieren directamente de la caché al búfer de usuario (o al revés); y que, si no se puede procesar con E/S rápida, se usa la ruta habitual basada en IRP. ↩ ↩2 ↩3 ↩4

  7. Microsoft Learn, Asynchronous disk I/O appears as synchronous on Windows. Sobre que, si los datos están en la caché, la petición se completa en el acto y se devuelve TRUE; y que, como la caché de Windows está implementada como una asignación de archivos y no hay un mecanismo de error de página asíncrono cuando la página no está, una lectura asíncrona con caché habilitada a veces se procesa de forma síncrona. ↩

  8. Microsoft Learn, FileStream.Flush method (.NET). Sobre que Flush() escribe el búfer interno de la secuencia en el sistema operativo y que, si se especifica Flush(true), además se vacían todos los búferes de archivo intermedios (los búferes del sistema operativo). ↩

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿En el momento en que WriteFile devuelve éxito, los datos ya están escritos en el disco?
No, por defecto no lo están. La caché de archivos de Windows funciona con el método write-back (escritura diferida): WriteFile devuelve éxito en el momento en que copia los datos a la caché de archivos del sistema. La escritura en el disco la realiza después el escritor diferido (lazy writer), que el administrador de caché pone en marcha cada segundo. Lo importante es la diferencia según el tipo de fallo. Aunque el proceso de la aplicación se bloquee, los datos que ya están en la caché no se pierden, porque se escribirán más tarde mientras el sistema operativo siga en marcha. En cambio, en un fallo que detiene todo el sistema operativo (corte de energía, pantalla azul), se pierde la caché sucia (dirty) que todavía no se había escrito. Es más exacto entender que «WriteFile tuvo éxito» significa «se entregó al sistema operativo», y no «se persistió».
¿Cómo puedo garantizar que los datos se escriban en el disco?
Hay tres herramientas. La primera es FlushFileBuffers, que escribe hasta el final en el dispositivo tanto los datos en búfer de ese archivo como sus metadatos (en .NET esto equivale a FileStream.Flush(true)). La segunda es FILE_FLAG_WRITE_THROUGH, que en cada escritura escribe a la caché y, al mismo tiempo, de inmediato también al disco. La tercera es FILE_FLAG_NO_BUFFERING, que no pasa por la caché en absoluto. La documentación de Microsoft indica que llamar a FlushFileBuffers en cada escritura es ineficiente, y que si se necesita una persistencia garantizada con escrituras frecuentes conviene combinar FILE_FLAG_NO_BUFFERING con FILE_FLAG_WRITE_THROUGH. Todas ellas renuncian a los beneficios de la caché y, por tanto, son más lentas, así que el criterio práctico no es «ponerlas en todo», sino reservarlas para las escrituras de datos que no se pueden permitir perder.
¿En qué se diferencian FILE_FLAG_WRITE_THROUGH y FILE_FLAG_NO_BUFFERING?
WRITE_THROUGH significa «escribir en la caché, pero también escribir en el disco antes de completar la operación». Las lecturas siguen beneficiándose de la caché; lo único que se elimina es el retraso de la escritura diferida. NO_BUFFERING significa que «la lectura y la escritura no pasan por la caché del sistema»: tanto la lectura como la escritura se convierten cada vez en E/S al dispositivo de disco (aunque lo que se evita es solo la caché de Windows, no se salta la caché de escritura interna del propio dispositivo). A cambio, impone restricciones estrictas. El tamaño de la operación y el desplazamiento (offset) dentro del archivo deben ser múltiplos exactos del tamaño de sector del volumen, y la dirección del búfer también debe estar alineada a un límite de sector físico. Además, incluso con NO_BUFFERING, los metadatos del sistema de archivos siguen en caché, así que para escribirlos con garantías también hace falta FlushFileBuffers o combinarlo con WRITE_THROUGH. Es típico de software que gestiona su propio búfer, como los motores de bases de datos; en una aplicación normal, lo razonable es empezar considerando WRITE_THROUGH o FlushFileBuffers.
¿La poca memoria libre que muestra el Administrador de tareas se debe a la caché de archivos?
En muchos casos sí, y además es un comportamiento normal. Windows utiliza de forma activa la memoria física libre como caché de archivos, así que si copia archivos grandes o realiza muchas lecturas y escrituras, la caché crece en consecuencia y el uso de memoria parece aumentar. Sin embargo, la mayoría de las páginas que ocupa la caché son del tipo que se reasigna con relativa rapidez en cuanto una aplicación solicita memoria, y conviene distinguir esto de una situación real de «memoria agotada e insuficiente». Cuando se sospeche de una falta de memoria, en la práctica es más útil fijarse en indicadores como la memoria confirmada (committed) o la frecuencia de fallos de página duros (hard faults), y no solo en la capacidad libre aparente.
Si toco el mismo archivo con un archivo mapeado en memoria y con ReadFile/WriteFile, ¿pueden desincronizarse los contenidos?
No, no se desincronizan frente a la E/S normal con la caché habilitada. La propia caché de Windows está implementada como una asignación de archivos (file mapping), y la vista mapeada y la caché de un mismo archivo local comparten los mismos datos, de modo que un cambio hecho por uno es visible desde el otro. Cuando varios procesos crean vistas a partir del mismo objeto de asignación de archivos, los datos también son coherentes entre ellos. Ahora bien, la lectura y escritura de un identificador (handle) abierto con FILE_FLAG_NO_BUFFERING no pasa por la caché, por lo que queda fuera de esta coherencia. Además, para escribir con garantías en disco los cambios de una vista mapeada, no basta con FlushViewOfFile, ya que no escribe los metadatos ni espera a la caché de hardware: hay que llamar a FlushFileBuffers después de FlushViewOfFile.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog