電子產(chǎn)業(yè)一站式賦能平臺

PCB聯(lián)盟網(wǎng)

搜索
查看: 69|回復: 0
收起左側

Linux守護進程

[復制鏈接]

660

主題

660

帖子

4567

積分

四級會員

Rank: 4

積分
4567
跳轉到指定樓層
樓主
發(fā)表于 2024-11-25 08:03:00 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式

3 Y3 k! [; E! k* q# }# D/ g點擊上方藍色字體,關注我們
5 s9 {) M# z, D0 F7 o* Q
1 U5 b) H# g+ r8 Z# Q4 A在命令輸出中,如果 TTY 一欄顯示為問號(?),這表示該進程沒有控制終端,通常意味著它是一個守護進程。同時,COMMAND 一欄中用中括號([])括起來的進程表示內(nèi)核線程。
  b& S, R! c, q% m. F
4 J2 t: Q# [+ c這些線程是在內(nèi)核空間中創(chuàng)建的,沒有對應的用戶空間代碼,因此不具備程序文件名和命令行信息,通常以字母 k 開頭,表示它們是內(nèi)核線程(Kernel)。
6 l! P" f% w& r* o1! \' M# i" N! W4 P2 Q& l
編寫守護進程的步驟
* p6 W7 K2 p# \/ @& z編寫守護進程通常包括以下幾個關鍵步驟,以確保其能夠在后臺獨立運行,并完成預定的任務。
1 U4 x8 p* o9 H  t% ?, c9 r) V+ F8 ^# h0 e" A9 |3 q
1、創(chuàng)建子進程并終止父進程. G9 N( Q# h$ s- G
使用 fork() 創(chuàng)建子進程后,父進程應調(diào)用 exit() 終止自身。這一過程實現(xiàn)了以下幾點:
1 Y& l, Y" N* U1 g, K2 F; l4 ~
  • 如果守護進程是通過簡單的 shell 命令啟動,父進程的退出將使 shell 認為命令已執(zhí)行完畢。
  • 子進程繼承了父進程的進程組 ID,但它有自己獨立的進程 ID,確保子進程不是進程組的組長,為后續(xù)調(diào)用 setsid() 準備條件。
    % P- E9 [% N. r& n9 k; ~( a" _
    4 `9 x! `' K4 h+ G; M
    2、子進程調(diào)用 setsid() 創(chuàng)建會話
    % l2 l0 g% [* v8 S7 P3 L9 o在子進程中調(diào)用 setsid() 是關鍵步驟。這將:$ M! ?7 w( a) H2 i8 a/ t3 S0 M2 o8 u
  • 創(chuàng)建一個新的會話,子進程成為新會話的首領。
  • 創(chuàng)建新的進程組,子進程成為組長。
  • 擺脫原有會話、進程組和控制終端的控制,實現(xiàn)完全獨立。
    " p$ b9 l- z8 B0 `( S! a; D4 u盡管子進程在 fork() 時繼承了父進程的控制權,但 setsid() 能確保其完全脫離。
    . p, Y3 g9 e/ ?! p' @7 p0 V

    6 N! K, M. P1 W) N9 a3、更改工作目錄為根目錄: s; U. p: i+ K$ }4 ~. u
    子進程會繼承父進程的當前工作目錄,而該目錄可能會導致文件系統(tǒng)無法卸載。通常,守護進程會將工作目錄更改為根目錄(/),以避免這種問題。也可以根據(jù)需要選擇其他目錄。: J1 a/ N5 t) X( @7 v7 S  A
    5 v# ~9 D8 s7 U2 ?! U5 G: S
    4、重設文件權限掩碼(umask)
    5 c4 u8 m4 r1 l" W文件權限掩碼 umask 控制新建文件的默認權限。由于子進程繼承了父進程的 umask,建議將其設置為 0,以確保子進程擁有最大權限,增強守護進程的靈活性。設置 umask 的方法是調(diào)用 umask(0)。
    3 u7 b. j8 H$ b9 I/ l* A  {
    0 {% b" A3 |' m7 \  h8 c5、關閉不再需要的文件描述符
    ) R) h5 g, o& e5 R4 S) z子進程會繼承父進程打開的所有文件描述符,這可能導致不必要的資源消耗。應關閉不再需要的文件描述符,以確保守護進程不再持有任何繼承自父進程的描述符,從而減少資源浪費。
    ( r+ P, M/ l' F$ M
    ; M, Y) Z, I+ c! d/ F$ s6、將文件描述符 0、1、2 定位到 /dev/null
    " x+ i8 I. Y% N3 j守護進程的標準輸入、標準輸出和標準錯誤通常會重定向到 /dev/null,這樣守護進程的輸出就不會顯示在任何地方,同時也不會試圖從交互式用戶那里接收輸入。
    9 K& o6 C7 k2 b; u% @/ L  _) y6 \+ {  t2 X( o) G/ u6 ^4 U0 k
    7、其他處理:忽略 SIGCHLD 信號
    0 n, z: S5 j& i. B; H處理 SIGCHLD 信號不是絕對必要的,但對于某些并發(fā)服務器進程尤其重要。通過將 SIGCHLD 信號的處理方式設置為 SIG_IGN,可以避免僵尸進程的產(chǎn)生。這樣,當子進程結束時,內(nèi)核將其交給 init 進程處理,減少了父進程的負擔,從而提高了服務器的并發(fā)性能。
    / C5 r% I1 Y' W3 _9 r; E2* c# l  r; \# @8 U. [
    守護進程的使用和案例設計
    / ?2 d9 @. W$ _% n為了深入理解如何創(chuàng)建和使用守護進程,我們將創(chuàng)建一個多功能的守護進程,具備以下功能:
    + p9 ^! }  I) I& H
  • 資源監(jiān)控功能:守護進程每隔 30 秒獲取系統(tǒng)的 CPU、內(nèi)存和磁盤使用信息,并將其寫入 /var/log/resource_monitor.log。
  • 定時清理功能:每隔 10 分鐘,清理 /tmp 目錄下的所有文件。
  • 信號處理功能:守護進程能夠捕獲 SIGTERM 信號,安全退出,并能夠處理 SIGHUP 信號重新加載配置文件。
    8 @+ _1 c: p6 B) ]# ]$ h0 O

    $ \1 _8 ?$ O- {4 z2.1、案例功能分析* k, }* O  ?/ J, c% J' o# s' v
    系統(tǒng)資源監(jiān)控. [, B$ t% @% C4 Q" m% T
  • 使用系統(tǒng)命令 stat 和 vmstat 來獲取 CPU 和內(nèi)存信息。
  • 使用 df 命令獲取磁盤使用情況。
  • 每次獲取的信息都寫入 /var/log/resource_monitor.log,便于運維人員檢查系統(tǒng)的健康狀態(tài)。! P+ T# R# n6 ^5 n, v: t: A

    3 G& G" f* A- z& S/ C7 _: f! G/ ^定時清理任務* n+ S0 d5 X4 v# a$ C3 m: S+ u0 f
  • 每隔 10 分鐘調(diào)用一個函數(shù)清理 /tmp 目錄下的文件。
  • 使用系統(tǒng)函數(shù) unlink() 刪除文件。* D9 W! ]0 H1 M/ ^: o) I

    % h# s& r. L# @# @: L; H信號處理1 E5 D6 H$ r4 u" |& D0 h0 ]
  • 捕獲 SIGTERM 信號,干凈地終止守護進程并進行資源釋放。
  • 捕獲 SIGHUP 信號,重新加載配置文件(如改變?nèi)罩疚募穆窂剑?font class="jammer">1 \6 e3 N% Q2 U/ D* I' |0 f  M7 V
    % P) m. R4 e0 N, w1 ]
    2.2、守護進程代碼結構
    , a/ I; M8 n1 a9 }( Q  w, r
  • daemonize():負責將進程變?yōu)槭刈o進程的常規(guī)步驟。
  • monitor_resources():負責監(jiān)控系統(tǒng)資源并將其寫入日志。
  • cleanup_tmp():每隔 10 分鐘清理一次 /tmp 目錄中的文件。
  • handle_signal():處理 SIGTERM 和 SIGHUP 信號。
  • reload_config():當捕獲 SIGHUP 時,重新加載配置文件。
    . N$ o! X+ _1 h2 h) F
    6 X3 C7 A; s- X9 {
    2.3、代碼實現(xiàn)' f* A) ^2 N0 l# Z5 M4 V: ]' E
  • #define LOG_FILE "/var/log/resource_monitor.log"#define CONFIG_FILE "/etc/daemon_config.conf"#define TMP_DIR "/tmp"
    5 j/ n" U; \- M: U$ l6 W& ^# W// 定義輪詢時間#define MONITOR_INTERVAL 30  // 資源監(jiān)控間隔 30 秒#define CLEANUP_INTERVAL 600 // 清理間隔 10 分鐘
    ) v8 x1 w) F7 {" f% Pint keep_running = 1;FILE *log_fp = NULL;5 s- S) s: A  l6 A* y- m6 ~
    // 守護進程初始化函數(shù)void daemonize() {    pid_t pid;
    , k5 |" q4 p6 o7 `    // 1. 創(chuàng)建子進程并終止父進程    pid = fork();    if (pid 0) exit(EXIT_FAILURE);    if (pid > 0) exit(EXIT_SUCCESS);  // 父進程退出
    " U: X+ n( @: S2 O8 }& f* S$ C* t. U    // 2. 創(chuàng)建新的會話    if (setsid() 0) exit(EXIT_FAILURE);
    ' @1 v3 O/ @- k" r    // 3. 忽略 SIGCHLD 信號    signal(SIGCHLD, SIG_IGN);
    , A+ P9 N! I7 q: u7 ]  J    // 4. 再次 fork,防止守護進程重新獲得終端    pid = fork();    if (pid 0) exit(EXIT_FAILURE);    if (pid > 0) exit(EXIT_SUCCESS);
    3 _& @) Q1 a, a& k/ b9 N9 @    // 5. 更改工作目錄到根目錄    chdir("/");6 P) H0 ~/ D2 \0 m" f: g: ~% `
        // 6. 重設文件權限掩碼    umask(0);' Z0 e8 u3 d- w+ E
        // 7. 關閉不再需要的文件描述符    close(STDIN_FILENO);    close(STDOUT_FILENO);    close(STDERR_FILENO);
    7 f8 s6 p+ x, `1 @& w) ]4 G    // 8. 重定向標準輸入、輸出、錯誤到 /dev/null    open("/dev/null", O_RDONLY);    open("/dev/null", O_WRONLY);    open("/dev/null", O_WRONLY);
    , I: U1 x6 o1 ?: T% j    // 打開系統(tǒng)日志    openlog("resource_daemon", LOG_PID, LOG_DAEMON);}
    * h* p2 l3 Y) r, z7 Z; j// 捕獲信號的處理函數(shù)void handle_signal(int signal) {    switch (signal) {        case SIGHUP:            syslog(LOG_INFO, "Reloading configuration file...");            // 重新加載配置文件            if (log_fp) {                fclose(log_fp);            }            log_fp = fopen(LOG_FILE, "a");            if (log_fp == NULL) {                syslog(LOG_ERR, "Failed to open log file");                exit(EXIT_FAILURE);            }            break;        case SIGTERM:            syslog(LOG_INFO, "Daemon is shutting down...");            if (log_fp) {                fclose(log_fp);            }            closelog();            keep_running = 0;  // 設置標志位,結束主循環(huán)            break;    }}
    ( z* `# F! r# M  n, M4 }, w3 q. Q% F// 資源監(jiān)控功能void monitor_resources() {    FILE *fp;    char buffer[128];$ S4 ~0 W% [% ?8 x  v
        // 記錄當前時間    time_t now = time(NULL);    fprintf(log_fp, "Timestamp: %s", ctime(&now));% p+ u5 a7 {" [6 c/ ]7 d
        // 記錄 CPU 和內(nèi)存使用情況    fp = popen("vmstat 1 2 | tail -1", "r");    if (fp != NULL) {        fgets(buffer, sizeof(buffer) - 1, fp);        fprintf(log_fp, "CPU/Memory Usage: %s. s5 ?0 a* ^+ v1 v- V
    ", buffer);        pclose(fp);    }
    4 v9 l! X+ ~0 m7 a    // 記錄磁盤使用情況    fp = popen("df -h /", "r");    if (fp != NULL) {        while (fgets(buffer, sizeof(buffer) - 1, fp) != NULL) {            fprintf(log_fp, "Disk Usage: %s", buffer);        }        pclose(fp);    }
    3 C+ G. v% [$ a4 b5 m    fflush(log_fp);  // 確保日志刷新到文件}
    % P# T; v: U6 ?0 a( I) ?+ F// 定時清理 /tmp 目錄void cleanup_tmp() {    DIR *dir;    struct dirent *entry;    char file_path[256];
    ! i+ v$ ?$ ^9 l5 c    dir = opendir(TMP_DIR);    if (dir == NULL) {        syslog(LOG_ERR, "Failed to open /tmp directory");        return;    }" R- D8 z6 Q0 N3 @, j
        while ((entry = readdir(dir)) != NULL) {        if (entry->d_type == DT_REG) {  // 只刪除常規(guī)文件            snprintf(file_path, sizeof(file_path), "%s/%s", TMP_DIR, entry->d_name);            if (unlink(file_path) == 0) {                syslog(LOG_INFO, "Deleted file: %s", file_path);            } else {                syslog(LOG_ERR, "Failed to delete file: %s", file_path);            }        }    }& N0 t5 ?$ ~/ \* b+ Y# S
        closedir(dir);}8 x2 e' A3 A& [3 I, n+ z
    int main() {    daemonize();
    ( j' l" b; w# c! y  g- E    // 打開日志文件    log_fp = fopen(LOG_FILE, "a");    if (log_fp == NULL) {        syslog(LOG_ERR, "Failed to open log file");        exit(EXIT_FAILURE);    }
    ' L9 t9 ~$ L6 Y, ~    // 捕獲信號處理    signal(SIGTERM, handle_signal);  // 用于進程關閉    signal(SIGHUP, handle_signal);   // 用于重新加載配置7 r) t) n0 i2 {( K1 E6 t8 u' }
        time_t last_cleanup = time(NULL);5 \7 J. e5 x* n% F) a/ y7 v0 @! `
        // 主循環(huán)    while (keep_running) {        monitor_resources();  // 監(jiān)控系統(tǒng)資源/ J% f- z& ]3 H  t
            // 檢查是否需要清理 tmp 目錄        if (difftime(time(NULL), last_cleanup) >= CLEANUP_INTERVAL) {            cleanup_tmp();            last_cleanup = time(NULL);        }1 z) O! W: z/ h: K* }/ v
            // 等待 30 秒后繼續(xù)        sleep(MONITOR_INTERVAL);    }9 _# N" `. G: ?4 W9 j1 i
        // 清理資源并退出    if (log_fp) {        fclose(log_fp);    }    closelog();
    0 a6 ^' w9 I  ?* z    return 0;}+ Z. |3 l* B/ r+ z
    2.4、代碼詳解* b+ q$ z. r9 w2 G
    守護進程初始化 (daemonize)! ^% S0 `! ]7 E8 K5 a& V
  • 將進程變?yōu)槭刈o進程,使用了雙 fork() 技術,確保進程在后臺運行并與終端脫離關系。
  • 使用 syslog 系統(tǒng)日志服務記錄進程啟動、關閉等信息。
    - g' ^4 C, ]. l9 R- {$ h) J8 i' N
    3 d; ?/ q" e" y# E6 w2 |
    信號處理 (handle_signal)2 Z" G2 W# M9 G! \) ?8 X$ B
  • 通過 signal() 函數(shù)捕獲 SIGTERM 和 SIGHUP 信號。
  • SIGTERM 信號用于干凈地終止守護進程。
  • SIGHUP 信號用于重新加載配置文件,這里模擬了重新打開日志文件的過程。
    0 h( \" M% s) V! T
    ) P) v* C& U4 ~* Q4 ~5 G( ^. o; U8 E) {
    資源監(jiān)控 (monitor_resources)# R- T% h- B; y% x" @+ o4 k
  • 使用 vmstat 命令監(jiān)控 CPU 和內(nèi)存使用情況,df 命令獲取磁盤使用狀態(tài)。
  • 每次監(jiān)控結果都記錄到日志文件中。
    $ q0 F! g% \. d: h0 P$ a
    5 M& C/ X; L# i
    定時清理 (cleanup_tmp)
    1 A4 q8 D( X2 N) J9 U# y( x! j4 f
  • 每隔 10 分鐘清理 /tmp 目錄下的文件。
  • 僅刪除常規(guī)文件,忽略目錄等。' H; ?( r' ^# V( w5 R+ l
    / L% I; ^) s: Z( R* I  D% z
    主循環(huán)
    ' \9 w: J; |% T( [2 G* H
  • 守護進程每 30 秒調(diào)用監(jiān)控和清理函數(shù),保持持續(xù)運行狀態(tài)。+ H5 L  v+ ^+ F
    9 \, W* g4 o! L1 C
    30 J! M$ y2 \& _9 G+ G8 u: G6 H
    編譯和運行守護進程
    . w$ K& E2 h. V, Q+ K2 W- V將上述代碼保存為 resource_monitor.c,使用以下命令進行編譯和運行:! m! B6 y4 g8 X4 c4 I3 T
    1 f! x3 T) K1 o, x
  • gcc resource_monitor.c -o resource_monitorsudo ./resource_monitor
    & G% w: W0 j  J' j! N- W* Y注意,守護進程需要寫入 /var/log/resource_monitor.log 文件,因此需要使用 sudo 權限運行。( o+ W% y+ L, o) E8 [
    4
    ' ?4 f: y2 R7 {8 y) T8 D檢查守護進程
    # W6 B# W# a0 B! j' }4 s查看日志文件內(nèi)容:
    , a8 M+ J& p: k1 Z% k0 w! t  s; f, q) J
  • cat /var/log/resource_monitor.log# l% N8 c. a$ y4 O+ m: t6 z
    查看守護進程狀態(tài):
    / A9 @6 M% S9 y, T4 [3 w, s, ?- C8 w5 f; \' I+ X' @- ?
  • ps -ef | grep resource_monitor8 v/ M( F, J2 y/ W1 }$ P4 \
    可以使用 kill 命令根據(jù)守護進程的 PID 將其終止:8 ]. |) S' W+ L9 L

    ! O# k1 ]: Y8 m
  • kill8 a* K$ M0 I8 Z# e
    $ A% W# I7 X/ \- t7 {# p

    ! |/ [2 `" ~: Y" R& n點擊閱讀原文,更精彩~
  • 回復

    使用道具 舉報

    發(fā)表回復

    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規(guī)則


    聯(lián)系客服 關注微信 下載APP 返回頂部 返回列表