從內核到應用層:深度剖析信號捕捉技術棧(含sigaction系統調用/SIGCHLD回收/volatile內存屏障)

Linux系列


文章目錄

  • Linux系列
  • 前言
  • 一、進程對信號的捕捉
    • 1.1 內核對信號的捕捉
    • 1.2 sigaction()函數
    • 1.3 信號集的修改時機
  • 二、可重入函數
  • 三、volatile關鍵字
  • 四、SIGCHLD信號


前言

Linux系統中,信號捕捉是指進程可以通過設置信號處理函數來響應特定信號。通過信號捕捉機制,進程可以對異步事件做出及時響應,從而提高程序的健壯性和靈活性。


一、進程對信號的捕捉

圖中內容及執行流程我已在Linux系列上上篇博客中介紹了,這里就不重復了。
在這里插入圖片描述

1.1 內核對信號的捕捉

當信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這稱為捕捉信號。由于信號處理函數的代碼是在用戶空間的,處理過程比較復雜,舉例如下:
1、用戶程序注冊(對指定信號捕捉)了SIGQUIT信號的處理函數sighandler

2、 當前正在執行main函數,這時發生中斷或異常切換到內核態

3、 在中斷處理完畢后要返回用戶態的main函數之前檢查到有信號SIGQUIT遞達。

4、 內核決定返回用戶態后,不是恢復main函數的上下文繼續執行,而是執行sighandler函數,sighandlermain函數使用不同的堆棧空間,它們之間不存在調用和被調用的關系,是兩個獨立的控制流程

5、 sighandler函數返回后自動執行特殊的系統調用sigreturn再次進入內核態。

6、 再次檢測sigpending位圖,如果沒有新的信號要遞達,這次再返回用戶態就是恢復main函數的上下文繼續執行了。

1.2 sigaction()函數

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);

功能:捕捉指定信號,并讀取和修改與指定捕捉信號相關聯的處理動作。

參數

signum:指定捕捉信號的編號。

act: 輸入型參數,act若非空,則根據act來修改信號的處理動作。

oldact:輸出型參數,oldact若非空,則獲取信號原來的處理動作。

struct sigaction:系統為用戶提供的結構體類型,幫助用戶訪問內核級結構體:
在這里插入圖片描述
今天我們主要使用,上面兩個成員對象。

  1. 信號處理方法,該方法需要一個整形變量,函數指針類型
  2. act.sa_mask 所代表的是在信號處理函數執行期間需要阻塞的信號集合。也就是說,當 指定信號被捕獲并且處理函數handler開始執行時,sa_mask 里的信號會被阻塞,一直到處理函數執行完畢。

下面我們通過兩個場景來認識他們:

例一

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;void handler(int signum)
{cout<<"I catch a signal:"<<signum<<endl;return;
}
int main()
{struct sigaction act;struct sigaction olact;memset(&act,0,sizeof(act));//初始化內存空間memset(&olact,0,sizeof(olact));sigaction(2,&act,&olact);//對二號信號捕獲,并修改處理方法while(true){cout<<"I am process,Pid:"<<getpid()<<endl;sleep(1);}return 0;
}

在這里插入圖片描述
可以看到這樣我們,就完成了對二號進程的捕獲并修改執行方法為自定義的行為。

例二

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<cstring>
using namespace std;void handler(int signum)
{cout<<"I catch a signal:"<<signum<<endl;sleep(10);//在執行handler方法期間,blocksig阻塞信號集中的信號被阻塞return;
}
int main()
{struct sigaction act;struct sigaction olact;memset(&act,0,sizeof(act));//初始化內存空間memset(&olact,0,sizeof(olact));sigset_t blocksig;sigaddset(&blocksig,2);//將二信號添加進blocksigact.sa_handler=handler;//將處理方法添加到act對象中act.sa_mask=blocksig;//將想要阻塞的信號位圖賦值給actsigaction(2,&act,&olact);//對二號信號捕獲,并修改處理方法while(true){cout<<"I am process,Pid:"<<getpid()<<endl;sleep(1);}return 0;
}

在這里插入圖片描述
從執行結構可以得到,當二號信號被捕獲執行處理方法,到該方法執行結束,二號信號一直被阻塞,當解除阻塞后,二號信號再次遞達。這里也可以使用SIG_IGN(忽略信號)、SIG_DFL(執行默認方法),來設定act.sa_handler。測試時建議嘗試其他信號,因為即使我們不手動的將二號信號添加到阻塞信號集,系統在執行二號信號時也會將它先阻塞,下面我們來詳細探討。

1.3 信號集的修改時機

當我們完成對指定信號的捕捉并執行對應處理方法時,操作系統會在執行該方法前,先將pending位圖中對應信號的標志位由1置為0,并將該信號添加到對應的阻塞信號集中。具體來說,在二號信號處理方法執行期間,即便進程再次收到二號信號,該信號也不會被遞達。只有當上一個信號處理方法執行完畢并返回后,操作系統解除對二號信號的阻塞,新收到的二號信號才會被遞達。

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<cstring>
using namespace std;void printsig()
{sigset_t set;sigemptyset(&set);sigpending(&set);for(int i=1;i<=31;i++)//依次檢測信號集{if(sigismember(&set,i))cout<<1;else cout<<0;}cout<<endl;return ;
}
void handler(int signum)
{int cnt=5;while(cnt--){printsig();sleep(1);}cout<<"I catch a signal:"<<signum<<endl;sleep(5);//在執行handler方法期間,blocksig阻塞信號集中的信號被阻塞return;
}
int main()
{struct sigaction act;struct sigaction olact;memset(&act,0,sizeof(act));//初始化內存空間memset(&olact,0,sizeof(olact));act.sa_handler=handler;//將處理方法添加到act對象中sigaction(2,&act,&olact);//對二號信號捕獲,并修改處理方法while(true){cout<<"I am process,Pid:"<<getpid()<<endl;sleep(1);}return 0;
}

在這里插入圖片描述
從程序執行結果可以得出,當方法被執行時,操作系統會先將pending信號集1--->0,并將該信號阻塞,知道上次執行結束才會完成遞達。

二、可重入函數

結合圖中展示,分析函數調用鏈

在程序運行過程中,main函數調用insert函數,打算向鏈表head中插入節點node1insert函數的插入操作分為兩個步驟,當main函數調用的insert函數剛完成第一步時,硬件中斷出現,進程被切換到內核態。在從內核態再次返回用戶態之前,系統檢測到有信號需要處理,于是進程轉而執行sighandler函數。在sighandler函數中,同樣調用了insert函數,并且向同一個鏈表head中插入節點node2sighandler函數中的insert操作順利完成了兩個步驟,之后從sighandler函數返回內核態,接著再次回到用戶態時,恢復上下文數據,程序從main函數調用的insert函數中斷處繼續執行,完成了剩余的第二步操作。原本main函數和sig handler函數先后嘗試向鏈表中插入兩個不同的節點,但最終鏈表中實際上僅成功插入了一個節點。 在這里插入圖片描述

在上述執行流程中,insert函數被main和handler兩條執行流重復調用,這一情況引發了結點丟失問題,并進而導致內存泄漏。像insert函數這種在被重復調用時可能出錯或已經出錯的函數,我們稱之為不可重入函數;與之相對應的,則被稱為可重入函數。

不可重入函數的特點:
調用了malloc或free,因為malloc也是用全局鏈表來管理堆的。
調用了標準I/O庫函數。標準I/O庫的很多實現都以不可重入的方式使用全局數據結構。

三、volatile關鍵字

接下來會通過這個關鍵字,拓展部分知識

例一

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<cstring>
using namespace std;int flag=0;void handler(int signum)
{cout<<"I captured a signal:"<<signum<<endl;flag=1;
}
int main()
{struct sigaction act;memset(&act,0,sizeof(act));act.sa_handler=handler;sigaction(2,&act,nullptr);while(!flag);cout<<"process quit "<<endl;return 0;
}
mytest:mytest.ccg++ -o $@ $^ -std=c++11

在這里插入圖片描述

相信這個執行結果大家都能理解,我就不對上面代碼作解釋了。
這里將flag設為全局變量,是因為main和sighandler是兩個獨立的執行流

