linux进程退出之exit与_exit
- _exit
- exit
- 流程
- 清理函数
- `atexit()`函数:
- `on_exit()`函数:
_exit
/* Terminate program execution with the low-order 8 bits of STATUS. */
/*
* status参数定义了进程的终止状态,父进程可以通过wait()来获取该状态值。
* 虽然status是int型,但是仅有低8位可以被父进程所用。
*/
extern void _exit (int __status) __attribute__ ((__noreturn__));
功能
调用_exit()函数会使程序立即退出,不会进行任何清理操作。用户调用_exit函数,本质上是调用exit_group系统调用。
exit
/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered,
perform stdio cleanup, and terminate program execution with STATUS. */
/*
1. exit()函数的最后也会调用_exit()函数,但是exit在调用_exit之前,还做了其他工作:
2. 1)执行用户通过调用atexit函数或on_exit定义的清理函数。
3. 2)关闭所有打开的流(stream),所有缓冲的数据均被写入(flush),通过tmpfile创建的临时文件都会被删除。
4. 3)调用_exit。
*/
extern void exit (int __status) __THROW __attribute__ ((__noreturn__));
流程
-
首先是exit函数会执行用户注册的清理函数。用户可以通过调用atexit()函数或on_exit()函数来定义清理函数。
这些清理函数在调用return或调用exit时会被执行。执行顺序与函数注册的顺序相反。当进程收到致命信号而退出时,注册的清理函数不会被执行;当进程调用_exit退出时,注册的清理函数不会被执行;当执行到某个清理函数时,若收到致命信号或清理函数调用了_exit()函数,那么该清理函数不会返回,从而导致排在后面的需要执行的清理函数都会被丢弃
。 -
其次是exit函数会冲刷(flush)标准I/O库的缓冲并关闭流。
glibc提供的很多与I/O相关的函数都提供了缓冲区,用于缓存大块数据。缓冲有三种方式:无缓冲(_IONBF)、行缓冲(_IOLBF)和全缓冲(_IOFBF)。
- 无缓冲:就是没有缓冲区,每次调用stdio库函数都会立刻调用read/write系统调用。
- 行缓冲:对于输出流,收到换行符之前,一律缓冲数据,除非缓冲区满了。对于输入流,每次读取一行数据。
- 全缓冲:就是缓冲区满之前,不会调用read/write系统调用来进行读写操作。
对于后两种缓冲,可能会出现这种情况:进程退出时,缓冲区里面可能还有未冲刷的数据。如果不冲刷缓冲区,缓冲区的数据就会丢失。比如行缓冲迟迟没有等到换行符,又或者全缓冲没有等到缓冲区满。尤其是后者,很容易出现,因为glibc的缓冲区默认是8192字节。exit函数在关闭流之前,会冲刷缓冲区的数据,确保缓冲区里的数据不会丢失。
- 存在临时文件,exit函数会负责将临时文件删除。
- 调用_exit()函数。
清理函数
可以使用atexit()
函数或on_exit()
函数来注册清理函数,以在程序终止时执行一些特定的清理操作。
atexit()
函数:
- 原型:
int atexit(void (*function)(void))
- 作用:用于注册一个清理函数,该函数在程序
正常终止
时被调用。 - 参数:
function
是一个指向无返回值的函数的指针,该函数没有参数。 - 返回值:成功注册清理函数时返回0,失败时返回非零值。
以下是使用atexit()
函数注册清理函数的示例:
#include <stdio.h>
#include <stdlib.h>
void cleanup1()
{
printf("Cleaning up...1\n");
}
void cleanup2()
{
printf("Cleaning up...2\n");
}
int main()
{
printf("Main program starts.\n");
// 注册清理函数
atexit(cleanup1);
atexit(cleanup2);
printf("Main program ends.\n");
exit(0);
}
运行结果:
在上述示例中,cleanup()
函数被注册为清理函数。当程序正常终止时,清理函数会被自动调用。
on_exit()
函数:
- 原型:
int on_exit(void (*function)(int status, void *arg), void *arg)
- 作用:用于注册一个清理函数,该函数在程序
终止时被调用,不论是正常终止还是异常终止
。 - 参数:
function
是一个指向接受两个参数的函数指针,第一个参数是终止状态码,第二个参数是可选的自定义参数。arg
是一个可选的自定义参数,会传递给清理函数。
- 返回值:成功注册清理函数时返回0,失败时返回非零值。
以下是使用on_exit()
函数注册清理函数的示例:
#include <stdio.h>
#include <stdlib.h>
void cleanup1() {
printf("Cleanup 1 called\n");
}
void cleanup2(int status, void *arg) {
printf("Cleanup 2 called with status: %d, arg: %p\n", status, arg);
}
int main() {
// 使用 on_exit 函数注册终止处理函数
on_exit(cleanup1, NULL);
on_exit(cleanup2, (void *)0x12345678);
printf("Main function executing\n");
// 这里可以添加一些其他逻辑
// return是一种更常见的终止进程的方法。
// 执行return(n)等同于执行exit(n),因为调用main()的运行时函数会将main的返回值当作exit的参数。
return 0;
}
运行结果:
在上述示例中,cleanup()
函数被注册为清理函数,并且不接受自定义参数。当程序终止时,清理函数会被自动调用,并传递终止状态码作为参数。
推荐使用atexit,因为atexit是c标准函数,不同系统都会支持。而on_exit可能会在某些平台下无法支持,此函数来自 SunOS 4,但也存在于 libc4、libc5 和 glibc 中。它不再出现在 Solaris (SunOS 5) 中。