【Linux系統】SIGCHLD 信號(選學了解)




在這里插入圖片描述




SIGCHLD 信號


使用waitwaitpid函數可以有效地清理僵尸進程。父進程可以選擇阻塞等待,直到子進程結束;或者采用非阻塞的方式,通過輪詢檢查是否有子進程需要被回收。

然而,無論是選擇阻塞等待還是非阻塞的輪詢方式,父進程與子進程之間都無法實現真正的異步執行,因為父進程仍需“分心”來管理子進程的狀態。

當子進程終止時,會向父進程發送一個SIGCHLD信號,這個信號的默認行為是被忽略。不過,父進程可以通過設置自定義的SIGCHLD信號處理函數來改變這一行為。這樣,父進程就可以專注于自己的任務,無需直接管理子進程的狀態。一旦子進程終止,它會通知父進程,父進程只需要在其信號處理函數中調用waitwaitpid來清理子進程即可。

因此,我們能夠通過自定義處理子進程發出的SIGCHLD信號,在接收到該信號時利用waitpid回收子進程資源。這種方法避免了主動等待子進程的結束,使得父子進程之間能夠更加高效地異步執行。



演示代碼如下:因為需要在函數 handler 中使用子進程 id,因此定義了一個全局變量 id

其實還有一種方法不用傳id也不用定義全局變量:waitpid(-1, nullptr, 0);

-1 表示回收該父進程下的任意一個子進程

pid 參數為 -1 時,waitpid 函數會等待任何一個子進程的狀態變化。這意味著它會捕獲任何已經終止的子進程,并回收其資源。這對于處理多個子進程的情況非常有用,因為父進程不需要知道具體是哪個子進程終止了。

#include<iostream>  // 引入輸入輸出流庫
#include<signal.h> // 引入信號處理庫
#include<sys/wait.h> // 引入等待子進程狀態改變的函數庫
#include<sys/types.h> // 引入系統類型定義
#include<unistd.h> // 引入Unix標準函數庫pid_t id; // 定義全局變量id,用于存儲子進程ID// 定義信號處理函數
void handler(int signum)
{waitpid(id, nullptr, 0); // 等待子進程結束,回收子進程資源std::cout << "子進程退出, 我也退出了" << '\n'; // 輸出子進程已退出的信息// 當接收到信號時,調用raise給自己發送9號信號(SIGKILL),強制終止進程raise(9);
}int main()
{id = fork(); // 創建子進程if(id < 0){perror("fork"); // 如果fork失敗,輸出錯誤信息return 1; // 返回錯誤碼1}// 子進程邏輯if(id == 0){std::cout << "I am 子 process" << '\n'; // 子進程輸出標識信息sleep(2); // 子進程暫停2秒exit(0); // 子進程正常退出}// 父進程邏輯else if (id > 0){std::cout << "I am 父 process" << '\n'; // 父進程輸出標識信息signal(SIGCHLD, handler); // 設置SIGCHLD信號的處理函數為handlerint cnt = 0; // 初始化計數器while(1){   sleep(1); // 每秒暫停1秒std::cout << "cnt = " << cnt++ << '\n'; // 輸出當前計數值}}   return 0; // 程序正常結束
}


運行結果如下:


在這里插入圖片描述




問題一:如果同時多個子進程退出,是否會全部回收


但是這樣通過信號回收子進程是有一定風險的!

因為信號是通過 pending 位圖保存的,當一個父進程同時有多個子進程同時退出,同時發送 SIGCHLD 信號,則位圖不能記錄信號接收數量,就大概率會遺漏處理某些子進程,導致多個子進程僵尸的情況

驗證如下:

#include <iostream>      // 引入輸入輸出流庫
#include <signal.h>      // 引入信號處理庫
#include <sys/wait.h>    // 引入等待子進程狀態改變的函數庫
#include <sys/types.h>   // 引入系統類型定義
#include <unistd.h>      // 引入Unix標準函數庫// 定義信號處理函數
void handler(int signum)
{pid_t id = waitpid(-1, nullptr, 0); // 等待任意一個子進程結束,回收其資源std::cout << "回收子進程 id : " << id << '\n'; // 輸出回收的子進程ID
}int main()
{pid_t id; // 定義變量id,用于存儲子進程ID// 循環創建15個子進程for (int i = 1; i <= 15; ++i){id = fork(); // 創建子進程if (id < 0){perror("fork"); // 如果fork失敗,輸出錯誤信息return 1; // 返回錯誤碼1}// 子進程邏輯if (id == 0){std::cout << "I am 子 process" << '\n'; // 子進程輸出標識信息sleep(2); // 子進程暫停2秒exit(0); // 子進程正常退出}}// 父進程邏輯if (id > 0){std::cout << "I am 父 process" << '\n'; // 父進程輸出標識信息signal(SIGCHLD, handler); // 設置SIGCHLD信號的處理函數為handler,當子進程結束時會觸發此函數int cnt = 0; // 初始化計數器while (1){sleep(1); // 每秒暫停1秒std::cout << "cnt = " << cnt++ << '\n'; // 輸出當前計數值}}return 0; // 程序正常結束
}