例二
代碼同上

mytest:mytest.ccg++ -o $@ $^ -std=c++11 -O2

在這里插入圖片描述

從程序執行結果可知,當將g++編譯器的優化級別設置為-O2時,即便通過發送二號信號(SIGINT)將flag變量修改為1,循環仍無法終止。這一現象的根源在于:當使用-O2這類高級優化級別編譯代碼時,編譯器會對代碼進行多維度優化以提升執行效率。針對while(!flag);這一循環結構,編譯器通過靜態代碼分析發現,循環體內部不存在對flag變量的修改操作,因此推斷該變量的值在循環過程中不會發生變化。

基于“內存訪問速度相對較慢”這一特性,編譯器為減少對內存的頻繁訪問,會將flag變量的值從內存加載至CPU寄存器中緩存。此后,在循環條件判斷時,CPU會直接從寄存器中讀取flag的值,而非重新從內存中獲取最新數據,這就導致flag內存不可見了。然而,信號處理機制對flag變量的修改是直接作用于內存的,由于寄存器中的緩存值未及時刷新,導致循環條件判斷始終基于寄存器中的舊值,最終造成循環無法終止的現象。

對于上面的結果我們可以,將 flag 聲明為 volatile 類型,即 volatile int flag = 0;volatile 關鍵字的作用是保存flag的內存可見性,告訴編譯器,這個變量的值可能會被意外地改變,例如被硬件或者其他線程、信號處理函數等修改,因此編譯器不能對其進行優化,這里就不展示了。

四、SIGCHLD信號

