一.进程创建
fork函数创建子进程
fork函数用于创建子进程,对于父进程返回子进程的Pid,对于子进程返回0
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方
创建子进程的过程 :
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
子进程也是一个独立的进程,也会有自己的进程控制块PCB,自己的代码和数据,子进程的数据和代码会先和父进程共享一部分,这些数据会被标识为只读
当父进程或子进程修改这些数据时候,就会触发写实拷贝
写实拷贝 : 通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本
修改之前,父子进程的页表映射的是同一块代码和数据
修改后,发送写实拷贝
二.进程退出
进程退出一般三种情况
- 程序运行完成,结果正确
- 程序运行完成,结果不正确
- 程序异常出错,比如收到信号
进程常见退出方法
1.main函数返回
函数会将main的返回值当做exit的参数。
2.调用库函数或系统调用
这是一个系统调用,用来退出当前进程
这是C语言库函数,用来退出当前进程
显然,C语言库函数void exit(int status) 是封装了系统调用,exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:
- 执行用户通过 atexit或on_exit定义的清理函数。
- 关闭所有打开的流,所有的缓存数据均被写入
- 调用_exit
exit会清理C语言中各个缓冲区数据,比如输入输出流的数据,而_exit却不会
3.异常中止
比如Ctrl+c会被解释为SIGINT信号,Ctrl+/会被解释为SIGQUIT信号,SIGSEGV段错误
这里不细说,后面在信号章节详细介绍
进程等待
在进程概念那一章说过, 如果父进程不回收子进程资源,子进程就会进入僵尸状态,进程的PCB没有被释放,会导致内存泄漏,进程等待是为了得到子进程的执行状态和回收资源
wait系统调用 : 等待任意一个子进程, wstatus是输出型参数,获得子进程的退出码,等待成功返回等待进程的pid,失败返回-1
waitpid系统调用 : 等待指定pid的子进程,如果设置为-1,则与wait等效,最后一个参数代表等待方式,可以选择阻塞等待,非阻塞等待等
选择阻塞等待,父进程就会阻塞在wait函数处,直到等待成功
进程退出码
进程的退出码有自己的格式,前两种退出方式都是第一个,异常退出则是第二个
我们可以根据这个来提取进程的退出状态或者退出信号
或者使用宏
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
三.进程替换
进程替换使得我们可以在当前进程去执行一个新的程序,而不用再创建一个新进程
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
第一个参数是要执行新程序的可执行程序名(带p),可执行程序的路径(不带p)
后面的参数可以看成命令行参数的形式,使用列表传参和使用数组传参最后都要以NULL结尾
envp表示要传给新程序的环境变量
进程替换原理
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序第一条指令开始执行
进程替换前,进程原本的虚拟内存映射到物理内存
执行进程替换后