LINUX.ORG.RU
решено  
Kpoxman

ПисАть в файл из разных процессов


0

1

Добрый день,

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

Безопасно ли таким образом писать лог-файл из разных процессов?


[#]  
mv

Нет.

***** ()
[#]  

use flock

***** ()
[#]  
pathfinder

>Т.е. содержимое буфера, переданное в write появится в файле полностью

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

** ()
[#]  

Если файл открыт с O_APPEND, то гарантируется, что запись будет произведена в конец файла, при этом перемещение указателя и запись будут осуществлены атомарно ("The adjustment of the file offset and the write operation are performed as an atomic step").

** ()
[#] Ответ на: комментарий от sjinks 30.03.2010 19:59:35  
pathfinder

>Если файл открыт с O_APPEND, то гарантируется

ЧЗХ

** ()
[#] Ответ на: комментарий от sjinks 30.03.2010 19:59:35  
pathfinder

>Если файл открыт с O_APPEND, то гарантируется

А вы умеете пудрить мозги. :) Все правильно, перемещение указателя и запись будут осуществлены атомарно. Но факт атомарности задания смещения и ОДНОЙ операции записи ещё не гарантирует запись ВСЕГО блока данных, который будет передан функции write.

** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 20:13:02  

> А вы умеете пудрить мозги. :)

Мы просто о разных вещах говорим :-)

> Но факт атомарности задания смещения и ОДНОЙ операции записи ещё не гарантирует запись ВСЕГО блока данных, который будет передан функции write

Естественно. ENOSPC/EIO/EFBIG и прочие прелести никто не отменял. Идея в том, что ситуации, когда первый процесс сделал seek, в это время второй записал данные и только после этого первый записал свои данные, перетирая данные второго процесса, быть не должно.

** ()
[#] Ответ на: комментарий от sjinks 30.03.2010 20:49:04  
pathfinder

>ENOSPC/EIO/EFBIG и прочие прелести никто не отменял

Это не самые главные прелести. А вот EINTR уже интереснее будет.

Если внимательно посмотреть, что говорил ТС:

>Т.е. содержимое буфера, переданное в write появится в файле ПОЛНОСТЬЮ, не прерываемое записями из других процессов.

А вот этого O_APPEND гарантировать не сможет.

** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 21:00:32  

> А вот EINTR уже интереснее будет.

Ну как сказать. Если верить документации, то EINTR возвращается, если the call was interrupted by a signal *before* any data was written.

> не прерываемое записями из других процессов

По кра "не перетертое записями из других процессов"

** ()
[#] Ответ на: комментарий от sjinks 30.03.2010 21:05:53  

Рано отправил:

*По крайней мере, "не перетертое записями из других процессов"

** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 21:00:32  
>>-----Цитата---->>

EINTR

<<-----Цитата----<<

Вот, нашёл:

>>-----Цитата---->>

man 7 signal

If a blocked call to one of the following interfaces is interrupted by a signal handler, then the call will be automatically restarted after the signal handler returns if the SA_RESTART flag was used; otherwise the call will with the error EINTR:

* read(2), readv(2), write(2), writev(2), and ioctl(2) calls on "slow" devices. A "slow" device is one where the I/O call may block for an indefinite time, for example, a terminal, pipe, or socket. (A disk is not a slow device according to this definition.)

<<-----Цитата----<<
** ()
[#] Ответ на: комментарий от sjinks 30.03.2010 21:06:47  
pathfinder

>Ну как сказать. Если верить документации, то EINTR возвращается, если the call was interrupted by a signal *before* any data was written.

Не ладно, давай другой случай. Предположим надо записать сообщение "10:00 Message 1\n". Конкурирующее приложение попытается записать "10:00 Message 2\n". Мы начинаем записывать. Уже успели записать "10:00 Me" и тут внезапно пришел сигнал. После этого write возвращает 8 вместо 16. Остальные надо дозаписать. Собираемся ещё раз вызвать write, для того, чтобы записать оставшиеся 8 байт. Но неожиданно происходит переключение контекста. Второй процесс полностью записывает строку. После этого первый процесс дозаписывает оставшиеся 8 байт. В итоге имеем:

10:00 Me10:00 Message 2
ssage 1
Вместо
10:00 Message 1
10:00 Message 2

** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 21:19:35  

> и тут внезапно пришел сигнал.

В том-то и дело, что сигнал не прервет запись. См. "A disk is not a slow device" выше — с "быстрыми" устройствами EINTR не будет.

Если мы пишем в сокет/пайп, то да, такая ситуация может иметь место. Но ни сокет, ни пайп не являются seekable devices, и O_APPEND к ним неприменим.

** ()
[#] Ответ на: комментарий от sjinks 30.03.2010 21:25:14  
pathfinder

>В том-то и дело, что сигнал не прервет запись.

Такая ситуация может произойти и без сигнала. Просто драйвер может и не захотеть принять за один вызов syscall весь блок данных. По крайней мере на это точно не надо рассчитывать. Это обычно связано с тем, что могут использоваться промежуточные буферы ограниченного размера. Попробуй за один заход затолкнуть гигабайт данных. Наверняка будет принято на запись лишь часть этих данных, для записи остального надо будет повторно вызывать write.

** ()
[#]  

>ПисАть в файл

Для нача́ла научи́сь пра́вильно писа́ть ударе́ния, а пото́м уж переходи́ к за́писи в фа́йлы.

anonymous ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 21:33:36  

> Попробуй за один заход затолкнуть гигабайт данных.

Сейчас попробую.

Но на самом деле размер ограничен размерностью типа ssize_t.

** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 21:33:36  
>>-----Цитата---->>

Попробуй за один заход затолкнуть гигабайт данных.

<<-----Цитата----<<
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFSIZE (1024*1048576*sizeof(char))

int main(int argc, char** argv)
{
	int fd;
	char* buf = (char*)malloc(BUFSIZE);
	ssize_t s;
	if (!buf) {
		perror("malloc");
		return EXIT_FAILURE;
	}

	fd = open("text.txt", O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR);
	if (-1 == fd) {
		perror("open");
		return EXIT_FAILURE;
	}

	s = write(fd, buf, BUFSIZE);
	if  (-1 == s) {
		perror("write");
		close(fd);
		return EXIT_FAILURE;
	}

	printf("Expected to write: %zu, written: %zd\n", BUFSIZE, s);
	close(fd);

	return EXIT_SUCCESS;
}
$ gcc write.c -o write -Wall -Wextra -O2
write.c: In function ‘main’:
write.c:10: warning: unused parameter ‘argc’
write.c:10: warning: unused parameter ‘argv’
$ ./write
Expected to write: 1073741824, written: 1073741824

Полтора гига тоже за раз записалось :-)

** ()
[#] Ответ на: комментарий от sjinks 30.03.2010 21:58:35  

у меня сдохло на 2 гигабайтах

Expected to write: 6442450944, written: 2147479552

anonymous ()
[#]  

> Безопасно ли таким образом писать лог-файл из разных процессов?

Да.

***** ()
[#] Ответ на: комментарий от sjinks 30.03.2010 21:58:35  
pathfinder

Похоже я действительно был не прав. Я все напутал :(((((. Ограниченность буфера может иметь отношение только к read (и то отчасти). write-у они никак не мешают. Единственная причина, по которой write вынужден вернуть меньшее количество байт, это возникший сигнал. При этом если не было записано ни одного байта, то вернется EINTR, в противном случае вернется записанное число байт. SA_RESTART должен решить проблему.

Правда мне совсем не понятно по части атомарности. Второй перезапуск и последующие так же будут одной атомарной операцией, или как?

Так же я понимаю что write может вернуть меньшее количество байт, чем отправлялось при записи. При этом как я понял не сказано, что это может быть только по причине сигнала. Драйверу может вернуть меньше, если ему так хочется, по каким-то своим неведомым причинам.

** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 22:45:48  
pathfinder
If the O_APPEND flag of the file status flags is set, the file offset shall be set to the end of the file prior to each write and no intervening file modification operation shall occur BETWEEN changing the file offset and the write operation.

В стандарте сказано об атомарности МЕЖДУ изменением и началом записи. Не уверен что делает саму операцию записи всего блока полностью атомарной.

** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 22:45:48  

> Правда мне совсем не понятно по части атомарности. Второй перезапуск и последующие так же будут одной атомарной операцией, или как?

Насколько я понимаю, увеличение размера файла происходит _до_ начала write, так что если ты умудришься поймать сигнал в середине write, в файле просто образуется дырка. Кто знает внутренности ядра - поправьте, если что.

***** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 22:45:48  

> write-у они никак не мешают. Единственная причина, по которой write вынужден вернуть меньшее количество байт, это возникший сигнал.

> write может вернуть меньшее количество байт, чем отправлялось при записи. При этом как я понял не сказано, что это может быть только по причине сигнала.



Так ты определись )))

**** ()
[#] Ответ на: комментарий от LamerOk 30.03.2010 23:00:58  
pathfinder

>Так ты определись )))

В общем так. Я не вижу других причин возвращать драйверу меньшее количество байт, кроме прихода сигнала. Но как я понял ничто не запрещает вернуть меньшее количество и без сигнала.

** ()
[#] Ответ на: комментарий от tailgunner 30.03.2010 22:59:31  

> увеличение размера файла происходит _до_ начала write

Совсем не обязательно. Оба действия происходят до возврата из вызова, но какое первым, а какое вторым - целиком и полностью зависит от реализации драйвера. Но в любом случае проверка на сигнал происходит при переключении контекстов ядро-пользователь (помимо передачи управления шедулером). Сильно сомневаюсь, что ядро будет обрабатывать сигнал, пока мячик на его стороне поля.

**** ()
[#] Ответ на: комментарий от tailgunner 30.03.2010 22:59:31  

Зачем знать внутренности ядра? достаточно знать man.

>>-----Цитата---->>

man 2 write

If the file was open(2)ed with O_APPEND, the file offset is first set to the end of the file before writing. The adjustment of the file offset and the write operation are performed as an atomic step.

<<-----Цитата----<<
anonymous ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:06:40  

> ничто не запрещает вернуть меньшее количество и без сигнала.

Т.е. ты всё-таки вернулся к своей прежней позиции:

> write даже без конкурентного доступа не может гарантировать, что весь буфер будет передан.


?

**** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:06:40  

тут кто-нибудь man читает вообще?

>>-----Цитата---->>

man 2 write

The number of bytes written may be less than count if, for example, there is insufficient space on the underlying physical medium, or the RLIMIT_FSIZE resource limit is encountered (see setrlimit(2)), or the call was interrupted by a signal handler after having written less than count bytes. (See also pipe(7).)

<<-----Цитата----<<
anonymous ()
[#] Ответ на: комментарий от anonymous 30.03.2010 23:09:40  

> достаточно знать man.

Еще б неплохо понимать, что в нём пишут. ))

file_offset != new_eof

**** ()
[#] Ответ на: комментарий от LamerOk 30.03.2010 23:08:05  
pathfinder

> увеличение размера файла происходит _до_ начала write

%) Вы про "увеличение размера файла" или про "изменение текущего смещения в файле" ?

** ()
[#] Ответ на: комментарий от LamerOk 30.03.2010 23:08:05  

>целиком и полностью зависит от реализации драйвера.

тебе надо писателем фантастом работать, но лучше попробовать работать читателем манов.

anonymous ()
[#] Ответ на: комментарий от anonymous 30.03.2010 23:11:24  
pathfinder

>The number of bytes written may be less than count if, for example...

Думаю здесь ключевое слово for example

** ()
[#] Ответ на: комментарий от LamerOk 30.03.2010 23:11:37  

всё понятно, имя ты себе точно подобрал

anonymous ()
[#] Ответ на: комментарий от LamerOk 30.03.2010 23:09:50  
pathfinder

>> ничто не запрещает вернуть меньшее количество и без сигнала.

>Т.е. ты всё-таки вернулся к своей прежней позиции:

Да. У меня по прежнему осталось сомнение, что O_APPEND делает всю операцию записи полностью атомарной.

** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:11:54  

> Вы

Не я, а tailgunner

> про


сам как думаешь? )

> Насколько я понимаю, увеличение размера файла происходит _до_ начала write, так что если ты умудришься поймать сигнал в середине write, в файле просто образуется дырка.

**** ()
[#] Ответ на: комментарий от anonymous 30.03.2010 23:14:03  

Спасибо за скальп. Я повешу его перед вигвамом, а ты пока погуляй и выучи английский.

**** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:17:03  

> Да. У меня по прежнему осталось сомнение, что O_APPEND делает всю операцию записи полностью атомарной.

Правильное мнение. ))

**** ()
[#] Ответ на: комментарий от LamerOk 30.03.2010 23:08:05  

> Оба действия происходят до возврата из вызова, но какое первым, а какое вторым - целиком и полностью зависит от реализации драйвера.

Какого драйвера? Драйвера диска? Точно нет. Драйвера ФС? Тоже нет вряд ли, потому что этот порядок продиктован семантикой.

***** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:11:54  

>> увеличение размера файла происходит _до_ начала write

> %) Вы про "увеличение размера файла" или про "изменение текущего смещения в файле" ?

Ты точно прочитал то, что я написал?

***** ()
[#]  
pathfinder

Я бы все таки посоветовал TC использовать flock в любом случае и не искать приключений на свою задницу.

** ()
[#] Ответ на: комментарий от tailgunner 30.03.2010 23:27:10  
pathfinder

>Ты точно прочитал то, что я написал?

>Насколько я понимаю, увеличение размера файла происходит _до_ начала write, так что если ты умудришься поймать сигнал в середине write, в файле просто образуется дырка. Кто знает внутренности ядра - поправьте, если что.

Что значит "дырка"? Я так понимаю при получении сигнала будет записано столько байт, сколько успели записать к моменту прихода сигнала. И файл увеличится на столько байт, сколько успели записать. "Дырки" здесь никакой не должно быть.

** ()
[#] Ответ на: комментарий от tailgunner 30.03.2010 23:20:57  

> Драйвера ФС? Тоже нет вряд ли

Именно фс. Кто у нас еще отвечает за размер файла? ))

> этот порядок продиктован семантикой.


И как же он её диктует? ))

**** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:33:33  

> при получении сигнала будет записано столько байт, сколько успели

Что ты понимаешь под словом "записано"?

**** ()
[#] Ответ на: комментарий от LamerOk 30.03.2010 23:34:44  

>> этот порядок продиктован семантикой.

> И как же он её диктует? ))

По слогам.

***** ()
[#] Ответ на: комментарий от LamerOk 30.03.2010 23:35:50  
pathfinder

>Что ты понимаешь под словом "записано"?

Если ты знаешь что-то больше меня, говори сразу. Не юли.

>Что ты понимаешь под словом "записано"?

Может тебе больше нравится слово "обработано".

** ()
[#] Ответ на: комментарий от tailgunner 30.03.2010 23:37:04  

Ну произнеси, чего уж там? ))

**** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:33:33  

> Что значит "дырка"?

Ы? В файлах бывают "дырки" - это место, в которое никогда не записывались данные.

> Я так понимаю при получении сигнала будет записано столько байт, сколько успели записать к моменту прихода сигнала. И файл увеличится на столько байт, сколько успели записать.

Это толкование, которое допускает битые записи при наличии сигналов. Если делать так: lseek to EOF, filesize+=nbytes, write nbytes, то после получения сигнала ты сможешь дописать то, что не записано из-за сигнала.

Если учесть, что все учебники говорят вещи типа "O_APPEND is perfect for log files", я думаю, что я прав :)

***** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:39:23  

> Может тебе больше нравится

Мне нравятся write(fd, buf, size) и состояние "до" и "после".

**** ()
[#] Ответ на: комментарий от pathfinder 30.03.2010 23:39:23  
pathfinder

>Что ты понимаешь под словом "записано"?

Я не имел дела с блочными устройствами. Писал драйвера для символьных устройств и то для древнего ядра 2.4. Там есть какие-то особенности, из-за которых может образоваться дырка?

** ()