Skip to content
Advertisement

How atomic the fork() syscall actually is?

Assuming check_if_pid_exists(pid) returns true when a process with such a pid exists (but possibly hasn’t been running yet) or false when there is no process with such pid, is there any chance in parent code for a race condition when the fork() returned the child pid, however the kernel hasn’t had a chance to initialize the data structures so that check_if_pid_exists(child) returns false? Or perhaps after returning from fork() we have a guarantee that check_if_pid_exists(pid) returns true?

pid_t child = fork();

if (child == 0) {
  /* here the child just busy waits */
  for (;;)
    ;
}

if (child > 0) {
  /* here the parent checks whether child PID already exists */
  check_if_pid_exists(child);
}

Advertisement

Answer

No.

The fork() returns after the new task is created and visible as expected by the parent. Most everything wouldn’t work otherwise.

Whether the process has had a chance to run at all or has started quite a bit, is not known at that point. Whether the child has completed is known as you receive the SIGCHLD signal once that happens.

Where you can have a race is with the SIGCHLD if not handled properly. That is, you are expected to ignore the SIGCHLD signal, call fork(), save the results appropriately so you have the PID of the child (i.e. allocate a struct to save the child pid_t value), then use one of the wait() functions to know whether the child died.

Assuming your fork()ed process is expected to run for a while, then the

check_if_pid_exists(child) == true

will likely be true 99.9999% of the time (actually, assuming the parent is in control of when the child is expected to exit, make it 100% of the time).

As mentioned by others, many things can prevent the new process from running:

  • Not enough memory
  • You already started too many children
  • The child tries to do something and encounters a fatal error and exits
  • Some third party thing prevents the fork()

Also, fork() may return -1 in case it fails to create the child.

However, if the question was about: how do I track the lifetime of a child? Then the correct answer is for the parent to check whether it died. You should not rely on a function such as check_if_pid_exists() searching for the process under /proc/... or similar implementation (see waitid), because such a function may determine that the process is still running, then the process dies, and yet the function still returns true…

// ignore SIGCHLD
struct sigaction sa, chld;
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
__sigemptyset (&sa.sa_mask);
sigaction(SIGCHLD, &sa, NULL);

[...]

int child_is_running = 0;

[...]

pid_t child = fork();
if(child == 0) ...do stuff in the child...

if(child < 0) ...handle error...

int child_is_running = 1;

[...]

wait(...);

if(...child exited...) child_is_running = 0;

Now you can write a safe check_if_pid_exists():

int check_if_pid_exists()
{
    return child_is_running;
}

In my example here, I only allow one child. If you need multiple, that’s where you need a better scheme (probably a struct to save the child info and a table of some sort or linked list of all your children).

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement