Several processes are written to the same file (C)

There is a program (Ubuntu 12.04 LTS, single core processor):

#include <stdio.h> #include <unistd.h> #include <signal.h> #include <fcntl.h> #include <sys/types.h> int main(){ mode_t mode = S_IRUSR | S_IWUSR; int i = 0, fd, pid; unsigned char pi1 = 0x33, pi2 = 0x34; if((fd = open("res", O_WRONLY | O_CREAT | O_TRUNC, mode)) < 0){ perror("open error"); exit(1); } if((pid = fork()) < 0){ perror("fork error"); exit(1); } if(pid == 0) { if(write(fd, &pi2, 1) != 1){ perror("write error"); exit(1); } }else{ if(write(fd, &pi1, 1) != 1){ perror("write error"); exit(1); } } close(fd); return 0; } 

The idea is to open the file for writing, and then go to fork. The position in which a record number will be reached for both processes. The strange thing is that if you run the program, then its output to the "res" file is not constant: I got angry then 34, then 4, then 3. The question is, why is such a conclusion? (In the end, if the position is divided, then the output should be either 34 or 43.).

In my suspicion, the process is interrupted in the write function when it finds a position for writing.

+4
source share
5 answers

When you create multiple processes using fork (), it is not possible to specify in which order they will be executed. It depends on the scheduler of the operating systems.

Thus, the presence of several processes in one file is a recipe for disaster.

As for the question, why sometimes one of the two numbers is omitted: the record first writes data, and then increases the file pointer. I think it is possible that the flow control will change at this very moment, so that the second stream will be recorded before the file position is updated. Thus, it overwrites the data that another process just wrote.

+3
source

I run your program several times, and the result is "34" or "43". So I wrote a shell script

 #!/bin/bash for i in {1..500} do ./your_program for line in $(cat res) do echo "$line" done done 

and run the program 500 times. As we can see, he gets β€œ3” or β€œ4” several times (about 20 times in 500). How can we explain this? The answer is: when we fork () a child process, the child has the same file description and the file state structure (which has the current file offset). In the normal case, the process first gets the offset = 0 and writes the first byte, and the offset = 1, and the other process gets the offset = 1, and it will write the second byte. But sometimes, if the parent process gets the offset = 0 from the file’s state structure, and the child gets the offset = 0 at the same time , the process writes the first byte and the other one overwrites the first byte, The result will be β€œ3” or β€œ4” (depends whether the parent is the first to record or the child). Because they both write the first byte of the file.

Fork and offset see this

+2
source

The standard says

Write requests in bytes {PIPE_BUF} must not alternate with data from other processes that write to the same channel.

Link - http://pubs.opengroup.org/onlinepubs/009696699/functions/write.html

Atomicity of recording is guaranteed only in case of recording into the pipe less than on PIPE_BUF, and it is not specified for a regular file, and we cannot allow this.

So, in this case, the race condition occurs, and this leads to incorrect data for some runs. (On my system, this also happens after several thousand runs).

I think you should consider using a mutex / semaphore / any other locking primitive to solve this problem.

+1
source

Are you sure you need fork () for sure? fork () creates another process with different amounts of memory (file descriptors, etc.). Maybe you will like pthreads? In the case of pthreads, you will use the same fd for all processes. But in any case, you should consider using mutexes in your project.

0
source

Here is what I think is going on.

  • You are an open file, and you are fork
  • The parent starts first (similar things can happen if the child starts first)
    • Parent writes 3 and exits
    • The parent was the control process for the terminal, so the kernel sends SIGHUP to all members of the foreground group
  • The default action of SIGHUP is to terminate the process so that the child dies quietly

An easy way to check this is to add a dream:

 sleep(5); /* Somewhere in the child, right before you write. */ 

You will see that the child process instantly dies: the recording is never performed .

Another way to check this is to ignore SIGHUP before using fork :

 sigignore(SIGHUP); /* Define _XOPEN_SOURCE 500 before including signal.h. */ /* You can also use signal(SIGHUP, SIG_IGN); */ 

You will see that the process now writes both numbers to a file.


The census hypothesis is unlikely. After fork both processes share a link to the same file descriptor in the entire system table, which also contains the file offset .

0
source

Source: https://habr.com/ru/post/1432720/


All Articles