最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux信號機制之信號的保存與處理技巧分享

 更新時間:2024年10月15日 08:50:51   作者:Eternity._  
本文旨在為廣大Linux學習者提供一份詳盡而實用的指南,幫助他們深入理解Linux中的信號機制,掌握信號的保存與處理技巧,我們將從信號的基本概念出發(fā),逐步深入到信號的捕獲、保存、處理以及恢復等各個環(huán)節(jié),通過生動的實例和詳細的解釋,讓讀者能夠輕松掌握

前言:在Linux操作系統(tǒng)的廣闊天地中,信號機制無疑是一個充滿挑戰(zhàn)與機遇的領域。信號,作為進程間通信的一種重要方式,不僅承載著豐富的信息,還扮演著進程控制與管理的重要角色。然而,對于許多初學者而言,信號的保存與處理往往是一個難以逾越的障礙

讓我們一同踏上這段充滿探索與發(fā)現的旅程,共同揭開Linux信號機制的神秘面紗吧!

1. 信號的保存

信號其他相關常見概念

  • 實際執(zhí)行信號的處理動作稱為信號遞達(Delivery)
  • 信號從產生到遞達之間的狀態(tài),稱為信號未決(Pending)
  • 進程可以選擇阻塞 (Block )某個信號
  • 被阻塞的信號產生時將保持在未決狀態(tài),直到進程解除對此信號的阻塞,才執(zhí)行遞達的動作
  • 注意:阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之后可選的一種處理動作

在內核中的表示

在Linux內核中,信號的保存主要依賴于三種數據結構:pending表、block表和handler表

pending表:

  • pending表是一張位圖(bitmap),用于記錄當前進程是否收到了信號,以及收到了哪些信號
  • 當進程接收到一個信號時,對應的信號位圖上的比特位就會由0置1,表示該信號處于未決(Pending)狀態(tài)

block表:

  • block表也是一張位圖,用于記錄特定信號是否被屏蔽(阻塞)
  • 比特位的內容為0表示不屏蔽,為1表示屏蔽。屏蔽的信號在解除屏蔽之前不會被操作系統(tǒng)處理

handler表:

  • handler表是一個函數指針數組,用于保存每個信號對應的處理方法
  • 這些處理方法可以是默認的,或者忽略的,當然也可以是用戶自定義的。當信號被遞達時,操作系統(tǒng)會根據handler表找到對應的處理方法并執(zhí)行

在這里插入圖片描述

舉個例子:上圖SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因為進程仍有機會改變處理動作之后再解除阻塞

sigset_t

sigset_t是一個在Unix和Linux系統(tǒng)中用于表示信號集的數據類型。信號集本質上是一個信號的集合,用于指定多個信號,通過使用sigset_t,可以輕松地指定一組信號,并在諸如信號阻塞、信號等待等操作中使用這組信號

sigset_t信號集操作函數:

  • sigemptyset():初始化信號集,將其設置為空集
  • sigfillset():初始化信號集,將其設置為包含所有信號的集合
  • sigaddset():向信號集中添加一個信號
  • sigdelset():從信號集中刪除一個信號
  • sigismember():檢查一個信號是否屬于某個信號集

2. 信號集操作函數

信號集操作函數用于處理與信號集(sigset_t類型)相關的操作。這些函數允許用戶初始化信號集、添加或刪除信號、檢查信號是否存在于信號集中,以及修改進程的信號屏蔽字

sigprocmask()函數:

讀取或更改進程的信號屏蔽字(阻塞信號集)

在這里插入圖片描述

返回值:若成功則為0,若出錯則為-1

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是非空指針,則 更改進程的信號屏蔽字,參數how指示如何更改。如果oset和set都是非空指針,則先將原來的信號 屏蔽字備份到oset里,然后根據set和how參數更改信號屏蔽字。假設當前的信號屏蔽字為mask,下表說明了how參數的可選值

在這里插入圖片描述

代碼示例:

void headler(int signo)
{
    cout << "headler: " << signo << endl;
    // exit(0);
}

int main()
{
    cout << "pid: " << getpid() << endl;
    signal(2, headler);

    sigset_t block, oblock;
	
	// 初始化
    sigemptyset(&block);
    sigemptyset(&oblock);

    sigaddset(&block, 2); // 設置對2號信號的屏蔽

    sigprocmask(SIG_BLOCK, &block, &oblock);

    while(1)
    {
        sleep(1);
    }

    return 0;
}

在這里插入圖片描述

那我們到底能不能屏蔽所有普通信號呢?我們來測試一下

修改代碼:

for(int signo = 1; signo <= 31; signo++) sigaddset(&block, signo);

在這里插入圖片描述

在這里插入圖片描述

我們發(fā)現9號信號,19號信號是不會被屏蔽的

注意:如果調用sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達

sigpending()函數:

讀取當前進程的未決信號集,通過set參數傳出

在這里插入圖片描述

返回值:調用成功則返回0,出錯則返回-1

int sigpending(sigset_t *set);

代碼示例:

void PrintPending(const sigset_t &pending)
{
    for(int signo = 32; signo > 0; signo--)
    {
        if(sigismember(&pending, signo))
        {
            cout << "1";
        }
        else{
            cout << "0";
        }
    }
    cout << endl;
}

int main()
{
	cout << "pid: " << getpid() << endl;
	
    // 屏蔽2號信號
    sigset_t set, oset;

    sigemptyset(&set);
    sigemptyset(&oset);

    sigaddset(&set, 2);
    sigprocmask(SIG_BLOCK, &set, &oset);
	
	int cnt = 0;    
    // 讓進程不斷獲取當前進程的pending
    sigset_t pending;
    while(1)
    {
        sigpending(&pending);
        PrintPending(pending);
        sleep(1);
		
		// 對2好信號進行解除屏蔽
		cnt++;
		if(cnt == 16)
		{
			cout << "對2號信號進行解除屏蔽,準備遞達" << endl;
			sigprocmask(SIG_SETMASK, &oset, nullptr);
		}
    }
    return 0;
}

在這里插入圖片描述

當我們對信號進行處理的時候,會先將pending位圖中的1 -> 0,然后再去調用信號捕捉方法

3. 信號的處理

進程從內核態(tài)返回到用戶態(tài)的時候(包含身份的變化),進行信號的檢測和信號的處理

  • 用戶態(tài)是一種受控的狀態(tài),能夠訪問的資源是有限的(只能訪問自己的[ 0 - 3GB] )
  • 內核態(tài)是一種操作系統(tǒng)的工作狀態(tài),能夠訪問大部分系統(tǒng)資源(可以讓用戶以OS的身份訪問[ 3 - 4GB])

調用系統(tǒng)調用接口就是在進程地址空間中進行的!

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

sigaction

sigaction是一個POSIX標準的系統(tǒng)調用,用于更改和檢查信號的處理方式。與傳統(tǒng)的signal函數相比,sigaction提供了更多的控制選項和更可靠的信號處理方式

在這里插入圖片描述

int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);
  • signum:信號編號,指定要設置的信號
  • act:指向sigaction結構的指針,在sigaction的實例中指定了對特定信號的處理。如果為NULL,則進程會以缺省方式對信號處理
  • oldact:指向的對象用來保存原來對相應信號的處理,如果為NULL,則不保存

act和oldact指向sigaction結構體

在這里插入圖片描述

代碼示例:

void Print(const sigset_t &pending);

void handler(int signo)
{
    cout << "get a signo: " << signo << endl;

    while(1)
    {
        sigset_t pending;
        sigpending(&pending);
        Print(pending);
        
        sleep(1);
    }
}

