Tenho o seguinte código C que grava em um arquivo tanto do processo pai quanto do filho após um fork()
. No entanto, a saída em testfile.txt
às vezes é corrompida ou está em uma ordem inesperada.
Estou anexando o código:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("testfile.txt", O_WRONLY | O_CREAT, 0644);
if (fd == -1) {
perror("open");
return 1;
}
if (fork() == 0) {
// Child process
write(fd, "Child\n", 6);
close(fd);
} else {
// Parent process
write(fd, "Parent\n", 7);
close(fd);
}
return 0;
}
Emitir :
- O arquivo às vezes contém
"Child\nParent\n"
e outras vezes"Parent\nChild\n"
- Em alguns casos, a saída é corrompida ou misturada
- Eu esperava que cada processo fosse escrito separadamente, mas eles parecem interferir um no outro
Pergunta:
- Por que isso está acontecendo?
- Como posso garantir gravações corretas e separadas de cada processo?
Abra o arquivo com o sinalizador `O_APPEND` para evitar corrupção:
A página do manual diz: "O arquivo é aberto no modo append. Antes de cada write(2), o deslocamento do arquivo é posicionado no final do arquivo, como se fosse com lseek(2). A modificação do deslocamento do arquivo e a operação de gravação são realizadas como uma única etapa atômica."
A ordem de saída não é controlada por este sinalizador.
Seus processos pai e filho são executados simultaneamente sem nenhuma medida em vigor para garantir a sincronização da operação de gravação. Isso significa que a ordem das duas gravações será arbitrária e pode variar dependendo de qual processo é agendado primeiro pelo sistema.
Além disso, as duas operações de gravação podem ocorrer ao mesmo tempo e o conteúdo pode ficar misturado. Isso é tecnicamente impossível em um sistema compatível com POSIX para gravações pequenas como a sua, porque uma operação de gravação deve ser atômica em relação a outros gravadores concorrentes para buffers de até
PIPE_SIZE
bytes. No entanto, para buffers maiores, é uma possibilidade.Se você quiser evitar isso, você terá que usar alguma forma de sincronização. Maneiras comuns de fazer isso são:
Se você só quer garantir exclusividade mútua (ou seja, sem conteúdo misto, uma gravação será executada completamente antes da outra), então um simples bloqueio de arquivo via
flock
syscall é o suficiente. Tecnicamente, isso já deveria ser o caso sem um bloqueio porque, como eu disse acima, essas pequenas gravações devem ser atômicas no Linux.Aqui está um exemplo:
Quando um dos dois processos estiver segurando o bloqueio, o outro processo bloqueará a
flock
chamada, que só será concluída quando o bloqueio for liberado pelo outro processo. Isso garante que as seções críticas entre oflock
e oclose
sejam executadas. Você sempre verá ouChild\nParent\n
,Parent\nChild\n
nunca algum conteúdo misto.Se, em vez disso, você também quiser garantir a ordem das duas operações, você terá que usar algo mais complexo. Um pipe ou um eventfd (este último é somente para Linux) são algumas das ferramentas mais simples que você pode usar para conseguir isso.
Aqui está um exemplo usando um pipe criado via
pipe
syscall:Neste caso você deve sempre ver
Parent\nChild\n
em seu arquivo.Como nota final: lembre-se de sempre verificar o valor de retorno de
fork
,write
e outras syscalls ou chamadas de função de biblioteca que podem falhar . Uma únicawrite
geralmente não garante que o buffer inteiro seja escrito, mais de uma escrita pode ser necessária.