¿El búfer se vaciará automáticamente al disco cuando salga un proceso?

21

Cuando redirijo la salida de un comando a un archivo (por ejemplo, echo Hello > file) ¿se garantizará que ese archivo tenga dichos datos justo después de que el comando salga? ¿O todavía hay una ventana muy pequeña entre las salidas de comando y los datos escritos en el archivo? Me gustaría leer el archivo justo después de que salga el comando, pero no quiero leer un archivo vacío.

Eric
fuente
1
Es probable que ejecuta la orden de inmediato, pero la cantidad de tiempo que se necesita para abrir realmente el archivo, escritura, y cerca dependerá de la velocidad y el tipo de su disco duro, todos los programas en ejecución, etc.
freginold
En términos del ejemplo dado, ¿cuál es 'el proceso'? ¿Son echoy >no procesos separados (de corta duración)? ¿Y dónde se ejecuta la salida de echopermanecer antes >?
oɔɯǝɹ
1
@ oɔɯǝɹ >es redirección de shell. Es lo mismo que si el programa hubiera abierto el archivo nombrado para escribir y reemplazado stdout con él, que es exactamente lo que hace el shell.
Dan D.
77
Creo que es responsabilidad del sistema operativo proporcionarle el filecontenido Helloindependientemente de si está enjuagado o no.
Salman A
1
Si el programa se ejecuta en la máquina A y está leyendo el archivo en la máquina B, con el sistema de archivos de la máquina A montado en la red, puede terminar leyendo un archivo vacío, dependiendo del tipo de sistema de archivos de la red y la configuración de montaje. Por lo tanto, puede deshabilitar el almacenamiento en caché para ese montaje.
pts

Respuestas:

21

Hay múltiples capas de memorias intermedias / cachés involucradas.

  1. El caché de la CPU.

    Los datos se juntan byte a byte y se almacenan en el caché de la CPU. Si el caché de la CPU está lleno y no se ha accedido a los datos durante un tiempo, el bloque que contiene nuestros datos puede escribirse en la memoria principal. Estos están, en su mayor parte, ocultos para los programadores de aplicaciones.

  2. Los amortiguadores en proceso.

    Hay un poco de memoria reservada en el proceso donde se recopilan los datos, por lo que debemos realizar la menor cantidad posible de solicitudes al sistema operativo, ya que es relativamente costoso. El proceso copia los datos en estos búferes, que nuevamente pueden estar respaldados por cachés de la CPU, por lo que no hay garantía de que los datos se copien en la memoria principal. La aplicación necesita vaciar explícitamente estos búferes, por ejemplo, usando fclose (3) o fsync (3). La función exit (3) también hace esto antes de que finalice el proceso, mientras que la función _exit (2) no lo hace , por lo que hay una gran advertencia en la página del manual para que esa función lo llame solo si sabe lo que es haciendo.

  3. El kernel amortigua

    Luego, el sistema operativo mantiene su propia caché, para minimizar la cantidad de solicitudes que necesita enviar a los discos. Este caché no pertenece a ningún proceso en particular, por lo que los datos allí pueden pertenecer a procesos que ya han finalizado, y dado que todos los accesos pasan por aquí, el siguiente programa verá los datos si ha llegado aquí. El núcleo escribirá estos datos en los discos cuando tenga tiempo de hacerlo o cuando se le solicite explícitamente.

  4. El caché de la unidad

    Las unidades de disco también mantienen un caché para acelerar los accesos. Estos se escriben con bastante rapidez, y hay un comando para escribir los datos restantes en los cachés e informar cuando se completa, que el sistema operativo utiliza en el apagado para asegurarse de que no quedan datos sin escribir antes de apagarse.

Para su aplicación, es suficiente que los datos se registren en las memorias intermedias del kernel (los datos reales todavía pueden vivir en cachés de CPU en este momento, y podrían no haberse escrito en la memoria principal): el proceso "echo" finaliza, lo que significa que cualquier búfer en proceso debe haberse vaciado y los datos entregados al sistema operativo, y cuando inicia un nuevo proceso, se garantiza que el sistema operativo devolverá los mismos datos cuando se le solicite.