運行結果如下:不少子進程沒有被回收,而是變成了僵尸進程


在這里插入圖片描述




解決辦法:循環等待回收子進程,否則退出


演示代碼如下:

#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>void handler(int signum)
{while (true){pid_t id = waitpid(-1, nullptr, 0);if(id > 0){std::cout << "回收子進程 id : " << id << '\n';}else if(id < 0){std::cout << "回收完畢, 暫時結束回收\n";break;}}
}int main()
{pid_t id;for (int i = 1; i <= 15; ++i){id = fork();if (id < 0){perror("fork");return 1;}// 子進程if (id == 0){std::cout << "I am 子 process" << '\n';sleep(2);exit(0);}}// 父進程if (id > 0){std::cout << "I am 父 process" << '\n';signal(SIGCHLD, handler);int cnt = 0;while (1){sleep(1);std::cout << "cnt = " << cnt++ << '\n';}}return 0;
}


運行結果如下:自己可以去查詢,可以確定當前沒有僵尸子進程


在這里插入圖片描述




問題二:如果有子進程不退出,問題一中的循環wait,是否會退出循環


演示代碼:

// 創建一個不退出的子進程
id = fork();
if (id == 0)
{std::cout << "I am 不退出的子進程" << '\n';sleep(6);
}

結果就是 循環沒退出,因為 waitpid 是阻塞式等待,會等待子進程退出,因為該子進程不退出則循環不退出一直阻塞等待


因此需要換成非阻塞式等待,同時當 waitpid 的返回值為 0,說明當前沒有退出的子進程,則此時可以主動退出循環

pid_t id = waitpid(-1, nullptr, WNOHANG);

#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>void handler(int signum)
{while (true){pid_t id = waitpid(-1, nullptr, WNOHANG);if (id > 0){std::cout << "回收子進程 id : " << id << '\n';}else if(id == 0) // 表示沒有子進程退出了(注意是沒有退出的子進程了, 不是沒有子進程){std::cout << "暫時沒有子進程退出\n";break;}else if (id < 0) // 表示沒有子進程了{std::cout << "waitpid error\n";break;}}
}int main()
{pid_t id;for (int i = 1; i <= 15; ++i){id = fork();if (id < 0){perror("fork");return 1;}// 子進程if (id == 0){std::cout << "I am 子 process" << '\n';sleep(2);exit(0);}}// 創建一個不退出的子進程id = fork();if (id == 0){std::cout << "I am 不退出的子進程" << '\n';sleep(6); // 時間長點, 模擬短時間內不退出}// 父進程if (id > 0){std::cout << "I am 父 process" << '\n';signal(SIGCHLD, handler);int cnt = 0;while (1){sleep(1);std::cout << "cnt = " << cnt++ << '\n';}}return 0;
}


運行結果如下


在這里插入圖片描述



waitpid 系統調用的工作原理如下:

  • 阻塞等待:當 waitpid 被調用時,如果當前沒有符合條件的已退出子進程,內核會讓父進程進入阻塞狀態。這意味著父進程會被掛起,不再占用CPU時間,直到有子進程的狀態發生變化(通常是退出)。
  • 非阻塞等待:如果 waitpid 調用時傳遞了 WNOHANG 選項,內核會立即返回,即使沒有子進程退出。在這種情況下,waitpid 不會阻塞父進程。
  • 狀態變化通知:當一個子進程退出時,內核會檢查該子進程的父進程是否正在等待子進程的狀態變化。如果是,內核會喚醒父進程,使其從 waitpid 調用中返回,并傳遞子進程的退出狀態。
  • 資源回收:父進程通過 waitpid 獲得子進程的退出狀態后,內核會釋放子進程占用的資源,防止子進程變成僵尸進程。

意思是:父進程使用waitpid 系統調用時,若為阻塞等待,則OS將該父進程掛起(即阻塞),當目標子進程退出時,若該父進程正處于等待子進程退出的狀態,則OS會傳遞子進程退出狀態信息并使父進程退出阻塞狀態(即使其從 waitpid 調用中返回)



子進程退出,OS是如何知道的,是因為OS需要輪詢子進程的狀態嗎

當然不是OS輪詢,前面講解過 OS 就是一個躺在中斷向量表上的一個代碼塊,OS的運行基本靠中斷,因此進程退出也是通過中斷通知OS,使其執行相應的后續”善后“工作


意思是子進程退出時,會向OS發送軟件中斷,此時進入內核態,執行該中斷對應的中斷處理例程:即更新子進程的 PCB,將子進程的狀態標記為“已退出”(Zombie 狀態),生成一個 SIGCHLD 信號并發送給父進程


子進程退出的詳細過程

  1. 子進程調用 exitexit_group 系統調用
    • 子進程在調用 exitexit_group 系統調用時,會進入內核態。
      • 不是子進程退出子進程發送的軟件中斷,而是子進程在調用 exit 或 exit_group 系統調用觸發的軟件中斷
  2. 進入內核態
    • 當子進程調用 exitexit_group 時,控制權轉移到內核,進入內核態。
    • 內核會執行相應的中斷處理例程(中斷服務程序)。
  3. 中斷處理例程
    • 內核的中斷處理例程會執行以下操作:
      • 更新子進程的 PCB:內核會更新子進程的進程控制塊(PCB),將子進程的狀態標記為“已退出”(Zombie 狀態)。
      • 生成 SIGCHLD 信號:內核會生成一個 SIGCHLD 信號并發送給父進程。
  4. 父進程接收 SIGCHLD 信號
    • 父進程接收到 SIGCHLD 信號后,會調用預先注冊的信號處理函數(如 handler)默認為忽略


主動忽略子進程的 SIGCHLD

Linux下,將SIGCHLD的處理動作置為SIG IGN,這樣fork出來的子進程在終止時會自動清理掉

由于UNIX 的歷史原因,要想不產?僵?進程還有另外?種辦法:?進程調 ?sigaction將
SIGCHLD的處理動作置為SIG_IGN,這樣fork出來的?進程在終?時會?動清理掉,不 會產?僵?進程,

也不會通知?進程。系統默認的忽略動作和???sigaction函數?定義的忽略 通常是沒有區別的,但這
是?個特例。此?法對于Linux可?,但不保證在其它UNIX系統上都可 ?。

signal(SIGCHLD, SIG_IGN);


底層原理:

父進程未調用 waitpid 的情況

  1. 子進程退出
    • 子進程調用 exitexit_group 系統調用,進入內核態。
    • 內核更新子進程的 PCB,將其狀態標記為“已退出”(Zombie 狀態)。
    • 內核生成 SIGCHLD 信號并發送給父進程。
  2. 父進程處理 SIGCHLD 信號
    • 如果父進程注冊了 SIGCHLD 信號處理函數(如 handler),內核會調用該處理函數。
    • 在信號處理函數中,父進程可以調用 waitpid 來獲取子進程的退出狀態并釋放資源。
  3. 父進程忽略 SIGCHLD 信號
    • 如果父進程將 SIGCHLD 信號的處理動作設置為 SIG_IGN,內核會自動回收子進程的資源,子進程不會變成僵尸進程。
    • 這意味著父進程不需要顯式調用 waitwaitpid 來回收子進程的資源。


問題:父進程忽略了該信號,內核如何知道父進程忽略了,然后進行的自動回收子進程的資源

  1. 內核記錄信號處理動作

    • 內核會記錄每個進程的信號處理動作。當父進程調用 signalsigaction 設置 SIGCHLD 信號的處理動作時,內核會更新父進程的信號處理表。

    • 內核會記錄 SIGCHLD 信號的處理動作為 SIG_IGN

子進程調用退出

  1. 內核生成 SIGCHLD 信號
    • 內核生成 SIGCHLD 信號并準備發送給父進程。
    • 內核會檢查父進程的信號處理表,查看 SIGCHLD 信號的處理動作。
  2. 檢查信號處理動作
    • 如果父進程的信號處理動作是 SIG_IGN,內核會知道父進程忽略了 SIGCHLD 信號。
    • 內核會自動回收子進程的資源,子進程不會變成僵尸進程。


問題:系統對該信號的默認處理不就是忽略嗎,為什么我們還要自己主動忽略

系統默認的忽略動作和???sigaction函數?定義的忽略 通常是沒有區別的,但這
是?個特例。此?法對于Linux可?,但不保證在其它UNIX系統上都可 ?。

其實,因為位圖本身一次只能記錄一個進程退出信號,因此即使循環等待等操作,還是會有極小概率處理不了某些退出子進程

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/67942.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/67942.shtml
英文地址,請注明出處:http://en.pswp.cn/web/67942.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【R語言】獲取數據

R語言自帶2種數據存儲格式&#xff1a;*.RData和*.rds。 這兩者的區別是&#xff1a;前者既可以存儲數據&#xff0c;也可以存儲當前工作空間中的所有變量&#xff0c;屬于非標準化存儲&#xff1b;后者僅用于存儲單個R對象&#xff0c;且存儲時可以創建標準化檔案&#xff0c…

Vim的基礎命令

移動光標 H(左) J(上) K(下) L(右) $ 表示移動到光標所在行的行尾&#xff0c; ^ 表示移動到光標所在行的行首的第一個非空白字符。 0 表示移動到光標所在行的行首。 W 光標向前跳轉一個單詞 w光標向前跳轉一個單詞 B光標向后跳轉一個單詞 b光標向后跳轉一個單詞 G 移動光標到…

11. 9 構建生產級聊天對話記憶系統:從架構設計到性能優化的全鏈路指南

構建生產級聊天對話記憶系統:從架構設計到性能優化的全鏈路指南 關鍵詞: 聊天對話記憶系統、多用戶會話管理、LangChain生產部署、Redis記憶存儲、高并發對話系統 一、服務級聊天記憶系統核心需求 多用戶隔離:支持同時處理數千個獨立對話持久化存儲:對話歷史不因服務重啟丟…

Block Blaster Online:免費解謎游戲的樂趣

Block Blaster Online 是一款免費的在線解謎游戲&#xff0c;它將挑戰你的思維和反應能力&#xff01;在這里&#xff0c;你可以匹配五彩繽紛的方塊&#xff0c;創造出令人驚嘆的組合&#xff0c;享受無盡的解謎樂趣。無需安裝&#xff0c;點擊即可開始&#xff0c;加入全球數百…

Guided Decoding (借助FSM,有限狀態自動機)

VLLM對結構化輸出的支持&#xff1a; vllm/docs/source/features/structured_outputs.md at main vllm-project/vllm GitHub VLLM對tool call的支持&#xff1a; vllm/docs/source/features/tool_calling.md at main vllm-project/vllm GitHub 以上指定輸出格式&#xf…

IFeatureWorkspace.CreateFeatureClass(),報錯對COM組件的調用返回了錯誤 HRESULT E_FAIL

1、問題描述&#xff1a;在AE開發中&#xff0c;新增一個空的shpfile文件的時候&#xff0c;報錯&#xff0c;如下圖&#xff1a; 2、原因分析&#xff1a;產生此問題的原因是未設置默認字段的默認參數&#xff0c;特別是未設置IGeometryDef 參數。 3、解決方案&#xff1a;在…

算法題(48):反轉鏈表

審題&#xff1a; 需要我們將鏈表反轉并返回頭結點地址 思路&#xff1a; 一般在面試中&#xff0c;涉及鏈表的題會主要考察鏈表的指向改變&#xff0c;所以一般不會允許我們改變節點val值。 這里是單向鏈表&#xff0c;如果要把指向反過來則需要同時知道前中后三個節點&#x…

內存的介紹

1、程序運行為什么需要內存 1.1、計算機程序運行的目的 (1)程序的目的是為了去運行&#xff0c;程序運行是為了得到一定的結果。 (2)計算機程序 代碼 數據。計算機程序運行完得到一個結果&#xff0c;就是說 代碼 數據 (經過運行后) 結果。 (3)從宏觀上來理解&#xff…

【NLP百面百過】大模型算法面試高頻面題(全面整理 ???)

目錄 一、大模型面試指南 重點面題精講 【LLM面題精講 - RAG系統面】 查看答案 【LLM面題精講 - 實體識別面】 查看答案 【LLM面題精講 - 文本分類面】 查看答案 【LLM面題精講 - 分布式訓練面】 查看答案 【LLM面題精講 - 大模型微調面】 查看答案 【LLM面題精講 - 大…

Java 大視界 -- Java 大數據在智能醫療影像診斷中的應用(72)

??親愛的朋友們,熱烈歡迎來到 青云交的博客!能與諸位在此相逢,我倍感榮幸。在這飛速更迭的時代,我們都渴望一方心靈凈土,而 我的博客 正是這樣溫暖的所在。這里為你呈上趣味與實用兼具的知識,也期待你毫無保留地分享獨特見解,愿我們于此攜手成長,共赴新程!?? 一、…

基于 docker 的mysql 5.7 主主集群搭建

創建掛載目錄和配置文件 主節點1 mkdir -p /mysql_master_1/mysql/log mkdir -p /mysql_master_1/mysql/data mkdir -p /mysql_master_1/mysql/conf vim /mysql_master_1/mysql/conf/my.cnf[mysqld] datadir/var/lib/mysql #MySQL 數據庫文件存放路徑 server_id 1 #指定數據…

list容器(詳解)

list的介紹及使用&#xff08;了解&#xff0c;后邊細講&#xff09; 1.1 list的介紹&#xff08;雙向循環鏈表&#xff09; https://cplusplus.com/reference/list/list/?kwlist&#xff08;list文檔介紹&#xff09; 1. list是可以在常數范圍內在任意位置進行插入和刪除的序…

MapReduce分區

目錄 1. MapReduce分區1.1 哈希分區1.2 自定義分區 2. 成績分組2.1 Map2.2 Partition2.3 Reduce 3. 代碼和結果3.1 pom.xml中依賴配置3.2 工具類util3.3 GroupScores3.4 結果 參考 本文引用的Apache Hadoop源代碼基于Apache許可證 2.0&#xff0c;詳情請參閱 Apache許可證2.0。…

kamailio-ACC_JSON模塊詳解【后端語言go】

要確認 ACC_JSON 模塊是否已經成功將計費信息推送到消息隊列&#xff08;MQueue&#xff09;&#xff0c;以及如何從隊列中取值&#xff0c;可以按照以下步驟進行操作&#xff1a; 1. 確認 ACC_JSON 已推送到隊列 1.1 配置 ACC_JSON 確保 ACC_JSON 模塊已正確配置并啟用。以下…

網件r7000刷回原廠固件合集測評

《網件R7000路由器刷回原廠固件詳解》 網件R7000是一款備受贊譽的高性能無線路由器&#xff0c;其強大的性能和可定制性吸引了許多高級用戶。然而&#xff0c;有時候用戶可能會嘗試第三方固件以提升功能或優化網絡性能&#xff0c;但這也可能導致一些問題&#xff0c;如系統不…

【C++STL標準模板庫】二、STL三大組件

文章目錄 1、容器2、算法3、迭代器 二、STL三大組件 1、容器 容器&#xff0c;置物之所也。 研究數據的特定排列方式&#xff0c;以利于搜索或排序或其他特殊目的&#xff0c;這一門學科我們稱為數據結構。大學信息類相關專業里面&#xff0c;與編程最有直接關系的學科&…

基于 Java 開發的 MongoDB 企業級應用全解析

基于Java的MongoDB企業級應用開發實戰 目錄 背景與歷史MongoDB的核心功能與特性企業級業務場景分析MongoDB的優缺點剖析開發環境搭建 5.1 JDK安裝與配置5.2 MongoDB安裝與集群配置5.3 開發工具選型 Java與MongoDB集成實戰 6.1 項目依賴與驅動選擇6.2 連接池與客戶端配置6.3…

需求分析應該從哪些方面來著手做?

需求分析一般可從以下幾個方面著手&#xff1a; 業務需求方面 - 與相關方溝通&#xff1a;與業務部門、客戶等進行深入交流&#xff0c;通過訪談、問卷調查、會議討論等方式&#xff0c;明確他們對項目的期望、目標和整體業務需求&#xff0c;了解項目要解決的業務問題及達成的…

算法題(57):找出字符串中第一個匹配項的下標

審題: 需要我們根據原串與模式串相比較并找到完全匹配時子串的第一個元素索引&#xff0c;若沒有則返回-1 思路&#xff1a; 方法一&#xff1a;BF暴力算法 思路很簡單&#xff0c;我們用p1表示原串的索引&#xff0c;p2表示模式串索引。遍歷原串&#xff0c;每次遍歷都匹配一次…

求組合數(遞推法、乘法逆元、盧卡斯定理、分解質因數)

文章目錄 遞推法 10^4代碼 乘法逆元 10^6代碼 盧卡斯定理 1 0 18 m o d 1 0 6 10^{18}mod 10^6 1018mod106代碼 分解質因數 常規的解法就不多加贅述了&#xff0c;如&#xff08;分子/分母&#xff0c;邊乘邊除&#xff09;&#xff0c;本文講述以下方法&#xff1a; 遞推法 了…