Как я уже упоминал в комментарии, если родитель получает вывод от дочерних процессов, обычно проще использовать пару сокетов дейтаграммы домена Unix (или пару для каждого дочернего процесса). Сокеты дейтаграмм домена Unix сохраняют границы сообщений, так что каждая дейтаграмма, успешно отправленная с использованием send()
, принимается в одном recv()
. Вы даже можете отправлять данные в виде двоичных структур. Никаких замков не нужно. Если вы используете пару сокетов для каждого дочернего элемента, вы можете легко установить неблокирующую родительскую сторону и использовать select()
или poll()
для чтения дейтаграмм со всех в одном цикле.
Вернемся к собственно вопросу.
Вот пример реализации append_file(filename, format, ...)
, использующей POSIX.1-2008 vdprintf()
< /a> для записи в файл с использованием рекомендуемых блокировок записей на основе fcntl()
:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int append_file(const char *filename, const char *format, ...)
{
struct flock lock;
va_list args;
int fd, cause;
/* Sanity checks. */
if (!filename || !*filename)
return errno = EINVAL;
/* Open the file for appending. Create if necessary. */
fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0666);
if (fd == -1)
return errno;
/* Lock the entire file exclusively.
Because we use record locks, append_file() to the same
file is NOT thread safe: whenever the first descriptor
is closed, all record locks to the same file in the process
are dropped. */
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(fd, F_SETLKW, &lock) == -1) {
cause = errno;
close(fd);
return errno = cause;
}
if (format && *format) {
cause = 0;
va_start(args, format);
if (vdprintf(fd, format, args) < 0)
cause = errno;
va_end(args);
if (cause) {
close(fd);
return errno = cause;
}
}
/* Note: This releases ALL record locks to this file
in this process! */
if (close(fd) == -1)
return errno;
/* Success! */
return 0;
}
int main(int argc, char *argv[])
{
int arg = 1;
if (argc < 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s FILENAME STRING [ STRING ... ]\n", argv[0]);
fprintf(stderr, "\n");
}
for (arg = 2; arg < argc; arg++)
if (append_file(argv[1], "%s\n", argv[arg])) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Если все писатели используют указанный выше append_file()
для добавления к файлу, можно безопасно переименовать файл в любой момент. (Обратите внимание, что один или несколько процессов могут выполнить последнее добавление к файлу после переименования, если они ожидали снятия блокировки записи во время переименования.)
Чтобы обрезать файл, сначала установите на него монопольную блокировку, а затем вызовите ftruncate(fd, 0)
.
Чтобы прочитать файл, используйте общую блокировку на основе fcntl()
F_RDLCK
(разрешая другим читателям одновременно; или F_WRLCK
, если вы намерены «атомарно» обрезать файл после того, как прочитали текущее содержимое), или вы можете увидеть частичная итоговая запись в конце.
person
Nominal Animal
schedule
22.07.2018
fork()
, а не потоки). - person Jonathan Leffler   schedule 22.07.2018int fd[2];
черезsocketpair(AF_UNIX, SOCK_DGRAM, 0, fd)
), возможно, по одной паре сокетов на каждого потомка. Сокет дейтаграммы домена Unix сохраняет границы сообщений (поэтому каждый успешныйsend()
принимается с использованием только одногоrecv()
. Вы даже можете отправлять/получать двоичные сообщения, скажем, парыstruct in_addr
илиstruct in6_addr
s. Блокировка не требуется, а простое неблокирующееselect()
/poll()
+recv()
цикл в родительском элементе может без проблем получать данные от нескольких клиентов. - person Nominal Animal   schedule 22.07.2018