Czy dołączanie pliku jest atomowe w systemie UNIX?

106

Ogólnie rzecz biorąc, co możemy przyjąć za pewnik, gdy dołączamy do pliku w systemie UNIX z wielu procesów? Czy można stracić dane (jeden proces nadpisuje zmiany drugiego)? Czy dane mogą zostać zniekształcone? (Na przykład, każdy proces dołącza po jednym wierszu do pliku dziennika, czy jest możliwe, że dwa wiersze ulegną zniekształceniu?) Jeśli dołączanie nie jest atomowe w powyższym sensie, to jaki jest najlepszy sposób zapewnienia wzajemnego wykluczenia?

Lajos Nagy
źródło

Odpowiedzi:

65

Zapis o rozmiarze poniżej „PIPE_BUF” powinien być atomowy. Powinno to mieć co najmniej 512 bajtów, chociaż mogłoby być większe (wydaje się, że Linux ma to ustawione na 4096).

Zakłada się, że mówisz o wszystkich komponentach w pełni zgodnych z POSIX. Na przykład nie jest to prawdą w NFS.

Ale zakładając, że piszesz do pliku dziennika, który otworzyłeś w trybie „O_APPEND” i utrzymujesz swoje wiersze (w tym znak nowej linii) w bajtach „PIPE_BUF”, powinieneś być w stanie mieć wiele zapisów w pliku dziennika bez żadnych problemów z uszkodzeniem. Wszelkie przerwania pojawią się przed lub po zapisie, a nie w środku. Jeśli chcesz, aby integralność plików przetrwała ponowne uruchomienie, musisz również dzwonić fsync(2)po każdym zapisie, ale to straszne dla wydajności.

Wyjaśnienie : przeczytaj komentarze i odpowiedź Oza Solomona . Nie jestem pewien, czy O_APPENDma to mieć PIPE_BUFatomowość w takim rozmiarze. Jest całkowicie możliwe, że tak właśnie jest zaimplementowany w Linuksie write()lub może to wynikać z rozmiarów bloków bazowego systemu plików.

freiheit
źródło
11
Na rozsądnych systemach plików fsync(2)daje taką samą gwarancję, jak sync(2)i nie ma tak dużego wpływu na wydajność.
ephemient
4
Czy jesteś tego pewien? Czy możesz podać link do tego zachowania? Znalazłem potwierdzenie, że deskryptor jest potokiem, ale nie mogłem znaleźć dowodów, że działa on dla żadnego pliku. w tym normalne obiekty plików spoza NFS.
Alan Franzoni
6
Gdzie dokładnie w ... / write.html? W przypadku O_APPEND nie widzę wzmianki o PIPE_BUF i widzę obietnicę, że między zmianą przesunięcia pliku a operacją zapisu nie powinna nastąpić żadna operacja modyfikacji pliku ” , ale nie jestem pewien, czy oznacza to, że sama operacja zapisu jest nieprzerwanie ...
akavel
6
Jak wskazuje ta odpowiedź , stwierdzenie o PIPE_BUFna tej stronie dotyczy tylko potoków i FIFO, a nie zwykłych plików.
Greg Inozemtsev
3
Wraz z nadejściem sygnałów sytuacja może być jeszcze gorsza: bugzilla.kernel.org/show_bug.cgi?id=55651 . Dlaczego jest to w ogóle oznaczone jako odpowiedź? PIPE_BUF nie ma nic wspólnego z plikami.
zmniejszył się
35

Edycja: zaktualizowano w sierpniu 2017 r. Najnowszymi wynikami systemu Windows.

Jako autor proponowanego Boost.AFIO, który implementuje asynchroniczny system plików i bibliotekę plików i / o C ++, udzielę odpowiedzi wraz z linkami do kodu testowego i wyników .

Po pierwsze, O_APPEND lub równowartość FILE_APPEND_DATA środkami Windows, że przyrosty w zakresie plików maksymalny (plik „długość”) są atomowy pod współbieżnych pisarzy. Gwarantuje to POSIX, a Linux, FreeBSD, OS X i Windows wszystkie implementują to poprawnie. Samba również implementuje go poprawnie, NFS przed wersją 5 nie działa, ponieważ nie ma możliwości dołączania atomowego w formacie drutu. Więc jeśli otworzysz plik z opcją tylko do dopisywania, współbieżne zapisy nie będą się rozrywać w żadnym większym systemie operacyjnym, chyba że zaangażowany jest NFS.

Jednak równoczesne odczyty do załączników atomowych mogą powodować poszarpane zapisy w zależności od systemu operacyjnego, systemu plików i flag, z którymi otworzyłeś plik - przyrost maksymalnego zasięgu pliku jest atomowy, ale widoczność zapisów w odniesieniu do odczytów może lub nie być atomowym. Oto krótkie podsumowanie według flag, systemu operacyjnego i systemu plików:


Brak O_DIRECT / FILE_FLAG_NO_BUFFERING:

Microsoft Windows 10 z NTFS: aktualizacja atomicity = 1 bajt do 10.0.10240 włącznie, od 10.0.14393 co najmniej 1 MB, prawdopodobnie nieskończona (*).

Linux 4.2.6 z ext4: aktualizacja atomicity = 1 bajt

FreeBSD 10.2 z ZFS: aktualizacja atomicity = co najmniej 1Mb, prawdopodobnie nieskończona (*)

O_DIRECT / FILE_FLAG_NO_BUFFERING:

Microsoft Windows 10 z NTFS: zaktualizuj atomicity = do 10.0.10240 włącznie do 4096 bajtów tylko w przypadku wyrównania strony, w przeciwnym razie 512 bajtów, jeśli FILE_FLAG_WRITE_THROUGH jest wyłączone, w przeciwnym razie 64 bajty. Zauważ, że ta atomowość jest prawdopodobnie cechą PCIe DMA, a nie zaprojektowaną w. Od 10.0.14393, co najmniej 1Mb, prawdopodobnie nieskończona (*).

Linux 4.2.6 z ext4: aktualizacja atomicity = co najmniej 1Mb, prawdopodobnie nieskończona (*). Zauważ, że wcześniejsze Linuksy z ext4 zdecydowanie nie przekraczały 4096 bajtów, XFS z pewnością miał niestandardowe blokowanie, ale wygląda na to, że ostatni Linux w końcu to naprawił.

FreeBSD 10.2 z ZFS: aktualizacja atomicity = co najmniej 1Mb, prawdopodobnie nieskończona (*)


Możesz zobaczyć surowe wyniki testów empirycznych na https://github.com/ned14/afio/tree/master/programs/fs-probe . Zauważ, że testujemy pod kątem zerwanych przesunięć tylko na wielokrotnościach 512 bajtów, więc nie mogę powiedzieć, czy częściowa aktualizacja sektora 512-bajtowego byłaby zerwana podczas cyklu odczytu-modyfikacji-zapisu.

Tak więc, aby odpowiedzieć na pytanie OP, zapisy O_APPEND nie będą kolidować ze sobą, ale odczyty współbieżne z zapisami O_APPEND prawdopodobnie zobaczą rozdarte zapisy w Linuksie z ext4, chyba że O_DIRECT jest włączony, po czym zapis O_APPEND musiałby mieć rozmiar sektora wielokrotnego.


(*) „Prawdopodobnie nieskończony” wywodzi się z następujących klauzul specyfikacji POSIX:

Wszystkie poniższe funkcje będą atomowe względem siebie w efektach określonych w POSIX.1-2008, gdy operują na zwykłych plikach lub dowiązaniach symbolicznych ... [wiele funkcji] ... read () ... write ( ) ... Jeśli dwa wątki wywołują jedną z tych funkcji, każde wywołanie powinno albo zobaczyć wszystkie określone efekty drugiego wywołania, albo żaden z nich. [Źródło]

i

Zapisy mogą być serializowane w odniesieniu do innych odczytów i zapisów. Jeśli można udowodnić (w jakikolwiek sposób), że odczyt () danych pliku występuje po zapisie () danych, musi to odzwierciedlać metodę write (), nawet jeśli wywołania są wykonywane przez różne procesy. [Źródło]

ale odwrotnie:

Ten tom POSIX.1-2008 nie określa zachowania współbieżnych zapisów do pliku z wielu procesów. Aplikacje powinny używać jakiejś formy kontroli współbieżności. [Źródło]

