欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux進(jìn)程信號(hào)的使用解讀

 更新時(shí)間:2025年06月25日 09:18:19   作者:有沒(méi)有沒(méi)有重復(fù)的名字  
這篇文章主要介紹了Linux進(jìn)程信號(hào)的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

什么是信號(hào)

信號(hào)是進(jìn)程之間事件異步通知的一種方式,屬于軟中斷。

  • 信號(hào)的處理能力是進(jìn)程的內(nèi)置功能的一部分
  • 進(jìn)程即便沒(méi)有收到信號(hào)也知道要如何處理信號(hào)
  • 當(dāng)進(jìn)程收到一個(gè)信號(hào)時(shí)可能不會(huì)立刻處理,而是在合適時(shí)機(jī)處理(按優(yōu)先級(jí))
  • 一個(gè)進(jìn)程必須當(dāng)信號(hào)產(chǎn)生,到信號(hào)開(kāi)始被處理,就一定會(huì)有時(shí)間窗口,進(jìn)程具有保存哪些信號(hào)已經(jīng)發(fā)生了的能力。

前后臺(tái)進(jìn)程

Linux中,一次登錄,一個(gè)終端一般會(huì)配上一個(gè)bash,每一次登錄,只允許一個(gè)進(jìn)程是前臺(tái)進(jìn)程,可以允許多個(gè)后臺(tái)進(jìn)程

本質(zhì)區(qū)別(前臺(tái)進(jìn)程獲取鍵盤(pán)輸入)

前臺(tái)進(jìn)程

 ./運(yùn)行后bash不再接受任何指令的進(jìn)程(bash變?yōu)楹笈_(tái)),可以用ctrl c二號(hào)信號(hào)殺掉

后臺(tái)進(jìn)程

./proc & 運(yùn)行(ctrl c不能退出:鍵盤(pán)輸入給bash,后臺(tái)進(jìn)程接受不到鍵盤(pán)輸入),可以用ctrl /殺掉

鍵盤(pán)是如何輸入給內(nèi)核,ctral+c如何變成信號(hào)的

鍵盤(pán)也是文件 ,具有文件緩沖區(qū),操作系統(tǒng)開(kāi)機(jī)后加載到內(nèi)存里,,即把鍵盤(pán)上數(shù)據(jù)拷貝到文件緩沖區(qū),再通過(guò)接口將文件緩沖區(qū)內(nèi)數(shù)據(jù)拷貝到用戶緩沖區(qū)

但是如何知道鍵盤(pán)有數(shù)據(jù)呢?

鍵盤(pán)可以給CPU發(fā)送硬件中斷(終端號(hào)),操作系統(tǒng)識(shí)別到中斷號(hào),以中斷號(hào)為索引到中斷表里找方法,再執(zhí)行。然后將數(shù)據(jù)發(fā)送給CPU寄存器(高低電平)。

信號(hào)概念 

查看信號(hào)

 編號(hào)34以上的是實(shí)時(shí)信號(hào)

信號(hào)是數(shù)字,這里是宏定義

信號(hào)處理常見(jiàn)方式

可選的處理動(dòng)作有以下三種:

  • 1. 忽略此信號(hào)。
  • 2. 執(zhí)行該信號(hào)的默認(rèn)處理動(dòng)作。
  • 3. 提供一個(gè)信號(hào)處理函數(shù),要求內(nèi)核在處理該信號(hào)時(shí)切換到用戶態(tài)執(zhí)行這個(gè)處理函數(shù),這種方式稱為捕捉一個(gè)信號(hào)。

signal函數(shù)

  • 修改特定進(jìn)程對(duì)信號(hào)的處理動(dòng)作
  • 只要設(shè)置一次,往后都有效
  • 收到信號(hào)才調(diào)用handler方式

  • signum信號(hào)編號(hào)
  • handler:誰(shuí)調(diào)用這個(gè)signal
  • typedef void (*sighandler_t)(int); 
  • 返回值是void,參數(shù)是int的函數(shù)指針類(lèi)型 

信號(hào)的產(chǎn)生

鍵盤(pán)組合鍵

  • Ctrl+C (SIGINT) 已經(jīng)驗(yàn)證過(guò),這?不再重復(fù)
  • Ctrl+\(SIGQUIT)可以發(fā)送終?信號(hào)并?成core dump?件,?于事后調(diào)試
  • Ctrl+Z(SIGTSTP)可以發(fā)送停?信號(hào),將當(dāng)前前臺(tái)進(jìn)程掛起到后臺(tái)等。

kill命令

kill -9 pid即可

系統(tǒng)調(diào)用

kill

  • pid:目標(biāo)進(jìn)程的 PID(進(jìn)程 ID)。
  • sig:要發(fā)送的信號(hào)(如 SIGTERM、SIGKILL 等)。

 mykill:
#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

using namespace std;

void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " signum pid\n\n";
}

int main(int argc, char *argv[])
{
    
     if(argc != 3)
     {
         Usage(argv[0]);
         exit(1);
     }
     int signum = stoi(argv[1]);
     pid_t pid = stoi(argv[2]);

     int n = kill(pid, signum);
     if(n == -1)
     {
         perror("kill");
         exit(2);
     }

    return 0;
}

./mykill 9 進(jìn)程pid即可 

 raise

給調(diào)用者發(fā)送一個(gè)制定信號(hào)

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

using namespace std;

void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " signum pid\n\n";
}

// mykill signum pid
int main(int argc, char *argv[])
{
    // signal(2, myhandler);
    int cnt = 0;
    while (true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt++;
        if(cnt % 2 == 0) 
        {
            //kill(getpid(), 2);
            raise(2);      
        }
    }
    return 0;
}

abort

引起一個(gè)正常信號(hào)直接終止(發(fā)送6號(hào)信號(hào))

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

using namespace std;

void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " signum pid\n\n";
}

void myhandler(int signo)
{
    cout << "process get a signal: " << signo <<endl;
    // exit(1);
}

// mykill signum pid
int main(int argc, char *argv[])
{
    // signal(2, myhandler);
    
    signal(SIGABRT, myhandler);
    int cnt = 0;
    while (true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt++;
        if(cnt % 2 == 0) 
        {
            //kill(getpid(), 2);
            //raise(2);   
            abort();   
        }
    }
    return 0;
}

此時(shí)發(fā)現(xiàn)即便把a(bǔ)bort信號(hào)捕捉了仍然會(huì)abort退出

但是如果把a(bǔ)bort注釋?zhuān)迷诮K端用kill -6 pid不會(huì)終止

總結(jié) 

無(wú)論哪種產(chǎn)生方式,最終一定是操作系統(tǒng)發(fā)送給操作系統(tǒng)的,因?yàn)椴僮飨到y(tǒng)是進(jìn)程的管理者

異常 

硬件

除零錯(cuò)誤
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

void handler(int signo)
{
    cout << "...get a sig, number: " << signo <<endl; //我什么都沒(méi)干,我只是打印了消息
}

int main()
{
    signal(SIG_FPE, handler);
    cout<<"div before"<<endl;
    sleep(1);
    int a = 10;
    int b = 0;

    a /= b;
    cout<<"div after"<<endl;
    sleep(1);
    return 0;

}
野指針問(wèn)題
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

void handler(int signo)
{
    cout << "...get a sig, number: " << signo <<endl; //我什么都沒(méi)干,我只是打印了消息
}

int main()
{
    signal(SIGSEGV, handler);
    cout<<"point error before"<<endl;
    sleep(1);
    int *p = nullptr;
    *p = 100;

    cout<<"point error after"<<endl;
    sleep(1);
    return 0;

}

操作系統(tǒng)如何知道錯(cuò)誤了呢?

除零錯(cuò)誤時(shí):CPU內(nèi)狀態(tài)寄存器的溢出標(biāo)志位被置為1,操作系統(tǒng)會(huì)知道(OS是硬件的管理者),向進(jìn)程發(fā)送信號(hào),殺死進(jìn)程。

任何異常只會(huì)影響對(duì)應(yīng)進(jìn)程,不會(huì)波及到操作系統(tǒng)

進(jìn)程出異常與進(jìn)程是否切換無(wú)關(guān),當(dāng)進(jìn)程被調(diào)度時(shí)要把自己進(jìn)程上下文帶走,把別人上下文拿回來(lái)(而CPU中狀態(tài)寄存器輸入上下文)

野指針錯(cuò)誤時(shí):頁(yè)表的查詢由MMU(硬件內(nèi)存管理單元)完成,出現(xiàn)異常訪問(wèn)時(shí),頁(yè)表轉(zhuǎn)換時(shí)因權(quán)限問(wèn)題或映射問(wèn)題轉(zhuǎn)換失敗

虛擬到物理地址轉(zhuǎn)換失敗,MMU硬件單元報(bào)錯(cuò),將轉(zhuǎn)換失敗的虛擬地址放到CPU一個(gè)寄存器里

出現(xiàn)異常不崩潰會(huì)一直被調(diào)度,捕捉信號(hào)不是為了解決異常,而是讓用戶清除為什么掛掉

捕捉信號(hào)不是為了解決異常而是讓用戶清楚為什么進(jìn)程會(huì)掛掉 

core dump

當(dāng)?個(gè)進(jìn)程要異常終?時(shí),可以選擇把進(jìn)程的?戶空間內(nèi)存數(shù)據(jù)全部保存到磁盤(pán)上,?件名通常是core,這叫做Core Dump。

云服務(wù)器core默認(rèn)為0,需要自己修改

示例代碼: 

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

using namespace std;

int main()
{
    // signal(2, SIG_IGN);
    signal(2, SIG_DFL);



    pid_t id = fork();
    if(id == 0)
    {
        //child
        int cnt = 500;
        while(cnt)
        {
            cout << "i am a child process, pid: " << getpid() << "cnt: " << cnt << endl;
            sleep(1);
            cnt--;
        }

        exit(0);
    }

    // father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)
    {
        cout << "child quit info, rid: " << rid << " exit code: " << 
            ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) <<
                " core dump: " << ((status>>7)&1) << endl; // ? & (0000 0000 ... 0001)
    }
    return 0;
}
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

int main()
{
    // signal(2, SIG_IGN);
    signal(2, SIG_DFL);

    while (1)
    {
        cout << "hello signal" << endl;
        sleep(1);
    }

    int a = 10;
    int b = 0;

    a /= b;

    cout << " a = " << a << endl;
    return 0;
}

core dump復(fù)現(xiàn)問(wèn)題錯(cuò)誤后直接定位到出錯(cuò)行 ,方便事后調(diào)試。

軟件

例如:管道如果只有讀沒(méi)有寫(xiě)就會(huì)觸發(fā)異常

  • 鬧鐘

鬧鐘的返回值是鬧鐘響之后的剩余時(shí)間,如果有多個(gè)鬧鐘就返回上個(gè)腦中的剩余時(shí)間 

鬧鐘響過(guò)后默認(rèn)調(diào)用17號(hào)信號(hào),但鬧鐘不是異常,如果進(jìn)行信號(hào)捕捉,里邊沒(méi)有寫(xiě)退出的話,響過(guò)后不會(huì)終止程序 

如果想每隔n秒響一次,就在捕捉后再次設(shè)置鬧鐘即可

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

using namespace std;

using namespace std;
void work()
{
    cout << "print log..." << endl;
}

// 信號(hào)為什么會(huì)一直被觸發(fā)??
void handler(int signo)
{
    // work();
    cout << "...get a sig, number: " << signo <<endl; //我什么都沒(méi)干,我只是打印了消息
    // exit(1);
    int n = alarm(5);
    cout << "剩余時(shí)間:" << n << endl;
}

int main()
{
    signal(SIGALRM, handler);
    int n = alarm(50);

    while(1)
    {
        cout << "proc is running..., pid: " << getpid() << endl;
        sleep(1);
    }

    sleep(1);

    return 0;
}

信號(hào)的保存

信號(hào)的發(fā)送是給進(jìn)程的pcb發(fā)

task_struct結(jié)構(gòu)體里有int single對(duì)象,int32位比特位(普通信號(hào),位圖管理信號(hào))

比特位的內(nèi)容是1還是0表明是否收到

比特位的位置(第幾個(gè))表示信號(hào)的編號(hào)

所謂的發(fā)信號(hào),本質(zhì)就是OS去修改task_struct中的信號(hào)位圖對(duì)應(yīng)的比特位(實(shí)質(zhì)是寫(xiě)信號(hào))

發(fā)信號(hào)的一定是操作系統(tǒng),操作系統(tǒng)是進(jìn)程的管理者,只有OS才能修改task_struct內(nèi)部的屬性

信號(hào)其他相關(guān)常見(jiàn)概念

實(shí)際執(zhí)行信號(hào)的處理動(dòng)作稱為信號(hào)遞達(dá)

信號(hào)從產(chǎn)生到遞達(dá)之間的狀態(tài),稱為信號(hào)未決。 進(jìn)程可以選擇阻塞某個(gè)信號(hào)。

被阻塞的信號(hào)產(chǎn)生時(shí)將保持在未決狀態(tài),直到進(jìn)程解除對(duì)此信號(hào)的阻塞,才執(zhí)行遞達(dá)的動(dòng)作.

注意,阻塞和忽略是不同的,只要信號(hào)被阻塞就不會(huì)遞達(dá),而忽略是在遞達(dá)之后可選的一種處理動(dòng)作。

 在內(nèi)核中的表示

1.每個(gè)信號(hào)都有兩個(gè)標(biāo)志位分別表示阻塞(block)和未決(pending),還有一個(gè)函數(shù)指針表示處理動(dòng)作。信號(hào) 產(chǎn)生時(shí),內(nèi)核在進(jìn)程控制塊中設(shè)置該信號(hào)的未決標(biāo)志,直到信號(hào)遞達(dá)才清除該標(biāo)志。在上圖的例子 中,SIGHUP信號(hào)未阻塞也未產(chǎn)生過(guò),當(dāng)它遞達(dá)時(shí)執(zhí)行默認(rèn)處理動(dòng)作。

2.SIGINT信號(hào)產(chǎn)生過(guò),但正在被阻塞,所以暫時(shí)不能遞達(dá)。雖然它的處理動(dòng)作是忽略,但在沒(méi)有解除阻塞之前 不能忽略這個(gè)信號(hào),因?yàn)檫M(jìn)程仍有機(jī)會(huì)改變處理動(dòng)作之后再解除阻塞。

3.SIGQUIT信號(hào)未產(chǎn)生過(guò),一旦產(chǎn)生SIGQUIT信號(hào)將被阻塞,它的處理動(dòng)作是用戶自定義函數(shù)sighandler。 如果在進(jìn)程解除對(duì)某信號(hào)的阻塞之前這種信號(hào)產(chǎn)生過(guò)多次,將如何處理?POSIX.1允許系統(tǒng)遞送該信號(hào)一次 或多次。Linux是這樣實(shí)現(xiàn)的:常規(guī)信號(hào)在遞達(dá)之前產(chǎn)生多次只計(jì)一次,而實(shí)時(shí)信號(hào)在遞達(dá)之前產(chǎn)生多次可 以依次放在一個(gè)隊(duì)列里。本章不討論實(shí)時(shí)信號(hào)。  

