进程终止、进程等待以及fork()、wait()、waitpid()

14 篇文章 0 订阅
12 篇文章 0 订阅


一、进程终止

(1)进程终止的情况

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止(也就是程序崩溃、闪退了)

在C\C++中,mian()函数一般情况return 0,而这个“0”就是我们将要说的进程退出码,它表示进程退出的信息,让父进程来进行读取,从而使在僵尸状态(Z)的子进程被父进程读取退出信息,使其进入死亡状态(X)。

在Linux命令行中可以使用echo $?表示在bash中,最近一次执行完毕时,对应进程的退出码。

[swb@VM-8-12-centos test]$ echo $?
0

第二次第三次echo的时候输出的是前一次echo这个进程的退出码。实际上每个命令执行都是一个进程,都会有一个退出码,这个退出码就是main函数的返回值,每个进程都有一个main函数

不同的错误码对应不同的错误信息,当然我们也可以自己设计一种错误码和错误信息的映射。

在C\C++的库文件中存在strerror函数可以打印出退出码对应不同的错误原因。

 1 #include <stdio.h>
  2 #include <string.h>
  3 
  4 int main(){
  5 
  6   int i = 0;
  7   for(;i < 10; i++){
  8     printf("%d : %s\n",i,strerror(i));
  9   }
 10 
 11   return 0;                                                                 
 12 }
[swb@test temp_test]$ ./test 
0 : Success
1 : Operation not permitted
2 : No such file or directory
3 : No such process
4 : Interrupted system call
5 : Input/output error
6 : No such device or address
7 : Argument list too long
8 : Exec format error
9 : Bad file descriptor

(2)进程终止的做法

正常终止(可以通过 echo $? 查看进程退出码):

  1. 从main返回(代表进程退出,从函数返回也一样,叫做函数返回)
  2. 调用exit(终止进程,会刷新缓冲区)
  3. _exit(终止进程,不会有任何刷新)
#include <unistd.h>
void _exit(int status);

参数:status 定义了进程的终止状态,父进程通过wait来获取该值(status有很多宏值,比如EXIT_SUCCESS)
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

终止进程中内核会做什么操作?
子进程从僵尸状态(Z)到死亡状态(X),会释放子进程的代码和数据,但操作系统可能并不会释放该进程的内核数据结构。(注: 内核数据缓冲池——slab分派器的作用)

异常退出:

ctrl + c,信号终止

二、进程等待

(1)概念

父进程执行fork之后,会创建子进程,子进程一般都是要帮助父进程完成某种任务,此时父进程需要通过wait/waitpid等待子进程退出。

(2)必要性

  • 子进程退出,父进程不读取子进程退出信息,有可能会造成僵尸进程的问题,从而造成进程泄露。
  • 进程一旦变成僵尸进程,kill -9也没法杀死该进程。
  • 父进程通过进程等待的方式,回收子进程获取子进程的退出信息。

(3)wait()——系统调用

等待任意退出的一个子进程

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

返回值:
成功返回被等待进程pid,失败返回-1。

参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。

(4)waitpid()——系统调用

最常用的父进程回收子进程资源,让子进程从Z状态到X状态。

#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);

返回值:
>0:等待子进程成功,返回子进程pid。
=0:等待失败。
参数:
pid:>0:指定等待哪一个子进程;-1:等待任意进程(和wait函数一样)
status:输出型参数(通过调用该函数,从函数内部拿出来特定的数据),其中的函数内部指的是从子进程的task_struct中拿出子进程退出的退出码,给父进程。
options:0:阻塞等待;非0:非阻塞等待。

(5)获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待(只看status低16比特位)。

子进程的退出有两种情况

  • 一种是正常终止(结果正确或者不正确)
  • 一种是异常终止(进程因为异常问题,收到了某种信号)

通过判断是否收到终止信号(0-6,低7位)来判断进程是否是正常终止,如果是正常终止(低7位全是0),再去看退出状态(8-15,8个比特位)。

位运算获取子进程的退出状态(退出码)和终止信号
如果要获取8-15这8位退出状态(退出码),可以让status右移8位,然后和000…000 1111 1111((status>>8)&0xFF)与一下(因为status是32位的,右移8位后,把前面的24位置0);
如果要获取0-6这7位终止信号,可以让status&0x7F(只需要获取低7位就行);
如果正常退出,退出信号都是0;如果异常退出,退出信号就不是0,此时前面的退出状态也就没有意义。
在这里插入图片描述

C语言中宏定义获取子进程的退出状态(退出码)和判断是否收到终止信号

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)


三、fork()函数

在linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);

返回值:子进程中返回0,父进程返回子进程id,出错返回-1。

进程调用fork(),当控制转移到内核中的fork代码后,内核将做:

  1. 分配新的内存块和内核数据结构(pcb+地址空间mm_struct+页表)给子进程
  2. 将父进程部分数据结构内容拷贝至子进程
  3. 添加子进程到系统进程列表当中
  4. fork返回,开始调度器调度

fork()函数执行之前父进程独立执行,fork()函数之后,父子进程分别执行(父子共享所有代码)。
子进程执行的后续代码并不等于共享的所有代码,子进程只能从fork()函数之后开始执行(注:与CPU中的程序计数器也就是eip或pc指针有关)。

fork()函数之后,操作系统做了什么?
因为进程具有独立性,所以在fork()之后,创建子进程的内核数据结构(struct task_struct、struct mm_struct和页表),以及继承了父进程的代码,用写时拷贝的方式将父进程的数据进行共享或独立。

fork()写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
父进程和子进程的虚拟地址空间以及页表都是各自有一份的,但是对应数据虚拟地址映射到的物理地址是同一个。
默认所有的数据都是只读的,如果有子进程想要写某个数据(页表项100),会发生写时拷贝,就会发生缺页中断,先拷贝一份数据到新的物理地址空间,然后修改子进程页表的映射关系,让子进程对应数据的虚拟地址映射到新的物理地址,然后中断结束,让子进程来修改数据。
在这里插入图片描述

fork常规用法

  1. 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  2. 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值