之前我們探討過使用 waitwaitpid 函數來清理僵尸進程。在處理子進程結束的問題上,父進程有兩種選擇:一是進行阻塞等待,直至子進程結束;二是采用非阻塞的輪詢方式,周期性地檢查是否有子進程結束,以便及時清理。然而,這兩種方式都存在明顯的弊端。若采用阻塞等待的方式,父進程在等待期間會被阻塞,無法處理自身的任務,這會極大地降低父進程的工作效率。而采用輪詢方式,雖然父進程可以在處理自身工作的同時檢查子進程的狀態,但這要求父進程時刻記得進行輪詢操作,無疑增加了程序實現的復雜度,也容易出現疏漏。實際上,當子進程終止時,它會向父進程發送 SIGCHLD 信號。該信號的默認處理方式是被忽略,但我們可以對其進行優化。父進程可以自定義 SIGCHLD 信號的處理函數,這樣一來,父進程就能夠專注于自身的工作,無需時刻關注子進程的狀態。當子進程終止時,會自動通知父進程,父進程只需在信號處理函數中調用 wait 函數,即可完成子進程的清理工作,既高效又便捷。 下面我們通過這樣的方式實現一下:

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<cstring>
#include<sys/wait.h>
#include<sys/types.h>
using namespace std;void handler(int signum)
{pid_t wid=waitpid(0,nullptr,WNOHANG);if(wid)cout<<"child quit success"<<endl;return;
}
int main()
{signal(SIGCHLD,handler);pid_t id=fork();if(id==0){sleep(5);//模擬子進程工作exit(0);}while(true){cout<<"I am father process"<<endl;sleep(1);}return 0;
}

在這里插入圖片描述
從執行結果可以得出,子進程在退出時給父進程發送了SIGCHLD信號。

當然還有一種防止僵尸進程的方法:父進程調 用sigactionSIGCHLD的處理動作置為SIG_IGN,這樣fork出來的子進程在終止時會自動清理掉,不會產生僵尸進程,也不會通知父進程:

int main()
{struct sigaction act;memset(&act,0,sizeof(act));act.sa_handler=SIG_IGN;sigaction(SIGCHLD,&act,nullptr);pid_t id=fork();if(id==0){sleep(5);exit(0);}while(true){cout<<"I am father process"<<endl;sleep(1);}return 0;
}

這個結果不方便展示,你自己嘗試一下。
本篇就分享到這里了,如果文章的知識,或代碼有錯誤請您聯系我,不勝感激!!!

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

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

相關文章

DDD領域驅動與傳統CRUD

DDD 是一套 應對復雜業務系統 的設計方法論&#xff0c;核心是 讓代碼直接映射業務邏輯&#xff0c;避免技術實現與業務需求脫節。 關鍵區別&#xff1a; 傳統開發&#xff1a;根據數據庫表寫 CRUD&#xff08;技術驅動&#xff09;。DDD&#xff1a;根據業務行為建模&#xf…

20. git diff

基本概述 git diff的作用是&#xff1a;比較代碼差異 基本用法 1.工作區 VS 暫存區 git diff [file]2.暫存區 VS 最新提交 git diff --staged [file] # 或 git diff --cached [file]3.工作區 VS 最新提交 git diff HEAD [file]高級用法 1.比較兩個提交間的差異 git dif…

大模型面經 | 春招、秋招算法面試常考八股文附答案(五)

大家好,我是皮先生!! 今天給大家分享一些關于大模型面試常見的面試題,希望對大家的面試有所幫助。 往期回顧: 大模型面經 | 春招、秋招算法面試常考八股文附答案(RAG專題一) 大模型面經 | 春招、秋招算法面試常考八股文附答案(RAG專題二) 大模型面經 | 春招、秋招算法…

Sql刷題日志(day5)

面試&#xff1a; 1、從數據分析角度&#xff0c;推薦模塊怎么用指標衡量&#xff1f; 推薦模塊主要目的是將用戶進行轉化&#xff0c;所以其主指標是推薦的轉化率推薦模塊的指標一般都通過埋點去收集用戶的行為并完成相應的計算而形成相應的指標數據&#xff0c;而這里的驅動…

封裝 element-ui 二次彈框

author 封裝 element-ui 彈框 param text 文本內容 &#xff08;不傳默認顯示 確定執行此操作嗎&#xff1f; &#xff09; param type 彈框類型&#xff08;不傳默認warning類型&#xff09; param title 彈框標題&#xff08;不傳默認顯示 提示 &#xff09; export fun…

【Rust 精進之路之第12篇-生命周期·入門】為何需要與顯式標注 (`‘a`):讓編譯器讀懂引用的“有效期”

系列: Rust 精進之路:構建可靠、高效軟件的底層邏輯 作者: 碼覺客 發布日期: 2025-04-20 引言:懸垂引用的“幽靈”與編譯器的“偵探” 在前面的章節中,我們深入學習了 Rust 的所有權系統,以及如何通過引用 (& 和 &mut) 進行借用,從而在不轉移所有權的情況下安…

[密碼學實戰]CTF競賽高頻加密與解密技術詳解

CTF競賽高頻加密與解密技術詳解 一、CTF加密體系全景圖 在CTF密碼學挑戰中&#xff0c;加解密技術主要分為四大戰域&#xff1a; #mermaid-svg-lmm07BXqYAGYjymI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-lm…

docker.desktop下安裝普羅米修斯prometheus、grafana并看服務器信息

目標 在docker.desktop下先安裝這三種組件,然后顯示當前服務的CPU等指標。各種坑已踩,用的是當前時間最新的鏡像 核心關系概述 組件角色依賴關系Prometheus開源監控系統,負責 數據采集、存儲、查詢及告警。依賴 Node-Exporter 提供的指標數據。Node-Exporter專用的 數據采集…

《MySQL:MySQL表的內外連接》

表的連接分為內連接和外連接。 內連接 內連接實際上就是利用where子句對兩種表形成的笛卡爾積進行篩選&#xff0c;之前的文章中所用的查詢都是內連接&#xff0c;也是開發中使用的最多的連接查詢。 select 字段 from 表1 inner join 表2 on 連接條件 and 其他條件&#xff1…

實現SpringBoot底層機制【Tomcat啟動分析+Spring容器初始化+Tomcat 如何關聯 Spring容器】

下載地址&#xff1a; https://download.csdn.net/download/2401_83418369/90675207 一、搭建環境 創建新項目 在pom.xml文件中導入依賴 &#xff08;一定要刷新Maven&#xff09;排除內嵌的Tomcat&#xff0c;引入自己指定的Tomcat <?xml version"1.0" enco…

從零開始構建微博爬蟲:實現自動獲取并保存微博內容

從零開始構建微博爬蟲&#xff1a;實現自動獲取并保存微博內容 前言 在信息爆炸的時代&#xff0c;社交媒體平臺已經成為信息傳播的重要渠道&#xff0c;其中微博作為中國最大的社交媒體平臺之一&#xff0c;包含了大量有價值的信息和數據。對于研究人員、數據分析師或者只是…

Uniapp微信小程序:輕松獲取用戶頭像和昵稱

參考文獻&#xff1a;Uniapp微信小程序&#xff1a;輕松獲取用戶頭像和昵稱-百度開發者中心 (baidu.com) uni.login({ provider: weixin, success: function (loginRes) { console.log(loginRes.authResult); // 打印登錄憑證 // 使用登錄憑證獲取用戶信息 uni.getUserInfo({ …

【自然語言處理與大模型】大模型(LLM)基礎知識③

&#xff08;1&#xff09;大模型的“7B”是什么意思&#xff1f; "B"通常代表“Billion”&#xff0c;即十億。因此&#xff0c;當提到7B時&#xff0c;指的是該模型擁有7 billion&#xff08;70億&#xff09;個參數。 &#xff08;2&#xff09;模型后面標的“ins…

聊聊自動化用例的維護

自動化測試中的農藥悖論&#xff1a;為何長期維護至關重要 自動化測試常被視為"一次編寫&#xff0c;永久有效"的解決方案&#xff0c;但隨著時間的推移&#xff0c;即使設計最精良的測試套件也會逐漸失效。這種現象被稱為農藥悖論&#xff08;Pesticide Paradox&am…

微幀Visionular斬獲NAB Show 2025年度產品獎

在本月剛結束的NAB Show 2025展會上&#xff0c;全球領先的視頻編碼與AI超高清服務提供商微幀Visionular大放異彩&#xff0c;其核心產品AI-driven Video Compression&#xff08;AI視頻智能編碼引擎&#xff09;不僅在展會中吸引了眾多行業目光&#xff0c;更憑借其卓越的編碼…

idea中運行groovy程序報錯

我的項目是使用的 gradle 構建的。 在 idea 中運行Groovy的面向對象程序報錯如下&#xff1a; Execution failed for task :Person.main(). > Process command G:/Program Files/jdk-17/jdk-17.0.12/bin/java.exe finished with non-zero exit value 1* Try: Run with --s…

【自然語言處理與大模型】個人使用LLaMA Factory微調的記錄

一、魔塔社區免費服務器如何使用webui微調&#xff1f; 一上來我就得先記錄一下&#xff0c;使用魔塔社區的免費服務器的時候&#xff0c;因為沒有提供ssh而導致無法看到webui的遺憾如何解決的問題&#xff1f; 執行命令 如果點這個鏈接無法彈出微調的webui&#xff0c;則可以在…

【官方正版,永久免費】Adobe Camera Raw 17.2 win/Mac版本 配合Adobe22-25系列軟

Adobe Camera Raw 2025 年 2 月版&#xff08;版本 17.2&#xff09;。目前為止最新版新版已經更新2個月了&#xff0c;我看論壇之前分享的還是2024版&#xff0c;遂將新版分享給各位。 Adobe Camera Raw&#xff0c;支持Photoshop&#xff0c;lightroom等Adobe系列軟件&#…

leetcode:1295. 統計位數為偶數的數字(python3解法)

難度&#xff1a;簡單 給你一個整數數組 nums&#xff0c;請你返回其中位數為 偶數 的數字的個數。 示例 1&#xff1a; 輸入&#xff1a;nums [12,345,2,6,7896] 輸出&#xff1a;2 解釋&#xff1a; 12 是 2 位數字&#xff08;位數為偶數&#xff09; 345 是 3 位數字&…

使用Handsontable實現動態表格和下載表格

1.效果 2.實現代碼 首先要加載Handsontable&#xff0c;在示例中我是cdn的方式引入的&#xff0c;vue的話需要下載插件 let hot null;var exportPlugin null;function showHandsontable(param) {const container document.getElementById("hot-container");// 如果…