Incorrect result from getpid() for grandchild with vfork() and -lpthread

321 Views Asked by At

In one of the special cases shown below, getpid() for the grandchild created with vfork() returns the PID of the parent process.

#include <stdio.h>
#include <stdlib.h>

int main() {
  if(vfork()) { /* parent */
    printf("parent pid = %d\n", getpid());
    exit(0);
  } else {
    if(vfork()) { /* child */
      printf("child pid = %d\n", getpid());
      exit(0);
    } else { /* grandchild */
      printf("grandchild pid = %d\n", getpid());
      exit(0);
    }
  }
}

Compiled as gcc main.c, this works as expected:

grandchild pid = 12241
child  pid = 12240
parent pid = 12239

Compiled as gcc main.c -lpthread, the grandchild PID is incorrect:

grandchild pid = 12431
child pid = 12432
parent pid = 12431

Any clues why? Is this one of the undefined behavior cases?

With ps and strace, I can see the correct PID. BTW, the same example code works fine with fork(), i.e. correct getpid() with or without -lpthread.

3

There are 3 best solutions below

5
On BEST ANSWER

getpid is not one of the two operations you're permitted to perform after vfork in the child; the only two are execve and _exit. It happens that glibc caches the process's pid in userspace, and does not update this cache on vfork (since it would modify the parent's cached value, and since it's not needed since valid code can't observe the result); that's the mechanism of the behavior you're seeing. The caching behavior differs slightly with -lpthread linked. But the underlying reason is that your code is invalid.

Pretty much, don't use vfork. There's basically nothing you can do with it.

1
On

here is the requirements for vfork()

   #include <sys/types.h>
   #include <unistd.h>

   pid_t vfork(void);

Notice the OPs posted code fails to include the needed header files.

4
On

From the manual page for vfork():

The vfork() function has the same effect as fork(2), except that the behavior is undefined if the process created by vfork() either modifies any data other than a variable of type pid_t used to store the return value from vfork(), or returns from the function in which vfork() was called, or calls any other function before successfully calling _exit(2) or one of the exec(3) family of functions.

It's not very nicely worded, but what this is saying is that the only things that a child process can do after vfork() are:

  • Check the return value.
  • Call one of the exec*() family of functions.
  • Call _exit().

This is because:

vfork() is a special case of clone(2). It is used to create new processes without copying the page tables of the parent process. It may be useful in performance-sensitive applications where a child is created which then immediately issues an execve(2).

In other words, the intended use of vfork() is only to create children that will execute other programs through exec*(), making this faster than a normal fork() because the page table of the parent is not duplicated in the child (since it's going to be replaced by exec*() anyway). Even then though, vfork() only has a real advantage if this kind of operation needs to be performed multiple times. Since parent memory is not copied, accessing it in any way is undefined behavior.