在下面显示的特殊情况之一中,vfork()
创建的孙子getpid()
返回父进程的PID。
#include <stdio.h>
#include <stdlib.h>
int main() {
if(vfork()) { /* parent */
printf("parent pid = %dn", getpid());
exit(0);
} else {
if(vfork()) { /* child */
printf("child pid = %dn", getpid());
exit(0);
} else { /* grandchild */
printf("grandchild pid = %dn", 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
有什么线索吗?这是未定义的行为案例之一吗?
通过ps
和strace
,我可以看到正确的PID。顺便说一句,相同的示例代码适用于fork()
,即带或不带-lpthread
的正确getpid()
。
getpid
不是孩子vfork
后允许执行的两个操作之一;只有两个是execve
和_exit
。碰巧 glibc 在用户空间中缓存进程的 pid,并且不会在vfork
上更新此缓存(因为它会修改父级的缓存值,并且由于有效代码无法观察结果,因此不需要它(;这就是你所看到的行为的机制。缓存行为与链接-lpthread
略有不同。但根本原因是您的代码无效。
差不多,不要使用vfork
.基本上你无能为力。
从手册页vfork()
:
vfork()
函数与fork(2)
具有相同的效果,只是如果 由vfork()
创建的进程修改除用于存储返回值的pid_t
类型的变量以外的任何数据 fromvfork()
,或从调用vfork()
的函数返回,或在成功之前调用任何其他函数 调用_exit(2)
或exec(3)
函数系列之一。
它的措辞不是很好,但这是在说子进程在vfork()
之后唯一可以做的事情是:
- 检查返回值。
- 调用
exec*()
函数系列之一。 - 呼叫
_exit()
。
这是因为:
vfork()
是clone(2)
的特例。 它用于创建新进程,而无需复制父进程的页表。 它可能在性能敏感的应用程序中很有用,在这些应用程序中,创建一个子项,然后立即 发出execve(2)
。
换句话说,vfork()
的预期用途只是创建将通过exec*()
执行其他程序的子项,使其比普通fork()
更快,因为父项的页表在子项中没有重复(因为它无论如何都会被exec*()
替换(。即便如此,只有当这种操作需要多次执行时,vfork()
才具有真正的优势。由于父内存未被复制,因此以任何方式访问它都是未定义的行为。
这里是vfork()
的要求
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
请注意,OP 发布的代码无法包含所需的头文件。