进程控制
⭐进程创建
我们先了解两个系统调用接口,fork和vfork的区别:-
pid_t fork(void):(#include<unistd.h>)
创建子进程的流程:(写时拷贝技术)
1.创建PCB
2.拷贝父进程PCB中的数据(拥有相同的虚拟地址空间,相同的页表……)
3.父子进程一开始映射同一块物理内存
4.等到物理内存修改的时候才为子进程重新开辟内存,拷贝数据到新的内存空间
返回值:
1.返回值为0,则是子进程
2.返回值是子进程的pid,则是父进程
3.返回值小于0,则创建出错
fork调用失败的原因:
1.系统中有太多的进程
2.实际用户的进程数超过了限制
注意:fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。 -
pid_t vfork(void):(#include<unistd.h>)
特性:创建一个子进程,但是vfork创建子进程之后父进程会阻塞,直到子进程退出或者程序替换。
实现:创建的子进程和父进程共用同一个虚拟地址空间,为了防止调用栈混乱,因此阻塞父进程。
区别:fork和vfork两个接口创建子进程,都是在内核中通过调用clone函数实现的。在clone函数中实现task_struct的创建,以及根据参数不同,拷贝不同的数据(例如:vfork就不需要为了子进程创建虚拟地址空间/页表)
⭐进程终止
进程的3种退出场景:
- 异常退出—返回值无意义,不能作为评判标准正常退出;
- 正常退出,结果符合预期 —根据返回值判断是否满足预期正常退出;
- 正常退出,结果不符合预期 —根据返回值判断是否满足预期
进程常见的退出方法:
-
(正常终止:可以通过 echo $? 查看进程退出码)
1.在main函数中return:return退出之前,会刷新一下缓冲区
2.在任意地方调用exit ( int statu ) 接口—库函数接口—退出前都会刷新缓冲区
3.在任意地方调用_exit ( int statu )接口—系统调用接口—退出时直接释放所有资源,不会刷新缓冲区 -
(异常退出:)
1.ctrl + c,信号终止
2.段错误:程序访问(读写)了系统未给予读写权限的内存空间
说明:虽然exit/_exit接口的参数status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。
如何打印/获取一个系统调用接口的错误信息:
- void perror(char *msg) :直接打印一个错误信息
- char *strerror(int errno):传入错误编号,解释成一个字符串错误信息返回(获取)
exit和_exit的工作流程:
exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:
⭐进程等待
概念:等待子进程的退出,获取这个子进程的返回值,回收子进程的所有资源,避免产生僵尸进程。
阻塞与非阻塞的区别:(一个函数调用是否会立即返回)
- 阻塞:为了完成一个功能,发起调用,若当前不具备完成条件,则一直等待!!直到完成后返回
- 非阻塞