[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 对象,之后的块才是在堆上分配的。
阅读全文