目录
父子进程之间的等待
介绍
为什么要有等待
内存泄漏
如何等待
介绍
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,进行阻塞式等待
- 此时父进程不做任何事,在等待队列中等待子进程的状态改变
- 不占用cpu的时间片,这期间调度器调用其他可用进程
- 阻塞的上层表现 -- 该进程无反应(因为cpu没有调度它)
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帮它拿到了