fork子进程,进程退出与等待
认识fork()
头文件<unistd.h>
提供的fork()
函数用于从已有的原进程
创建一个新的子进程
,而原进程在关系式称为父进程
fork()的返回值
1 |
|
父子进程中fork()
函数的返回值(此处用变量id
储存)是不同的:
父进程里id
的值为子进程的PID
,其值>0
;子进程里id
值固定为0
id > 0
父进程id == 0
子进程id < 0
fork()失败
分流
利用父子进程中fork()
返回值的不同,可以用if...else...
进行分流,让父子进程执行不同的代码
fork()的过程
进程调用fork
,当控制转移到内核中的fork代码后,内核做
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
fork
返回,开始调度器调度
当一个进程调用fork之后,就有一对二进制代码相同的父子进程。而且它们都运行到相同的地方。但每个进程都可以独立地继续运行代码,并按代码分流至不同的代码段
但这两个进程谁先执行完全由调度器决定,而谁先结束,则由实际执行情况和调度器决定
拷贝的过程 与 写时拷贝
fork
时,子进程会将父进程虚拟内存的内容都复制一份在自己的虚拟内存中,但通过页表,二者映射到了同一块物理内存的区域,这样在二者都没有写入行为时,减少了物理内存中冗余的拷贝行为,有效提高了运行效率
但当父子进程中有一方发生写入行为时,就会触发写时拷贝
,此时物理内存中发生拷贝行为,但只拷贝进程映射的数据段
,而由于不发生进程替换时父子进程的代码段一定相同,物理内存中,代码段
映射部分并不发生拷贝
进程退出
进程退出的场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
进程常见退出方法
main
函数返回exit
退出_exit
退出
异常退出: ctrl
+ c
查看退出码
在终端使用命令echo $?
可以查看退出码
注
尽管子进程返回的是int
,父进程只取退出码的最低8位
,所以以下三种情况
main
函数返回-1
exit(-1)
_exit(-1)
在终端输echo $?
可得退出码255
exit
和 _eixt
辨析
main
函数返回就不惜说了,来辨析一下头文件<stdlib.h>
提供的exit
和_exit
函数
_exit()
_exit
会直接终止进程并返回状态码,而不会执行用户定义的清理函数,也不会清理缓冲区
exit()
exit
实际上最后也会调用_exit
,但它会先执行一系列善后工作,顺序如下:
- 执行用户通过 atexit或on_exit定义的清理函数。
- 关闭所有打开的流,所有的缓存数据均被写入
- 调用
_exit
return
退出: 执行return n
等同于执行exit(n)
进程等待
进程等待的必要性
- 子进程退出后需要父进程来回收僵尸进程,防止产生其引发内存泄漏等问题
- 僵尸进程难以处理,
kill -9
也清理不掉 - 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待的方法
wait
方法
#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int*status);
参数
:显然status
是输出型参数,获取子进程的退出状态
,若不关心,可传参NULL
返回值
: 若成功,则返回被等待进程的pid
,若失败,则返回-1
waitpid
方法
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;参数:
pid
:
pid == -1
,等待任一个子进程。与wait等效。
pid > 0
.等待其进程ID与pid相等的子进程。
status
:
输出型参数传参&status
: 将子进程的状态码
存入status
以下两个宏函数用于处理状态码
:
WIFEXITED(status)
: 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status)
: 若WIFEXITED非零,提取子进程退出码
。(查看进程的退出码
)
options
:
WNOHANG
: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
0
: 阻塞等待指定pid
的进程
阻塞等待
option
== 0
时waitpid
采用阻塞等待,父进程会阻塞等待到子进程退出
示例代码
1 |
|
非阻塞轮询
option
== WNOHANG
时,waitpid
采用非阻塞等待,若等不到子进程退出,就会继续执行后面的代码,所以一般加上while
等循环用于轮询,二者共同构成非阻塞轮询
1 |
|