Comprender las escrituras simultáneas de archivos de múltiples procesos

8 minutos de lectura

avatar de usuario
jitihsk

Desde aquí: ¿Es el archivo adjunto atómico en UNIX?

Considere un caso en el que múltiples procesos abren el mismo archivo y lo agregan. O_APPEND garantiza que buscar hasta el final del archivo y luego comenzar la operación de escritura es atómico. Por lo tanto, varios procesos pueden agregarse al mismo archivo y ningún proceso sobrescribirá la escritura de otros procesos en la medida en que cada tamaño de escritura sea <= PIPE_BUF.

Escribí un programa de prueba donde varios procesos se abren y escriben en el mismo archivo (write(2)). Me aseguro de que cada tamaño de escritura sea > PIPE_BUF (4k). Esperaba ver instancias en las que un proceso sobrescriba los datos de otra persona. Pero eso no sucede. Probé con diferentes tamaños de escritura. ¿Es solo suerte o hay alguna razón por la que eso no sucede? Mi objetivo final es comprender si varios procesos que se agregan al mismo archivo necesitan coordinar sus escrituras.

Aquí está el programa completo. Cada proceso crea un búfer int, llena todos los valores con su rankabre un archivo y escribe en él.

Especificaciones: OpenMPI 1.4.3 en Opensuse 11.3 de 64 bits

Compilado como: mpicc -O3 test.c, ejecutado como: mpirun -np 8 ./a.out

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int 
main(int argc, char** argv) {
    int rank, size, i, bufsize = 134217728, fd, status = 0, bytes_written, tmp_bytes_written;
    int* buf;
    char* filename = "/tmp/testfile.out";

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    buf = (int*) malloc (bufsize * sizeof(int));   
    if(buf == NULL) {
        status = -1;
        perror("Could not malloc");
        goto finalize;
    }
    for(i=0; i<bufsize; i++) 
        buf[i] = rank;

    if(-1 == (fd = open(filename, O_APPEND|O_WRONLY, S_IWUSR))) {
        perror("Cant open file");
        status = -1;
        goto end;
        exit(-1);
    }

    bytes_written = 0;
    if(bufsize != (tmp_bytes_written = write(fd, buf, bufsize))) {
        perror("Error during write");
        printf("ret value: %d\n", tmp_bytes_written);
        status = -1;
        goto close;
    }

close:
    if(-1 == close(fd)) {
        perror("Error during close");
        status = -1;
    }
end:
    free(buf);
finalize:
    MPI_Finalize();
    return status;
}

  • perror( filename ); es mucho más útil que perror( "Cant open file");

    – William Pursell

    17/10/2012 a las 21:26

  • Hice la misma prueba en una caja de Linux (centos 7 3.10.0-327.13.1.el7.x86_64), vi el comportamiento que desea. Consulte stackoverflow.com/questions/38219512/….

    – aporcar

    6 de julio de 2016 a las 10:26

La atomicidad de escribe menos de PIPE_BUF se aplica solo a tuberías y FIFO. Para escrituras de archivos, POSIX dice:

Este volumen de POSIX.1-2008 no especifica el comportamiento de las escrituras simultáneas en un archivo desde varios procesos. Las aplicaciones deben usar alguna forma de control de concurrencia.

… lo que significa que está solo: diferentes tipos de UNIX le darán diferentes garantías.

  • ¿Cómo sé lo que garantiza mi Linux? ¿Está esto documentado en alguna parte para un Linux en particular?

    – jitihsk

    18 de octubre de 2012 a las 3:25

  • @KVM: Esta publicación bastante divertida de Linus Torvalds implica que un solo write() en una O_APPEND El archivo debe ser atómico en Linux, al menos en “sistemas de archivos similares a UNIX”; el ejemplo dado son los archivos de registro. Sin embargo, tenga en cuenta que NFS para uno ciertamente no se comporta de esta manera.

    – café

    18 de octubre de 2012 a las 4:08

  • @KVM: Sí, pero Linus está hablando del comportamiento tradicional de UNIX y Linux en lugar de lo que exige cualquier estándar escrito.

    – café

    18/10/2012 a las 21:15

  • Un enlace de archivo alternativo por el correo electrónico al que me referí anteriormente.

    – café

    12 abr 2017 a las 10:55

  • Tenga en cuenta que POSIX 7 write documentación dice: “Si el O_APPEND se establece la bandera de las banderas de estado del archivo, el desplazamiento del archivo se establecerá al final del archivo antes de cada escritura y no se producirá ninguna operación de modificación de archivo intermedia entre el cambio del desplazamiento del archivo y la operación de escritura.” Eso suena como una garantía de atomicidad. Tenga en cuenta, sin embargo, que no es una garantía de lo completowrite() no se garantiza que escriba todos los bytes solicitados, aunque las escrituras parciales de IME en un archivo real no ocurren.

    –Andrew Henle

    5 de enero de 2019 a las 13:06

