[APUE] Родитель и потомок имеют одинаковое смещение файла после fork? - PullRequest
5 голосов
/ 28 октября 2009

В разделе 8.3 * 1001 APUE, о совместном использовании файлов между родительским и дочерним процессами,
Он сказал: 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 ]

4 голосов
/ 28 октября 2009

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

со страницы руководства my write (2): «Корректировка смещения файла и операция записи выполняются как атомарный шаг».

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

На практике многие системы записывают файлы журнала таким способом. Многие связанные процессы (дочерние элементы одного и того же родителя) будут иметь файловый дескриптор, который был открыт родителем. Пока каждый из них пишет целую строку за раз (с одним вызовом write (2)), файл журнала будет читать так, как вы этого хотите. Написание персонажа за раз не будет иметь таких же гарантий. Использование буферизации вывода (например, с помощью stdio) аналогичным образом устранит гарантии.

3 голосов
/ 28 октября 2009

Ну, я был не прав.

Итак, они делят компенсацию, но происходит еще кое-что странное. Если бы они не делили смещение, вы бы получили вывод, который выглядел так:

this is a long long output from chredt

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

Итак, они делят смещение.

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

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

Если смещения используются совместно и обновляются атомарно, в результате получается, что в файле будет ровно столько байтов, сколько объединены обе строки.

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

  1. процесс A считывает смещение в A.offset
  2. процесс B считывает смещение в B.offset
  3. процесс A записывает байт в A.offset
  4. процесс A устанавливает смещение = A.offset + 1
  5. процесс B записывает байт в B.offset
  6. процесс A считывает смещение в A.offset
  7. процесс B устанавливает смещение = B.offset + 1
  8. процесс A записывает байт в A.offset
  9. процесс A устанавливает смещение = A.offset + 1
  10. процесс B считывает смещение в B.offset
  11. процесс B записывает байт в B.offset
  12. процесс B устанавливает смещение = B.offset + 1

Примерно такова должна быть последовательность событий. Как очень странно.

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

1 голос
/ 27 октября 2010

используйте pwrite, поскольку запись иногда заканчивается условием гонки, когда один и тот же ресурс (write ()) используется несколькими процессами, так как запись не оставляет файл pos = 0 после завершения, например, вы в конечном итоге в середине файла, так что указатель файла (fd) указывает на это местоположение, и если другой процесс хочет что-то сделать, он производит или работает не так, как он хотел, поскольку файловый дескриптор будет использоваться совместно для разветвления !!

Попробуй вернуть мне

1 голос
/ 28 октября 2009

Ну, я настроил код для компиляции на vanilla GCC / glibc, и вот пример вывода:

thhis isias a l long oulout futput frd
 parent

И я думаю, что это поддерживает идею о том, что файловая позиция является общей для , а является для гонок, вот почему это так странно. Обратите внимание, что данные, которые я показал, имеют 47 символов. Это больше, чем 38 или 39 символов каждого отдельного сообщения, и меньше, чем 77 символов обоих сообщений вместе - единственный способ увидеть, что происходит, - это если процессы иногда мчатся, чтобы обновить позицию файла - каждый из них пишет каждый из них пытается увеличить позицию, но из-за гонки происходит только одно увеличение, и некоторые символы перезаписываются.

Подтверждающие доказательства: man 2 lseek в моей системе четко сказано

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

1 голос
/ 28 октября 2009

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

...