3

在下面显示的一种特殊情况下,getpid()对于使用创建的孙子vfork()进程返回父进程的 PID。

#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);
    }
  }
}

编译为gcc main.c,这按预期工作:

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

编译为gcc main.c -lpthread,孙子 PID 不正确:

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

任何线索为什么?这是未定义的行为案例之一吗?

使用psstrace,我可以看到正确的 PID。顺便说一句,相同的示例代码适用于fork(),即无论是否正确getpid(),都可以正常工作-lpthread

4

3 回答 3

4

getpid不是您被允许vfork在孩子身上进行的两项手术之一;仅有的两个是execve_exit。碰巧 glibc 将进程的 pid 缓存在用户空间中,并且不会更新此缓存vfork(因为它会修改父缓存的值,并且由于有效代码无法观察结果,因此不需要它);这就是你所看到的行为机制。缓存行为与-lpthread链接略有不同。但根本原因是您的代码无效。

差不多,不要用vfork。基本上你无能为力。

于 2020-01-31T15:11:52.487 回答
2

手册页vfork()

vfork() 函数与 具有相同的效果fork(2),除了如果由以下任一创建的进程修改了用于存储返回值的类型变量以外的任何数据,或从被调用的函数返回,或调用任何数据,则行为未定义成功调用之前的其他函数或函数族之一。vfork()pid_tvfork()vfork()_exit(2)exec(3)

它的措辞不是很好,但它的意思是子进程可以做的唯一事情vfork()是:

  • 检查返回值。
  • 调用exec*()函数族之一。
  • 打电话_exit()

这是因为:

vfork() 是 的一个特例clone(2)。它用于在不复制父进程的页表的情况下创建新进程。它可能在性能敏感的应用程序中很有用,在这些应用程序中创建了一个子级,然后立即发出一个execve(2).

换句话说, 的预期用途vfork()只是创建将通过 执行其他程序的子级exec*(),这比正常速度更快,fork()因为父级的页表在子级中没有重复(因为exec*()无论如何它都会被替换)。即便如此,vfork()只有在需要多次执行此类操作时才具有真正的优势。由于没有复制父内存,因此以任何方式访问它都是未定义的行为。

于 2020-01-31T15:51:21.127 回答
0

这是要求vfork()

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

   pid_t vfork(void);

请注意,OP 发布的代码未能包含所需的头文件。

于 2020-02-01T16:24:42.650 回答