跟atexit函數相識已久,man手冊裡對atexit的解釋是這麼一段:
The atexit() function registers the given function to be called at normal process termination, either via exit() or via the program’s main(). Functions so registered are called the reverse order of their registration; no arguments are passed.
乍一看,就形成了這樣的印象:“哦,atexit函數就是來注冊一個函數A,使main函數退出後,要執行一下函數A,進程才會徹底over”。
直到工作中遇到一個段錯的bug,日志中發現,進程在執行atexit注冊過的函數時,main函數裡的線程依然在快活地運行,這種現象顛覆了我以往的認知,讓我不得不重新思考,atexit注冊的函數到底什麼時候執行?何為“退出main函數”?
先上一段簡單代碼:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> bye( printf( sleep( printf( *do_thread( ( printf( sleep( main( pthread_create(&pid_t, NULL, ( * }
上面的程序先用atexit注冊一個程序正常退出後的執行函數,再創建一線程用來不斷輸出信息,然後主線程執行exit;
運行程序會輸出什麼呢?
我一開始的猜測運行結果應該是:
before bye
after bye
或者是:
--------------I'm thread!
before bye
after bye
因為在我的理解中,bye()是在退出main函數之後執行,那時候,main函數裡創建的線程什麼的應該都不復存在了,代碼會清清靜靜地執行bye()函數。事實證明,我太想當然了。
上面程序實際運行結果是:
--------------I --------------I --------------I --------------I --------------I --------------I --------------I --------------I --------------I --------------I after bye
為什麼在執行bye()的時候線程還在呢?
來看一下exit()函數的源碼:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sysdep.h> #include #include DEFINE_HOOK (__libc_atexit, ( __run_exit_handlers ( status, exit_function_list ** (*listp != exit_function_list *cur = * (cur->idx > exit_function * f = &cur->fns[--cur-> (f-> (*atfct) ( (*onfct) ( status, * (*cxafct) ( *arg, onfct = f-> onfct (status, f-> atfct = f-> cxafct = f-> cxafct (f-> *listp = cur-> (*listp != exit ( __run_exit_handlers (status, &__exit_funcs, libc_hidden_def (exit)
從上面的源碼可以看出:exit()先是執行atexit注冊的函數,然後再執行_exit函數,_exit會關閉進程所有的文件描述符,清理內存以及其他一些內核清理函數。所以當exit()執行到_exit()的時候,之前創建的線程才會停止運行。之前我腦海裡存在的“退出main函數”的概念還是太抽象了,其背後存在的其實是一個動作流,會執行atexit注冊的函數,刷新流(stdin, stdout, stderr),把文件緩沖區的內容寫回文件,關閉進程所有的文件描述符,清理內存以及其他一些內核清理函數。exit()和_exit()源碼有待好好研究。
再次回首篇頭那一段atexit的解釋,別有一番深意。