handler表:

  • 信號(hào)的范圍[1,31],每一種信號(hào)都要有自己的一種處理方法(系統(tǒng)默認(rèn)),用戶可以通過(guò)signal()函數(shù)替換處理方法 

pending表:

  • 信號(hào)位圖,記錄是否收到信號(hào)及哪些信號(hào)

block表:

  • 用來(lái)記錄某種信號(hào)是否被屏蔽
  • 對(duì)一個(gè)特定任務(wù)可以屏蔽,能夠接受到但是不會(huì)執(zhí)行,想做了就解除屏蔽
  • 屏蔽是一種狀態(tài),和現(xiàn)在收到信號(hào)沒(méi)有無(wú)關(guān)
  • 信號(hào)沒(méi)有產(chǎn)生也能被屏蔽

阻塞(屏蔽)和忽略

  • 阻塞/屏蔽(未讀):屏蔽掉信號(hào),阻止其立即遞達(dá),但信號(hào)會(huì)被記錄在未決集中,待解除阻塞后處理。
  • 忽略(拒收):不會(huì)遞達(dá),處理信號(hào)(信號(hào)抵達(dá)三種方式之一),明確丟棄信號(hào),內(nèi)核直接刪除該信號(hào),不會(huì)遞達(dá)也不會(huì)記錄

  • SIG_IGN忽略 
  • SIG_IGN將1強(qiáng)轉(zhuǎn)成函數(shù)指針類(lèi)型 
  • SIG_DFL使用信號(hào)默認(rèn)方式
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

int main()
{
    // signal(2, SIG_IGN);
    signal(2, SIG_DFL);

    while(1)
    {
        cout << "hello signal" << endl;
        sleep(1);
    }
    return 0;
}

sigset_t(數(shù)據(jù)類(lèi)型)

從上圖來(lái)看,每個(gè)信號(hào)只有一個(gè)bit的未決標(biāo)志,非0即1,不記錄該信號(hào)產(chǎn)生了多少次,阻塞標(biāo)志也是這樣表示的。 因此,未決和阻塞標(biāo)志可以用相同的數(shù)據(jù)類(lèi)型sigset_t來(lái)存儲(chǔ)

sigset_t稱為信號(hào)集,這個(gè)類(lèi)型可以表示每個(gè)信號(hào) 的“有效”或“無(wú)效”狀態(tài),在阻塞信號(hào)集中“有效”和“無(wú)效”的含義是該信號(hào)是否被阻塞,而在未決信號(hào)集中“有 效”和“無(wú)效”的含義是該信號(hào)是否處于未決狀態(tài)。阻塞信號(hào)集也叫做當(dāng)前進(jìn)程的信號(hào)屏蔽字

這里的“屏蔽”應(yīng)該理解為阻塞而不是忽略。  

信號(hào)集操作函數(shù)

信號(hào)集是系統(tǒng)提供的一種數(shù)據(jù)類(lèi)型,方便我們未來(lái)對(duì)pending表和block表做操作

sigset_t類(lèi)型對(duì)于每種信號(hào)用一個(gè)bit表示“有效”或“無(wú)效”狀態(tài),至于這個(gè)類(lèi)型內(nèi)部如何存儲(chǔ)這些bit則依賴于系統(tǒng) 實(shí)現(xiàn),從使用者的角度是不必關(guān)心的,使用者只能調(diào)用以下函數(shù)來(lái)操作sigset_ t變量,而不應(yīng)該對(duì)它的內(nèi)部數(shù)據(jù)做 任何解釋,比如用printf直接打印sigset_t變量是沒(méi)有意義的

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

1.函數(shù)sigemptyset初始化set所指向的信號(hào)集,使其中所有信號(hào)的對(duì)應(yīng)bit清零,表示該信號(hào)集不包含 任何有效信號(hào)。

2.函數(shù)sig?llset初始化set所指向的信號(hào)集,使其中所有信號(hào)的對(duì)應(yīng)bit置位,表示 該信號(hào)集的有效信號(hào)包括系 統(tǒng)支持的所有信號(hào)。

3.注意,在使用sigset_ t類(lèi)型的變量之前,一定要調(diào) 用sigemptyset或sig?llset做初始化,使信號(hào)集處于確定的狀態(tài)。初始化sigset_t變量之后就可以在調(diào)用sigaddset和sigdelset在該信號(hào)集中添加或刪除某種有效信號(hào)。

4.這四個(gè)函數(shù)都是成功返回0,出錯(cuò)返回-1。sigismember是一個(gè)布爾函數(shù),用于判斷一個(gè)信號(hào)集的有效信號(hào)中是否包含 某種 信號(hào),若包含則返回1,不包含則返回0,出錯(cuò)返回-1。

 sigprocmask

調(diào)用函數(shù)sigprocmask可以讀取或更改進(jìn)程的信號(hào)屏蔽字(阻塞信號(hào)集)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
返回值:若成功則為0,若出錯(cuò)則為-1

如果oset是非空指針,則讀取進(jìn)程的當(dāng)前信號(hào)屏蔽字通過(guò)oset參數(shù)傳出。如果set是非空指針,則 更改進(jìn)程的信 號(hào)屏蔽字,參數(shù)how指示如何更改。如果oset和set都是非空指針,則先將原來(lái)的信號(hào) 屏蔽字備份到oset里,然后 根據(jù)set和how參數(shù)更改信號(hào)屏蔽字。假設(shè)當(dāng)前的信號(hào)屏蔽字為mask,下表說(shuō)明了how參數(shù)的可選值。

  • SIG_BLOCK:新增屏蔽信號(hào)(或運(yùn)算有1為1)
  • SIG_UNBLOCK:在進(jìn)程block表里去掉傳入的set信號(hào)
  • SIG_SETMASK:直接設(shè)置進(jìn)進(jìn)程pcb的block表里,覆蓋式的設(shè)置全新屏蔽字段

sigset_t *oset:輸出型參數(shù),將上一個(gè)block表進(jìn)行保存(為了未來(lái)恢復(fù))

sigpending

讀取當(dāng)前進(jìn)程的未決信號(hào)集,通過(guò)set參數(shù)傳出。調(diào)用成功則返回0,出錯(cuò)則返回-1。 

set輸出型參數(shù):把pending表以位圖形式帶出來(lái)

示例代碼:

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

using namespace std;

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

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

int main()
{
    // 0. 對(duì)2號(hào)信號(hào)進(jìn)行自定義捕捉
    signal(2, handler);

    // 1. 先對(duì)2號(hào)信號(hào)進(jìn)行屏蔽 --- 數(shù)據(jù)預(yù)備
    sigset_t bset, oset; // 在哪里開(kāi)辟的空間???用戶棧上的,屬于用戶區(qū)
    sigemptyset(&bset);
    sigemptyset(&oset);
    sigaddset(&bset, 2); // 我們已經(jīng)把2好信號(hào)屏蔽了嗎?并沒(méi)有設(shè)置進(jìn)入到你的進(jìn)程的task_struct
    // 1.2 調(diào)用系統(tǒng)調(diào)用,將數(shù)據(jù)設(shè)置進(jìn)內(nèi)核
    sigprocmask(SIG_SETMASK, &bset, &oset); // 我們已經(jīng)把2好信號(hào)屏蔽了嗎?ok

    // 2. 重復(fù)打印當(dāng)前進(jìn)程的pending 0000000000000000000000000
    sigset_t pending;
    int cnt = 0;
    while (true)
    {
        // 2.1 獲取
        int n = sigpending(&pending);
        if (n < 0)
            continue;
        // 2.2 打印
        PrintPending(pending);

        sleep(1);
        cnt++;
        // 2.3 解除阻塞
        if(cnt == 20)
        {
            cout << "unblock 2 signo" << endl;
            sigprocmask(SIG_SETMASK, &oset, nullptr); // 我們已經(jīng)把2好信號(hào)屏蔽了嗎?ok
        }
    }
    // 3 發(fā)送2號(hào) 0000000000000000000000010

    return 0;
}

9號(hào)和19號(hào)新號(hào)不可以被捕捉和屏蔽(演示代碼)

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

using namespace std;

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

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

int main()
{
    // 4. 我可以將所有的信號(hào)都進(jìn)行屏蔽,信號(hào)不就不會(huì)被處理了嗎? 肯定的!9
    sigset_t bset, oset;
    sigemptyset(&bset);
    sigemptyset(&oset);
    for (int i = 1; i <= 31; i++)
    {
        sigaddset(&bset, i); // 屏蔽了所有信號(hào)嗎???
    }
    sigprocmask(SIG_SETMASK, &bset, &oset);

    sigset_t pending;
    while (true)
    {
        // 2.1 獲取
        int n = sigpending(&pending);
        if (n < 0)
            continue;
        // 2.2 打印
        PrintPending(pending);
        sleep(1);
    }
    return 0;
}

信號(hào)處理

信號(hào)是什么時(shí)候被處理的

  • 當(dāng)進(jìn)程從內(nèi)核態(tài)返回到用戶態(tài)時(shí)進(jìn)行對(duì)信號(hào)的檢測(cè)和處理
  • 調(diào)用系統(tǒng)調(diào)用時(shí)——操作系統(tǒng)會(huì)自動(dòng)做身份切換,用戶身份變成內(nèi)核身份(或者反著來(lái))
  • 由CPU中esc寄存器決定:當(dāng)esc寄存器低兩位為11時(shí)為用戶態(tài),為00時(shí)為內(nèi)核態(tài)
  • int 80 從用戶態(tài)陷入內(nèi)核態(tài)的一條匯編,將11改為00

剛開(kāi)機(jī)操作系統(tǒng)就加載了,所以在物理內(nèi)存較靠下的位置 ,進(jìn)程pcb的3——4G空間是內(nèi)核空間,由內(nèi)核級(jí)頁(yè)表映射到物理內(nèi)存,內(nèi)核級(jí)頁(yè)表只有一個(gè)(所有進(jìn)程都可以看到),用戶級(jí)頁(yè)表好幾個(gè)(進(jìn)程具有獨(dú)立性)。

進(jìn)程視角:調(diào)用系統(tǒng)中的方法時(shí),就是在自己的地址空間中進(jìn)行的(類(lèi)似于共享區(qū)與代碼段之間的跳轉(zhuǎn))。

