Comprendere le scritture di file simultanee da più processi

Da qui: il file append atomic in UNIX

Considera un caso in cui più processi aprono lo stesso file e si aggiungono ad esso. O_APPEND garantisce che cercare la fine del file e iniziare l’operazione di scrittura è atomico. Quindi più processi possono aggiungere allo stesso file e nessun processo sovrascriverà la scrittura di altri processi fino a quando ciascuna dimensione di scrittura è <= PIPE_BUF.

Ho scritto un programma di test in cui più processi si aprono e scrivono sullo stesso file ( write(2) ). Mi assicuro che ogni dimensione di scrittura sia> PIPE_BUF (4k). Mi aspettavo di vedere casi in cui un processo sovrascrive i dati di qualcun altro. Ma questo non succede. Ho provato con diversi formati di scrittura. È solo fortuna o c’è una ragione per cui non succede? Il mio objective finale è capire se più processi che si aggiungono allo stesso file devono coordinare le loro scritture.

Ecco il programma completo. Ogni processo crea un buffer int, riempie tutti i valori con il suo rank , apre un file e scrive su di esso.

Specifiche: OpenMPI 1.4.3 su Opensuse 11.3 64-bit

Compilato come: mpicc -O3 test.c, eseguito come: mpirun -np 8 ./a.out

 #include  #include  #include  #include  #include  #include  #include  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; } 

L’atomicità delle scritture inferiore a PIPE_BUF applica solo a pipe e FIFO. Per le scritture di file, POSIX dice:

Questo volume di POSIX.1-2008 non specifica il comportamento delle scritture concorrenti su un file da più processi. Le applicazioni dovrebbero utilizzare una qualche forma di controllo della concorrenza.

… il che significa che sei da solo – diversi like di UNIX daranno garanzie diverse.

In primo luogo, O_APPEND o l’equivalente FILE_APPEND_DATA su Windows significa che gli incrementi dell’estensione massima del file (file “length”) sono atomici sotto scrittori concorrenti, e che è di qualsiasi importo, non solo PIPE_BUF. Questo è garantito da POSIX e Linux, FreeBSD, OS X e Windows lo implementano correttamente. Anche Samba lo implementa correttamente, NFS prima di v5 non ha la capacità di formattazione del wire per appendicarsi atomicamente. Quindi, se apri il tuo file con solo append, le scritture simultanee non si strapperanno l’una rispetto all’altra su qualsiasi sistema operativo principale a meno che non sia coinvolto NFS.

Questo non dice nulla sul fatto che le letture vedranno comunque una scrittura lacerata, e su questo POSIX dice quanto segue riguardo l’atomicità di read () e write () ai normali file:

Tutte le seguenti funzioni devono essere atomiche l’una rispetto all’altra negli effetti specificati in POSIX.1-2008 quando operano su file regolari o collegamenti simbolici … [molte funzioni] … read () … write ( ) … Se due thread chiamano ciascuna di queste funzioni, ogni chiamata deve vedere tutti gli effetti specificati dell’altra chiamata o nessuna di esse. [Fonte]

e

Le scritture possono essere serializzate rispetto ad altre letture e scritture. Se un read () di dati di file può essere provato (con qualsiasi mezzo) per verificarsi dopo una write () dei dati, deve riflettere tale write (), anche se le chiamate sono fatte da processi diversi. [Fonte]

ma al contrario:

Questo volume di POSIX.1-2008 non specifica il comportamento delle scritture concorrenti su un file da più processi. Le applicazioni dovrebbero utilizzare una qualche forma di controllo della concorrenza. [Fonte]

Un’interpretazione sicura di tutti e tre questi requisiti suggerirebbe che tutte le scritture che si sovrappongono in un extent nello stesso file devono essere serializzate l’una rispetto all’altra e leggere in modo tale che le scritture strappate non appaiono mai ai lettori.

Un’interpretazione meno sicura, ma ancora consentita, potrebbe essere quella di leggere e scrivere solo serializzati tra thread all’interno dello stesso processo e tra processi scritti in serializzazione rispetto a solo letture (cioè c’è ordinamento I / O sequenzialmente coerente tra thread in un processo, ma tra i processi i / o è solo acquisizione-rilascio).

Naturalmente, solo perché lo standard richiede questa semantica non significa che le implementazioni siano conformi, anche se in realtà FreeBSD con ZFS si comporta perfettamente, Windows molto recente (10.0.14393) con NTFS si comporta perfettamente, e Linux recenti con ext4 si comportano correttamente se O_DIRECT è attivo . Se desideri maggiori dettagli su come i principali sistemi operativi e di archiviazione rispettano lo standard, vedi questa risposta

Non è una fortuna, nel senso che se si scavasse nel kernel si può probabilmente provare che, in circostanze particolari, non accadrà mai che la write un processo è intercalata con un’altra. Presumo che:

  • Non stai colpendo alcun limite di dimensioni del file
  • Non stai compilando il filesystem in cui crei il file di test
  • Il file è un file normale (non un socket, una pipe o qualcos’altro)
  • Il filesystem è locale
  • Il buffer non si estende su più mapping di memoria virtuale (questo è noto per essere vero, perché è malloc() ed, che lo mette sull’heap, che è contiguo.
  • I processi non vengono interrotti, segnalati o tracciati mentre write() è occupato.
  • Non ci sono errori di I / O su disco, guasti RAM o altre condizioni anomale.
  • (Forse altri)

Probabilmente scoprirete che se tutte queste ipotesi valgono, è il caso che il kernel del sistema operativo che state usando utilizza sempre una singola chiamata di sistema write() con una singola scrittura contigua atomica al seguente file.

Ciò non significa che puoi contare sul fatto che è sempre vero. Non sai mai quando potrebbe non essere vero quando:

  • il programma viene eseguito su un diverso sistema operativo
  • il file si sposta su un filesystem NFS
  • il processo riceve un segnale mentre write() è in corso e write() restituisce un risultato parziale (meno byte di quanto richiesto). Non sono sicuro che POSIX lo permetta davvero, ma programma in modo difensivo!
  • eccetera…

Quindi il tuo esperimento non può dimostrare che puoi scrivere su scritture non intercalate.