Możesz przeczytać więcej o ich znaczeniu w tej odpowiedzi

Niall Douglas
źródło
29

Napisałem skrypt, aby empirycznie przetestować maksymalny atomowy rozmiar dodatku. Skrypt napisany w bashu generuje wiele procesów roboczych, z których wszystkie zapisują podpisy specyficzne dla pracownika w tym samym pliku. Następnie odczytuje plik, szukając nakładających się lub uszkodzonych podpisów. Możesz zobaczyć źródło skryptu w tym poście na blogu .

Rzeczywisty maksymalny rozmiar dołączania atomowego zależy nie tylko od systemu operacyjnego, ale także od systemu plików.

W systemie Linux + ext3 rozmiar wynosi 4096, aw systemie Windows + NTFS - 1024. Więcej rozmiarów można znaleźć w komentarzach poniżej.

Oz Solomon
źródło
Z jakim systemem plików testowałeś w Linuksie? Zastanawiam się, czy może opiera się na rozmiarach bloków systemu plików.
freiheit
@freiheit Wydaje mi się, że w czasie, gdy testowałem go na ext3. Jeśli uruchomisz go na innym FS i uzyskasz inny wynik, napisz komentarz.
Oz Solomon,
3
@OzSolomon, użyłem twojego skryptu na Debianie 7.8 i byłem w stanie uzyskać zapisy atomowe do 1008 bajtów włącznie (1024 - 16 bajtów narzutu?) Zarówno na mojej partycji ext4, jak i na montowaniu tmpfs. Wszystko poza tym skutkowało za każdym razem korupcją.
Eric Pruitt
6
Twój test wydaje się zakładać, że echo $line >> $OUTPUT_FILEspowoduje to pojedyncze wywołanie, writeniezależnie od rozmiaru $line.
Tomas
16

Oto, co mówi standard: http://www.opengroup.org/onlinepubs/009695399/functions/pwrite.html .

Jeśli O_APPENDflaga stanu pliku jest ustawiona, przesunięcie pliku powinno być ustawione na koniec pliku przed każdym zapisem, a między zmianą przesunięcia pliku a operacją zapisu nie powinna wystąpić żadna interwencja modyfikacji pliku.

Bastien Léonard
źródło
20
„pomiędzy” - ale co z interwencjami podczas pisania, które dla mojego zrozumienia następują po „między”? (Tj .: <change_offset_action> ... "the_between_period" ... <write_action>) - czy rozumiem, że nie ma co do tego gwarancji?
akavel
@akavel zgodził się; nie ma gwarancji, że sam zapis jest atomowy. Ale jestem zdezorientowany: na podstawie gwarancji zawartej w Twojej wycenie wydaje się, że możemy stwierdzić, że aplikacja wielowątkowa dołączająca ten sam plik nie będzie mieszać części różnych zapisów pisemnych. Jednak z eksperymentów zgłoszonych przez OzSolomona widzimy, że nawet to założenie jest naruszone. Czemu?
maksymalnie
@max Niestety, obawiam się, że nie dostać się pytanie: po pierwsze, eksperyment OzSolomon jest multi- proces , a nie multi- gwintowany (jeden proces) aplikacja; po drugie, nie rozumiem, jak wyciągasz wniosek, że „aplikacja wielowątkowa [...] nie będzie się mieszać” - to jest dokładnie to, czego nie gwarantuje cytat Bastiena, o czym wspominam w komentarzu. Czy możesz wyjaśnić swoje pytanie?
akavel
2
Hmm, nie mogę zrekonstruować własnej logiki w momencie, gdy pisałem ten komentarz ... Tak, jeśli twoja interpretacja jest poprawna, to oczywiście różne zapisy mogą być mieszane. Ale teraz, kiedy ponownie czytam cytat Bastiena, myślę, że to musi oznaczać, że nikt nie może przerywać "podczas pisania" - w przeciwnym razie cały akapit w standardzie byłby bezużyteczny, nie dając dosłownie żadnych gwarancji (nawet, że zapis się wydarzy na końcu, ponieważ ktoś inny może przesunąć przesunięcie w trakcie wykonywania kroku „zapisu”.
maksymalnie