一個 Linux 后台程序編程案例分析


Linux 下的一個進程打開一個日志文件,不定期地往該文件里寫入日志。此時可以在控制台使用 mv 命令給該日志文件改個名字或者用 rm 命令把這個日志文件刪除掉。Linux 下是允許這么干的!對於改日志文件名的情形還好一點,后續的日志還是會寫入更名后的文件里,只是會影響后面日志文件自動清理功能(比如把日志文件名改得像個進程文件名);而對於刪除文件的情形,就直接導致后續的日志無法計入日志文件,一直到第二天凌晨日志文件切換時才回復正常。

為此,增加了一個日志文件保護功能,放在一個獨立的線程 logGuardEntry 里運行。這個保護功能主要是定期檢查當前所使用的日志文件是否存在,以及日志記錄是否正常,若檢測到異常,則關閉當前日志輸出的文件句柄,並重新打開所使用的文件,文件不存在則重建。

用 debug 版調試運行,功能正常后生成 release 版,卻測試發現增加的日志文件保護功能沒有起作用。用 gdb 掛上進程查看線程棧,想看下 logGuardEntry 線程內部出了什么狀況,結果發現根本就沒有 logGuardEntry 這個線程!

仔細排查,才發現問題和 daemonInit 函數調用有關系。main 函數里相關調用示意如下:

int main()
{
    ...
    startLogWriter(...);
    ...
#ifndef _DEBUG
    daemonInit();
#endif
    ...
}

startLogWriter 函數體末尾有一行:

startThread(logGuardEntry,...);

 即啟動一個以 logGuardEntry 為入口函數的線程,實施日志文件保護的功能。

daemonInit 函數里有如下代碼段:

void daemonInit()
{
    ...
    pid = fork(); if (pid != 0) {
        exit(0);
    }
    ...
}

這是讓程序以守護進程運行的通常做法,即讓主進程退出,而讓子進程經過進一步處理成為守護進程繼續運行。但是 fork 調用生成的子進程在 fork() 這條語句完成時,只會有一個線程,即調用 fork 的線程(上面就是 main 所在的線程,即主線程)。這是 Linux 出於某種合理的考慮而這樣設計的。上面的 main 函數里,先調用了 startLogWriter,里面會啟動一個 logGuardEntry 線程,這時主進程有兩個線程在運行;而隨后調用 daemonInit,導致主進程退出,而子進程卻丟掉了 logGuardEntry 線程,導致測試時發現日志保護功能根本不起作用。

當然,這個問題改起來很簡單,把 startLogWriter 調用放到 daemonInit 之后就好了,即:

int main()
{
    ...
#ifndef _DEBUG
    daemonInit();
#endif startLogWriter(...);
    ...
}

就是說,對於以守護進程運行的后台程序而言,daemonInit 調用盡量早一些做,尤其不要在調用 daemonInit 之前啟動工作線程。

由 fork 調用的工作機制,不禁會想:子進程是不是可以沒有 main() 函數所在的線程,即所謂主線程(比如,把上面的 daemonInit 調用挪到 logGuardEntry 線程里調用)?

實際試驗了一下,果然是可以的。以下是用 gdb 掛上進程看到的內情:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM