下面的代码永远不会结束。 这是为什么?
#include <sys/types.h> #include <stdio.h> #include <unistd.h> #define SIZE 5 int nums[SIZE] = {0, 1, 2, 3, 4}; int main() { int i; pid_t pid; pid = vfork(); if(pid == 0){ /* Child process */ for(i = 0; i < SIZE; i++){ nums[i] *= -i; printf(”CHILD: %d “, nums[i]); /* LINE X */ } } else if (pid > 0){ /* Parent process */ wait(NULL); for(i = 0; i < SIZE; i++) printf(”PARENT: %d “, nums[i]); /* LINE Y */ } return 0; }
更新:
这段代码只是为了说明我对vfork()
一些混淆。 看起来好像当我使用vfork()
,subprocess不会复制父进程的地址空间。 相反,它共享地址空间。 在这种情况下,我期望nums数组得到两个进程更新,我的问题是在什么顺序? 操作系统如何在两者之间同步?
至于为什么代码永远不会结束,这可能是因为我没有任何显式退出的_exit()
或exec()
语句。 我对吗?
UPDATE2:
我只是读: 56. fork()和vfork()之间的区别系统调用? 我觉得这篇文章能帮助我解决第一个困惑。
从vfork()系统调用的subprocess在父进程中执行(这可以覆盖父进程的数据和堆栈),这会暂停父进程,直到subprocess退出。
不要使用vfork
。 这是你可以得到的最简单的建议。 vfork
唯一给你的是挂起父项,直到子项调用exec*
或_exit
。 关于共享地址空间的部分是不正确的,一些操作系统是这样做的,其他选择不是因为它是非常不安全的,并且导致严重的错误。
上次我看了应用程序如何在实际中使用vfork
,绝大多数人是错的。 这是非常糟糕的,我扔掉了在当时正在操作的操作系统上启用地址空间共享的6个字符变化。 几乎每个使用vfork
的人至少都会泄漏内存,如果不是更糟的话。
如果您真的想使用vfork
,除了在子进程中返回之后立即调用_exit
或execve
以外,不要执行其他任何操作。 其他任何东西,你正在进入未定义的领土。 我的意思是“任何事情”。 你开始解析你的字符串来为你的exec调用提供参数,并且你几乎可以保证某些东西会触及它不应该触及的东西。 我也指execve
,而不是执行家族的其他职能。 许多libc在那里执行execvp
, execl
, execle
等在vfork
上下文中不安全的东西。
你的例子中具体发生了什么:
如果您的操作系统共享地址空间,那么从主返回的孩子意味着您的环境清理了一些东西(自从调用printf之后刷新标准输出,由printf分配的可用内存等)。 这意味着还有其他函数会调用它将覆盖父进程的栈帧。在父进程中返回的
vfork
返回一个被覆盖的栈帧,任何事情都可能发生,甚至可能在栈上没有返回地址回到了。 你首先通过调用printf进入未定义行为的国家,然后从主返回带来你进入未定义的行为大陆和主返回后的清理运行使你旅行到未定义的行为星球。
从vfork(2)
手册页引用:
除了如果vfork()创建的进程修改除了用于存储来自vfork()的返回值的类型pid_t的变量之外的任何数据,vfork()函数与fork()具有相同的效果,或从调用vfork()的函数返回,或者在成功调用_exit()或某个exec系列函数之前调用任何其他函数。
你正在做很多这样的事情,所以你不应该期待它的工作。 我认为这里真正的问题是:为什么你使用vfork()
而不是fork()
?
从官方规格 :
如果由vfork()创建的进程修改除用于存储来自vfork()的返回值的类型pid_t的变量以外的任何数据,则行为是未定义的,
在你的程序中,你可以修改pid
变量以外的数据,这意味着行为是不确定的。
您还必须调用_exit
来结束进程,或者调用exec
系列函数之一。
孩子必须退出,而不是从main
返回。 如果孩子从main
返回,那么从vfork
返回时,父框架不存在。
只需调用_exit而不是调用return或将_exit(0)插入到“子进程”的最后一行。 返回0调用退出(0),同时关闭标准输出,所以当另一个printf如下,程序崩溃。