【Linux进程控制一】进程的终止和等待
【Linux进程控制一】进程的终止和等待
- 一、进程终止
- 1.main函数的return
- 2.strerror函数
- 3.库函数exit
- 4.系统调用_exit和库函数exit的区别
- 5.异常信号
- 6.变量errno
- 二、进程等待
- 1.什么是进程等待?
- 2.wait接口
- 3.status
- 4.waitpid接口
一、进程终止
1.main函数的return
写C/C++代码时总会在写了int main函数的结尾写return 0,但是程序肯定不止能return 0
1.main函数和非main函数执行到return语句时表示此函数执行完毕
2.程序正常执行完毕且结果正确时return返回0
3.程序正常执行完毕但结果不正确时return返回非0
非0值有很多,1、2、3、4等数字分别有什么含义呢?
2.strerror函数
作用:将错误码转换为错误字符串
0对应成功,且在134号错误之后都是未知错误
查看最近进程的退出码: 指令echo $?
测试echo $?
指令:
所以return返回的就是错误码
3.库函数exit
exit的参数即为错误码,和main函数return的值一样
exit函数和return的区别:
1.return只有在main中使用时才能代表此进程退出
2.exit函数在程序任一地方使用都可以直接退出进程,并且返回错误码
测试一下:
使用echo $?
打印出来的退出码是11
4.系统调用_exit和库函数exit的区别
man的2号手册可以看见_exit系统调用:
它和exit一样都是终止进程且_exit的参数也代表错误码
两个有什么区别?用两段代码来验证:
//第一次运行的代码:printf("打印成功!");exit(22);
//第二次运行的代码:printf("打印成功!");_exit(22);
第一次打印了文字,第二次没有打印
printf打印本质是行刷新,如果不使用\n换行数据就一直存储在缓冲区不会打印
当用库函数exit结束进程就能把缓冲区的数据打印出来(刷新缓冲区数据)
而系统调用_exit就不会刷新缓冲区数据
深入思考:
exit是C语言提供的库函数,而_exit是操作系统提供的系统调用,_exit无法刷新缓冲区就说明缓冲区不在操作系统内,也就是说缓冲区不由操作系统来维护,而是由C标准维护
5.异常信号
对于任意一个进程,都只有两种退出情况:
1.代码执行完毕,正常退出
2.进程发生了异常,被迫退出
命令行输入Ctrl+C传递信号杀掉进程是异常终止
比如说访问空指针,野指针,除零错误等等,这些都会导致进程直接终止
报出一个错误Floating point exception,说明可能存在 / 或者 % 的除数为 0 的情况,这就说明进程发生了异常
进程发生异常的本质,是收到了异常信号
通过指令kill -l
来查看异常信号:
8号正是singal Floating point exception的缩写
该程序会陷入死循环,然后每隔一秒输出自己的pid
当进程执行起来后我们对其发送8信号
左侧窗口执行进程后,pid=559712,右侧窗口执行指令kill -8 559712,就向559712发送了8号信号,此时进程直接退出,并输出Floating point exception
明明是一个死循环,我们可以通过发送异常信号直接终止,而代码中明明没有除零错误,我们也可以通过信号对其强行加上异常
程序异常崩溃时,return的退出码是无意义的,因为退出码对应的return语句还没执行就已经崩溃了
6.变量errno
errno是C语言中的一个全局变量,它里面存储的是上一次函数调用的错误码,比如使用fopen函数打开文件时,如果打开失败不仅文件指针fp会被赋值为NULL,同时错误码errno也会被系统自动赋值
在当前目录下,不存在一个叫做111.c的文件,我们用fopen以r形式打开,那么fopen是打不开文件的,于是就会向errno进行写入
执行结果:输出了错误码2,strerror将错误码转换为错误字符串:即没有对应的文件或目录
二、进程等待
1.什么是进程等待?
进程等待用于回收子进程的资源,避免子进程的PCB一直占用资源,并且可以获取子进程的退出信息,得知子进程任务的执行情况,进程等待主要通过两个系统调用接口wait
和waitpid
来完成
2.wait接口
使用wait接口,需要包含头文件<sys/types.h>
和<sys/wait.h>
,其函数原型为:
pid_t wait(int* stat_loc);
wait的返回值是一个int类型
返回值大于0,返回等待到的子进程的pid
返回值小于0,等待失败
先通过fork创建了一个子进程
子进程进入if语句,进行五秒倒计时,然后退出,并且退出码为5
父进程则通过wait函数进行等待,传入指针&status,接收返回值pid,最后输出status和pid的值
- wait的返回值就是子进程的pid
- status一开始被初始化为0,wait之后,status = 1280,可知wait确实会修改传入的参数
- 子进程总共sleep了五秒,而父进程在等待的这五秒中,没有任何动作,就等着子进程结束,然后对它进行回收,这个过程父进程处于阻塞状态,称为阻塞等待
status为什么是1280?
3.status
status要当作一个位图来看:
- 灰色部分:(status是一个int类型,占32比特)后16比特是无效的,不填入任何内容
- 黄色部分:第8 - 15位,共8比特,表示wait到的子进程的退出码
- 绿色部分:第7位,core dump标志位本章节不考虑该位置
- 蓝色部分:第0 - 6位,共7比特,用户表示wait到的子进程的退出信号
因为return的退出码是5,二进制为0101,所以status后16位是0000 0101 0000 0000,转换为十进制正是wait修改后的status值1280,退出无异常所以退出信号为0
从status中提取出退出码和退出信号,就要对其进行位操作:
status直接与01111111进行按位与&,就能得到退出信号,01111111的十六进制表示为0X7F
int sig = status & 0x7F;
status右移8位后,与11111111进行按位与&,就能得到退出码,11111111的十六进制表示为0XFF
int code = (status >> 8) & 0xFF;
可以看到退出码为5,退出信号为0
Linux还给用户提供了两个宏函数,用于检测status:
WIFEXITED
:检测进程是否正常退出,返回一个布尔值,如果进从正常退出,返回真
WEXITSTATUS
:提取子进程的退出码,也就是第8 - 15位
使用:
if(WIFSIGNALED(status))printf("exit code = %d\n", WEXITSTATUS(status));
elseprintf("子进程退出异常...\n");
4.waitpid接口
进程等待的另外一个接口是waitpid接口,需要包含头文件<sys/types.h>
和<sys/wait.h>
,其函数原型为:
pid_t waitpid(pid_t pid, int* stat_loc, int options);
一个进程是可以有多个子进程的,但一个wait只能等待一个子进程,如果有多个子进程,那么wait函数只能等待第一个结束的子进程,而waitpid则是针对pid来对进程进行等待
waitpid
的第一个参数是子进程的pid,第二个参数用于接收退出信息也就是刚刚的status,第三个参数用于控制等待的模式
通过代码验证wait和waitpid的区别:
我们通过fork创建了两个子进程,第一个子进程输出自己的pid后会sleep五秒,而第二个子进程输出pid后sleep一秒
父进程只wait一次,最后父进程输出wait的返回值,而返回值就是等待到的子进程的pid,这样就可以判断父进程wait到了哪一个子进程
child1的pid = 624488,child2的pid = 624489,而wait的返回值为624489,说明wait到了第二个进程。因为第二个进程先结束,所以被wait先接收了
现在我们把上述代码中的wait改为waitpid再次尝试:
通过waitpid的第一个参数,指定等待id1,也就是第一个子进程,第三个参数先设为0,后续讲解该参数的作用
返回值和child1匹配上了,可以说明虽然child1更晚结束,但是waitpid只会等待指定的进程,就算有其他子进程先结束了waitpid也不会去先回收
第三个参数用于控制进程等待的模式:
0
:进行阻塞等待
WNOHANG
:进行非阻塞等待
讲到wait时,简单提到了阻塞等待,也就是父进程在wait的时候,什么也不做,进入阻塞状态,直到wait成功
而非阻塞等待不同,进行非阻塞等待时,如果本次waitpid暂时没有等待到,那么父进程不会阻塞,waitpid直接返回0,表示本次等待没有等待到子进程。此时父进程就可以空出时间去完成别的任务,而不是什么也不做一直在等了
代码验证:
先通过fork创建了一个子进程,子进程sleep五秒。父进程陷入一个while死循环,每次循环开始,都以WNOHANG模式waitpid一次,由于该模式不会阻塞等待,只要当前子进程没有结束,那么waitpid直接返回0,去执行后面的if语句
如果当前返回值为0,说明当前子进程没有结束
如果当前返回值> 0,说明子进程结束了,waitpid也成功了,此时返回值就是子进程的pid ,跳出循环
子进程一共执行五秒后才退出,以非阻塞等待的模式,父进程就可以把这五秒拿去做其他事情