avatar de usuario
niall douglas

En primer lugar, O_APPEND o el equivalente FILE_APPEND_DATA en Windows significa que los incrementos de la extensión máxima del archivo (archivo “longitud”) son atómico bajo escritores concurrentes, y eso es por cualquier cantidad, no solo PIPE_BUF. Esto está garantizado por POSIX, y Linux, FreeBSD, OS X y Windows lo implementan correctamente. Samba también lo implementa correctamente, NFS antes de v5 no lo hace, ya que carece de la capacidad de formato de cable para agregar atómicamente. Entonces, si abre su archivo con solo agregar, las escrituras simultáneas no se desgarrarán entre sí en ningún sistema operativo importante a menos que NFS esté involucrado.

Sin embargo, esto no dice nada sobre si las lecturas alguna vez verán una escritura rasgada, y sobre eso POSIX dice lo siguiente sobre la atomicidad de read() y write() en archivos normales:

Todas las siguientes funciones serán atómicas entre sí a los efectos especificados en POSIX.1-2008 cuando operen sobre archivos regulares o enlaces simbólicos… [many functions] … leer () … escribir () … Si dos subprocesos llaman a una de estas funciones, cada llamada verá todos los efectos especificados de la otra llamada, o ninguno de ellos. [Source]

y

Las escrituras se pueden serializar con respecto a otras lecturas y escrituras. Si se puede probar (por cualquier medio) que ocurre una lectura () de los datos del archivo después de una escritura () de los datos, debe reflejar esa escritura (), incluso si las llamadas son realizadas por diferentes procesos. [Source]

pero a la inversa:

Este volumen de POSIX.1-2008 no especifica el comportamiento de las escrituras simultáneas en un archivo desde varios procesos. Las aplicaciones deben usar alguna forma de control de concurrencia. [Source]

Una interpretación segura de estos tres requisitos sugeriría que todas las escrituras que superponen una extensión en el mismo archivo deben serializarse una con respecto a la otra y en lecturas de tal manera que las escrituras rotas nunca aparezcan a los lectores.

Una interpretación menos segura, pero aún permitida, podría ser que las lecturas y las escrituras solo se serialicen entre sí entre subprocesos dentro del mismo proceso, y entre procesos, las escrituras se serialicen con respecto a las lecturas únicamente (es decir, hay un orden de E/S secuencialmente consistente entre subprocesos en un proceso, pero entre procesos i/o es solo adquirir-liberar).

Por supuesto, el hecho de que el estándar requiera esta semántica no significa que las implementaciones cumplan, aunque, de hecho, FreeBSD con ZFS se comporta perfectamente, Windows muy reciente (10.0.14393) con NTFS se comporta perfectamente y Linux reciente con ext4 se comporta correctamente si O_DIRECT está activado. . Si desea obtener más detalles sobre qué tan bien los principales sistemas operativos y sistemas de archivo cumplen con el estándar, consulte esta respuesta

  • ¿Nunca he oído hablar de NFS v5?

    – ostrokach

    10 de abril de 2017 a las 2:49

  • @ostrokach Ha estado en desarrollo durante años. Tienen una lista de correo en algún lugar donde leí lo anterior.

    –Niall Douglas

    10/04/2017 a las 21:31

avatar de usuario
Celada

No es suerte, en el sentido de que si profundizas en el núcleo probablemente puedas probar que en tus circunstancias particulares nunca sucederá que uno procese’ write se intercala con otro. Estoy asumiendo que:

  • No está alcanzando ningún límite de tamaño de archivo
  • No está llenando el sistema de archivos en el que crea el archivo de prueba
  • El archivo es un archivo normal (no un zócalo, tubería o cualquier otra cosa)
  • El sistema de archivos es local.
  • El búfer no abarca varias asignaciones de memoria virtual (se sabe que esta es cierta porque es malloc()ed, que lo pone en el montón, que es contiguo.
  • Los procesos no son interrumpidos, señalados o rastreados mientras write() está ocupado.
  • No hay errores de E/S de disco, fallas de RAM o cualquier otra condición anormal.
  • (Tal vez otros)

Probablemente descubra que si todas esas suposiciones son ciertas, es el caso de que el kernel del sistema operativo que está usando siempre logra una sola write() llamada al sistema con una única escritura contigua atómica en el siguiente archivo.

Eso no significa que puedas contar con que esto siempre sea cierto. Nunca se sabe cuándo podría no ser cierto cuando:

  • el programa se ejecuta en un sistema operativo diferente
  • el archivo se mueve a un sistema de archivos NFS
  • el proceso recibe una señal mientras el write() está en progreso y el write() devuelve un resultado parcial (menos bytes de los solicitados). No estoy seguro de si POSIX realmente permite que esto suceda, ¡pero programo a la defensiva!
  • etc…

Por lo tanto, su experimento no puede probar que puede contar con escrituras no intercaladas.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad