Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1814573
  • 博文数量: 438
  • 博客积分: 9799
  • 博客等级: 中将
  • 技术积分: 6092
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-25 17:25
文章分类

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-03-29 13:20:46

当一个进程终止时,不管是正常还是异常地,内核通过向父进程发送SIGCHLD来通知父进程。因为子进程的终止是一个异步事件--它可以在父进程运行时的 任何时间发生--所以这个信号是从内核发送给父进程的异步通知。父进程可以选择忽略这个信号,或它提供一个当信号发生时被调用的函数:一个信号处理器。这 个信号的默认行为是被忽略。我们在第10章讲述这些选项。至于现在,我们需要知道一个调用wait或waitpid的进程会:


1、阻塞,如果它所有的子进程都还在运行;


2、如果一个子进程终止并等待它的终止状态被获取,则随着子进程的终止状态立即返回;


3、如果它没有任何子进程,则立即返回一个错误。


如果进程是因为收到SIGCHLD信号而正调用wait,那么我们预期wait会立即返回。但是如果我们在任何随机的时间点时调用它,那么它会阻塞。



  1. #include <sys/wait.h>

  2. pid_t wait(int *statloc);

  3. pit_t waitpid(pid_t pid, int *statloc, int options);

  4. 两者成功返回进程ID,失败返回0或-1


这两个函数的区别如下:


1、wait函数可以阻塞调用者,直到一个子进程终止,而waitpid有选项可以避免它阻塞;


2、waitpid函数不等待最先终止的子进程;它有许多选项来控制进程等待哪个进程。


如果一个子进程已经终止并成为一个僵尸,那么wait会用子进程的状态立即返回。否则,它会阻塞调用者,直到子进程终止。如果调用者阻塞并有多个子进程,那么wait当某个进程终止时返回。我们总是可以知道哪个子进程终止了,因为这个函数返回这个进程ID。


对于两个函数,参数statloc是一个整型指针。如果参数不是一个空指针的话,那么终止的进程的终止状态由这个参数指向的地址存储。如果我们不关心终止状态,我们可以简单地传为一个空指针作为参数。


传统地,这两个函数返回的整型状态由实现来定义。(对于正常返回)其中的某些位来指明退出状态,(对于一个异常返回)另一些位指明信号号,一个位来指明是 否有核心文件产生,等等。POSIX.1规定了这个终止状态要用各种定义在里的宏查看。四个互斥宏告诉我们进程如何 终止,且它们都以WIF开头。基于这四个宏哪个为真,其它一些宏被用来获得退出状态、信号号、等等。这四个互斥宏在前面已经展示过了。


我们将在9.8节讨论工作控制时讨论一个进程如何可以被停止。


下面的代码展示了各种终止状态:



  1. #include <unistd.h>
  2. #include <sys/wait.h>

  3. void
  4. pr_exit(int status)
  5. {
  6.     if (WIFEXITED(status))
  7.         printf("normal termination, exit status = %d\n",
  8.             WEXITSTATUS(status));
  9.     else if (WIFSIGNALED(status))
  10.         printf("abnormal termination, signal number = %d%s\n",
  11.             WTERMSIG(status),
  12. #ifdef WCOREDUMP
  13.             WCOREDUMP(status) ? " (core file generated)" : "");
  14. #else
  15.             "");
  16. #endif
  17.     else if (WIFSTOPPED(status))
  18.         printf("child stopped, signal number = %d\n",
  19.             WSTOPSIG(status));
  20. }

  21. int
  22. main(void)
  23. {
  24.     pid_t pid;
  25.     int status;

  26.     if ((pid = fork()) < 0) {
  27.         printf("fork error\n");
  28.         exit(1);
  29.     }
  30.     else if (pid == 0) /* child */
  31.         exit(7);

  32.     if (wait(&status) != pid) { /* wait for child */
  33.         printf("wait error\n");
  34.         exit(1);
  35.     }
  36.     pr_exit(status); /* and print its status */

  37.     if ((pid = fork()) < 0) {
  38.         printf("fork error\n");
  39.         exit(1);
  40.     } else if (pid == 0) /* child */
  41.         abort(); /* generates SIGABRT */

  42.     if (wait(&status) != pid) { /* wait for child */
  43.         printf("wait error\n");
  44.         exit(1);
  45.     }
  46.     pr_exit(status); /* and print its stauts */


  47.     if ((pid = fork()) < 0) {
  48.         printf("fork error\n");
  49.         exit(1);
  50.     } else if (pid == 0) /* child */
  51.         status /= 0; /* divide by 0 generates SIGFPE */

  52.     if (wait(&status) != pid) {
  53.         printf("wait error\n");
  54.         exit(1);
  55.     }
  56.     pr_exit(status);

  57.     exit(0);
  58. }

函数pr_exit使用前面介绍的宏来打印终止状态的描述。我们将在本文很多程序里调用这个函数。注意这个WCOREDUMP宏有定义时会宏理这个宏。

FreeBSD 5.2.1、Linux 2.4.22、Mac OS X 10.3,和Solaris 9都支持WCOREDUMP宏。


