在線上環境排查問題時,發現一個標記為 Z(殭屍)的進程。直覺告訴我:殺掉它的父進程,讓 init 進程(PID 1)接管並回收。但 ps 顯示,這個殭屍進程的父進程已經是 init 了。按理說,init 會定期調用 wait() 回收子進程,為何仍殘留殭屍?
do_exit 的兩大任務任何進程結束最終都會調用內核函數 do_exit,它必須完成兩件關鍵事:
init)。SIGCHLD,等待 wait)。單線程進程終止流程簡單直接。但多線程程序中,只有線程組的主線程有資格通知父進程。其他線程退出時,直接調用 release_task 釋放資源,不進入殭屍狀態,也不通知父進程。
這導致一個關鍵後果:若主線程先於其他線程退出(如調用 pthread_exit),主線程的 task_struct 必須保留下來(哪怕變為殭屍),直到線程組最後一個線程結束為止。 此時即便 init 已接管該主線程,也無法立即回收,因為內核還在等待線程組完全終止。
以下測試代碼創建一個子線程睡眠 100 秒,主線程調用 daemon() 讓 init 接管,隨後主線程退出:
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <pthread.h>
- void thread_fun(void)
- {
- printf("begin thread...\n");
- sleep(100);
- printf("end thread...\n");
- }
- int main(int argc, char *argv[])
- {
- pthread_t tid;
- int ret;
- daemon(0, 0); // 讓 init 接管主線程
- printf("process begin...\n");
- ret = pthread_create(&tid, NULL, (void *)thread_fun, NULL);
- if (ret != 0)
- {
- printf("pthread error\n");
- exit(1);
- }
- printf("main thread is end...\n");
- pthread_exit(0); // 主線程先退出
- printf("never exce\n");
- }
執行後用 ps 查看:
18099 1 Zsl a.out <defunct> 0.0 0.0
可見:雖然父進程已是 init,主線程仍處於 Z(殭屍)狀態。
要消除這個殭屍進程,有兩種方式:
kill 18099(會向所有線程發送信號)。ps -eLf 找出該進程的其他線程(LWP),逐個 kill。init 接管只能處理「已成孤兒」的進程回收,但無法打破「線程組必須整體終止主線程才能被回收」的內核規則。多線程程序中,主線程過早退出是產生殭屍的常見原因,開發時應避免主線程先於工作線程結束,或妥善使用 pthread_join 等待所有線程。