Simon Richter
fuente
77
Teniendo en cuenta el almacenamiento en caché de la CPU no me parece relevante. Ese es un nivel de detalle innecesario aquí. Como pasaría por todos los detalles hasta que se cambie alguna cantidad física que represente un bit en un disco duro o memoria ssd para voltearlo.
mvw
3
De hecho, el caché de la CPU es bastante ortogonal.
Simon Richter
2
Y lo que es más importante, el caché de la CPU es coherente entre los núcleos, por lo que está totalmente fuera de la imagen. En x86, es incluso coherente con DMA (y x86 tiene un modo de orden de memoria de orden de almacenamiento total), por lo que cualquier cosa que pueda leer la memoria verá los datos almacenados más recientemente en esa dirección en el orden global de las operaciones de memoria. (Un núcleo de CPU verá sus propias tiendas incluso antes de que sean visibles globalmente, debido al reenvío de tiendas desde la cola de la tienda). En plataformas que no son x86 sin DMA coherente con el caché, el kernel de Linux se asegura de que el caché se vacíe antes que DMA a esas direcciones.
Peter Cordes
1
"En su mayor parte, están ocultos para los programadores de aplicaciones". ¿Por qué el "en su mayor parte"? Soy un desarrollador incrustado y excepto durante el cargador de arranque (por lo tanto, no "aplicación") ignoro por completo el caché de la CPU. No creo que ningún desarrollador de aplicaciones pueda verse afectado por los efectos del caché de la CPU.
Sam
1
Los fallos / aciertos de la caché @Sam junto con la ejecución especulativa pueden explotarse en algunas CPU para evitar las restricciones de acceso de lectura. ¿Quizás a esto se refiere la respuesta?
John Dvorak
22

Si la aplicación no tiene cachés internos, los cambios se escribirán inmediatamente en el archivo. Lo mismo para tu ejemplo. El archivo es una entidad lógica en la memoria que se actualizará inmediatamente. Cualquier operación posterior en el archivo verá los cambios realizados por el programa.

Sin embargo , esto no significa que el cambio se haya escrito en el disco físico. Los cambios pueden permanecer en las cachés del sistema de archivos del sistema operativo o en las cachés de hardware. Para vaciar los búferes del sistema de archivos, use el synccomando

Me gustaría leer el archivo justo después de que salga el comando, pero no quiero leer un archivo vacío.

No deberías encontrarte con ningún problema práctico aquí.

mtak
fuente
1
"Si la aplicación no tiene cachés internos", ese es un "si" muy grande: la gran mayoría de las implementaciones de la biblioteca de E / S usan stdout de búfer por defecto. Dicho esto, el estándar C, por ejemplo, exige que el búfer stdout se vacíe al salir (pero potencialmente no si exitno se llama al menos implícitamente). Otras bibliotecas / lenguajes (por ejemplo, Java!) Ofrecen menos garantías.
Konrad Rudolph
¿Qué pasa si solo lo limito a la primitiva de redireccionamiento (es decir, el comando en mi pregunta)? No tiene cachés internos, ¿verdad?
Eric
@ Eric No, deberías estar bien.
mtak
10
No estoy seguro si recibo esta respuesta. La pregunta es sobre "cuándo sale el proceso". Cada aplicación con cachés de escritura interna los vaciará al disco al salir del proceso, si eso no sucedió antes. IOW, esos cachés no importan aquí.
MSalters
2
Además, un búfer interno se vaciará al salir o simplemente se desvanecerá de la existencia, ¿verdad? Entonces, incluso si los búferes internos no se vacían, el contenido no será observable, no importa cuánto tiempo se espere.
WorldSEnder
21

¿El búfer se vaciará automáticamente al disco cuando salga un proceso?

En general la respuesta es no .

Depende del comando. Como mencionan las otras respuestas, si el comando no almacena internamente los datos, todos los datos estarán disponibles cuando finalice el comando.

Pero la mayoría, si no todas, las bibliotecas de E / S estándar hacen stdout de búfer de forma predeterminada (hasta cierto punto) y ofrecen diferentes garantías sobre el vaciado automático de búferes cuando se cierra la aplicación.

C garantiza que una salida normal vaciará los tampones . "Salida normal" significa que exitse llama, ya sea explícitamente o al regresar de main. Sin embargo, una salida anormal puede eludir esta llamada (y, por lo tanto, dejar atrás los búferes sin vaciar).

Aquí hay un ejemplo simple:

#include <signal.h>
#include <stdio.h>

int main() {
    printf("test");
    raise(SIGABRT);
}

Si compila esto y lo ejecuta, testserá no necesariamente deben escribir en la salida estándar.

Otros lenguajes de programación ofrecen aún menos garantías: Java, por ejemplo, no se auto-descarga al finalizar el programa . Si el búfer de salida contiene una línea sin terminar, puede perderse, a menos que System.out.flush()se llame explícitamente.

Dicho esto, su cuerpo pregunta se refiere a algo ligeramente diferente: si los datos llegan en el archivo en absoluto , debe hacerlo inmediatamente después de la expiración de la orden (sujeta a las advertencias descritas en las otras respuestas).

Konrad Rudolph
fuente
77
También he visto una salida anormal cuando una herramienta de línea de comandos está escribiendo en un archivo y stdout o stderr, como un registro de depuración, y el usuario ha realizado una canalización a la cabeza o menos y luego escribió 'q' para salir menos. El archivo de disco no siempre se vacía por completo si la herramienta de línea de comandos no maneja SIGPIPE.
Zan Lynx el
1, pero "debe hacerlo inmediatamente después el comando terminará" no es del todo bien: ninguna write()o pwrite()llamada al sistema ocurrirá antes de salir del proceso, y fue entonces cuando los cambios en los archivos se hacen visibles. Entonces, el último cambio de archivo es definitivamente antes de la finalización del proceso, inmediatamente antes, a más tardar. Creo que incluso con un mmap(MAP_SHARED)archivo, no hay forma de que algo observe la finalización del proceso antes de que ocurran todos los cambios de archivo.
Peter Cordes
9

Creo que ninguna pregunta aborda este problema lo suficiente todavía:

Me gustaría leer el archivo justo después de que salga el comando, pero no quiero leer un archivo vacío.

Como explican las otras respuestas, un programa que se comporta bien vacía sus búferes de archivos internos antes de que el proceso finalice normalmente . Posteriormente, los datos pueden permanecer en el núcleo o en las memorias intermedias de hardware antes de escribirse en el almacenamiento persistente. Sin embargo , la semántica del sistema de archivos de Linux garantiza que todos los procesos vean el contenido de los archivos de la misma manera que lo hace el núcleo, incluidos los buffers internos 1 .

Esto se implementa típicamente teniendo como máximo un búfer en el núcleo por objeto de archivo y para requerir que todo el acceso a los archivos pase por este búfer.

  • Si un proceso lee un archivo, el núcleo presentará el contenido del búfer al proceso, si la parte del archivo solicitado está actualmente en el búfer; si no es así, el núcleo buscará los datos del medio de almacenamiento subyacente y lo colocará dentro del búfer, luego volverá al paso anterior.

  • Si un proceso escribe en un archivo, los datos se colocan primero dentro del búfer en el núcleo para ese archivo. Finalmente, el contenido del búfer se vaciará al almacenamiento. Mientras tanto, el acceso de lectura se satisface desde el mismo búfer (ver arriba).


1 Al menos para archivos regulares, directorios y enlaces simbólicos. FIFOs y sockets son un asunto diferente ya que su contenido nunca se almacena de forma persistente de todos modos. Hay algunos casos especiales de archivos regulares cuyo contenido depende de quién pregunta; ejemplos son archivos en procfs y sysfs (piense /proc/selfque es un enlace simbólico al ID del proceso del proceso que lee el enlace simbólico).

David Foerster
fuente
2
Estrictamente hablando, no es la semántica del sistema de archivos de Linux lo que garantiza esto, es la semántica POSIX la que lo hace. En particular, BSD se comporta exactamente igual, al igual que macOS e incluso Windows (aunque este es uno de los pocos casos en que Windows sigue la semántica POSIX). Esto también supone que nadie está haciendo cosas extrañas con mmap()O_DIRECT, lo que puede provocar que las cosas no estén sincronizadas entre el disco y el caché de la página (pero eso resolverá en el momento en que el proceso que lo hace salga).
Austin Hemmelgarn
2
@AustinHemmelgarn: Estrictamente hablando, ambos tenemos razón, ya que Linux se diseñó teniendo en cuenta el soporte para aplicaciones Unix (System V) y luego se hizo compatible con POSIX, que también basa muchos conceptos en el Sistema V.
David Foerster
5

Suponiendo que su comando es ejecutado por algún programa que usa la biblioteca de tiempo de ejecución C, en algún momento debería invocar fclosepara cerrar el archivo abierto.

La página del manual para la fclosefunción C dice:

NOTAS Tenga en cuenta que fclose () solo vacía los búferes de espacio de usuario proporcionados por la biblioteca C. Para garantizar que los datos se almacenen físicamente en el disco, los búferes del núcleo también se deben vaciar, por ejemplo, con sync (2) o fsync (2).

y la página de manual para fflushtiene la misma nota. La página del manual closedice:

Un cierre exitoso no garantiza que los datos se hayan guardado correctamente en el disco, ya que el núcleo difiere las escrituras. No es común que un sistema de archivos vacíe los búferes cuando se cierra la secuencia. Si necesita asegurarse de que los datos están almacenados físicamente, use fsync (2). (Dependerá del hardware del disco en este momento).

Tenga en cuenta que los datos están disponibles para otros procesos, incluso si no están sincronizados con la unidad. Tal vez eso ya sea lo suficientemente bueno para ti.

Si tiene dudas, escriba una prueba.

mvw
fuente
2
C o no, todo usará / debería usar la close()llamada al sistema para cerrar el descriptor de un archivo.
Attie
@Attie: No es necesario que closelos archivos antes de salir (en programas hacky que no comprobar si hay errores); el kernel los limpiará y lo llamará efectivamente closedespués de que su proceso falle. fcloseSin embargo, es necesario que realice cualquier flujo stdio almacenado en el búfer, o deje que libc lo haga por usted exit(3), en lugar de llamar directamente al sistema de salida.
Peter Cordes
Si tiene dudas, escriba una prueba. Este es un mal consejo para detectar condiciones de carrera. La prueba en un núcleo que se ejecuta en una pieza de hardware podría decirle que la carrera no puede ocurrir bajo las condiciones de software producidas por su prueba en ese sistema, o si lo hace, es muy raro detectarlo. Pero no puede decirle si se supone que ese comportamiento es seguro en todos los sistemas de archivos, núcleos y todo el hardware (por ejemplo, PowerPC). es decir, no puede saber si la garantía de la que depende es un detalle de implementación o una garantía intencional a prueba de futuro. (En este caso lo es.)
Peter Cordes
Depende de la situación. Este consejo puede ayudar a algunas personas que intentan ejecutar su script de shell. No fue concebido como una solución general para entornos más avanzados pero menos probables, por ejemplo, un ingeniero de software que trabaja en un núcleo del sistema operativo, algunas personas que trabajan en la actualización del microcódigo de Intel o alguna chica que trabaja en algún sistema para la ISS.
mvw
3

Cuando redirijo la salida de un comando a un archivo (por ejemplo, echo Hello > file) ¿se garantizará que ese archivo tenga esos datos justo después de que el comando salga?

Sí. El shell abre el archivo de echosalida y lo envía directamente a eso. Una vez que sale el comando, está listo.

¿O todavía hay una ventana muy pequeña entre las salidas de comando y los datos escritos en el archivo?

Si los datos ya están en los medios es otra cuestión, lo que solo importa si después hay una falla de hardware, o si inspecciona la partición en vivo con algún software forense, sin pasar por el sistema de archivos montado.

Me gustaría leer el archivo justo después de que salga el comando, pero no quiero leer un archivo vacío.

No se preocupe, el núcleo solo mantiene una vista del archivo, independientemente de la frecuencia con la que se abre.

Deduplicador
fuente
"el núcleo solo mantiene una vista del archivo": no es del todo cierto para mmap(MAP_SHARED): las tiendas en la región mmaped no son coherentes con las lecturas del archivo (por ese hilo u otros procesos). Por eso msync(2)existe. Al menos eso es lo que advierten las páginas del manual; dependiendo de la implementación, Linux en realidad puede asignar páginas físicas desde el caché de página, en cuyo caso supongo que básicamente es coherente (módulo de ordenamiento de memoria). De todos modos, todavía sucede todo antes _exit(2).
Peter Cordes
2

Como regla general, cualquier dato que posea el kernel es mantenido y limpiado por el kernel, punto. Dichos datos incluyen datos transferidos a la memoria del kernel por una llamada al sistema como write(2).

Sin embargo, si su aplicación (por ejemplo, la biblioteca C) realiza el almacenamiento en búfer además de esto, entonces el núcleo obviamente no tiene idea y, por lo tanto, no garantiza su limpieza.

Además, no creo que haya ninguna garantía de tiempo para la limpieza; en general, se realiza con el "mejor esfuerzo" (léase: "cuando tengo un segundo").

Mehrdad
fuente
Hay una garantía de que cualquier limpieza / limpieza del búfer ocurrirá antes de que waitpid()regrese el proceso padre , si es que la limpieza ocurre. es decir, otros procesos no pueden observar directamente la finalización del proceso antes de cualquier modificación de archivo realizada por ese proceso. (Dije "directamente" para descartar la observación indirecta a través de las marcas de tiempo del archivo NFS, porque el almacenamiento en caché de NFS no es perfectamente coherente entre los hosts.)
Peter Cordes
@PeterCordes: supongo que depende de lo que quieras decir con "limpieza" en lugar de "mantener". Para mí "mantener" es "proporcionar una visión coherente" (que tiene la garantía que mencionó) y "limpiar" es "vaciar en el disco", lo que no creo que tenga una garantía de tiempo.
Mehrdad
Ah, ya veo, está respondiendo la parte de la pregunta "vaciado al disco" que es irrelevante para lo que verán los procesos posteriores al leer el archivo. "limpiar" en el sentido de "limpiar la memoria caché / memoria intermedia de E / S sucia". Correcto, no hay garantía de sincronización a menos que use fsync/ fdatasync, aunque la reescritura del búfer en Linux comenzará después de /proc/sys/vm/dirty_writeback_centisecscentésimas de segundo (si no se retrasa por otro tráfico de E / S), y varios otros sintonizables en ese directorio procfs también afectan las cosas (por ejemplo, cómo grande para dejar crecer los búferes antes de hacer cualquier reescritura).
Peter Cordes
2

¿O todavía hay una ventana muy pequeña entre las salidas de comando y los datos escritos en el archivo?

No, no hay

Me gustaría leer el archivo justo después de que salga el comando, pero no quiero leer un archivo vacío.

Puede leer el contenido final del archivo justo después de que salga el comando; en su lugar, nunca leerá el archivo vacío. (En C y C ++, use las llamadas al sistema wait , waitpid , wait3 o wait4 para esperar a que el programa salga y solo luego lea el archivo. Si está utilizando un shell, otro lenguaje de programación o una biblioteca (por ejemplo, la biblioteca C sistema de llamada o la clase de proceso Java ), probablemente ya use una de estas llamadas del sistema).

Como lo han señalado otras respuestas y comentarios, puede terminar leyendo un archivo vacío después de la salida del programa si el programa ha salido sin vaciar sus memorias intermedias de salida (por ejemplo, debido a _exit , abortar o recibir una señal fatal, o porque es un programa Java que sale normalmente). Sin embargo, no hay nada que pueda hacer al respecto en este momento: los datos no vaciados se pierden para siempre, la espera adicional no los recuperará.

pts
fuente
0

Perdón por tal vez agregar otra respuesta superflua, pero la mayoría parece centrarse en la pista falsa del título de la pregunta. Pero por lo que puedo decir, la pregunta no es sobre el almacenamiento intermedio, sino esto:

Cuando redirijo la salida de un comando a un archivo (por ejemplo, echo Hello> file), ¿se garantizará que ese archivo tenga esos datos justo después de que salga el comando?

Si incondicionalmente. El uso de ">" que está describiendo, junto con "|" y "<", es el modelo de procesamiento basado en tubería en el que se basa el mundo de Unix y Linux. Encontrará cientos, si no miles de scripts totalmente dependiendo de este comportamiento en cada instalación de Linux.

Funciona según lo que desee por diseño, y si hubiera la más mínima posibilidad de una condición de carrera, se habría solucionado probablemente hace décadas.

AnoE
fuente
Esto es superfluo, desafortunadamente. Solo un par de respuestas se centran principalmente en la pista falsa de la confirmación de datos para el almacenamiento no volátil. Consulte la respuesta de @ pts y varias otras para obtener una descripción clara: la modificación del archivo ocurre antes de la salida, o no ocurre nada.
Peter Cordes