进程的终止
正常终止
1、从main函数返回
2、调用exit
3、调用_exit或_Exit
4、最后一个线程从其启动例程返回
5、最后一个线程调用pthread_exit
异常终止
1、调用abort
2、接到一个信号并终止
3、最后一个线程对其取消请求作出响应
atexit():钩子函数
除了使用atexit()来实现钩子函数之外,还可以使用on_exit()来实现。
NAME
atexit - register a function to be called at normal process termination
注册一个函数,在程序正常终止时将调用这个函数。
SYNOPSIS
#include <stdlib.h>
int atexit(void (*function)(void));
DESCRIPTION
The atexit() function registers the given function to be called at normal process termination, either via exit(3) or via return from the program's main().
我们在atexit()函数中注册了一个函数,在进程正常终止时,atexit()函数就会通过exit(3)或从程序中的main()返回,然后调用该函数。
Functions so registered are called in the reverse order of their registration; no arguments are passed.
注册的函数被调用的顺序与它们注册的顺序相反;不传递任何参数。
The same function may be registered multiple times: it is called once for each registration.
同一个函数可以被注册多次:每注册一次都会被调用一次。
例子:
#include <stdio.h>
#include <stdlib.h>
void f1(void)
{
puts("f1() is working!");
}
void f2(void)
{
puts("f2() is working!");
}
void f3(void)
{
puts("f3() is working!");
}
int main()
{
puts("begin!");
/*只是把三个函数挂到钩子上,不是调用*/
atexit(f1);
atexit(f2);//以逆序被调用
atexit(f3);
//什么时候才调用,即将执行exit(0)或return 0之前.
puts("end!");
return 0;
}
程序运行结果如下:
我们再来写一个伪代码说一下钩子函数有什么用处。
我们写程序时有可能会发生下面的情况。
fd1 =open();
if(fd1<0)
{
perror();
exit(1);
}
fd2 =open();
if(fd2<0)
{
close(fd1);
perror();
exit(1);
}
......
......
fd100 =open();
if(fd100<0)
{
close(fd1);
close(fd2);
close(fd3);
......
close(fd99);
perror();
exit(1);
}
这就很麻烦,现在有钩子函数了,我们就可以这样做。
fd1 =open();
if(fd1<0)
{
perror();
exit(1);
}
atexit(); -->作用是close(fd1);
fd2 =open();
if(fd2<0)
{
perror();
exit(1);
}
atexit(); -->作用是close(fd2);
....
fd100 =open();
if(fd100<0)
{
perror();
exit(1);
}
atexit(); -->作用是close(fd100);
这里举打开文件的例子只是一种情况,我们还可以回收使用malloc开辟的没有释放的空间。
exit和 _exit或_Exit
exit
exit函数在手册的第三章。所以使用man 3 exit来查看。
NAME
exit - cause normal process termination
引起正常进程终止
SYNOPSIS
#include <stdlib.h>
void exit(int status);
DESCRIPTION
The exit() function causes normal process termination and the value of status & 0377 is returned to the parent (see wait(2)).
参数 status & 0377 (0377是八进制,换成二进制就是11111111)将被返回给父进程,也就是将status的第八位返回给父进程,第一位是符号位,也就是说status的范围是 -128 — +127。
通常,我们传入参数0,表示正常退出。其他表示非正常退出。
请问下面代码中这个return 0;是给谁看的?
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello.\n");
return 0;
}
答:这个return 0;是给当前进程的父进程看的。
我们将该程序通过gcc编译器编译之后,然后通过命令./xxx来执行,从" ./ " 可体现出来当前进程的父进程是shell,是shell将其创建出来的。
使用命令
echo $?
我查了下,
该命令的含义可以表示两种,
一种是最后运行的命令的返回值,即执行上一个指令的返回值 。
另外一种是最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
输出结果如下:
0
现在,我们将代码进行修改,将return 0;给去掉,再试着执行一下代码。
执行结果仍然是
Hello.
现在,再试着使用命令
echo $?
看(视频教程中)得到的结果:
7
我的执行结果是0。
那为什么是7呢?
是因为我们把return 0;这句代码去掉之后,上一条执行的命令就是printf(“Hello.\n”);
而printf的返回值就是成功输出字符的个数。
所以当我们使用
echo $?
打印出来的结果自然是printf的返回结果。
需要说明:
(视频中输出正常,打印出来的是7,所以返回的是上一个指令的返回值。)
(用我的虚拟机执行我的代码,输出正常,打印出来的是0,所以返回的是最后命令的退出状态。)
exit和 _exit或_Exit的区别
exit是库函数。
_exit 或 _Exit是系统调用。
所以,一定是 exit 依赖_exit或_Exit来实现函数功能。
那exit和 _exit或_Exit只是这一点的区别吗?
并不是的。
看下图。
从图中可以看到,在最下面是内核,也就是说当前的程序调用肯定要和内核发生交互(也就是系统调用的实现)。
虚线框是进程的虚拟空间。当用户在用户函数或main函数中或C启动历程中调用_exit或_Exit函数的时候,都是往图中的左侧走的,会直接跳出虚线框,代表的是直接终止。
而当用户在用户函数或main函数中或C启动历程中调用exit函数的时候,不是直接跳出虚线框的,而是先调用n多个终止处理程序(比如钩子函数),之后再调用标准I/O清理程序,然后才依赖于_exit或_Exit来结束当前的进程。
那什么时候应该用exit函数,什么时候又该用_exit或_Exit函数呢?
我们来举个例子来说一下。
int func()
{
//可以返回0、1、2
return 0/1/2;
}
int main()
{
int f;
f=func();
....
....
...
switch(f)
{
case 0:
case 1:
case 2:
//假设在当前情况下,没有0,1,2这种可能,说明程序出的问题在省略号部分,机器有可能是因为写越界,从而导致该部分将f的值做了修改。
default:
//假设使用exit(1);那么程序就会先调用n多个终止处理程序(比如钩子函数),之后再调用标准I/O清理程序(刷新或同步各种内容),那么可能造成的问题可能就是故障扩大。
//那在这种情况下应该怎么做呢?
//1、除了应该调用_exit或_Exit函数,直接退出。
//2、还可以使用信号abort();将当前进程直接杀死,顺便得到出错的原因。
}
}