2013-01-23 96 views
2

以下代码永不结束。这是为什么?vfork永无止境

#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()时,子进程不会复制父进程的地址空间。相反,它共享地址空间。在这种情况下,我期望nums数组被两个进程更新,我的问题是按什么顺序?操作系统如何在两者之间同步?至于为什么代码永远不会结束,这可能是因为我没有任何_exit()exec()语句显式退出。我对吗?

UPDATE2:
我刚才读:56. Difference between the fork() and vfork() system call? ,我认为这篇文章可以帮助我与我的第一个困惑。

从的vfork()系统调用的子进程来执行父的 地址空间(这可以覆盖父的数据和堆栈),这 暂停父进程,直到子进程退出。

+0

'int main(void)'80年代很久以前。 –

+0

我想我倾向于同意'vfork'上的Linux手册页:“Linux非常不幸地从过去恢复了这个幽灵”。考虑到'fork'(在你可能使用的任何Linux版本中)一个轻量级的操作,我会停止担心'vfork'可能会或可能不会做什么。 –

+0

你的假设有一个错误。 “相反,它共享地址空间。”这是不正确的。它可以共享地址空间,但不一定。一些操作系统只实现挂起父母的'vfork'语义,但仍然像普通的'fork'那样拷贝地址空间。原因是共享地址空间导致了很多错误(我似乎记得某些系统调用malloc和父内存泄漏时的execvp)。 – Art

回答

3

请勿使用vfork。这是你可以得到的最简单的建议。 vfork给你的唯一情况是暂停父母,直到孩子拨打exec*_exit。关于共享地址空间的部分是不正确的,一些操作系统执行它,其他选择不,因为它是非常不安全的并且导致严重的错误。

上次我看着应用程序如何使用vfork在现实中,绝大多数人是错的。这真是太糟糕了,我扔掉了那个在我当时正在操作的操作系统上启用地址空间共享的6个字符变化。几乎每个使用vfork的人至少会泄漏内存,如果不是更糟的话。

如果您确实想要使用vfork,除了在子进程中返回后立即调用_exitexecve之外,不要执行任何操作。还有什么,你正在进入未定义的领土。我的意思是“任何事情”。你开始解析你的字符串来为你的exec调用提供参数,并且你几乎可以保证某些东西会触及它不应该触及的东西。我的意思是execve,而不是exec系列的其他功能。许多libc在那里做的事情在上下文中是不安全的execvpexecl,execle等等。

什么是你的榜样具体发生了什么:

如果您的操作系统共享地址空间中的孩子从您的环境清理的东西了(平标准输出,因为你叫printf的主要手段回国,免费内存由printf等分配)。这意味着还有其他函数调用会覆盖父进程中的堆栈帧。vfork在父进程中返回时会返回到已被覆盖且可能发生的堆栈帧,它甚至可能没有堆栈上的返回地址回到了。您首先通过调用printf进入了未定义的行为国家,然后从主返回将您带入未定义的行为大陆,并且在从主返回后的清理运行使您前往未定义的行为星球。

+0

您能否解释一下execl会在vfork中造成哪些不安全?你说“许多libc”,所以你可以说,如果glibc是违法者之一?在不允许内存过量使用的系统上工作时,内核内存管理器可能会阻止内存密集型应用程序的分支,从而使vfork成为必要的罪恶。我想知道在哪里可以找到execl可能存在的危险的实际参考。 – codetaku

+0

@codetaku检查你的execl的实现。如果它触及到任何全局状态,尤其是调用malloc,它是不安全的。这是在不久前发现的,几乎每个操作系统都在许多exec函数中调用malloc。它受到了关注,因为一个特定的漏洞通过创建一些守护进程fork和exec来泄露内存并开始崩溃,大量的libc被修复,但是由于这是一个隐蔽的bug,它很有可能被攻击早在。 – Art

0

孩子必须_exit而不是从main返回。如果孩子从main返回,则从vfork返回时,父级框架不存在。

1

official specification

行为是不确定如果()由vfork的创建的流程修改比用于存储从vfork的()的返回值类型将为pid_t的变量之外的任何数据,

在您的程序中,您修改的数据不是pid变量,这意味着行为未定义。

您还必须致电_exit才能结束该过程,或者致电exec函数系列之一。

+1

我讨厌说POSIX是错的,但是POSIX是错误的。 'vfork'之后调用'execve'以外的任何东西都是血腥的危险。以glibc源代码为例。一些exec *函数调用malloc。这意味着如果我们共享地址空间,就相当于在父项中调用malloc。由于家长不知道这一点,我们泄漏记忆。 10 - 15年前就有一些漏洞触发了一些在vfork下重复调用exec并且内存不足的漏洞。 – Art

+0

我的意思是“'execve'或'_exit'”当然。 – Art

+1

我很好奇,并做了一些代码考古学。这个问题在1998年的FreeBSD libc中得到了修复,这意味着该漏洞是在当年发布的。在1999年之前,Linux并没有共享地址空间,这意味着glibc从来不必处理这个bug的功能漏洞,这意味着他们从来没有打算修复它,所以它在15年后仍然存在。我敢打赌,其他的libc也有同样的错误。 – Art

8

要从vfork(2)手册页引述如下:如果通过vfork的创建的过程()

该了vfork()函数具有作为叉(相同的效果),不同之处在于的行为是未定义要么修改除了用于存储vfork()的返回值的函数以外的任何数据,或者从调用vfork()的函数返回,或者在成功调用_exit()或其中一个exec系列之前调用任何其他函数的功能。

你正在做这些事情的一大堆,所以你不应该指望它工作。我认为这里真正的问题是:为什么你使用vfork()而不是fork()

+2

除了菲利普肯德尔的回答,请阅读:[** 56。 fork()和vfork()系统调用之间的区别?**](http://dev.fyicenter.com/Interview-Questions/UNIX/Difference_between_the_fork_and_vfork_system.html) –

+0

@GrijeshChauhan您的链接非常感谢,并帮助我解决我的困惑!谢谢 – zsljulius

+0

@PhilipKendall我分享这也从unix.stackexchange.com:[有什么区别fork()和vfork()?](http://unix.stackexchange.com/questions/5337/whats-the-差异之间叉和vfork) –

0

只是调用_exit而不是调用return或将_exit(0)插入到“子进程”中的最后一行。 return 0在关闭stdout时调用exit(0),所以当另一个printf出现时,程序崩溃。