一个进程的终止无非就是以下三种:
1.正常运行结束
2.运行结束但结果不正确
3.因为异常而中途退出
进程退出常见方法:
正常终止:
1.从main函数返回
2.调用exit
3._exit
异常终止:
Ctrl+c,通过信号终止
在main函数中
#include
#include
int main()
{
printf("hello Linux\n");
exit(0);
printf("hello!\n");
return 0;
}
echo $?用于打印输出上一进程的退出码
在普通函数中且打印时不加‘\n’的情况
#include
#include
void func()
{
printf("func");
exit(0);
}
int main()
{
printf("hello Linux\n");
func();
printf("hello!\n");
return 0;
}
#include
#include
void func()
{
printf("func\n");
exit(0);
}
int main()
{
printf("hello Linux\n");
func();
printf("hello!\n");
return 0;
}
#include
#include
void func()
{
printf("func\n");
return;
}
int main()
{
printf("hello Linux\n");
func();
printf("hello!\n");
return 0;
}
通过上述例子我们可以看到无论是在main函数中还是普通函数中exit的作用都是直接退出进程,而return在main函数中是退出进程,在普通函数中只是退出该函数,不是把整个进程给退出了
在函数打印时加‘\n’的情况
#include
#include
#include
void func()
{
printf("func\n");
_exit(0);
}
int main()
{
printf("hello Linux\n");
func();
printf("hello!\n");
return 0;
}
#include
#include
#include
void func()
{
printf("func");
_exit(0);
}
int main()
{
printf("hello Linux\n");
func();
printf("hello!\n");
return 0;
}
_exit()和exit()一样无论是在main函数还是普通函数中都是退出进程而不是退出函数,不同的是_exit前打印函数在不加’\n’时,不会把打印的东西打印出来,而exit函数在不加‘\n’时会打印出来
注意:
exit也会调用_exit函数,但在调用_exit函数之前做了以下工作:
1.执行用户通过atexit或on_exit去定义的清理函数。
2.关闭所有打开的流,所有的缓存数据均被写入
3.调用_exit
exit是库函数接口,_exit是系统调用接口
return退出进程是一种更为常见的退出进程的方式,在main函数中执行return n;等同于执行exit(n),因为调用main函数运行时,函数会将main函数的返回值当做exit的参数
什么是退出码?为什么要有退出码?
退出码其实就是一些运行结果的代号,比如0表示正常退出,2表示文件不存在等,通过返回退出码从而让关心他的父进程直到他做的事情怎么样了
我们来打印一下退出码,看一下对应的退出码都代表什么意思
#include
#include
void print()
{
for(int i = 0; i < 200; i++)
{
printf("%d: %s\n", i, strerror(i));
}
return;
}
int main()
{
print();
return 0;
}
上述只列出部分退出码
错误信息是通过的strerror()函数来输出的
进程在退出时,会返回退出码,而这个退出码一般是交给父进程,让关心它的父进程知道他办的事情怎么样了,在上述过程中我们一直都是使用echo $?去获取最近的退出进程的退出码,显然这种方式不是有效地,因为并不是直接交给了父进程,接下来我们将通过两个函数让父进程拿到子进程的退出码
进程等待的必要性:
1.让父进程知道父进程交给他做的事情他做的怎么样了
2.回收该子进程的资源,避免内存泄漏,因为处于该僵尸状态的子进程,即使是kill -9也无能为力
先来介绍一下wait函数:
1.参数:这里有一个形参,类型是指针,这其实是一个输出型参数本地状态(local status),就是参数变化外面调用部分对应的变量也会发生改变,而这个指针里面放的就是该进程的退出状态,如果不关心可以设为NULL。
2.返回值:返回值pid_t,若回收成功返回被等待进程的pid,失败则返回-1.
#include
#include
#include
#include
void Func()
{
for(int i = 0; i < 10; i++)
{
pid_t id = fork();
if(id > 0)
{
printf("I am father ppid: %d pid: %d\n", getppid(), getpid());
sleep(5);
wait(NULL);//wait函数回收子进程
}
else if(id == 0)
{
printf("I am child ppid: %d pid: %d\n", getppid(), getpid());
sleep(2);
exit(0);
}
else
{
perror("进程错误!\n");
}
}
sleep(5);
}
int main()
{
Func();
return 0;
}
我们可以看到,相比于wait函数,wait_pid多了两个参数,这两个参数又分别有什么作用?
1.第一个参数:用于等待指定pid的进程,如果给-1,就是可以等待任意退出的进程。
2.第二个参数:与wait函数相同都是输出型参数,返回状态信息。
3.第三个参数:用于选择等待方式,要等待的指定子进程有三种情况,一种是已经退出正等待回收,一种是还在运行,另一种是该进程不存在。在进程回收等待的过程中,有两种方式,一种是阻塞式等待一种是非阻塞式等待,阻塞式等待,如果该进程未执行完退出,就等到该进程退出,非阻塞式等待,如果该进程还在运行,就不再等待该进程完成。
非阻塞式等待更有利于利用资源,减少进程阻塞的发生。
options值为0则表示阻塞式等待,值为WNOHANG表示非阻塞式等待
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
#include
#include
#include
#include
void Func()
{
for(int i = 0; i < 10; i++)
{
pid_t pid = fork();
if(pid > 0)
{
printf("I am father ppid: %d pid: %d\n", getppid(), getpid());
sleep(3);
}
else if(pid == 0)
{
printf("I am child ppid: %d pid: %d\n", getppid(), getpid());
sleep(2);
exit(0);
}
else
{
perror("进程错误!\n");
}
int status = 0;
pid_t ret = waitpid(-1, &status, 0);
if(ret == pid && WIFEXITED(status))
{
printf("等待子进程成功\n");
}
else{
printf("等待子进程失败\n");
}
}
sleep(5);
}
int main()
{
Func();
return 0;
}
参数status并不能只看做一个简单整型变量,他里面存的是状态信息,那又是怎么存储的呢?
一个整型占32比特位,这里的int是被当做好几部分使用的
1.0-7位表示当被信号所杀的终止信号是什么
当终止信号为0时,则表示正常终止,此时查看退出状态信息,这里的退出状态信息时可信的,当终止信号不为0时,说明是异常退出,此时就8-15位的退出状态就是不可信的。因为8-15位是正常终止下的退出状态,而是不是正常终止就要看0-7位终止信号是多少
2.其中8-15位表示正常终止情况下的退出状态信息,若8-15为都为0,则表示正常终止下的正常退出,8-15不为0时表示正常运行结束但结果不正确。剩下的部分并没有被使用。
查看进程是否是正常退出:WIFEXITED(status)—(status) & 0X7F): 若为正常终止子进程返回的状态,则为真。
查看进程的退出码:WEXITSTATUS(status)----(status >> 8) & 0XFF): 若WIFEXITED非零,提取子进程退出码。
接下来我们通过程序来取出这部分的退出码:
#include
#include
#include
#include
void Func()
{
for(int i = 0; i < 10; i++)
{
pid_t pid = fork();
if(pid > 0)
{
printf("I am father ppid: %d pid: %d\n", getppid(), getpid());
sleep(3);
}
else if(pid == 0)
{
printf("I am child ppid: %d pid: %d\n", getppid(), getpid());
sleep(2);
exit(0);
}
else
{
perror("进程错误!\n");
}
int status = 0;
pid_t ret = waitpid(-1, &status, 0);
if(ret == pid && WIFEXITED(status))
{
printf("等待子进程成功\n");
printf("对应子进程的退出码:%d\n", (status >> 8) & 0XFF);
}
else{
printf("等待子进程出异常了\n");
printf("对应子进程的退出码:%d\n", (status) & 0X7F);
}
}
sleep(5);
}
int main()
{
Func();
return 0;
}
对于上面的(status) & 0X7F)和(status >> 8) & 0XFF)可以通过库里面的宏(宏函数)来实现,WIFEXITED(status)和WEXITSTATUS(status)这两个宏就是用来计算上述两个操作的。
WIFEXITED(status)和(status) & 0X7F)等价,WEXITSTATUS(status)和(status >> 8) & 0XFF)等价