015 UNIX再学习 -- exit 和 wait 系列函数

                                          
                                                                                   
                                                                                
                                           

我们一开始讲进程环境时,就有提到了。进程有 8 种方式使进程终止。

其中 5 种为正常终止,它们是:

(1)在 main 函数中执行 return
(2)调用 exit 函数,并不处理文件描述符,多进程
(3)调用 _exit 或 _Exit
(4)最后一个线程从其启动例程返回
(5)从最后一个线程调用 pthread_exit
异常终止有 3 种方式,它们是:
(6)调用 abort,产生 SIGABRT 信号
(7)进程接收到某些信号
(8)最后一个线程对“取消”请求做出响应 

我们之前是讲过的,参看:C语言再学习 -- 关键字return和exit ()函数   接下来重点介绍 exit 和 wait 系列函数。

一、exit 系列函数

1、exit 函数


    
    
  1. #include <stdlib.h>
  2. void exit(int status)

(1)函数功能

正常终止调用进程

(2)参数解析

status:进程退出码,相当于 main 函数的返回值。被终止的进程的父进程可以通过 wait 或 waitpid 函数,获取 status 参数的低 8 位,以了解导致该进程终止的具体原因。
exit(0)表示正常退出exit(x)(x不为0)都表示异常退出,这个 x 是返回给操作系统(包括UNIX,Linux,和MS DOS)的,以供其他程序使用。
通常情况下,程序成功执行完一个操作正常退出的时候会带有值 EXIT_SUCCESS。在这里,EXIT_SUCCESS 是宏,它被定义为 0。如果程序中存在一种错误情况,当您退出程序时,会带有状态值EXIT_FAILURE,被定义为 1
标准C中有 EXIT_SUCCESS 和 EXIT_FAILURE 两个宏,位于 /usr/include/stdlib.h中:

     
     
  1. #define EXIT_FAILURE    1   /* Failing exit status.  */
  2. #define EXIT_SUCCESS    0   /* Successful exit status.  */

(3)示例说明


     
     
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3.  
  4. int main () 
  5.    printf( "程序开头....\n"); 
  6.     
  7.    printf( "退出程序....\n"); 
  8.    exit( 0);  //等同 return 0;
  9.  
  10.    printf( "程序结尾....\n"); 
  11.  
  12.    return( 0); 
  13. 输出结果: 
  14. 程序开头.... 
  15. 退出程序.... 

(4)进阶

exit 和 return 的区别,我就不再重复了,之前讲的够详细了。现在讲点新的知识点。
exit 函数在终止调用进程之前还会做三件收尾工作:
1》》调用实现通过 atexit 或 on_exit 函数注册的退出处理函数。
注册退出函数 atexit

     
     
  1. #include <stdlib.h>
  2. int atexit(void (*function)(void));
  3. 成功返回 0, 失败返回非 0
参数解析:
function 为退出处理函数指针,所指向的函数既无返回值亦无参数,在进程终止前被调用,为进程料理临终事宜。
函数解析:
注意 atexit 函数本身并不调用退出处理函数,而只是将 function 参数所表示的退出处理函数地址,保存(注册)在系统内核的某个地方(进程表项)。待到 exit 函数被调用或在 main 函数里执行 return 语句时,再由系统内核根据这些退出处理函数的地址来调用它们。此过程亦称回调。
ISO C规定,一个进程最多可登记32个终止处理函数,这些函数由 exit 按登记相反的顺序自动调用。如果同一函数登记多次,也会被调用多次。
示例说明:

     
     
  1. #include <stdlib.h> 
  2. #include <stdio.h> 
  3. //所指向的函数既无返回值亦无参数
  4. void my_exit1 (void) 
  5.     printf ( "first exit handler\n"); 
  6. void my_exit2 (void) 
  7.     printf ( "second exit handler\n"); 
  8. int main() 
  9.     atexit (my_exit2); 
  10.     atexit (my_exit1); 
  11.     atexit (my_exit1);  //回调顺序正好和注册顺序相反
  12.     printf ( "main is done\n"); 
  13.     return 0; // 相当于exit(0) 
  14. 输出结果: 
  15. main is done 
  16. first exit handler 
  17. first exit handler 
  18. second exit handler 
注册退出函数 on_exit

     
     
  1. #include <stdlib.h>
  2. int on_exit(void (*function)(int , void *), void *arg);
  3. 成功返回 0,失败返回非 0
参数解析:
第一个参数:退出处理函数指针,所指向的函数无返回值但有两个参数。其中第一个参数来自传递给 exit 函数的 status 参数或在 main 函数里执行 return 语句的返回值,而第二个参数来自传递给 on_exit  函数的 arg 参数。该函数在进程终止前被调用,为进程料理临终事宜。
第二个参数:泛型指针,将作为第二个参数传递给 function 所指向的退出处理函数。
示例说明:

     
     
  1. #include <stdlib.h> 
  2. #include <stdio.h> 
  3. //所指向的函数无返回值但有两个参数
  4. void my_exit1 (int status, void *arg) 
  5.     printf ( "%s", ( char*)arg); 
  6.   printf ( "status = %d\n", status);
  7. void my_exit2 (int status, void *arg) 
  8.     printf ( "%s", ( char*)arg); 
  9. int main() 
  10.     on_exit (my_exit2, "second exit handler\n"); 
  11.     on_exit (my_exit1, "first exit handler\n"); 
  12.     on_exit (my_exit1, "first exit handler\n");  //回调顺序正好和注册顺序相反
  13.     printf ( "main is done\n"); 
  14.     return 1; // 相当于exit(1) 
  15. 输出结果:
  16. main is done
  17. first exit handler
  18. status = 1
  19. first exit handler
  20. status = 1
  21. second exit handler
2》》冲刷并关闭所有仍处于打开状态的标准 I/O 流。
仍处于打开状态的标准 I/O 流,即缓冲 I/O。其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。
每次读文件时,会连续读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区中读取;同样,每次写文件时,也仅仅是写入内存中的缓冲区,等满足了一定的条件(如达到一定数量或遇到特定字符等,最典型的就是咱们的vim中使用的:w命令),再将缓冲区中的内容一次性写入文件。
这种技术大大增加了文件读写的速度,但也给咱们的编程带来了一些麻烦。比如有些数据你认为已经被写入到文件中,实际上因为没有满足特定的条件,它们还只是被保存在缓冲区内,这时用 _exit() 函数直接将进程关闭掉,缓冲区中的数据就会丢失。因此,若想保证数据的完整性,最好使用 exit() 函数。
缓冲区之前讲过很多次了,参看:UNIX再学习 -- 标准I/O
举个例子:

     
     
  1. //示例一
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. int main (void)
  5. {
  6.   printf ( "111111111\n");
  7.   printf ( "222222222"); //冲刷了缓冲区
  8.   exit ( 0);
  9. }
  10. 输出结果:
  11. 111111111
  12. 222222222

     
     
  1. //示例二
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. #include <unistd.h>
  5. int main (void)
  6. {
  7.   printf ( "111111111\n");
  8.   printf ( "222222222"); //缓冲区数据丢失
  9.  _exit ( 0);
  10. }
  11. 输出结果:
  12. 111111111
使用 fflush() 冲刷缓冲:

     
     
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main (void)
  4. {
  5.  printf ("11111111111111\n");
  6.  printf ("22222222222222");
  7.  fflush (stdout);
  8.  _exit (0);
  9. }
  10. 输出结果:
  11. 11111111111111
  12. 22222222222222
3》》删除所有通过 tmpfile 函数创建的临时文件。
暂时不讲 tempfile 。感兴趣可自行 man tempfile

最后注意,exit 并不处理文件描述符、多进程(父进程和子进程)以及作业控制。
exit 函数的内部调用了更底层的系统调用函数 _exit,后者也可以被用户空间的代码直接调用,比如在用 vfork 函数创建的子进程里。

2、_exit/_Exit 函数


     
     
  1. #include <unistd.h>
  2. void _exit( int status);
  3. #include <stdlib.h>
  4. void _Exit( int status);
  5. 不返回

(1)函数功能

正常终止调用进程

(2)参数解析

status:进程退出码,相当于 main 函数的返回值。

(3)函数解析

_exit 是UC函数,_Exit 是标C函数。在UNIX系统中,两者是同义的,并不冲洗标准 I/O 流。 
_exit 在终止调用进程之前也会做三件收尾工作,但与 exit 函数所做的不同。事实上,exit 函数在做完它那三件收尾工作之后紧接着就会调用 _exit 函数
1》》关闭所有仍处于打开状态的文件描述符
2》》将调用进程的所有子进程托付给 init 进程收养
3》》向调用进程的父进程发送 SIGCHLD (17) 信号
从这三件收尾工作可以看出,它们所影响完全都是调用进程自己的资源,与其父进程没有任何关系。这对用 vfork 函数创建的子进程而言显得尤其重要。因为用 vfork 函数创建的子进程存在太多与其父进程共享的资源。如果在子进程里调用了 exit 函数或者在子进程的 main 函数里执行了 return 语句(这两者本质上是等价的),不仅销毁了子进程的资源,同时也销毁了父进程的资源,而父进程恰恰要在子进程终止以后从挂起中恢复运行,其后果可想而知。

