Linux系统编程——进程的控制:结束进程、等待进程结束

结束进程

首先,我们回顾一下 C 语言中 continue, break, return 的作用:

continue: 结束本次循环

break: 跳出整个循环,或跳出 switch() 语句

return: 结束当前函数


而我们可以通过 exit() 或 _exit() 来结束当前进程。


所需头文件:

#include <stdlib.h> 


void exit(int value);

功能:

结束调用此函数的进程。

参数:

status:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。

返回值:


所需头文件:

#include <unistd.h>


void _exit(int value);

功能:

结束调用此函数的进程。

参数:

status:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。

返回值:


exit() 和 _exit() 函数功能和用法是一样的,无非时所包含的头文件不一样,还有的区别就是:exit()属于标准库函数,_exit()属于系统调用函数。


下面的例子验证调用 exit() 函数,会刷新 I/O 缓冲区(关于缓冲区的更多详情,请看《浅谈标准I/O缓冲区》):

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4.   
  5. int main(int argc, char *argv[])  
  6. {  
  7.     printf("hi, mike, you are so good"); // 打印,没有换行符"\n"  
  8.       
  9.     exit(0);      // 结束进程,标准库函数,刷新缓冲区,printf()的内容能打印出来  
  10.     // _exit(0);  // 结束进程,系统调用函数,printf()的内容不会显示到屏幕  
  11.       
  12.     while(1);   // 不让程序结束  
  13.   
  14.     return 0;  
  15. }  


运行结果如下:



上面的例子,结束进程的时候改为调用 _exit(),代码如下:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4.   
  5. int main(int argc, char *argv[])  
  6. {  
  7.     printf("hi, mike, you are so good"); // 打印,没有换行符"\n"  
  8.       
  9.     //exit(0);      // 结束进程,标准库函数,刷新缓冲区,printf()的内容能打印出来  
  10.     _exit(0);  // 结束进程,系统调用函数,printf()的内容不会显示到屏幕  
  11.       
  12.     while(1);   // 不让程序结束  
  13.   
  14.     return 0;  
  15. }  

printf() 的内容是不会显示出来的,运行结果如下:



接下来,我们一起验证一下结束函数( return )和结束进程( exit() )的区别。


测试代码如下:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4.   
  5. void fun()  
  6. {  
  7.     sleep(2);  
  8.       
  9.     return;  // 结束 fun() 函数  
  10.       
  11.     while(1);  
  12. }  
  13.   
  14.   
  15. int main(int argc, char *argv[])  
  16. {  
  17.     fun();  
  18.       
  19.     printf("after fun\n");  
  20.       
  21.     while(1);   // 不让程序结束  
  22.   
  23.     return 0;  
  24. }  

运行结果如下:



通过上面的运行结果得知,return 的作用只是结束调用 return 的所在函数,只要这个函数不是主函数( main() ),只要主函数没有结束,return 并不能结束进程


我们将上面例子 fun() 里的 return 改为 exit():

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4.   
  5. void fun()  
  6. {  
  7.     sleep(2);  
  8.       
  9.     exit(0);  // 结束当前进程  
  10.       
  11.     while(1);  
  12. }  
  13.   
  14. int main(int argc, char *argv[])  
  15. {  
  16.     fun();  
  17.       
  18.     printf("after fun\n");  
  19.       
  20.     while(1);   // 不让程序结束  
  21.   
  22.     return 0;  
  23. }  



exit() 是可以结束 fun() 所在的进程,即可让程序结束运行,结果图如下:



等待进程结束

当一个进程正常或异常终止时,内核就向其父进程发送 SIGCHLD 信号,相当于告诉父亲他哪个儿子挂了,而父进程可以通过 wait() 或 waitpid() 函数等待子进程结束,获取子进程结束时的状态,同时回收他们的资源(相当于,父亲听听死去儿子的遗言同时好好安葬它)。


wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。


所需头文件:

#include <sys/types.h>

#include <sys/wait.h>


pid_t wait(int *status);

功能:

等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。


在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块的信息(包括进程号、退出状态、运行时间等)。


调用 wait() 函数的进程会挂起(阻塞),直到它的一个子进程退出或收到一个不能被忽视的信号时才被唤醒(相当于继续往下执行)。


若调用进程没有子进程,该函数立即返回;若它的子进程已经结束,该函数同样会立即返回,并且会回收那个早已结束进程的资源。


所以,wait()函数的主要功能为回收已经结束子进程的资源。


参数:

status: 进程退出时的状态信息。


如果参数 status 的值不是 NULL,wait() 就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的。


这个退出信息在一个 int 中包含了多个字段,直接使用这个值是没有意义的,我们需要用宏定义取出其中的每个字段


下面我们来学习一下其中最常用的两个宏定义,取出子进程的退出信息:

WIFEXITED(status)

如果子进程是正常终止的,取出的字段值非零。

WEXITSTATUS(status)

返回子进程的退出状态,退出状态保存在 status 变量的 8~16 位。在用此宏前应先用宏 WIFEXITED 判断子进程是否正常退出,正常退出才可以使用此宏。


返回值:

成功:已经结束子进程的进程号

失败:-1


从本质上讲,系统调用 waitpid() 和 wait() 的作用是完全相同的,但 waitpid() 多出了两个可由用户控制的参数 pid 和 options,从而为我们编程提供了另一种更灵活的方式。


pid_t waitpid(pid_t pid, int *status, int options);

功能:

等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。

参数:

pid: 参数 pid 的值有以下几种类型:

pid > 0

等待进程 ID 等于 pid 的子进程。

pid = 0

等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它。

pid = -1

等待任一子进程,此时 waitpid 和 wait 作用一样。

pid < -1

等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。


status: 进程退出时的状态信息。和 wait() 用法一样。


options: options 提供了一些额外的选项来控制 waitpid()。

0

同 wait(),阻塞父进程,等待子进程退出。

WNOHANG

没有任何已经结束的子进程,则立即返回。

WUNTRACED

如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了,有兴趣的读者可以自行查阅相关材料)

返回值:

waitpid() 的返回值比 wait() 稍微复杂一些,一共有 3 种情况:


当正常返回的时候,waitpid() 返回收集到的已经子进程的进程号;


如果设置了选项 WNOHANG,而调用中 waitpid() 发现没有已退出的子进程可等待,则返回 0;


如果调用中出错,则返回 -1,这时 errno 会被设置成相应的值以指示错误所在,如:当 pid 所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid() 就会出错返回,这时 errno 被设置为 ECHILD;


测试例子:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <sys/types.h>  
  5. #include <sys/wait.h>  
  6.   
  7. int main(int argc, char *argv[])  
  8. {  
  9.     pid_t pid;  
  10.       
  11.     pid = fork(); // 创建进程  
  12.     if( pid < 0 ){ // 出错  
  13.         perror("fork");  
  14.         exit(0);  
  15.     }  
  16.       
  17.     if( pid == 0 ){// 子进程  
  18.         int i = 0;  
  19.         for(i=0;i<5;i++)  
  20.         {  
  21.             printf("this is son process\n");  
  22.             sleep(1);  
  23.         }  
  24.           
  25.         _exit(2); // 子进程退出,数字 2 为子进程退出的状态  
  26.           
  27.     }else if( pid > 0){ // 父进程  
  28.           
  29.         int status = 0;  
  30.           
  31.         // 等待子进程结束,回收子进程的资源  
  32.         // 此函数会阻塞  
  33.         // status 某个字段保存子进程调用 _exit(2) 的 2,需要用宏定义取出  
  34.         wait(&status);   
  35.         // waitpid(-1, &status, 0); // 和 wait() 没区别,0:阻塞  
  36.         // waitpid(pid, &status, 0); // 指定等待进程号为 pid 的子进程, 0 阻塞  
  37.         // waitpid(pid, &status, WNOHANG); // WNOHANG:不阻塞  
  38.         if(WIFEXITED(status) != 0){ // 子进程是否正常终止  
  39.             printf("son process return %d\n", WEXITSTATUS(status));  
  40.         }  
  41.           
  42.         printf("this is father process\n");   
  43.     }  
  44.       
  45.     return 0;  
  46. }  

运行结果如下:



本教程示例代码下载请点此。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
进程间通信是指在不同进程之间进行数据交换和同步的一种技术。Linux提供了多种进程间通信的方式,包括管道、消息队列、共享内存和信号量等。在实验六中,我们将学习如何使用这些方式进行进程间通信。 1. 管道 管道是一种半双工的通信方式,它可以在两个进程之间传递数据。在Linux中,管道分为匿名管道和命名管道。匿名管道只能用于父子进程之间的通信,而命名管道可以用于任意两个进程之间的通信。 使用匿名管道进行进程间通信的步骤如下: - 父进程创建管道,并调用fork函数创建子进程。 - 子进程通过管道接收数据。 - 父进程通过管道发送数据。 - 子进程接收到数据后进行处理。 使用命名管道进行进程间通信的步骤如下: - 创建命名管道。 - 打开命名管道并进行读写操作。 2. 消息队列 消息队列是一种进程间通信机制,它允许不同进程之间通过一个消息传递序列来进行通信。在Linux中,每个消息都有一个类型,接收进程可以选择接收某个特定类型的消息。 使用消息队列进行进程间通信的步骤如下: - 创建消息队列。 - 发送消息到消息队列。 - 接收消息并进行处理。 3. 共享内存 共享内存是一种进程间通信的方式,它允许不同进程之间共享同一个物理内存区域。这种方式比较高效,但需要考虑进程间的同步和互斥问题,否则会出现数据不一致的情况。 使用共享内存进行进程间通信的步骤如下: - 创建共享内存区域。 - 进程通过共享内存区域进行数据交换。 - 进程需要进行同步和互斥操作。 4. 信号量 信号量是一种进程间同步的机制,它可以用来保证不同进程之间的共享资源在同一时刻只能被一个进程访问。在Linux中,每个信号量都有一个计数器,当计数器为0时,进程需要等待;当计数器大于0时,进程可以继续执行。 使用信号量进行进程间通信的步骤如下: - 创建信号量。 - 进程对信号量进行P操作(等待)。 - 进程对信号量进行V操作(释放)。 总体来说,不同的进程间通信方式各有优缺点,应根据实际需求选择适合的方式。在实验六中,我们将通过编写代码来学习如何使用这些方式进行进程间通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值