父子进程之间的等待(wait和waitpid的介绍+原理),status的介绍+恢复退出码(位运算+宏),非阻塞等待(宏),signal查看

目录

父子进程之间的等待

介绍

为什么要有等待

内存泄漏

如何等待

介绍

pid_t wait (int* status)

介绍

status指针

示例

​编辑

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

pid

 options

WNOHANG -- 非阻塞等待

示例

status

查看status

status问题

正常退出时,恢复退出码

位运算

进程崩溃,查看signal

引入

kill指令

示例 -- 野指针

示例 -- kill -9

为什么一定要通过上面的这些函数拿到退出信息

那么,这些问题wait / waitpid 是如何解决的呢?


父子进程之间的等待

介绍

通常用于实现协作和同步,以确保父进程等待子进程完成 / 等待子进程的某个事件

为什么要有等待

  • 在某些情况下,父子进程需要在执行过程中相互协同工作,以确保程序的正确性和完整性
  • 等待可以用于协调它们的活动,使它们按照某种特定的顺序执行任务
  • 父进程可能需要等待这些子进程完成执行,以便获取子进程的结果、收集信息或继续执行其他操作
  • 只有当父进程获取到子进程的退出状态信息时, 才会回收子进程资源(所以,如果父进程在进程已经退出的情况下,没有进程回收,将会造成子进程的资源泄漏)

内存泄漏

  • 这里的内存泄漏是指进程的task_struct还保留在内存中(也就是系统资源泄漏)
  • task_struct由os维护,其他人没有权限来释放,只能等待父进程回收 / 父进程退出后由init进程回收
  • 而我们平常说的内存泄漏是指进程在堆上开辟的空间没有被释放
  • 但其实一旦进程退出,这些由语言申请的空间都会被释放掉

如何等待

介绍

 头文件:

pid_t wait (int* status)

介绍
  • 用于父进程等待子进程的终止,并获取其退出状态
  • 如果当前没有子进程终止,父进程将被阻塞,等待一个子进程终止(也就是阻塞式等待)
  • 当一个子进程终止后,父进程会解除阻塞,并获取已终止子进程的pid(返回值),如果没有子进程终止,返回-1
status指针
  • 用于存储子进程的退出状态信息
  • 当该位置有参数时,由os提供该值
  • 如果不关心子进程的退出状态,可以传递NULL
示例

下面的代码流程是:

  • 创建一个子进程,子进程打印信息后睡眠5s,
  • 而父进程打印信息后睡眠7s,再等待进程状态变化
  • (其中的2s内可以看到子进程处于僵尸状态)
  • 后调用wait函数,用以回收已终止子进程的资源

可以看到,我们成功等待到了子进程:

 

子进程24295从僵尸状态Z+ -> 进程退出了,说明父进程等待子进程成功后,就释放掉了子进程的资源

 

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

pid
  • 如果为-1,则代表等待任意一个子进程终止(与wait()等价)
  • >0,它在等待指定的子进程
 options

默认是0,进行阻塞式等待,和wait()功能一样

  • 此时父进程不做任何事,在等待队列中等待子进程的状态改变
  • 不占用cpu的时间片,这期间调度器调用其他可用进程
  • 阻塞的上层表现 -- 该进程无反应(因为cpu没有调度它)
WNOHANG -- 非阻塞等待
  • "WNOHANG"是宏定义(系统提供的大写标识位:一般都是宏定义,将没有特殊意义的数字定义成宏,使用时更加清晰)
  • 如果指定的子进程状态未改变,立即返回0 
  • 此时,父进程就可以执行waitpid后面的代码了(可以在等待的过程中完成一些小的工作模块)
  • 父进程在继续执行其他任务的同时,周期性地调用waitpid函数,以不断检查子进程的状态,从而及时获取子进程的退出状态
示例

可以看到,在父进程在等待子进程的过程中,依然在工作中

最终成功等待到子进程

 

status

和wait中的status用法一样

如果为waitpid(-1,NULL,0),则与wait(NULL)等价,看到的结果相同

查看status

如果我们尝试查看status呢?就可以使用waitpid(ret,&status,0):

我们让子进程睡眠5s后,带着退出码=2023退出,然后由父进程等待子进程退出,拿到退出码

 但是,结果似乎并不是我们想象中的那样(虽然子进程成功退出了,但是status的值很奇怪捏):

拿到的退出码怎么会是59136呢?

status问题

为什么上面的代码会出现值不一样的情况呢?

实际上status在函数内部并不是当做一个整数来对待,而是拆成32位bit位

其中低16位是我们需要了解的

正常退出时,恢复退出码
  • 如果程序执行完毕 (可以通过 宏(WIFEXITED) / 退出码 来判断是否正常退出)
  • 它会将实际的退出码放入[status的低16位]的后8位,然后将该数作为status的值
  • 所以我们得到的status会比设置的退出码(2023)大很多
位运算
  • 可以通过位运算的方式,将原本的退出码还原 (status>>8) & 0x0000 00ff
  • 右移8位是为了将退出状态移到该数的低8位,然后按位与拿到低8位
  • 也可以通过拿到该值 (WEIXTSTATUS)
进程崩溃,查看signal
引入
  • 如果进程崩溃提前终止了呢?这时的退出状态就没有意义了(会是0)
  • 其实想一想,运行的程序崩溃 本质上就是 os将这个进程杀死了
  • 那它是如何在茫茫进程中,单单选中了崩溃的进程把它杀死了呢?
  • 是通过程序发送信号通知os的
kill指令
  • 实际上,我们的kill指令就是通过os向进程发送信号,来对进程进行操作
  • 其中,信号是有编号的,1-31是普通信号,我们要学的也就是这31个信号
  • 然后,通过将 信号 放入 status的低7位 (第8位是core dump标志,在gdb调试崩溃程序中用到)
  • 我们可以通过按位与拿到低7位(& 0x0000 007f)
示例 -- 野指针
示例 -- kill -9

除了出现错误使程序崩溃,也可以是外力直接杀掉

  • 当子进程在死循环时,可以使用kill指令杀掉它(通过发送信号)

为什么一定要通过上面的这些函数拿到退出信息

  • 如果设置为全局变量呢?似乎可以拿到
  • 但其实,因为你要根据不同的判断条件来修改退出码,所以此时必定发生写时拷贝
  • 那么就会导致父子进程中的那个全局变量实际上有两个,父进程无法拿到子进程的变量,进程之间具有独立性

  • 除此之外,信号你又该如何拿到呢?

那么,这些问题wait / waitpid 是如何解决的呢?

  • 首先,那些退出信息也是数据,它就被存放在描述进程的pcb中
  • 还记得僵尸进程吗?
  • 为什么它有害,就是因为它的task_struct还保留在内存中,占用着资源
  • 必须要由父进程拿到子进程的退出码,才能释放它
  • 但是,进程之前不是有独立性吗?父进程是如何拿到子进程中的退出码的?
  • 实际上是由os完成的
  • os会自动回收子进程的资源,并将退出状态信息传递给父进程
  • 那么,wait和waitpid也是一样的道理,也是os完成实际的工作

  • wait和waitpid是系统调用,它是由os提供的接口
  • 因此由os维护的task_struct自然可以被自己的接口调用啦!!!
  • 所以,父进程是无法拿到子进程内部的信息的,但是os帮它拿到了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值