操作系統(tǒng)視角:任何一個(gè)時(shí)刻,都是有進(jìn)程執(zhí)行的,我們想執(zhí)行操作系統(tǒng)的代碼就可以執(zhí)行。

操作系統(tǒng)的本質(zhì)是一個(gè)死循環(huán)。

操作系統(tǒng)的運(yùn)行是被動(dòng)的,由時(shí)鐘來(lái)推動(dòng)進(jìn)行,當(dāng)時(shí)鐘信號(hào)來(lái)時(shí),停止運(yùn)行當(dāng)前進(jìn)程代碼,去查看進(jìn)程的調(diào)度,只要進(jìn)程在跑,cpu就會(huì)調(diào)度進(jìn)程,只要調(diào)度,時(shí)間片就會(huì)被消耗完畢,消耗完后把進(jìn)程從CPU上剝離下來(lái)。(下次調(diào)度將pcb等匯總到CPU上(內(nèi)核態(tài)))

操作系統(tǒng)不相信用戶還體現(xiàn)在不會(huì)執(zhí)行用戶的代碼(要執(zhí)行代碼必須從內(nèi)核態(tài)跳轉(zhuǎn)到用戶態(tài))

沒(méi)用調(diào)用系統(tǒng)調(diào)用的代碼也會(huì)有內(nèi)核態(tài)和用戶態(tài)的切換(二次調(diào)用時(shí))上邊紅字

信號(hào)捕捉

內(nèi)核如何實(shí)現(xiàn)信號(hào)的捕捉

如果信號(hào)的處理動(dòng)作是用戶自定義函數(shù),在信號(hào)遞達(dá)時(shí)就調(diào)用這個(gè)函數(shù),這稱為捕捉信號(hào)。由于信號(hào)處理函數(shù)的代碼 是在用戶空間的,處理過(guò)程比較復(fù)雜,舉例如下: 用戶程序注冊(cè)了SIGQUIT信號(hào)的處理函數(shù)sighandler。 當(dāng)前正在執(zhí)行 main函數(shù),這時(shí)發(fā)生中斷或異常切換到內(nèi)核態(tài)。

在中斷處理完畢后要返回用戶態(tài)的main函數(shù)之前檢查到有信號(hào) SIGQUIT遞達(dá)。 內(nèi)核決定返回用戶態(tài)后不是恢復(fù)main函數(shù)的上下文繼續(xù)執(zhí)行,而是執(zhí)行sighandler函 數(shù),sighandler 和main函數(shù)使用不同的堆??臻g,它們之間不存在調(diào)用和被調(diào)用的關(guān)系,是 兩個(gè)獨(dú)立的控制流程。

sighandler函數(shù)返 回后自動(dòng)執(zhí)行特殊的系統(tǒng)調(diào)用sigreturn再次進(jìn)入內(nèi)核態(tài)。 如果沒(méi)有新的信號(hào)要遞達(dá),這次再返回用戶態(tài)就是恢復(fù) main函數(shù)的上下文繼續(xù)執(zhí)行了。  

實(shí)現(xiàn)信號(hào)捕捉代碼

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

using namespace std;

void handler(int signo)
{
    cout<< "catch a signal, signal number : " << signo << endl;
}

int main()
{
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));
    
    act.sa_handler = handler;

    sigaction(2,&act,&oact);

    while (true)
    {
        cout << "I am a process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

pending位圖,什么時(shí)候從1->0

當(dāng)進(jìn)行信號(hào)處理時(shí),倘若已經(jīng)進(jìn)入到了信號(hào)的捕捉代碼里,先把信號(hào)位圖由1清零,才調(diào)用handler方法

執(zhí)行信號(hào)捕捉方法之前,先清0,在調(diào)用

演示代碼

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

using namespace std;

void PrintPending()
{
    sigset_t set;
    sigpending(&set);

    for (int signo = 1; signo <= 31; signo++)
    {
        if (sigismember(&set, signo))
            cout << "1";
        else
            cout << "0";
    }
    cout << "\n";
}

void handler(int signo)
{
    PrintPending();
    sleep(1);
}

int main()
{
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));
    
    act.sa_handler = handler;

    sigaction(2,&act,&oact);

    while (true)
    {
        cout << "I am a process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

當(dāng)某個(gè)信號(hào)的處理函數(shù)被調(diào)用時(shí)

內(nèi)核自動(dòng)將當(dāng)前信號(hào)加入進(jìn)程的信號(hào)屏蔽字

信號(hào)被處理的時(shí)候,對(duì)應(yīng)的信號(hào)也會(huì)被添加到block表中,防止信號(hào)捕捉被嵌套調(diào)用

操作系統(tǒng)不允許對(duì)同一種信號(hào)進(jìn)行嵌套式的捕捉(如果沒(méi)有屏蔽會(huì)導(dǎo)致在處理時(shí)調(diào)用handler函數(shù))

當(dāng)信號(hào)處理函數(shù)返回時(shí)自動(dòng)恢復(fù)原來(lái)的信號(hào)屏蔽字,這樣就保證了在處理某個(gè)信號(hào)時(shí),如果這種信號(hào)再次產(chǎn)生,那么它會(huì)被阻塞到當(dāng)前處理結(jié)束為止。 

演示代碼

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

using namespace std;

void PrintPending()
{
    sigset_t set;
    sigpending(&set);

    for (int signo = 1; signo <= 31; signo++)
    {
        if (sigismember(&set, signo))
            cout << "1";
        else
            cout << "0";
    }
    cout << "\n";
}

void handler(int signo)
{
    cout << "catch a signal, signal number : " << signo << endl;
    while (true)
    {
        PrintPending();
        sleep(1);
    }
}

int main()
{
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));
    
    act.__sigaction_handler = handler;

    sigaction(2,&act,&oact);

    while (true)
    {
        cout << "I am a process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

如果在調(diào)用信號(hào)處理函數(shù)時(shí)

除了當(dāng)前信號(hào)被自動(dòng)屏蔽之外,還希望自動(dòng)屏蔽另外一些信號(hào)

  • 用sa_mask字段說(shuō)明這些需 要額外屏蔽的信號(hào),當(dāng)信號(hào)處理函數(shù)返回時(shí)自動(dòng)恢復(fù)原來(lái)的信號(hào)屏蔽字。
  • sa_?ags字段包含一些選項(xiàng),本章的代碼都 把sa_?ags設(shè)為0,sa_sigaction是實(shí)時(shí)信號(hào)的處理函數(shù)

示例代碼 

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

using namespace std;

void PrintPending()
{
    sigset_t set;
    sigpending(&set);

    for (int signo = 1; signo <= 31; signo++)
    {
        if (sigismember(&set, signo))
            cout << "1";
        else
            cout << "0";
    }
    cout << "\n";
}

void handler(int signo)
{
    cout << "catch a signal, signal number : " << signo << endl;
    while (true)
    {
        PrintPending();
        sleep(1);
    }
}

int main()
{
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));
    
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 1);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);

    act.__sigaction_handler = handler;

    sigaction(2, &act, &oact);

    while (true)
    {
        cout << "I am a process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

可重入函數(shù)

會(huì)導(dǎo)致節(jié)點(diǎn)丟失,內(nèi)存泄漏問(wèn)題

main函數(shù)調(diào)用insert函數(shù)向一個(gè)鏈表head中插入節(jié)點(diǎn)node1,插入操作分為兩步,剛做完第一步的 時(shí)候,因 為硬件中斷使進(jìn)程切換到內(nèi)核,再次回用戶態(tài)之前檢查到有信號(hào)待處理,于是切換 到sighandler函 數(shù),sighandler也調(diào)用insert函數(shù)向同一個(gè)鏈表head中插入節(jié)點(diǎn)node2,插入操作的 兩步都做完之后從 sighandler返回內(nèi)核態(tài),再次回到用戶態(tài)就從main函數(shù)調(diào)用的insert函數(shù)中繼續(xù) 往下執(zhí)行,先前做第一步 之后被打斷,現(xiàn)在繼續(xù)做完第二步。結(jié)果是,main函數(shù)和sighandler先后 向鏈表中插入兩個(gè)節(jié)點(diǎn),而最后只 有一個(gè)節(jié)點(diǎn)真正插入鏈表中了。

像上例這樣,insert函數(shù)被不同的控制流程調(diào)用,有可能在第一次調(diào)用還沒(méi)返回時(shí)就再次進(jìn)入該函數(shù),這稱 為重入,insert函數(shù)訪問(wèn)一個(gè)全局鏈表,有可能因?yàn)橹厝攵斐慑e(cuò)亂,像這樣的函數(shù)稱為 不可重入函數(shù),反之, 如果一個(gè)函數(shù)只訪問(wèn)自己的局部變量或參數(shù),則稱為可重入(Reentrant) 函數(shù)。想一下,為什么兩個(gè)不同的 控制流程調(diào)用同一個(gè)函數(shù),訪問(wèn)它的同一個(gè)局部變量或參數(shù)就不會(huì)造成錯(cuò)亂?

如果一個(gè)函數(shù)符合以下條件之一則是不可重入的:

調(diào)用了malloc或free,因?yàn)閙alloc也是用全局鏈表來(lái)管理堆的。

調(diào)用了標(biāo)準(zhǔn)I/O庫(kù)函數(shù)。標(biāo)準(zhǔn)I/O庫(kù)的很多實(shí)現(xiàn)都以不可重入的方式使用全局?jǐn)?shù)據(jù)結(jié)構(gòu)。

volatile

  • volatile防止編譯器過(guò)度優(yōu)化,保存內(nèi)存可見(jiàn)性(保證每次都從內(nèi)存讀?。?/li>
  • 邏輯反也是一種計(jì)算
  • 在優(yōu)化條件下,flag在CPU內(nèi)執(zhí)行,在內(nèi)存修改不會(huì)影響到寄存器

makefile

mysignal:mysignal.cc
	g++ -o $@ $^ -O3 -g -std=c++11
.PHONY:clean
clean:
	rm -f mysignal
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <signal.h>

using namespace std;

volatile int flag = 0;

void handler(int signo)
{
    cout << "catch a signal: " << signo << endl;
    flag = 1;
}

int main()
{
    signal(2, handler);
    // 在優(yōu)化條件下, flag變量可能被直接優(yōu)化到CPU內(nèi)的寄存器中
    while(!flag); // flag 0, !falg 真

    cout << "process quit normal" << endl;
    return 0;
}

SIGCHLD信號(hào)

子進(jìn)程退出時(shí)主動(dòng)向父進(jìn)程發(fā)送SIGCHLD(17)信號(hào)

#include <iostream>
#include <cstdlib>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
 
void handler(int signo)
{
     void handler(int signo)
 {
     sleep(5);
     pid_t rid;
     while ((rid = waitpid(-1, nullptr, WNOHANG)) > 0)
     {
         cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child             
          process quit: " << rid << endl;
     }
 }
}
 
// 驗(yàn)證子進(jìn)程退出,給父進(jìn)程發(fā)送SIGCHLD
int main()
{
    signal(SIGCHLD, handler);
    // 問(wèn)題1: 1個(gè)子進(jìn)程,10個(gè)呢?
    // 問(wèn)題2: 10個(gè)子進(jìn)程,6個(gè)退出了!
    for (int i = 0; i < 10; i++)
    {
        if (fork() == 0)
        {
            sleep(5);
            std::cout << "子進(jìn)程退出" << std::endl;
            // 子進(jìn)程
            exit(0);
        }
    }
 
    while (true)
    {
        sleep(1);
    }
    return 0;
}

如果不用知道子進(jìn)程退出結(jié)構(gòu)直接這樣即可。

int main()
{
    signal(SIGCHLD, handler);
    // 問(wèn)題1: 1個(gè)子進(jìn)程,10個(gè)呢?
    // 問(wèn)題2: 10個(gè)子進(jìn)程,6個(gè)退出了!
    for (int i = 0; i < 10; i++)
    {
        if (fork() == 0)
        {
            sleep(5);
            std::cout << "子進(jìn)程退出" << std::endl;
            // 子進(jìn)程
            exit(0);
        }
    }
 
    while (true)
    {
        sleep(1);
    }
    return 0;
}

17號(hào)信號(hào)默認(rèn)SIG_DFL他的默認(rèn)行為是IGN,所以仍舊會(huì)有僵尸進(jìn)程

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • centos開(kāi)機(jī)自動(dòng)啟動(dòng)RabbitMq軟件的方法

    centos開(kāi)機(jī)自動(dòng)啟動(dòng)RabbitMq軟件的方法

    本文詳細(xì)講解了centos開(kāi)機(jī)自動(dòng)啟動(dòng)RabbitMq軟件的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以收藏下
    2021-11-11
  • 如何利用watch幫你重復(fù)執(zhí)行命令

    如何利用watch幫你重復(fù)執(zhí)行命令

    這篇文章主要給大家介紹了關(guān)于如何利用watch幫你重復(fù)執(zhí)行命令的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用linux系統(tǒng)具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-05-05
  • linux輸入輸出重定向使用詳解

    linux輸入輸出重定向使用詳解

    linux中使用重定向符號(hào)可以實(shí)現(xiàn)輸出輸入重定向,默認(rèn)條件下,標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出都是終端,用重定向符號(hào)可以把標(biāo)準(zhǔn)輸出和錯(cuò)誤內(nèi)容進(jìn)行重定向,例如把標(biāo)準(zhǔn)輸出重定向到文件,看下面的詳細(xì)解釋
    2014-01-01
  • Linux中crontab定時(shí)任務(wù)不執(zhí)行的原因

    Linux中crontab定時(shí)任務(wù)不執(zhí)行的原因

    本篇文章主要介紹了Linux中crontab定時(shí)任務(wù)不執(zhí)行的原因,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • 關(guān)于linux中系統(tǒng)輸入輸出的管理詳解

    關(guān)于linux中系統(tǒng)輸入輸出的管理詳解

    這篇文章主要給大家介紹了關(guān)于linux中系統(tǒng)輸入輸出的管理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用linux具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • ubuntu系統(tǒng)theano和keras的安裝方法

    ubuntu系統(tǒng)theano和keras的安裝方法

    這篇文章主要介紹了ubuntu系統(tǒng)theano和keras的安裝方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Linux定時(shí)刪除日志的簡(jiǎn)單實(shí)現(xiàn)方法

    Linux定時(shí)刪除日志的簡(jiǎn)單實(shí)現(xiàn)方法

    這篇文章主要給大家介紹了關(guān)于Linux定時(shí)刪除日志的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Linux具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Linux systemV消息隊(duì)列和信號(hào)量詳解

    Linux systemV消息隊(duì)列和信號(hào)量詳解

    這篇文章主要介紹了Linux systemV消息隊(duì)列和信號(hào)量,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • Ubuntu18.04 安裝 Anaconda3的教程詳解

    Ubuntu18.04 安裝 Anaconda3的教程詳解

    這篇文章主要介紹了Ubuntu18.04 安裝 Anaconda3的教程,本文通過(guò)實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • SSH的ssh-keygen命令基本用法詳解

    SSH的ssh-keygen命令基本用法詳解

    ssh-keygen可用來(lái)生成ssh公鑰認(rèn)證所需的公鑰和私鑰文件,本文為大家詳細(xì)介紹ssh-keygen的基本使用方法
    2018-10-10

最新評(píng)論