atexit() 和 exit() 的注意事項

有時候程式不正常結束時, 希望透過 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 了。

留言

  1. 用abort()取代exit() 用SIGABRT取代atexit()如何?

    回覆刪除
  2. 不太好, 考量到 signal 隨時都可能觸發, 在 signal handler 內能安全執行的動作很有限 (man 7 signal 有列可安全使用的 system call, 像 printf 之類很多 thread-unsafe 的函式都不能用), 而 atexit() 註冊的 callback, 則沒有此限制

    另外 atexit() 可註冊多個 callback, 執行順序和註冊順序相反。SIGABRT handler 只有一個, 用起來比較卡。

    回覆刪除
  3. call chain 可以自己做,看要作成stack還是queue都不成問題。也就是說SIGARBRT handler是:
    foreach callback in (callback_stack){
    callback(signum);
    }
    然後規格上明定abort()不可能返回(除非你用longjmp()惡搞),所以在跑這個foreach前可以把程序鎖起來:「用sigprocmask()不允許其他signal發生,然後用sigwaitinfo()清除已發生的signal」。
    那麼之後callback_stack中的動作就可以安心用printf()了。

    回覆刪除
  4. @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 破壞了此約定。

    回覆刪除
  5. 這樣的話相當於自己重作一套 atexit() + exit(), 若在 call exit() 前也做了你說的「保證措施」, 效果應該差不多。

    經你這麼一提, 我想問題應該出在這類善後 callback function 不該做太複雜的事 (如扯到 multi-thread)。callback 寫得簡單的話, 也就沒有 exit() 呼叫情境的顧慮了

    回覆刪除
  6. @Scott: 我上面是回洪吉亮的, 我寫完後再看你們的留言, 又搞得更清楚了, 情況如你所言

    回覆刪除

張貼留言

這個網誌中的熱門文章

virtualbox 使用 USB 裝置

熟悉系統工具好處多多

如何 git merge 更改檔名的檔案