下面是程序运行的结果:


normal termination, exit status = 7
abnormal termination, signal number = 6
abnormal termination, signal number = 8


不幸的是,没有一个可移植的方法来把从WTERMISG得到的信号号映射到可描述的名字。(10.21节有一种方法。)我们并须查看头文件来核查各信号的值。


正如我们已经提过的,如果我们有不只一个子进程,wait在任何一个子进程终止时返回。如果我们想等一个指定进程终止是会发生什么呢(假定我们知道我们要 等待的进程ID)?在UNIX系统的早期版本,我们必须调用wait并把返回的进程ID和我们感兴趣的那个进行比较。如果终止的进程不是我们想要的,那我 们必须保存进程ID和终止状态,然后再次调用wait。我们需要持续这样做,直到需要的进程被终止。下一次我们想等一个特定的进程时,我们需要遍历已经终 止的进程的列表来看我们是否已经等待过它,如果没有,则再次调用wait。我们需要的是一个等待特定进程的函数。这种功能(和更多功能)由POISX.1 的waitpid函数提供。


waitpid的pid参数的解释取决于它的值:


pid = -1:等待任何一个子进程。这种用法等同于wait。


pid >0:等待进程ID为pid的子进程。


pid == 0:等待任何进程组ID和调用进程相同的子进程(我们在9.4节讨论进程组ID)


pid < -1:等待任何进程组ID等于pid的绝对值的子进程。


waitpid函数返回终止的子进程的ID,并把子进程的终止状存储到statloc指向的内存地址里。对于wait,唯一的真实的错误是调用进程没有子 进程。(其它错误返回也是可能的,万一函数调用被一个信号中断。我们将在第十章讨论这个。)然而对于waitpid,还有可能当指定的进程或进程组不存 在,可不是调用进程的子进程时会得到错误。


options参数让我们更多地控制waitpid的操作。这个参数是0或者由下表里的常量的与或值组成:

   
常量 描述
WCONTINUED 如果实现支持工作控制,那么返回任何由pid指定的、在停止后又继续的、但其状态还没有被报告的子进程的状态。(POISX.1的XSI扩展。)
WNOHANG 如果pid指定的子进程不是立即可用的,waitpid函数不会阻塞。这种情况下,返回值为0.
WUNTRACED 如果实现支持工作控制,那么返回任何由pid指定的、其状态在停止后还未被报告的子进程的状态。WIFSTOPPED宏决定了返回值是否对应于一个停止的子进程。


Solaris支持一个补充的,但不是标准的选项常量,WNOWAIT,它让系统保存这个进程,它的终止状态由waitpid返回,以便它可以被再次等待。


waitpid函数提供了三个不被wait函数提供的特性:


1、waitpid函数让我们等待一个特定的进程,而wait函数返回任何终止的子进程的状态。我们将在讨论popen函数里再回到这个特性。


2、waitpid函数提供了一个wait的非阻塞版本。有时我们想得到子进程的状态,而不想阻塞。


3、waitpid函数用WUNTRACED和WCONTINUED选项提供了对工作控制的支持。


下面的代码避免僵尸程序:


  1. #include <sys/wait.h>
  2. #include <unistd.h>
  3. #include <stdio.h>

  4. int
  5. main(void)
  6. {
  7.     pid_t pid;

  8.     if ((pid = fork()) < 0) {
  9.         fprintf(stderr, "fork error\n");
  10.         exit(1);
  11.     } else if (pid == 0) { /* first child */
  12.         if ((pid = fork()) < 0) {
  13.             printf("fork error\n");
  14.             exit(1);
  15.         } else if (pid > 0)
  16.             exit(0); /* parent from second fork == first child */
  17.         /*
  18.          * We're the second child; our parent becomes init as soon
  19.          * as our real parent calls exit() in the statement above.
  20.          * Here's where we'd continue executing, knowing that when
  21.          * we're done, init will reap our status.
  22.          */
  23.         sleep(2);
  24.         printf("second child, parent pid = %d\n", getppid());
  25.         exit(0);
  26.     }

  27.     if (waitpid(pid, NULL, 0) != pid) { /* wait for first child */
  28.         printf("waitpid error\n");
  29.         exit(1);
  30.     }

  31.     /*
  32.      * We're the parent (the original process); we continue executing,
  33.      * knowing that we're not the parent of the seconde child.
  34.      */
  35.     exit(0);
  36. }


如果我们想写一个进程以便它fork一个子进程,但不想等待它完成,而我们又不想这个子进程变为一个僵尸,直到我们终止,技巧是调用fork两次。


我们在第二个子进程里调用sleep,保证第一个子进程在打印父进程ID之前退出。在fork之后,父进程或子进程可以继续执行,我们不会知道两个会先继 续执行。如果我们不让第二个子进程睡眠,而且它在fork后比它的父进程更早继续执行,那么它打印的父进程ID就会是它的父进程,而不是进程ID 1。


运行结果:
$ ./a.out
$ second child, parent pid = 1


注意shell当原始进程终止时打印它的命令提示符,它发生在第二个子进程打印它父进程ID之前。

阅读(1002) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~