有時候程式不正常結束時, 希望透過 atexit() 最做低限度的善後程續。但試用的結果是, atexit() 本身滿好用的, 正常結束後會符合預期做事。但是呼叫 exit() 隱藏的問題, 卻不那麼直覺。
目前遇過兩個狀況:
- 在 signal handler 內使用 exit(): 想在收到異常通知時透過 exit() 善後。結果可能會讓程式卡住掛不掉。這也許和註冊的 callback 做什麼事有關。不過 exit() 不是 async-signal-safe functions (見 man 7 signal), 文件也提到在 signal 內用非 async-signal-safe functions 的行為是未定義的, 所以這種狀況出包也沒什麼好探究的了。
- multi-thread 的處理: 註冊的 callback 裡有和 thread 相關的善後機制, 然後在錯的 thread 裡或錯的時機呼叫 exit() 造成 dead-lock。當狀況異常想呼叫 exit() 時, 其實也沒太多心力將 thread 之間的行為弄得更好。
另外關於 _exit(), man page 提到它不會呼叫 callback, 但有一些 implementation dependent 的行為, 感覺上不怎麼踏實。可以確定的是, _exit() 會關掉 file descriptors 而可能造成未知的延遲 (文件又提到可用 tcflush 避免這點, 但看不懂到底是有效還是 implementation dependent)。所以若要確保程式會立即結束, 用 abort() 或 kill(getpid(), SIGKILL) 應該比較穩吧, 只是就無法提供 exit status 了。
用abort()取代exit() 用SIGABRT取代atexit()如何?
回覆刪除不太好, 考量到 signal 隨時都可能觸發, 在 signal handler 內能安全執行的動作很有限 (man 7 signal 有列可安全使用的 system call, 像 printf 之類很多 thread-unsafe 的函式都不能用), 而 atexit() 註冊的 callback, 則沒有此限制
回覆刪除另外 atexit() 可註冊多個 callback, 執行順序和註冊順序相反。SIGABRT handler 只有一個, 用起來比較卡。
call chain 可以自己做,看要作成stack還是queue都不成問題。也就是說SIGARBRT handler是:
回覆刪除foreach callback in (callback_stack){
callback(signum);
}
然後規格上明定abort()不可能返回(除非你用longjmp()惡搞),所以在跑這個foreach前可以把程序鎖起來:「用sigprocmask()不允許其他signal發生,然後用sigwaitinfo()清除已發生的signal」。
那麼之後callback_stack中的動作就可以安心用printf()了。
@fcamel,
回覆刪除1. 在 signal handler 內用 _exit() 提供 exit status 基本上沒問題。
所謂『關掉 file descriptors 而可能造成未知的延遲』,是指若你程式之前寫了很多資料到檔案,比起用 abot() / raise(SIGKILL) ,用 _exit() 可能有額外 I/O。這個我在寫錄影程式 (1~10 MB/s write) 時有遇到。但通常因異常狀況而要 exit() 時,等一下 disk I/O 沒關係。
若真的想確保『接到 signal 後盡快結束且讓 parent process 知道原因』且原本的 signal 就會結束你的 process 當然也可將 signal disposition 設回 SIG_DFL,讓 kernel 結束 process,然後 parent process 可用 WIFSIGNALED() / WTERMSIG() 知道是因為哪個 signal 而結束。在 Shell script 中不好用就是了。
NOTE: exit 時並不像 DB 實作中呼叫 fsync() / fdatasync(),資料並不是保證寫到磁碟中。
2. 觀念上為何在 signal handler 內只能呼叫 _exit() 而不能用 exit(): 後者要跑用 atexit() 註冊的 callback,signal handler 內只能做『簡短而可控制』的事情。故凡是要跑 callback 的都不能在 signal handler 內呼叫。
能註冊 callback 的 API 要跟使用者間有約定 callback 可能在哪些 thread 被呼叫。在 signal handler context 跑 atexit() callbacks 破壞了此約定。
這樣的話相當於自己重作一套 atexit() + exit(), 若在 call exit() 前也做了你說的「保證措施」, 效果應該差不多。
回覆刪除經你這麼一提, 我想問題應該出在這類善後 callback function 不該做太複雜的事 (如扯到 multi-thread)。callback 寫得簡單的話, 也就沒有 exit() 呼叫情境的顧慮了
@Scott: 我上面是回洪吉亮的, 我寫完後再看你們的留言, 又搞得更清楚了, 情況如你所言
回覆刪除