跟atexit函数相识已久,man手册里对atexit的解释是这么一段:
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(). Functions so registered are called in the reverse order of their registration; no arguments are passed.
乍一看,就形成了这样的印象:“哦,atexit函数就是来注册一个函数A,使main函数退出后,要执行一下函数A,进程才会彻底over”。
直到工作中遇到一个段错的bug,日志中发现,进程在执行atexit注册过的函数时,main函数里的线程依然在快活地运行,这种现象颠覆了我以往的认知,让我不得不重新思考,atexit注册的函数到底什么时候执行?何为“退出main函数”?
先上一段简单代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <pthread.h> 5 6 void bye(void) 7 { 8 printf("before bye\n"); 9 sleep(10); 10 printf("after bye\n"); 11 } 12 13 void *do_thread(void) 14 { 15 while(1) 16 { 17 printf("--------------I'm thread!\n"); 18 sleep(1); 19 } 20 } 21 22 int main(void) 23 { 24 pthread_t pid_t; 25 26 atexit(bye); 27 pthread_create(&pid_t, NULL, (void *)do_thread, NULL); 28 29 exit(EXIT_SUCCESS); 30 }
上面的程序先用atexit注册一个程序正常退出后的执行函数,再创建一线程用来不断输出信息,然后主线程执行exit;
运行程序会输出什么呢?
我一开始的猜测运行结果应该是:
before bye
after bye
或者是:
--------------I'm thread!
before bye
after bye
因为在我的理解中,bye()是在退出main函数之后执行,那时候,main函数里创建的线程什么的应该都不复存在了,代码会清清静静地执行bye()函数。事实证明,我太想当然了。
上面程序实际运行结果是:
1 before bye 2 --------------I'm thread! 3 --------------I'm thread! 4 --------------I'm thread! 5 --------------I'm thread! 6 --------------I'm thread! 7 --------------I'm thread! 8 --------------I'm thread! 9 --------------I'm thread! 10 --------------I'm thread! 11 --------------I'm thread! 12 after bye
为什么在执行bye()的时候线程还在呢?
来看一下exit()函数的源码:
1 /* Copyright (C) 1991,95,96,97,99,2001,2002,2005,2009 2 Free Software Foundation, Inc. 3 This file is part of the GNU C Library. 4 5 The GNU C Library is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Lesser General Public 7 License as published by the Free Software Foundation; either 8 version 2.1 of the License, or (at your option) any later version. 9 10 The GNU C Library is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General Public 16 License along with the GNU C Library; if not, see 17 <http://www.gnu.org/licenses/>. */ 18 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <unistd.h> 22 #include <sysdep.h> 23 #include "exit.h" 24 25 #include "set-hooks.h" 26 DEFINE_HOOK (__libc_atexit, (void)) 27 28 29 /* Call all functions registered with `atexit' and `on_exit', 30 in the reverse of the order in which they were registered 31 perform stdio cleanup, and terminate program execution with STATUS. */ 32 void 33 attribute_hidden 34 __run_exit_handlers (int status, struct exit_function_list **listp, 35 bool run_list_atexit) 36 { 37 /* We do it this way to handle recursive calls to exit () made by 38 the functions registered with `atexit' and `on_exit'. We call 39 everyone on the list and use the status value in the last 40 exit (). */ 41 while (*listp != NULL) 42 { 43 struct exit_function_list *cur = *listp; 44 45 while (cur->idx > 0) 46 { 47 const struct exit_function *const f = 48 &cur->fns[--cur->idx]; 49 switch (f->flavor) 50 { 51 void (*atfct) (void); 52 void (*onfct) (int status, void *arg); 53 void (*cxafct) (void *arg, int status); 54 55 case ef_free: 56 case ef_us: 57 break; 58 case ef_on: 59 onfct = f->func.on.fn; 60 #ifdef PTR_DEMANGLE 61 PTR_DEMANGLE (onfct); 62 #endif 63 onfct (status, f->func.on.arg); 64 break; 65 case ef_at: 66 atfct = f->func.at; 67 #ifdef PTR_DEMANGLE 68 PTR_DEMANGLE (atfct); 69 #endif 70 atfct (); 71 break; 72 case ef_cxa: 73 cxafct = f->func.cxa.fn; 74 #ifdef PTR_DEMANGLE 75 PTR_DEMANGLE (cxafct); 76 #endif 77 cxafct (f->func.cxa.arg, status); 78 break; 79 } 80 } 81 82 *listp = cur->next; 83 if (*listp != NULL) 84 /* Don't free the last element in the chain, this is the statically 85 allocate element. */ 86 free (cur); 87 } 88 89 if (run_list_atexit) 90 RUN_HOOK (__libc_atexit, ()); 91 92 _exit (status); 93 } 94 95 96 void 97 exit (int status) 98 { 99 __run_exit_handlers (status, &__exit_funcs, true); 100 } 101 libc_hidden_def (exit)
从上面的源码可以看出:exit()先是执行atexit注册的函数,然后再执行_exit函数,_exit会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数。所以当exit()执行到_exit()的时候,之前创建的线程才会停止运行。之前我脑海里存在的“退出main函数”的概念还是太抽象了,其背后存在的其实是一个动作流,会执行atexit注册的函数,刷新流(stdin, stdout, stderr),把文件缓冲区的内容写回文件,关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数。exit()和_exit()源码有待好好研究。
再次回首篇头那一段atexit的解释,别有一番深意。