在OSX上,fork+wait时遇到EINTR错误。

4

我在fork 101方面失败了。我期望这个程序能够fork一个子进程,并输出子进程和父进程的printf语句:

pid_t fpid;

if ((fpid = fork()) < 0)
{
    printf("fork: %s\n", strerror(errno));
    exit(-1);
}

if (0 == fpid) // child
{
    printf("\nI am the child\n");
}
else
{
    printf("\nI am the parent\n");
    pid_t wpid;
    while ((wpid = waitpid(WAIT_ANY, NULL, 0)))
    {
        if (errno == ECHILD)
            break;
        else if (wpid < 0)
            printf("wait: %s\n", strerror(errno));
    }
}

然而,我得到了这个输出:

我是父进程

等待:系统调用被中断

所以我的问题是:为什么子进程没有机会运行?有没有人想过孩子们!此外,EINTR是从哪里来的?显然,这与我的第一个问题有关。

此外,当我在独立程序中运行该代码时,它能正常工作,但在我的大型程序中却不能工作;我的大型程序可能会干扰waitpid吗?

FWIW,这是在OSX 10.9上进行的。


孩子进程没有发送SIGCHLD信号吗?也许可以阻塞该信号? - Kerrek SB
奇怪,它在我的Linux机器上可以运行。我不太了解OSX...它是否有某种沙盒功能或需要特殊的应用程序权限来执行“fork()”? - TypeIA
在FreeBSD上运行良好,它是OSX的近亲。 - codnodder
我没有明确使用SIGCHLD,但我正在使用setjmp(),这可能有关吗? - Yusuf X
2
waitpid() 执行后,wpid 的值是多少?如果不是 -1,则您无权查看 errno。请参阅fork() 在 Mac OS X 中的子进程中设置的 errno 以获取相关场景。 - Jonathan Leffler
2个回答

4
在OSX上,在执行exec之前/之后,子进程中做许多事情是不合法的。请参见fork man page底部的注意事项。安全函数列表在sigaction(2) man page中。其中printf()不在其中。
此外,stdout很可能被缓冲。可能没有刷新printf()的结果。如果您调用exit(),则会刷新,但这在fork的子进程中也是不合法的。(使用_exit()是适当的,但它不刷新打开的流。)如此,您似乎没有退出子进程,这意味着执行流继续到您展示的代码的调用者,然后可能返回到程序的其余部分。由于fork的子进程的限制,它可能会卡在那里。
如果您在子进程中执行以下操作,则可能会有更好的运气:
const char msg[] = "\nI am the child\n";
write(STDOUT_FILENO, msg, sizeof(msg) - 1);
_exit(0);

最后,我认为你应该传递fpid而不是WAIT_ANYwaitpid()。你有一个特定的子进程需要等待。在更大的程序上下文中,你不想夺取其他子组件产生的通知终止的子进程。并且你总是需要循环中断系统调用,直到它们返回除EINTR之外的其他内容。

因此,该警告指出:“在这种情况下,执行自己是合理的”,这就是我需要做的。如果应用程序不一定在工作目录中,我如何知道要传递什么来表示“自己”? - Yusuf X
考虑到您正在使用OS X,您可以使用_NSGetExecutablePath()。如果您正在使用Cocoa,则可以使用[[[NSBundle mainBundle] executablePath] fileSystemRepresentation]。不过,在fork之前必须调用它们。当然,当您执行自己时,您需要传递一个特殊的参数,让新进程知道不要像正常情况下运行应用程序。如果应用程序被捆绑和/或链接到AppKit,则子进程可能会获得单独的Dock图标,这是您不想要的。 - Ken Thomases
@Gilles,这个限制是POSIX规范的一部分。这也是常识。当你调用fork()时,只有调用fork()的线程会在子进程中继续存在,其他线程会消失。这些其他线程可能正在持有系统库中的共享资源,比如锁。因此,调用任何非异步取消安全的例程容易导致死锁或发现内部数据结构处于不一致状态。 - Ken Thomases
@KenThomases 在大多数Unix系统上,只有多线程程序才会受到影响。否则,在传统的Unix系统中,fork会复制进程,接下来发生什么事情都没有关系。还有其他可能的不良影响,例如stdio缓冲区被复制,但在单线程程序中,在fork之后避免使用某些库函数是没有要求的。 - Gilles 'SO- stop being evil'
@Gilles,但是仅仅因为应用程序代码没有创建其他线程并不意味着程序一定是单线程的。系统库可以为自己的目的使用线程。实际上,您必须假设所有程序都是多线程的。 - Ken Thomases

0

只有在出现失败的情况下才检查errno,例如系统调用返回-1

代码应该像这样:

pid_t fpid;

if ((fpid = fork()) < 0)
{
    printf("fork: %s\n", strerror(errno));
    exit(-1);
}

if (0 == fpid) // child
{
    printf("\nI am the child\n");
}
else
{
    printf("\nI am the parent\n");

    pid_t wpid;
    while ((wpid = waitpid(WAIT_ANY, NULL, 0)))
    {
        if (-1 == wpid)
        {
            perror("waitpid() failed");
        }
        else if (wpid == fpid)
        {
            /* My child ended, so stop waiting for it. */
            break;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接