Подтвердить что ты не робот

Понимание одновременной записи файлов из нескольких процессов

Отсюда: Является ли приложение append атомарным в UNIX

Рассмотрим случай, когда несколько процессов открывают один и тот же файл и добавляют к нему. O_APPEND гарантирует, что поиск конца файла, а затем начало операции записи является атомарным. Таким образом, несколько процессов могут присоединяться к одному и тому же файлу, и ни один процесс не будет перезаписывать любые записи других процессов, поскольку каждый размер записи равен <= PIPE_BUF.

Я написал тестовую программу, в которой несколько процессов открываются и записываются в один и тот же файл (write(2)). Я уверен, что каждый размер записи составляет > PIPE_BUF (4k). Я ожидал увидеть случаи, когда процесс перезаписывает чужие данные. Но этого не происходит. Я тестировал с разными размерами записи. Это просто удача или есть причина, почему этого не происходит? Моя конечная цель - понять, нужно ли координировать свои записи несколько процессов, добавляемых к одному файлу.

Вот полная программа. Каждый процесс создает внутренний буфер, заполняет все значения с помощью rank, открывает файл и записывает на него.

Технические характеристики: OpenMPI 1.4.3 на Opensuse 11.3 64-бит

Скомпилирован как: mpicc -O3 test.c, запускается как: 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;
}
4b9b3361

Ответ 1

Атомарность записи меньше PIPE_BUF применяется только к трубам и FIFO. Для записи файлов POSIX говорит:

В этом томе POSIX.1-2008 не указано поведение одновременных записывает в файл из нескольких процессов. Приложения должны использовать некоторые форма управления concurrency.

... это означает, что вы сами по себе - разные UNIX-понравятся, дадут разные гарантии.

Ответ 2

Во-первых, O_APPEND или эквивалентный FILE_APPEND_DATA в Windows означает, что приращения максимального размера файла (длина файла) являются atomic в параллельных сценариях, и это на любую сумму, а не только на PIPE_BUF. Это гарантируется POSIX, и Linux, FreeBSD, OS X и Windows реализуют его правильно. Samba также реализует его правильно, NFS до v5 не делает, поскольку ему не хватает возможности форматирования каналов для атомарного добавления. Поэтому, если вы откроете свой файл только с помощью append-only, одновременная запись не будет разорваться по отношению друг к другу на какой-либо основной ОС, если не задействована NFS.

Это ничего не говорит о том, будут ли читатели когда-либо видеть разрывную запись, и на этом POSIX говорит следующее об атомарности read() и write() для обычных файлов:

Все следующие функции должны быть атомарными по каждому другие в эффектах, указанных в POSIX.1-2008, когда они работают регулярные файлы или символические ссылки... [много функций]... read()... write()... Если каждый из двух потоков вызывает одну из этих функций, каждый вызов должен либо увидеть все указанные эффекты другого вызова, либо ни один из них. [Источник]

и

Писания могут быть сериализованы по отношению к другим чтениям и записи. Если read() данных файла может быть доказано (каким-либо образом), чтобы произойти после write() данных, он должен отражать, что write(), даже если вызовы производятся различными процессами. [Источник]

но наоборот:

В этом томе POSIX.1-2008 не указано поведение одновременных записывает в файл из нескольких процессов. Приложения должны использовать некоторые форма управления concurrency. [Источник]

Безопасная интерпретация всех трех этих требований предполагает, что все записи, перекрывающиеся в одном и том же файле, должны быть сериализованы друг относительно друга и читать так, чтобы разрывающие записи никогда не появлялись для читателей.

Менее безопасным, но все же допускаемым интерпретацией может быть то, что чтение и запись только сериализации друг с другом между потоками внутри одного и того же процесса, а между процессами записи сериализуются только для чтения (т.е. последовательный последовательный порядок ввода/вывода между потоками в процессе, но между процессами i/o - только получение-релиз).

Конечно, только потому, что стандарт требует этой семантики, это не значит, что реализации соответствуют, хотя на самом деле FreeBSD с ZFS ведет себя отлично, очень недавняя Windows (10.0.14393) с NTFS ведет себя отлично, а последние Linuxes с ext4 ведут себя правильно, если O_DIRECT включен. Если вы хотите получить более подробную информацию о том, насколько хорошо основные ОС и системы регистрации соответствуют стандарту, см. этот ответ

Ответ 3

Это не удача в том смысле, что если вы вникнете в ядро, вы, вероятно, можете доказать, что в ваших конкретных обстоятельствах никогда не произойдет, что один процесс write чередуется с другим. Я предполагаю, что:

  • Вы не нажимаете никаких ограничений размера файла
  • Вы не заполняете файловую систему, в которой вы создаете тестовый файл
  • Файл представляет собой обычный файл (не сокет, труба или что-то еще)
  • Файловая система локальная
  • Буфер не охватывает несколько сопоставлений виртуальной памяти (этот, как известно, является истинным, потому что он malloc() ed, который помещает его в кучу, который он смежный.
  • Процессы не прерываются, не сигнализируются или не отслеживаются, пока write() занят.
  • Ошибок ввода-вывода диска, ошибок ОЗУ и других ненормальных условий нет.
  • (Возможно, другие)

Вероятно, вы действительно обнаружите, что если все эти предположения сохраняются, то это означает, что ядро ​​операционной системы, в которой вы случайно используете, всегда выполняет один системный вызов write() с единой последовательной последовательностью в следующий файл.

Это не значит, что вы можете рассчитывать на то, что это всегда правда. Вы никогда не знаете, когда это может быть неверно, если:

  • программа запускается в другой операционной системе
  • файл перемещается в файловую систему NFS
  • процесс получает сигнал, когда выполняется write(), а write() возвращает частичный результат (меньше байтов, чем запрошено). Не уверен, что POSIX действительно позволяет это произойти, но я программирую оборонительно!
  • и т.д...

Таким образом, ваш эксперимент не может доказать, что вы можете использовать для неперемещенных записей.