使用 vfork() 和 -lpthread 的孙子的 getpid() 结果不正确
Incorrect result from getpid() for grandchild with vfork() and -lpthread
在下面显示的一种特殊情况下,getpid()
对于使用 vfork()
returns 父进程的 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
有什么线索吗?这是未定义的行为案例之一吗?
使用 ps
和 strace
,我可以看到正确的 PID。顺便说一句,相同的示例代码在 fork()
上工作正常,即在有或没有 -lpthread
.
的情况下正确 getpid()
getpid
不是允许您在 child 中的 vfork
之后执行的两个操作之一;仅有的两个是 execve
和 _exit
。碰巧 glibc 将进程的 pid 缓存在用户空间中,并且不会在 vfork
上更新此缓存(因为它会修改 parent 的缓存值,并且由于有效代码不能观察结果);这就是您所看到的行为的机制。 -lpthread
链接的缓存行为略有不同。但根本原因是您的代码无效。
差不多,不要用vfork
。基本上您无能为力。
来自 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.
它的措辞不是很好,但它的意思是 child 进程在 vfork()
之后唯一可以做的事情是:
- 检查 return 值。
- 调用
exec*()
函数族之一。
- 呼叫
_exit()
.
这是因为:
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)
.
换句话说,vfork()
的预期用途只是创建 children,它将通过 exec*()
执行其他程序,这比正常的 fork()
因为 parent 的页面 table 在 child 中没有重复(因为它无论如何都会被 exec*()
替换)。尽管如此,vfork()
只有在这种操作需要多次执行时才有真正的优势。由于 parent 内存未被复制,因此以任何方式访问它都是未定义的行为。
这是vfork()
的要求
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
请注意 OP 发布的代码未能包含所需的头文件。
在下面显示的一种特殊情况下,getpid()
对于使用 vfork()
returns 父进程的 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
有什么线索吗?这是未定义的行为案例之一吗?
使用 ps
和 strace
,我可以看到正确的 PID。顺便说一句,相同的示例代码在 fork()
上工作正常,即在有或没有 -lpthread
.
getpid()
getpid
不是允许您在 child 中的 vfork
之后执行的两个操作之一;仅有的两个是 execve
和 _exit
。碰巧 glibc 将进程的 pid 缓存在用户空间中,并且不会在 vfork
上更新此缓存(因为它会修改 parent 的缓存值,并且由于有效代码不能观察结果);这就是您所看到的行为的机制。 -lpthread
链接的缓存行为略有不同。但根本原因是您的代码无效。
差不多,不要用vfork
。基本上您无能为力。
来自 the manual page for vfork()
:
The
vfork()
function has the same effect asfork(2)
, except that the behavior is undefined if the process created byvfork()
either modifies any data other than a variable of typepid_t
used to store the return value fromvfork()
, or returns from the function in whichvfork()
was called, or calls any other function before successfully calling_exit(2)
or one of theexec(3)
family of functions.
它的措辞不是很好,但它的意思是 child 进程在 vfork()
之后唯一可以做的事情是:
- 检查 return 值。
- 调用
exec*()
函数族之一。 - 呼叫
_exit()
.
这是因为:
vfork()
is a special case ofclone(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 anexecve(2)
.
换句话说,vfork()
的预期用途只是创建 children,它将通过 exec*()
执行其他程序,这比正常的 fork()
因为 parent 的页面 table 在 child 中没有重复(因为它无论如何都会被 exec*()
替换)。尽管如此,vfork()
只有在这种操作需要多次执行时才有真正的优势。由于 parent 内存未被复制,因此以任何方式访问它都是未定义的行为。
这是vfork()
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
请注意 OP 发布的代码未能包含所需的头文件。