fork() 基础

"Fork",除了它是一个当你不停地敲入后看起来非常奇怪的单词以外,通常是指 Unix 产生新进程的方式。由于系统调用的用法将会在其他 IPC 的文档中出现,本文只是一个快速的,不太精确的 fork() 初级读本。如果你已经通晓 fork() ,最好跳过此节。

"Seek ye the Gorge of Eternal Peril"

fork() 可被想象为力量的象征,力量有时可以被想象为毁灭的象征。因此,当你的系统由于 fork() 而杂乱无章时,一定要小心。这并不是说永远也别碰 fork(), 你只需要保持谨慎。它是象剑刃之类的一种东西,如果你很小心,就不会被开膛破肚。

如果你还在看的话,我想应该拿出点实际东西出来了。正如我所说, fork() 是 Unix 启动新进程的方式。最基本的,它是这样工作的:父进程(已经存在的那一个) fork() 一个子进程(新的一个)。子进程得到父进程数据的一个 拷贝.瞧!以前只有一个进程而现在有了两个。

当然,在 fork() 进程时 你必须得应付各种各样的情况,否则,你的系统管理员会对你怒目而视,因为你填满了系统的进程表 ,而他们不得不按下机器的重启键 。

首先,你必须知道在 Unix 下的一些进程的运作方式。当一个进程死亡时,它并不是完全的消失了。进程终止,它不再运行,但是还有一些残留的小东西等待父进程收回。这些残留的东西包括子进程的返回值和其他的一些东西。当父进程 fork() 一个子进程后,它必须用 wait() (或者 waitpid())等待子进程退出。正是这个 wait() 动作来让子进程的残留物消失。

自然的,在上述规则之外有个例外:父进程可以忽略 SIGCLD 软中断而不必要 wait()。可以这样做到(在支持它的系统上):

    main()
    {
        signal(SIGCLD, SIG_IGN);  /* now I don't have to wait()! */
        .
        .
        fork();fork();fork();  /* Rabbits, rabbits, rabbits! */

现在,子进程死亡时父进程没有 wait(),通常用 ps 可以看到它被显示为“<defunct>”。它将永远保持这样只到父进程 wait(),或者按以下方法处理。

这里是你必须知道的另一个规则:当父进程在它 wait() 子进程之前死亡了(假定它没有忽略 SIGCLD),子进程将把 init (PID 1)进程作为它的父进程。如果子进程工作得很好并能够控制,这并不是问题。但如果子进程已经是 defunct,我们就有了一点小麻烦。看,原先的父进程不可能再 wait(),因为它已经消亡了。这样,init 怎么知道 wait() 这些 zombie 进程

答案:不可预料的。在一些系统上,init 周期性的破坏掉它所有的 defunct 进程。在另外一些系统中,它干脆拒绝成为任何 defunct 进程的父进程,而是马上毁灭它们。如果你使用上述系统的一种,可以写一个简单的循环,用 属于 init 的 defunct 进程填满进程表。这大概不会令你的系统管理员很高兴吧?

你的任务:确定你的父进程不要忽略 SIGCLD,也不要 wait()fork() 的所有进程。不过,你也每必要 总是这样做(比如,你要起一个 daemon 或是别的什么东西),但是你必须小心编程,如果你是一个 fork() 的新手。另外,也不要在心理上有任何束缚。

总结:子进程成为 defunct 只到父进程 wait(),除非父进程忽略了 SIGCLD 。更进一步,父进程没有 wait() 就消亡(仍假设父进程没有忽略 SIGCLD )的子进程(活动的或者 defunct)成为 init 的子进程,init 用重手法处理它们。

“我已经有了心理准备,把按钮给我”

好的!这里是怎样使用 fork() 的例子:
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    main()
    {
        pid_t pid;
        int rv;

        switch(pid=fork()) {
            case -1:
                perror("fork");  /* something went wrong */
                exit(1);         /* parent exits */

            case 0:
                printf(" CHILD: This is the child process!\n");
                printf(" CHILD: My PID is %d\n", getpid());
                printf(" CHILD: My parent's PID is %d\n", getppid());
                printf(" CHILD: Enter my exit status (make it small): ");
                scanf(" %d", &rv);
                printf(" CHILD: I'm outta here!\n");
                exit(rv);

            default:
                printf("PARENT: This is the parent process!\n");
                printf("PARENT: My PID is %d\n", getpid());
                printf("PARENT: My child's PID is %d\n", pid);
                printf("PARENT: I'm now waiting for my child to exit()...\n");
                wait(&rv);
                printf("PARENT: My child's exit status is: %d\n", WEXITSTATUS(rv));
                printf("PARENT: I'm outta here!\n");
        }
    }

这个例子中有大量的地方需要讲解,我们从头开始。

pid_t 是通用的进程类型。在 Unix 中,它是一个 short。因此,我调用 fork() 并把返回值存入 pid 变量。 fork() 很容易,它的返回值只可能是三种情况:

0:
如果返回 0,你正在子进程中。你可以通过调用 getppid() 得到父进程的 PID 。当然,你也可以通过调用 getpid 得到自己的 PID 。

-1:
如果返回 -1,有些东西出了错,将不会产生子进程。用 perror() 看看发生了什么事情。你可能已经填满了进程表——如果你一转身,发现系统管理员拿着消防斧正冲你走过来。

其他:
fork() 返回的其他值意味着你正在父进程中,返回值是子进程的 PID 。这是得到子进程 PID 的唯一方法,因为没有诸如 getcpid() 之类的系统调用(显而易见是因为父进程与子进程之间的一对多关系)。

当子进程最终调用 exit() 时,返回值将被传递到正在 wait() 的父进程。正如你从 wait() 调用所看到的那样,当我们打印返回值时会有些古怪。WEXITSTATUS() 到底是什么东西?它是一个宏,从 wait() 返回值中提取实际的返回值。对,在这个 int 中隐藏了太多的信息,你需要自己来挖掘。

现在你发问了,“wait() 怎么知道在哪个进程上等待?我的意思是,由于父进程可以有多个子进程, wait() 实际等待地是哪一个?”我的朋友,答案非常简单,它等待最先退出的那一个。你可以通过以子进程的 PID 为参数调用 waitpid() 指明是哪一个子进程。

上例中另一个需要注意的有趣的地方是父进程和子进程都使用 rv 变量。难道这意味着它被进程共享了吗?不!如果是这样,我就不会写这篇 IPC 的文章了。每个进程对所有变量都有自己的一个拷贝。还复制了其他的很多东西,你必须阅读 man 看看究竟是什么。

上述程序中最后要注意的一个问题:我使用了一个 switch 语句来处理 fork(),这并不是非常典型,大多数情况下,你将会看到 if 语句;有时短得象这样:ere; sometimes it's as short as:

    if (!fork()) {
        printf("I'm the child!\n");
        exit(0);
    } else {
        printf("I'm the parent!\n");
        wait(NULL);
    }
Oh yeah——上述程序也示范了如果你不关心返回值的话,怎样使用 wait():你只须用 NULL 参数调用它。

结论

现在你知道了 fork() 的所有的有力的功能!它不光在计算机相关领域非常有用,而且会令你的朋友在 party 上大开眼界。另外,它会帮助你在异性面前更有魅力,男的除外。
回到 IPC 首页 (http://www.ecst.csuchico.edu/~beej/guide/ipc/)

Copyright © 1997 by Brian "Beej" Hall. This guide may be reprinted in any medium provided that its content is not altered, it is presented in its entirety, and this copyright notice remains intact. Contact beej@ecst.csuchico.edu for more information.