[APUE] Родитель и потомок имеют одинаковое смещение файла после разветвления?
В APUE раздел 8.3 fork function
о совместном использовании файлов между родительским и дочерним процессами,
Он сказал: It is important that the parent and the child share the same file offset.
И в разделе 8.9 Race Conditions
есть пример: и родитель и потомок пишут в
файл, который открывается перед вызовом функции fork. Программа содержит условия гонки,
потому что вывод зависит от порядка, в котором процессы запускаются ядром, и от того, как долго выполняется каждый процесс.
Но в моем тестовом коде выходные данные перекрываются.
[Langzi @ Freedom apue] $ cat race.out
это длинный длинный вывод, это длинный длинный вывод от родителя
Похоже, что родительский и дочерний элементы имеют отдельные смещения файлов, а не разделяют одно и то же смещение.
Есть ли ошибка в моем коде? Или я неправильно понял значение совместного смещения?
Любые советы и помощь будут оценены.
ниже мой код:
#include "apue.h"
#include <fcntl.h>
void charatatime(int fd, char *);
int main()
{
pid_t pid;
int fd;
if ((fd = open("race.out", (O_WRONLY | O_CREAT | O_TRUNC),
S_IRUSR | S_IWUSR)) < 0)
err_sys("open error");
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
charatatime(fd, "this is a long long output from child\n");
else
charatatime(fd, "this is a long long output from parent\n");
exit(0);
}
void charatatime(int fd, char *str)
{
// try to make the two processes switch as often as possible
// to demonstrate the race condition.
// set synchronous flag for fd
set_fl(fd, O_SYNC);
while (*str) {
write(fd, str++, 1);
// make sure the data is write to disk
fdatasync(fd);
}
}
5 ответов
Ну, я был не прав.
Итак, они делят компенсацию, но происходит еще кое-что странное. Если бы они не разделяли смещение, вы получили бы вывод, который был бы похож на это:
this is a long long output from chredt
потому что каждый начинает писать со своим смещением 0 и продвигает символ за раз. Они не будут вступать в конфликт о том, что записать в файл, пока не дойдут до последнего слова предложения, которое в итоге будет чередоваться.
Итак, они делят компенсацию.
Но странная вещь: не похоже, что смещение атомарно обновляется, потому что ни один из процессов не отображается полностью. Это похоже на то, что некоторые части одного перезаписывают некоторые части другого, хотя они также увеличивают смещение, чтобы этого не происходило.
Если бы смещение не передавалось, вы бы получили столько же байтов в файле, сколько и самой длинной из двух строк.
Если смещения используются совместно и обновляются атомарно, в результате получается, что в файле будет ровно столько байтов, сколько объединены обе строки.
Но в итоге вы получите несколько байтов в файле, который находится где-то посередине, и это означает, что смещения используются совместно, а не обновляются атомарно, и это просто странно. Но это, видимо, то, что происходит. Как странно.
- процесс A считывает смещение в A.offset
- процесс B считывает смещение в B.offset
- процесс A записывает байт в A.offset
- процесс A устанавливает смещение = A.offset + 1
- процесс B записывает байт в B.offset
- процесс A считывает смещение в A.offset
- процесс B устанавливает смещение = B.offset + 1
- процесс A записывает байт в A.offset
- процесс A устанавливает смещение = A.offset + 1
- процесс B считывает смещение в B.offset
- процесс B записывает байт в B.offset
- процесс B устанавливает смещение = B.offset + 1
Примерно такова должна быть последовательность событий. Как очень странно.
Существуют системные вызовы pread и pwrite, поэтому два процесса могут обновить файл в определенной позиции, не гоняясь за тем, кто выигрывает за глобальное смещение.
Родитель и потомок совместно используют одну и ту же запись таблицы файлов в ядре, которая включает смещение. Таким образом, родитель и потомок не могут иметь разные смещения без одного или обоих процессов, закрывающих и повторно открывающих файл. Таким образом, любая запись родителем использует это смещение и изменяет (увеличивает) смещение. Затем любая запись дочернего элемента использует новое смещение и изменяет его. Написание одного символа за раз усугубляет эту ситуацию.
Из моей справочной страницы write(2): "Корректировка смещения файла и операция записи выполняются как атомарный шаг".
Таким образом, из этого вы можете быть уверены, что ни одна запись (родительская или дочерняя) не будет писать поверх другой. Вы также можете заметить, что если вы должны были написать (2) все ваше предложение сразу (за один вызов, чтобы написать (2)), это гарантирует, что предложение будет написано вместе, одним куском.
На практике многие системы записывают файлы журнала таким способом. Многие связанные процессы (дочерние элементы одного и того же родителя) будут иметь файловый дескриптор, который был открыт родителем. Пока каждый из них пишет целую строку за раз (с одним вызовом write(2)), файл журнала будет читать так, как вы этого хотите. Написание персонажа за раз не будет иметь таких же гарантий. Использование буферизации вывода (например, с помощью stdio) аналогичным образом устранит гарантии.
Ну, я настроил код для компиляции на vanilla GCC/glibc, и вот пример вывода:
thhis isias a l long oulout futput frd
parent
И я думаю, что это поддерживает идею о том, что файловая позиция является общей и зависит от гонки, вот почему это так странно. Обратите внимание, что данные, которые я показал, имеют 47 символов. Это больше, чем 38 или 39 символов каждого отдельного сообщения, и меньше, чем 77 символов обоих сообщений вместе - единственный способ увидеть, что это происходит, если процессы иногда мчатся, чтобы обновить позицию файла - каждый из них пишет каждый персонаж пытается увеличить позицию, но из-за гонки происходит только один шаг, и некоторые символы перезаписываются.
Подтверждающее доказательство: man 2 lseek
в моей системе четко сказано
Обратите внимание, что файловые дескрипторы, созданные dup(2) или fork(2), совместно используют указатель текущей позиции файла, поэтому поиск таких файлов может зависеть от условий гонки.
Используйте pwrite, так как запись иногда заканчивается условием гонки, когда один и тот же ресурс (write()) совместно используется несколькими процессами, так как запись не оставляет файл pos=0 после завершения, например, вы попадаете в середину файла, поэтому указатель файла (fd) указывая на это место, и если другой процесс хочет что-то сделать, он производит или работает не так, как он хотел, поскольку файловый дескриптор будет использоваться совместно для разветвления!!
Попробуй дать мне обратную связь
Если я правильно помню из своего класса ОС, разветвление дает ребенку собственное смещение (хотя оно начинается с той же позиции, что и у родителей), оно просто сохраняет ту же таблицу открытых файлов. Хотя большая часть того, что я читаю, кажется, утверждается иначе.