void Print(const sigset_t &pending)
{
    for(int signo = 31; signo > 0; signo--)
    {
        if(sigismember(&pending, signo))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

int main()
{
    cout << "pid: " << getpid() << endl;

    struct sigaction act, oact;
    act.sa_handler = handler;

    // 增加對3號信息的屏蔽
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);

    // 對2信號進行屏蔽
    sigaction(2, &act, &oact); 

    while(1) sleep(1);

    return 0;
}

在這里插入圖片描述

當某個信號的處理函數被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函數返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那么 它會被阻塞到當前處理結束為止,如果在調用信號處理函數時,除了當前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號,則用sa_mask字段說明這些需要額外屏蔽的信號,當信號處理函數返回時自動恢復原來的信號屏蔽字

多個信號情況:

代碼示例:

void Print(const sigset_t &pending);

void handler(int signo)
{
    cout << "get a signo: " << signo << endl;
    sleep(1);
}

void Print(const sigset_t &pending)
{
    for(int signo = 31; signo > 0; signo--)
    {
        if(sigismember(&pending, signo))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

int main()
{
    signal(2, handler);
    signal(3, handler);
    signal(4, handler);
    signal(5, handler);

    sigset_t mask, omask;
    sigemptyset(&mask);
    sigemptyset(&omask);

    sigaddset(&mask, 2);
    sigaddset(&mask, 3);
    sigaddset(&mask, 4);
    sigaddset(&mask, 5);

    sigprocmask(SIG_SETMASK, &mask, &omask);

    cout << "pid: " << getpid() << endl;

    int cnt = 20;
    while(1) 
    {
        sigset_t pending;
        sigpending(&pending);
        Print(pending);

        cnt--;
        sleep(1);
        if(cnt == 0)
        {
            sigprocmask(SIG_SETMASK, &omask, nullptr);
            cout << "cancel 2,3,4,5 block" << endl;
        }
    }

    return 0;
}

在這里插入圖片描述

由實驗結果來看,我們系統(tǒng)是等所有的信號處理完全了,統(tǒng)一再進行返回的,并且他并不是按照順序來處理信號的

在這里插入圖片描述

4. 可重入函數

可重入函數是指可以被多個任務(如線程、進程)同時調用,并且能保證每個任務調用該函數時都能得到正確結果的函數。換句話說,這種函數在執(zhí)行的任何時刻都可以被中斷,然后在中斷點恢復執(zhí)行而不會導致錯誤

在這里插入圖片描述

  • main函數調用 insert函數向一個鏈表head中插入節(jié)點node1,插入操作分為兩步,剛做完第一步的 時候,因為硬件中斷使進程切換到內核,再次回用戶態(tài)之前檢查到有信號待處理,于是切換 到sighandler函數,sighandler也調用insert函數向同一個鏈表head中插入節(jié)點node2,插入操作的 兩步都做完之后從sighandler返回內核態(tài),再次回到用戶態(tài)就從main函數調用的insert函數中繼續(xù) 往下執(zhí)行,先前做第一步之后被打斷,現在繼續(xù)做完第二步。結果是,main函數和sighandler先后 向鏈表中插入兩個節(jié)點,而最后只有一個節(jié)點真正插入鏈表中
  • insert函數被不同的控制流程調用,有可能在第一次調用還沒返回時就再次進入該函數,這稱為重入,insert函數訪問一個全局鏈表,有可能因為重入而造成錯亂,像這樣的函數稱為 不可重入函數,反之,如果一個函數只訪問自己的局部變量或參數,則稱為可重入(Reentrant) 函數

不可重入函數(符合以下任一條件):

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

5. volatile

volatile是一個類型修飾符,用于告訴虛擬機該變量是極有可能多變的,從而免于一些優(yōu)化措施,確保變量的正確性和線程間的通信。它主要用于多線程環(huán)境下的變量共享,確保變量的可見性和有序性

代碼示例:

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

int flag = 0;

void headler(int signo)
{
    cout << "signo: " << signo << endl;
    flag = 1;
    cout << "change flag to: " << flag << endl;
}

int main()
{
    signal(2, headler);

    cout << "pid: " << getpid() << endl;
    while(!flag);
    cout << "qiut normal!" << endl;
    return 0;
}

標準情況下,鍵入 CTRL-C ,2號信號被捕捉,執(zhí)行自定義動作,修改 flag=1 , while 條件不滿足,退出循環(huán),進程退出

在這里插入圖片描述

優(yōu)化情況下(-O2)(不是數字0),鍵入 CTRL-C ,2號信號被捕捉,執(zhí)行自定義動作,修改 flag=1 ,但是 while 條件依舊滿足,進程繼續(xù)運行

在這里插入圖片描述

在這里插入圖片描述

所以要想不讓編譯器優(yōu)化,我們需要加上volatile

volatile int flag = 0;

在這里插入圖片描述

6. 總結

SIGCHLD信號(了解)

SIGCHLD信號在子進程狀態(tài)改變時發(fā)送給其父進程。子進程的狀態(tài)改變包括以下幾種情況:

  • 子進程終止,無論是正常終止還是異常終止(如有core dump或無core dump)
  • 子進程停止,例如接收到SIGSTOP信號
  • 停止的子進程被SIGCONT信號喚醒并繼續(xù)執(zhí)行

代碼示例:

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>  
#include <sys/types.h>  
#include <sys/wait.h> 

using namespace std;

void handle(int signo) {  
    int status;  
    pid_t pid;  
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {  
        if (WIFEXITED(status)) {  
            printf("Child %d exited with status %d\n", pid, WEXITSTATUS(status));  
        } else if (WIFSIGNALED(status)) {  
            printf("Child %d killed by signal %d\n", pid, WTERMSIG(status));  
        }  
    }  
}  
  
int main() {  
    pid_t pid;  
    struct sigaction act;  
  
    // 設置SIGCHLD信號的處理函數  
    act.sa_handler = handle;  
    sigemptyset(&act.sa_mask);  
    act.sa_flags = 0;  
    sigaction(SIGCHLD, &act, NULL);  
  
    // 創(chuàng)建子進程  
    pid = fork();  
    if (pid < 0) 
    {  
        perror("fork");  
        exit(1);  
    } 
    else if (pid == 0)
     {  
        // 子進程代碼  
        printf("Child process (PID: %d) is running\n", getpid());  
        sleep(5); // 模擬子進程工作  
        exit(0);  // 子進程正常退出  
    } 
    else 
    {  
        // 父進程代碼  
        printf("Parent process (PID: %d) is running\n", getpid());  
        // 父進程可以繼續(xù)執(zhí)行其他任務,等待SIGCHLD信號來回收子進程  
        while (1) {  
            sleep(10); // 模擬父進程工作  
            printf("Parent process is still running\n");  
        }  
    }  
  
    return 0;  
}

父進程設置了SIGCHLD信號的處理函數handle_sigchld,該函數會在子進程狀態(tài)改變時被調用。在處理函數中,父進程使用waitpid()函數來回收子進程的資源

在這里插入圖片描述

隨著我們對Linux中信號保存與處理機制的深入探討,我們不難發(fā)現,信號不僅是進程間通信的一種重要手段,更是Linux操作系統(tǒng)內核提供的一種強大而靈活的控制機制。通過信號的捕獲、保存、處理以及恢復,我們可以實現對進程行為的精確控制,從而滿足各種復雜的系統(tǒng)需求

以上就是Linux信號機制之信號的保存與處理技巧分享的詳細內容,更多關于Linux信號機制的資料請關注腳本之家其它相關文章!

相關文章

  • Linux下內存泄漏問題的診斷與解決方法

    Linux下內存泄漏問題的診斷與解決方法

    內存泄漏是Linux系統(tǒng)中常見的性能問題之一,表現為系統(tǒng)可用內存逐漸減少,最終可能導致進程崩潰或系統(tǒng)響應遲緩,本文給大家詳細介紹了處理Linux下內存泄漏問題的診斷與解決方法,需要的朋友可以參考下
    2025-04-04
  • CentOS服務器iptables配置簡單教程

    CentOS服務器iptables配置簡單教程

    這篇文章主要為大家詳細介紹了CentOS服務器iptables配置簡單教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • CentOS7下從零開始安裝ethereum/以太坊

    CentOS7下從零開始安裝ethereum/以太坊

    大家好,本篇文章主要講的是CentOS7下從零開始安裝ethereum/以太坊,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • linux查找日志cat和grep方式

    linux查找日志cat和grep方式

    在Linux系統(tǒng)中,我們常常需要查詢日志文件中的特定信息,這時候掌握一些關鍵字查詢技巧就顯得非常重要,例如,要查詢關鍵字前后30行,可以使用grep命令的'-C'選項,若要查詢兩個關鍵字同時出現的情況,可以將grep命令連續(xù)使用,對于壓縮后的文件
    2024-10-10
  • windows和Linux安裝Jmeter與簡單使用方式

    windows和Linux安裝Jmeter與簡單使用方式

    這篇文章主要介紹了windows和Linux安裝Jmeter與簡單使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • IO多路復用之epoll全面總結(必看篇)

    IO多路復用之epoll全面總結(必看篇)

    下面小編就為大家?guī)硪黄狪O多路復用之epoll全面總結(必看篇)。小編覺得挺不錯的?,F在就分享給大家。也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • Ubuntu下pycharm無法導入類的解決方法

    Ubuntu下pycharm無法導入類的解決方法

    下面小編就為大家分享一篇Ubuntu下pycharm無法導入類的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • Ubuntu中查看文件夾大小的常用方法(附避坑指南)

    Ubuntu中查看文件夾大小的常用方法(附避坑指南)

    在 Linux(Ubuntu/CentOS)服務器上進行開發(fā)或模型訓練時,磁盤空間報警是常有的事,本文將總結 Linux 下查看文件夾大小的正確姿勢,從基礎命令到交互式神器,幫你快速定位磁盤殺手”
    2026-01-01
  • 使用‘fsck’修復Linux中文件系統(tǒng)錯誤的方法

    使用‘fsck’修復Linux中文件系統(tǒng)錯誤的方法

    這篇文章主要給大家介紹了關于如何使用'fsck'修復Linux中文件系統(tǒng)錯誤的相關資料,文中通過示例代碼以及圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-03-03
  • centos 5.1下的安全設置(適合所有的linux版本)

    centos 5.1下的安全設置(適合所有的linux版本)

    因為root用戶對系統(tǒng)具有全權的操作權限,為了避免一些失誤的操作,建議在一般情況下,以一般用戶登錄系統(tǒng),必要的時候需要root操作權限時,再通過“su -”命令來登錄為root用戶進行操作。
    2010-03-03

最新評論

宝应县| 北川| 昆明市| 调兵山市| 德钦县| 金堂县| 蓝田县| 双柏县| 定西市| 自治县| 阳信县| 南投县| 岳阳市| 武功县| 屯留县| 吴江市| 汶上县| 阿瓦提县| 宁国市| 潞西市| 麻栗坡县| 固原市| 精河县| 石泉县| 贞丰县| 桦川县| 神木县| 蒙自县| 合江县| 三门峡市| 城口县| 光泽县| 铁岭县| 皋兰县| 淮南市| 高雄市| 西盟| 弥渡县| 浦江县| 综艺| 巴里|