[glibc] exit如何调用atexit处理器,实现疑问?
摘要:atexit 处理器中再次调用 exit 为什么能正常运行?atexit 处理器中再次调用 atexit 注册的函数为什么能正常被调用?带着这些疑问来看看 glibc 是用什么数据结构存储终止处理器的,另外看看打印这些结构时遇到了哪些问题
前言
之前在写 apue 系列的时候,曾经对系统接口的很多行为产生过好奇,当时就想研究下对应的源码,但是苦于 linux 源码过于庞杂,千头万绪不知从何开启,就一直拖了下来。
最近在查一个问题时无意间接触到了 code browser 这个在线源码查看器,它同时解决了源码包下载和环境搭建的问题,版本也帮你选好了,直接原地起飞进入源码查看:
下面是查找 glibc exit 的过程:
语法高亮、风格切换、跳转 (定义/引用) 等功能做的还是很全面的,看代码绰绰有余,简直是我等 coder 之福音。
这里感谢 Bing 同学的介绍,感兴趣读者可以在文末参考它写的关于 glibc exit 的另一篇文章,也很不错的。
glibc exit
之前写过一篇介绍 linux 进程环境的文章(《 [apue] 进程环境那些事儿》),其中提到了 glibc exit 会主动调用 atexit 注册的处理器,且有以下特性:
LIFO,先进后出的顺序
注册几次调用几次
atexit 处理器中再次调用 exit 能完成剩余处理器的调用
atexit 处理器中再次注册的 atexit 处理器能被调用
下面带着这些问题,来看 glibc exit 的源码,以及它是如何实现上面这些特性的。
atexit 处理器结构
开门见山:
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}
static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;
uint64_t __new_exitfn_called;
exit 只调用了一个 __run_exit_handlers 接口,它需要的 atexit 处理器列表存储在 __exit_funcs 参数中,是从这里传入的。
未曾开言先转腚,来看下 __exit_funcs 的结构:
enum
{
ef_free, /* `ef_free' MUST be zero! */
ef_us,
ef_on,
ef_at,
ef_cxa
};
struct exit_function
{
/* `flavour' should be of type of the `enum' above but since we need
this element in an atomic operation we have to use `long int'. */
long int flavor;
union
{
void (*at) (void);
struct
{
void (*fn) (int status, void *arg);
void *arg;
} on;
struct
{
void (*fn) (void *arg, int status);
void *arg;
void *dso_handle;
} cxa;
} func;
};
struct exit_function_list
{
struct exit_function_list *next;
size_t idx;
struct exit_function fns[32];
};
exit_function_list 作为容器有点类似 stl 中的 deque,是由 exit_function 块组成的链表,兼顾了可扩展性与遍历效率两个方面:
其中 idx 记录了实际的元素个数,块之间通过 next 指针链接。
注意第一个块是在栈上分配的 initial 对象,之后的块才是在堆上分配的。
