一、进程终止
1.2 进程退出码
我们常见的main函数的返回值就是进程退出码,退出码包含进程退出的信息
退出场景
代码运行完毕,结果正确 ,返回0(退出码) 代码运行完毕,结果不正确,返回非0(退出码) 代码异常终止,返回非0的未知退出码
1.3 进程常见退出场景
正常终止(可以通过 echo $? 查看进程退出码):
- 从main返回
- 调用exit
- _exit
exit和_exit的参数都是退出码,但两者退出方式有所不同。
异常退出:
- ctrl + c,信号终止
- 程序出错
查看最近一次进程退出的退出码:echo $? 每一个退出码对应一种退出信息,但已知错误也是有限的,我们可以通过strerror()函数查看每个退出码对应的信息,可以看到Linux下退出对应的有效退出码只到133.
那异常退出,返回的错误码是多少呢? 我们可以给程序弄一些运行错误,比如除0,使用野指针等 可以看到退出是139,没有对应的退出信息。
二、进程等待
2.1 进程等待必要性
- 通过获取子进程退出的信息,能够得知子进程执行结果。
- 可以保证:时序问题,子进程先退出,父进程后退出
- 进程退出的时候会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放该子进程占用的资源!
2.2 进程等待的方法
2.3 等待函数wait
wait函数的使用 其实很简单,只需要根据返回值执行结果。 返回值:
参数:
- 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt--)
{
printf("child[%d] running: %d\n",getpid(),cnt);
sleep(0);
}
}
exit(1);
}
printf("father start waitting\n");
pid_t ret = wait(NULL);
if(ret > 0)
{
printf("waitting successful:%d\n",ret);
}
else
{
printf("waitting fail: %d\n",ret);
}
return 0;
}
关于wait的参数status下一个函数介绍
2.4 等待函数waitpid
pid_ t waitpid(pid_t pid, int *status, int options); 返回值:
- 当正常返回的时候waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数: pid:
- pid=-1,等待任意一个子进程。与wait等效。
- pid>0,等待其进程ID与pid相等的子进程。
status:
- WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
- WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
- 0 : 默认行为,阻塞等待
- WNOHANG: 非阻塞等,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
2.4.1 参数status
从前面可以知道一个程序的退出大致可分为两种
程序正常结束,有退出码,返回退出码 程序异常结束,没有退出码,返回异常信号
既然父进程等待子进程结束,获取子进程退出信息,那么就需要拿到向对应的退出码或者异常信号。status参数就是用来获取对应信息的,那返回的信息父进程怎么解析呢?
status是一个输出型参数传入的是整型指针,整型有32个比特位,低16个比特位存放退出信息,如下图所示
那么怎么获取两种信息呢? 假设父进程传参为:
获取退出码:只需让它右移8位,然后与上低8位全1的数即0xff 获取异常信号:不用移位,直接与上低7位是1的数即可,即0x7f
程序正常结束
进程异常结束
当然也不用每次都这么麻烦 系统给了两个宏定义,可以直接使用
- WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
- WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
2.4.2 参数options
- 阻塞的本质:其实是进程的PCB被放入了等待队列,并将进程的状态改为S状态
- 返回的本质:进程的PCB从等待队列拿到R(运行)队列,从而被CPU调度
阻塞状态就是父进程啥也不干,等着子进程 非阻塞状态就是父进程在等子进程的时候,自己去做其他事,做完或者做完一部分,再去询问子进程是否结束,如果子进程结束,那么父进程等待过程结束,否则父进程接着做自己的事。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt--)
{
printf("child[%d] running: %d\n",getpid(),cnt);
sleep(1);
}
exit(1);
}
printf("father start waitting\n");
int status = 0;
while(1)
{
pid_t ret = waitpid(id,&status,WNOHANG);
if(ret == 0)
{
printf("Do father thing\n");
sleep(1);
}
else if(ret > 0)
{
if(WIFEXITED(status))
{
printf("exit code: %d\n",WEXITSTATUS(status));
}
else
{
printf("error, get a signal!\n");
}
break;
}
else
{
printf("fail\n");
break;
}
}
}
三、进程替换
3.1 替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
3.2 替换函数
C语言库里面的相关替换函数 函数解释:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值
其实它们本质都在调用一个系统接口,只是经过了封装而已。 命名解释:
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
带 l 的表示,命令行参数以列表传入,如execl、execlp、execle 带v的表示将命令行参数以数组的形式传入
带p的表示自动传入环境变量 execlp 使用环境变量,可以直接使用ls,就能找到对应的地址 带e的表示传入自定义的环境变量给要替换的程序 传入自己的写的环境变量,当然真实的环境环境变量不会这么随意 在test1.c文件里面打印环境变量,若不用程序替换执行该程序,就会打印全局的环境变量。程序替换传入自己的环境变量后就会打印传入的,入下图:
这几个函数就是这四种情况的组合,看参数使用即可。
|