(4)示例说明


     
     
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <unistd.h> 
  4. #include <sys/types.h> 
  5.  
  6. int main(void) 
  7.     //使用vfork函数创建子进程 
  8.     pid_t pid = vfork(); 
  9.     if( -1 == pid) 
  10.     { 
  11.         perror( "vfork"), exit( -1); 
  12.     } 
  13.     if( 0 == pid) //子进程 
  14.     { 
  15.         printf( "子进程%d开始运行\n",getpid()); 
  16.         sleep( 3); 
  17.         printf( "子进程结束\n"); 
  18.         //子进程不退出,则结果不可预知 
  19.         _exit( 0); //终止子进程 
  20.     } 
  21.     printf( "父进程%d开始执行\n",getpid()); 
  22.     printf( "父进程结束\n"); 
  23.     return 0
  24. 输出结果: 
  25. 子进程 2762开始运行 
  26. 子进程结束 
  27. 父进程 2761开始执行 
  28. 父进程结束 

3、pthread_exit 函数

不是今天的重点,等到讲完 线程回过头再详细讲它。

    
    
  1. #include <pthread.h>
  2. void pthread_exit(void *retval);

(1)函数功能

主要用于终止正在运行的线程,通过参数 retval 来带出线程的退出状态信息,在同一个进程中的其他线程可以通过调用 pthread_join 函数来获取退出状态信息。

(2)示例说明


    
    
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <pthread.h> 
  4. void* task(void* p) 
  5.     static int i=0; 
  6. //  int i=0; 
  7.     for(i=0;i <=100;i++) 
  8.     { 
  9.         if(i==10) 
  10.         { 
  11.             pthread_exit((void*)&i); 
  12.         } 
  13.         printf("子进程中:i=%d\n",i); 
  14.     } 
  15. int main() 
  16.     //1.启动线程打印1100之间的数 
  17.     pthread_t tid
  18.     pthread_create(&tid,NULL,task,NULL); 
  19.     //2.等待子进程结束,并且获取返回值 
  20.     int* pi=NULL; 
  21.     pthread_join(tid,(void**)&pi); 
  22.     printf("子线程中变量的值是:%d\n",*pi); 
  23.     return 0
  24. 编译:gcc test.c -pthread 
  25. 输出结果: 
  26. 子进程中:i=0 
  27. 子进程中:i=1 
  28. 子进程中:i=2 
  29. 子进程中:i=3 
  30. 子进程中:i=4 
  31. 子进程中:i=5 
  32. 子进程中:i=6 
  33. 子进程中:i=7 
  34. 子进程中:i=8 
  35. 子进程中:i=9 
  36. 子线程中变量的值是:10 

二、wait 系列函数

下面我们来讲一下 wait 系列函数。

1、wait 函数 


    
    
  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. pid_t wait( int *status);
  4. 成功返回所回收子进程的 PID,失败返回 -1

(1)函数功能

主要用于挂起正在运行的进程进入等待状态,直到有一个子进程终止。

(2)参数解析

status 是一个整型指针,如果 status 不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针

(3)函数解析

1》》父进程在创建若干子进程以后调用 wait 函数
若所有子进程都在运行,则阻塞,直至有子进程终止。
若有一个子进程已终止,则返回该子进程的 PID 并通过 status 参数 (若非 NULL)输出其终止状态。
若没有需要等待的子进程,则返回 -1,置 error 为 ECHILD
示例说明:

    
    
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/wait.h>
  5. #include <errno.h>
  6. int main(void)
  7. {
  8.   pid_t pid1;
  9.   if((pid1=fork())< 0)
  10.   perror( "fork");
  11.   if(pid1 == 0)
  12.  {
  13.    printf( "这是子进程,pid1=%d,",getpid());
  14.    printf( "父进程的pid1=%d\n",getppid());
  15.   _exit( 0);
  16.  }
  17.   pid_t pid2;
  18.   if((pid2=fork())< 0)
  19.   perror( "fork");
  20.   if(pid2 == 0)
  21.  {
  22.    printf( "这是子进程,pid2=%d,",getpid());
  23.    printf( "父进程的pid2=%d\n",getppid());
  24.   _exit( 0);
  25.  }
  26.   printf( "这是父进程,pid=%d\n",getpid());
  27.   while ( 1)
  28.     {
  29.         pid_t pid = wait ( NULL);
  30.         if (pid == -1)
  31.         {
  32.     if (errno != ECHILD)
  33.             {
  34.                 perror ( "wait");
  35.                 exit (EXIT_FAILURE);
  36.             }
  37.     printf ( "子进程都死光了\n");
  38.             break;
  39.         }
  40.         printf ( "%d子进程终止\n", pid);
  41.     }
  42.     return 0;
  43. }
  44. 输出结果:
  45. 这是父进程,pid= 2944
  46. 这是子进程,pid1= 2945,父进程的pid1= 2944
  47. 2945子进程终止
  48. 这是子进程,pid2= 2946,父进程的pid2= 2944
  49. 2946子进程终止
  50. 子进程都死光了
示例解析:
如果不使用 wait ,父进程先于子进程终止,则子进程成为孤儿进程了。使用 wait 在于可挂起正在运行的父进程进入等待状态,直到有子进程终止。
思考,父进程使用 sleep 不是也可以保证子进程先被调度,避免产生孤儿进程么。
wait 和 sleep 有何区别,稍后会讲。
2》》如果一个子进程在 wait  函数套用之前,已经终止并处于僵尸状态,wait 函数会立即返回,并取得该子进程的终止状态,同时子进程僵尸消失。由此可见 wait 函数主要完成三个任务。
1、阻塞父进程的运行,直到子进程终止再继续,停等同步。
2、获取子进程的 PID 和终止状态,令父进程得知谁因何而死。
3、为子进程收尸,防止大量僵尸进程耗费系统资源。
以上三个任务中,即使前两个与具体需求无关,仅仅第三个也足以凸显 wait 函数的重要性,尤其是对那些多进程服务器型的应用而言。
僵尸进程的产生,上一篇有讲,参看:UNIX再学习 -- 函数 fork 和 vfork
下面简单用示例介绍一下僵尸进程:

    
    
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <unistd.h> 
  4.  
  5.  
  6. int main (void) 
  7.     pid_t pid; 
  8.  
  9.     pid = fork (); 
  10.     if  (pid == -1
  11.         perror ( "fail to fork"), exit ( 1); 
  12.     else if (pid == 0
  13.     { 
  14.         printf ( "这是子进程 pid = %d", getpid ()); 
  15.         printf ( "父进程的 ppid = %d\n",  getppid ()); 
  16.     } 
  17.     else 
  18.     { 
  19.         //while (1); 
  20.         sleep ( 10);
  21.         printf ( "这是父进程 ppid = %d\n", getpid ()); 
  22.  
  23.     } 
  24.  
  25.     return 0
  26. }
  27. 实验:
  28. 在一个终端执行 ./a.out
  29. # ./a.out
  30. 这是子进程 pid = 2868父进程的 ppid = 2867
  31. (十秒后)
  32. 这是父进程 ppid = 2867
  33. 在另一个终端,查看进程信息
  34. # ps -C a.out -o ppid,pid,stat,cmd
  35. PPID   PID STAT CMD
  36. 2366  2867 S+   ./a.out
  37. 2867  2868 Z+   [a.out] <defunct>
结论:
虽然,子进程已经结束,但是父进程仍未终止,没有回收子进程的退出状态,子进程即成为僵尸进程。
而父进程调用 wait,会终止子进程的僵尸态,示例说明:

    
    
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <unistd.h> 
  4. int main (void) 
  5.     pid_t pid, pr; 
  6.  
  7.     pid = fork (); 
  8.     if  (pid == -1
  9.         perror ( "fail to fork"), exit ( 1); 
  10.     else if (pid == 0
  11.     { 
  12.         printf ( "这是子进程 pid = %d", getpid ()); 
  13.         printf ( "父进程的 ppid = %d\n",  getppid ()); 
  14.     } 
  15.     else 
  16.     { 
  17.   pr = wait ( NULL);
  18.         //while (1); 
  19.         sleep ( 10); //可以保证子进程先被调度 
  20.         printf ( "这是父进程 ppid = %d\n", getpid ()); 
  21.  
  22.     } 
  23.  
  24.     return 0
  25. 实验:
  26. 在一个终端执行 ./a.out
  27. # ./a.out
  28. 这是子进程 pid = 2889父进程的 ppid = 2888
  29. (十秒后)
  30. 这是父进程 ppid = 2888
  31. 在另一个终端,查看进程信息
  32. # ps -C a.out -o ppid,pid,stat,cmd
  33. PPID   PID STAT CMD
结论:
可以发现,确实子进程的僵尸态没有了。不过需要注意的是,wait 使用的位置。
上面有这样一句话,如果一个子进程在 wait  函数套用之前,已经终止并处于僵尸状态,所以说,子进程必须在 wait 函数套用之前结束。如果,wait 先于子进程,是没有效果的。

    
    
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <unistd.h> 
  4.  
  5.  
  6. int main (void) 
  7.     pid_t pid, pr; 
  8.     pr = wait ( NULL);
  9.  
  10.     pid = fork (); 
  11.     if  (pid == -1
  12.         perror ( "fail to fork"), exit ( 1); 
  13.     else if (pid == 0
  14.     { 
  15.         printf ( "这是子进程 pid = %d", getpid ()); 
  16.         printf ( "父进程的 ppid = %d\n",  getppid ()); 
  17.     } 
  18.     else 
  19.     { 
  20.   
  21.         //while (1); 
  22.         sleep ( 10);
  23.         printf ( "这是父进程 ppid = %d\n", getpid ()); 
  24.  
  25.     } 
  26.  
  27.     return 0
  28. 实验:
  29. 在一个终端执行 ./a.out
  30. # ./a.out
  31. 这是子进程 pid = 2932父进程的 ppid = 2931
  32. (十秒后)
  33. 这是父进程 ppid = 2931
  34. 在另一个终端,查看进程信息
  35. # ps -C a.out -o ppid,pid,stat,cmd
  36. PPID   PID STAT CMD
  37. 2366  2931 S+   ./a.out
  38. 2931  2932 Z+   [a.out] <defunct>
再有一个,上述例子中都是使用的 wait (NULL),如果参数非 NULL,则可以输出子进程终止状态。

    
    
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <unistd.h> 
  4.  
  5.  
  6. int main (void) 
  7.     pid_t pid, pr;
  8.    int status = 0
  9.  
  10.     pid = fork (); 
  11.     if  (pid == -1
  12.         perror ( "fail to fork"), exit ( 1); 
  13.     else if (pid == 0
  14.     { 
  15.         printf ( "这是子进程 pid = %d", getpid ()); 
  16.         printf ( "父进程的 ppid = %d\n",  getppid ()); 
  17.   _exit ( 100);
  18.     } 
  19.     else 
  20.     { 
  21.    int status = 0
  22.   pr = wait (&status);
  23.         //while (1); 
  24.         sleep ( 10); //可以保证子进程先被调度 
  25.         printf ( "这是父进程 ppid = %d\n", getpid ()); 
  26.    printf ( "status = %d, pr = %d\n", status, pr);
  27.  
  28.     } 
  29.  
  30.     return 0
  31. 输出结果:
  32. 这是子进程 pid = 3078父进程的 ppid = 3077
  33. 这是父进程 ppid = 3077
  34. status = 25600, pr = 3078
总结:
子进程异常退出(_exit (100)),通过 wait 函数参数 status 获得终止状态。
3》》子进程的终止状态通过 wait 函数的 status 参数输出给该函数调用者。<sys/wait.h> 头文件提供了几个辅助分析进程终止状态的工具宏。

    
    
  1. 查看: /usr/include/i386-linux-gnu/sys/wait.h 
  2. /* This will define all the `__W*' macros.  */
  3. # include <bits/waitstatus.h>
  4. # define WEXITSTATUS(status) __WEXITSTATUS (__WAIT_INT (status))
  5. # define WTERMSIG(status) __WTERMSIG (__WAIT_INT (status))
  6. # define WSTOPSIG(status) __WSTOPSIG (__WAIT_INT (status))
  7. # define WIFEXITED(status) __WIFEXITED (__WAIT_INT (status))
  8. # define WIFSIGNALED(status) __WIFSIGNALED (__WAIT_INT (status))
  9. # define WIFSTOPPED(status) __WIFSTOPPED (__WAIT_INT (status))
讲解:
参看:WAIT(2)  
WIFEXITED (status)   (常用)
判断子进程是否正常终止,是则为真。
WIEXITSTATUS (status)  (常用)
获取子进程调用 exit、_exit、Exit 函数时所传入的参数或者 main 函数中 return 语句返回值的低 8 位。
WIFSIGNALED (status)
判断子进程是否异常终止,是则为真 
WTERMSIG (status) 
宏获取导致子进程异常终止的信号。
WIFSTOPPED (status)
判断当前暂停子进程是否返回,是则为真。
WSTOPSIG (status)
获取使子进程暂停的信号。
示例说明:

    
    
  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. #include <unistd.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. int main (void)
  7. {
  8.   int status;
  9.   pid_t pc,pr;
  10.  pc =  fork();
  11.   if (pc < 0)
  12.    printf( "error ocurred!\n");
  13.   else if(pc == 0)
  14.  {
  15.    printf( "This is child process with pid of %d\n",getpid());
  16.    exit ( 3);
  17.  }
  18.   else
  19.  {
  20.   pr = wait(&status);
  21.    printf ( "status = %d\n", status);
  22.    printf ( "status >> 8 = %d\n", status >> 8);
  23.    if(WIFEXITED(status))
  24.   {
  25.     printf( "The child process %d exit normally.\n",pr);
  26.     printf( "The WEXITSTATUS return code is %d.\n",WEXITSTATUS(status));
  27.     printf( "The WIFEXITED return code is %d.\n",WIFEXITED(status));
  28.   }
  29.    else
  30.     printf( "The child process %d exit abnormally.\n",pr);
  31.  }
  32.   return 0;
  33. }
  34. 输出结果:
  35. This is child process with pid of 3370
  36. status = 768
  37. status >> 8 = 3
  38. The child process 3370 exit normally.
  39. The WEXITSTATUS return code is 3.
  40. The WIFEXITED return code is 1.
示例解析:
通过示例可以看出 wait 返回子进程 ID;WEXITSTATUS (status) 返回 exit (3) 参数 3;子进程正常终止,WIFEXITED 为真。 

思考:其中的我添加打印了 status 的值为 768,以及status >> 8 的值为 3,啥意思? status 到底是什么?
我们上面说了,status 是一个整型指针,如果 status 不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。那终止状态是什么,怎么表示的?
再有,获取子进程调用 exit、_exit、Exit 函数时所传入的参数或者 main 函数中 return 语句返回值的低 8 位
这句话就说明了, status 是  exit、_exit、Exit 函数时所传入的参数或者 main 函数中 return 语句返回值的 低 8 位。
那么 低 8 位、高 8 位 又是怎么回事?  
下面开始讲解下:
exit、wait、WEXITSTATUS 中的 status 关系
(1)exit 函数参数 status 为整型,称为终止状态(或退出状态,exit status)。
参看:Exit Status
查看 /linux-2.6.37.1/kernel/exit.c

     
     
  1. SYSCALL_DEFINE1( exit, int, error_code)
  2. {
  3.  do_exit((error_code& 0xff)<< 8);
  4. }
  5. 查看函数 NORET_TYPE void do_exit(long code)
  6. 有 tsk->exit_code = code;
  7. 所以子进程的返回值就是这么确定的,它的低 8位为零,次低 8位为真正退出码。
(2)wait 函数参数 status 为整型指针。
高8位 记录进程调用exit退出的状态(正常退出);
低8位 记录进程接受到的信号 (非正常退出)
如果正常退出(exit) ---高8位是退出状态号,低8位是0
如果非正常退出(signal)----高八位是0,低8位是siganl id


(3)再查看 /usr/include/i386-linux-gnu/bits/waitstatus.h 获得 __WEXITSTATUS(status) 定义。

    
    
  1. /* Everything extant so far uses these same bits.  */
  2. /* If WIFEXITED(STATUS), the low-order 8 bits of the status.  */
  3. #define __WEXITSTATUS(status) (((status) & 0xff00) >> 8)
  4. /* If WIFSIGNALED(STATUS), the terminating signal.  */
  5. #define __WTERMSIG(status) ((status) & 0x7f)
  6. /* If WIFSTOPPED(STATUS), the signal that stopped the child.  */
  7. #define __WSTOPSIG(status) __WEXITSTATUS(status)
  8. /* Nonzero if STATUS indicates normal termination.  */
  9. #define __WIFEXITED(status) (__WTERMSIG(status) == 0)
  10. /* Nonzero if STATUS indicates termination by a signal.  */
  11. #define __WIFSIGNALED(status) \
  12.   (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)
  13. /* Nonzero if STATUS indicates the child is stopped.  */
  14. #define __WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
  15. /* Nonzero if STATUS indicates the child continued after a stop.  We only
  16.    define this if <bits/waitflags.h> provides the WCONTINUED flag bit.  */
  17. #ifdef WCONTINUED
  18. # define __WIFCONTINUED(status) ((status) == __W_CONTINUED)
  19. #endif
  20. /* Nonzero if STATUS indicates the child dumped core.  */
  21. #define __WCOREDUMP(status) ((status) & __WCOREFLAG)
总结一下:
wait 参数整型指针 (int *status) , 高8位记录进程调用 exit 退出的状态(即exit 的参数status)(正常退出);低8位 记录进程接受到的信号 (非正常退出) 。WEXITSTATUS 返回值为((status) & 0xff00) >> 8,即 exit 退出状态。
简单点来表示就是:  exit (3) --> wait (&(3<<8 ))--> WEXITSTATUS (3<<8) = ((3<<8)&0xff) >>8 = 3

2、waitpid 函数


    
    
  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. pid_t waitpid( pid_t pid, int *status, int options);
  4. 成功返回所回收子进程的 PID 或 0,失败返回 -1

(1)函数功能

等待并回收任意或特定子进程

(2)参数解析

第一个参数:进程号,可取以下值。
pid < -1     等待并回收特定进程组(由 pid标识)的任意子进程
pid == -1  等待并回收任意子进程,相当于 wait 函数  (掌握)
pid == 0    等待并回收与调用进程同进程组的任意子进程
pid > 0      等待并回收特定子进程(由pid标识) (掌握)
第二个参数:输出子进程的终止状态,可置 NULL
第三个参数:选项,可取以下值 (默认给 0 即可)
0    阻塞模式,若所等子进程仍在运行,则阻塞,直至其终止。  (掌握)
WNOHANG   非阻塞模式,若所等子进程仍在运行,则返回 0  (掌握)
WCONTINUED  若实现支持作业控制,那么由 pid 指定的任一子进程在停止后已经继续,但其装填尚未报告,则返回其状态。
WUNTRACED  若某实现支持作业控制,而由 pid 指定的任一子进程已处于停止状态,并且其状态自停止以来还未报告过,则返回其状态。WIFSTOPPED 宏确定返回值是否对应于一个停止的子进程。

(3)函数解析

1》》回收特定子进程
事实上,无论一个进程是正常终止还是异常终止,都会通过系统内核向其父进程发送 SIGCHLD (17)信号。父进程可以忽略该信号,也可以提供一个针对该信号的处理函数,在信号处理函数中以异步的方式回收子进程。这样做不仅流程简单,而且僵尸的存货时间短,回收效率高。
其中 error 等于 ECHILD 表示没有需要等待的子进程。
示例说明:

    
    
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/wait.h>
  5. #include <errno.h>
  6. int main (void)
  7. {
  8.   pid_t pid;
  9.   if ((pid = fork ()) < 0)
  10.   perror ( "fork"), exit ( 1);
  11.   else if (pid == 0)
  12.  {
  13.    printf ( "这是子进程 pid = %d\n", getpid ());
  14.    printf ( "父进程的 pid = %d\n", getppid ());
  15.    exit ( 0);
  16.  }
  17.   else
  18.    printf ( "这是父进程 pid = %d\n", getpid ());
  19.  
  20.   //回收特定子进程
  21.  pid = waitpid (pid, NULL, 0);
  22.   if (pid == -1)
  23.  {
  24.    if (errno != ECHILD)
  25.    perror ( "waitpid"), exit ( 1);
  26.  }
  27.   else
  28.    printf ( "%d子进程终止\n", pid);
  29.   printf ( "%d父进程终止\n", getpid ());
  30.   return 0;
  31. }
  32. 输出结果:
  33. 这是父进程 pid = 2892
  34. 这是子进程 pid = 2893
  35. 父进程的 pid = 2892
  36. 2893子进程终止
  37. 2892父进程终止
2》》非阻塞模式回收所有子进程
waitpid函数以非阻塞模式运行时,可以等待并回收所有子进程,等待的同时做空闲处理。
示例说明:

    
    
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/wait.h>
  5. #include <errno.h>
  6. int main (void)
  7. {
  8.   pid_t pid1;
  9.   if ((pid1 = fork ()) < 0)
  10.   perror ( "fork"), exit ( 1);
  11.   if (pid1 == 0)
  12.  {
  13.    printf ( "这是子进程pid1 = %d", getpid ());
  14.    printf ( "父进程pid1 = %d\n", getppid ());
  15.    exit ( 0);
  16.  }
  17.   pid_t pid2;
  18.   if ((pid2 = fork ()) < 0)
  19.   perror ( "fork"), exit ( 1);
  20.   if (pid2 == 0)
  21.  {
  22.    printf ( "这是子进程pid2 = %d", getpid ());
  23.    printf ( "父进程pid2 = %d\n", getppid ());
  24.    exit ( 0);
  25.  }
  26.   //sleep (2);
  27.   printf ( "这是父进程pid = %d\n", getpid ());
  28.   while ( 1)
  29.  {
  30.    pid_t pid = waitpid ( -1, NULL, WNOHANG);
  31.    if (pid == -1)
  32.   {
  33.     if (errno != ECHILD)
  34.     perror ( "waitpid"), exit ( 1);
  35.     printf ( "子进程都死光了\n");
  36.     break;
  37.   }
  38.    if (pid)
  39.     printf ( "%d子进程终止\n", pid);
  40.    else
  41.     printf ( "在这里进行空闲处理\n");、
  42.    //表示所等子进程仍在运行,此时父进程出现空闲时间,可在这里进行空闲处理。
  43.  }
  44.   return 0;
  45. }
  46. 输出结果:
  47. 这是父进程pid = 3029
  48. 在这里进行空闲处理
  49. 在这里进行空闲处理
  50. ....
  51. 在这里进行空闲处理
  52. 在这里进行空闲处理
  53. 这是子进程pid2 = 3031父进程pid2 = 3029
  54. 这是子进程pid1 = 3030父进程pid1 = 3029
  55. 在这里进行空闲处理
  56. 3031子进程终止
  57. 3030子进程终止
  58. 子进程都死光了
3》》通过 WUNTRACED 和 WCONTINUED 支持作业控制 (了解)
这是两个常数,可以用"|"运算符把它们连接起来使用,比如:
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
4》》使用2次 fork 避免僵尸进程
如果一个进程 fork 一个子进程,但不要它等待子进程终止,也不希望子进程处于僵尸状态直到父进程终止,实现这一要求的诀窍是调用 fork 两次。

    
    
  1. #include <stdio.h> 
  2. #include <sys/wait.h> 
  3. #include <stdlib.h> 
  4.  
  5. int main(void)
  6.   pid_t pid; 
  7.   if((pid = fork()) < 0)
  8.   perror( "fork"), exit ( 1);
  9.   else if(pid == 0)
  10.  { 
  11.    if((pid = fork()) < 0
  12.    perror( "fork"), exit ( 1);
  13.    else if(pid > 0)
  14.     exit( 0); 
  15.    else
  16.   { 
  17.    sleep( 2); 
  18.     printf( "second child,parent pid = %d\n",getppid()); 
  19.     exit( 0); 
  20.   } 
  21.  }
  22.  
  23.   if(waitpid(pid, NULL, 0) != pid) //回收第一次fork的子进程,但是第二个没有回收 
  24.   perror( "waitpid"), exit ( 1);
  25.   return 0
  26. 输出结果:
  27. second child,parent pid = 1

3、wait 和 waitpid 函数区别

(1)在一个子进程终止前,wait 使其调用者阻塞,而 waitpid 有一选项 WNOHANG ,可使调用者不阻塞。
(2)waitpid 并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。
(说的是 waitpid 第一个参数选项,如等待特定的子进程)
(3)waitpid 通过 WUNTRACED 和 WCONTINUED 支持作业控制。 
(4)wait(&status) 等同于 waitpid(-1, &status, 0);

4、wait 与 sleep 区别

我们上面简单提到了一下两者都可以避免产生孤儿进程。但是有什么区别呢,下面我们简单介绍下。
讲之前先说一下优先级,if (pid = fork () < 0)   对不对? 
答案是错误的。 '<' 的优先级 高于 '=',所有应该是加括号   if ((pid = fork ()) < 0)   

(1)使用 wait 函数,避免孤儿进程


    
    
  1. #include <stdio.h> 
  2. #include <unistd.h> 
  3. #include <stdlib.h> 
  4.  
  5. int main (void) 
  6.     pid_t pid, pr; 
  7.     if ((pid = fork ()) < 0
  8.         perror ( "fork"), exit ( 1); 
  9.     else if (pid == 0
  10.     { 
  11.         sleep ( 3);  
  12.         printf ( "这是子进程 pid = %d", getpid ()); 
  13.         printf ( "父进程的 ppid = %d\n",  getppid ()); 
  14.    exit ( 0);
  15.     } 
  16.     else  
  17.     { 
  18.   pr = wait ( NULL);
  19.         printf ( "这是父进程 ppid = %d\n", getpid ()); 
  20.     } 
  21.     return 0
  22. 输出结果:
  23. 这是子进程 pid = 3603父进程的 ppid = 3602
  24. 这是父进程 ppid = 3602

(2)使用 sleep,避免孤儿进程


    
    
  1. #include <stdio.h> 
  2. #include <unistd.h> 
  3. #include <stdlib.h> 
  4.  
  5. int main (void) 
  6.     pid_t pid; 
  7.     if ((pid = fork ()) < 0
  8.         perror ( "fork"), exit ( 1); 
  9.     else if (pid == 0
  10.     { 
  11.         sleep ( 3);  
  12.         printf ( "这是子进程 pid = %d", getpid ()); 
  13.         printf ( "父进程的 ppid = %d\n",  getppid ()); 
  14.    exit ( 0);
  15.     } 
  16.     else  
  17.     { 
  18.   sleep ( 5);
  19.         printf ( "这是父进程 ppid = %d\n", getpid ()); 
  20.     } 
  21.     return 0
  22. 输出结果:
  23. 这是子进程 pid = 3611父进程的 ppid = 3610
  24. 这是父进程 ppid = 3610
但是,如果你注意看输出时的等待时间,就会发现。
wait 等待 3 秒然后,同时输出结果。而 sleep 先等待 3 秒,打印子进程,然后再等待 2 秒打印父进程。
所以说,sleep 是休眠指定时间,到时间继续往下执行。而 wait 是等待,需要被触发才能继续往下执行
当然,wait 还有其他功能,是 sleep 没有的。上面讲的很清楚了,就不一一说明了。
这里只是简单提一句,等讲到线程、Linux部分,可以继续深入探讨。

三、总结

第八章东西有点多,一章拆成了几篇文章来写,看的节奏有点慢。最近也有点分心了,周末出去玩没问题,但是占用休息和本来用来学习的时间用在修图上,这就不该了。收起玩心,抓紧时间复习吧!
                                   
                                   
               
                   
  •                                                
  •                                                     点赞                         2                        
  •                        
  •                                                     收藏
  •                        
  •                                                     分享
  •                                                                                                                        
  •                                                        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值