首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 進程管理深度解析:孤兒進程與殭屍進程(二)

     編輯:關於C語言

承接上篇,本文聚焦殭屍進程:子進程結束但父進程未回收,導致 PCB 滯留進程表。大量殭屍進程會耗盡 PID,癱瘓系統建立新進程能力。

殭屍進程示範程式

父進程建立子進程後睡眠 3 秒,期間不調用 wait,並用 system("ps") 觀察進程狀態:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    pid_t pid;
    if ((pid = fork()) == -1) {
        perror("fork");
        return 1;
    } else if (pid == 0) {  // 子進程
        printf("child_pid pid=%d\n", getpid());
        exit(0);  // 立即結束,進入殭屍狀態
    }
    // 父進程
    sleep(3);  // 3秒內不回收,子進程成為殭屍
    system("ps");  // 觀察進程列表
    exit(0);
}

執行結果分析

child_pid pid=1108
  PID TTY          TIME CMD
  847 pts/1    00:00:00 bash
 1107 pts/1    00:00:00 a.out      // 父進程
 1108 pts/1    00:00:00 a.out <defunct>  // 殭屍進程!標記 <defunct>
 1109 pts/1    00:00:00 ps

PID 1108 顯示 <defunct> 標記,確認為殭屍進程。父進程 PID 1107 仍在運行,但未回收子進程。

殭屍進程危害

  • PID 耗盡:系統 PID 有限 (預設 32768),大量殭屍佔用導致無法 fork 新進程
  • 進程表項佔用:每個殭屍佔用一個 task_struct,浪費核心記憶體
  • 父進程洩漏:長期運行的服務程式 (daemon) 若不處理 SIGCHLD,必然產生殭屍洩漏

四種回收策略對比

策略適用場景優點缺點
同步阻塞 wait()簡單父子協作代碼簡單父進程阻塞,無法並行處理
非阻塞輪詢 waitpid(-1, &status, WNOHANG)事件循環/遊戲主循環不阻塞主流程輪詢頻率難把控,CPU 浪費或延遲
SIGCHLD 信號處理 + waitpid高併發網路服務、Daemon事件驅動、即時回收、不阻塞信號處理函數限制多,需重入安全
雙重 fork 技巧不關心子進程退出碼的場景完全避免殭屍,無需父進程處理增加一次 fork 開銷,孫進程由 init 收養

推薦:SIGCHLD 信號處理標準模式

void sigchld_handler(int sig) {
    int saved_errno = errno;
    while (waitpid(-1, NULL, WNOHANG) > 0);  // 循環回收所有已終止子進程
    errno = saved_errno;
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;  // 重啟被中斷系統調用、不因子進程停止產生信號
    sigaction(SIGCHLD, &sa, NULL);

    // 主業務邏輯...
    while (1) { /* 服務循環 */ }
}

關鍵點:

  1. 使用 sigaction 而非 signal (可移植、可靠)
  2. SA_RESTART:被信號中斷的系統調用自動重啟
  3. SA_NOCLDSTOP:僅子進程終止產生信號,停止/繼續不產生
  4. 處理函數中 循環調用 waitpid(WNOHANG) 回收所有殭屍,避免信號合併導致遺漏
  5. 保存/恢復 errno,維護線程安全

雙重 Fork 技巧 (適用於不需退出碼場景)

if (fork() == 0) {  // 子進程
    if (fork() == 0) {  // 孫進程
        // 執行實際任務
        exit(0);
    }
    exit(0);  // 子進程立即結束
}
waitpid(pid, NULL, 0);  // 父進程僅等待子進程,孫進程成孤兒由 init 收養

孫進程父進程 (子進程) 先結束 → 孫進程成孤兒 → init 收養 → 孫進程結束由 init 回收 → 零殭屍。

總結

  • 孤兒進程:父早亡 → init 收養 → 無害,正常現象
  • 殭屍進程:父不回收 → 佔用資源 → 有害,必須處理
  • 服務程式必須:安裝 SIGCHLD 處理器 或 使用雙重 fork
  • 短期工具程式:退出前統一 wait 即可

掌握進程回收機制,是編寫生產級 Linux 系統程式的門檻技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved