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

Linux進程信號的使用解讀

 更新時間:2025年06月25日 09:18:19   作者:有沒有沒有重復的名字  
這篇文章主要介紹了Linux進程信號的使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

什么是信號

信號是進程之間事件異步通知的一種方式,屬于軟中斷。

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

前后臺進程

Linux中,一次登錄,一個終端一般會配上一個bash,每一次登錄,只允許一個進程是前臺進程,可以允許多個后臺進程

本質區(qū)別(前臺進程獲取鍵盤輸入)

前臺進程

 ./運行后bash不再接受任何指令的進程(bash變?yōu)楹笈_),可以用ctrl c二號信號殺掉

后臺進程

./proc & 運行(ctrl c不能退出:鍵盤輸入給bash,后臺進程接受不到鍵盤輸入),可以用ctrl /殺掉

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

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

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

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

信號概念 

查看信號

 編號34以上的是實時信號

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

信號處理常見方式

可選的處理動作有以下三種:

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

signal函數(shù)

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

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

信號的產(chǎn)生

鍵盤組合鍵

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

kill命令

kill -9 pid即可

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

kill

  • pid:目標進程的 PID(進程 ID)。
  • sig:要發(fā)送的信號(如 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 進程pid即可 

 raise

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

#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

引起一個正常信號直接終止(發(fā)送6號信號)

#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;
}

此時發(fā)現(xiàn)即便把abort信號捕捉了仍然會abort退出

但是如果把abort注釋,用在終端用kill -6 pid不會終止

總結 

無論哪種產(chǎn)生方式,最終一定是操作系統(tǒng)發(fā)送給操作系統(tǒng)的,因為操作系統(tǒng)是進程的管理者

異常 

硬件

除零錯誤
#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; //我什么都沒干,我只是打印了消息
}

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;

}
野指針問題
#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; //我什么都沒干,我只是打印了消息
}

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)如何知道錯誤了呢?

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

任何異常只會影響對應進程,不會波及到操作系統(tǒng)

進程出異常與進程是否切換無關,當進程被調(diào)度時要把自己進程上下文帶走,把別人上下文拿回來(而CPU中狀態(tài)寄存器輸入上下文)

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

虛擬到物理地址轉換失敗,MMU硬件單元報錯,將轉換失敗的虛擬地址放到CPU一個寄存器里

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

捕捉信號不是為了解決異常而是讓用戶清楚為什么進程會掛掉 

core dump

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

云服務器core默認為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復現(xiàn)問題錯誤后直接定位到出錯行 ,方便事后調(diào)試。

軟件

例如:管道如果只有讀沒有寫就會觸發(fā)異常

  • 鬧鐘

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

鬧鐘響過后默認調(diào)用17號信號,但鬧鐘不是異常,如果進行信號捕捉,里邊沒有寫退出的話,響過后不會終止程序 

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

#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;
}

// 信號為什么會一直被觸發(fā)??
void handler(int signo)
{
    // work();
    cout << "...get a sig, number: " << signo <<endl; //我什么都沒干,我只是打印了消息
    // exit(1);
    int n = alarm(5);
    cout << "剩余時間:" << 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;
}

信號的保存

信號的發(fā)送是給進程的pcb發(fā)

task_struct結構體里有int single對象,int32位比特位(普通信號,位圖管理信號)

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

比特位的位置(第幾個)表示信號的編號

所謂的發(fā)信號,本質就是OS去修改task_struct中的信號位圖對應的比特位(實質是寫信號)

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

信號其他相關常見概念

實際執(zhí)行信號的處理動作稱為信號遞達

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

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

注意,阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之后可選的一種處理動作。

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

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

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

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

handler表:

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

pending表:

  • 信號位圖,記錄是否收到信號及哪些信號

block表:

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

阻塞(屏蔽)和忽略

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

  • SIG_IGN忽略 
  • SIG_IGN將1強轉成函數(shù)指針類型 
  • SIG_DFL使用信號默認方式
#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ù)類型)

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

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

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

信號集操作函數(shù)

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

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

#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所指向的信號集,使其中所有信號的對應bit清零,表示該信號集不包含 任何有效信號。

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

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

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

 sigprocmask

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

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

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

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

sigset_t *oset:輸出型參數(shù),將上一個block表進行保存(為了未來恢復)

sigpending

讀取當前進程的未決信號集,通過set參數(shù)傳出。調(diào)用成功則返回0,出錯則返回-1。 

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

示例代碼:

#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. 對2號信號進行自定義捕捉
    signal(2, handler);

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

    // 2. 重復打印當前進程的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好信號屏蔽了嗎?ok
        }
    }
    // 3 發(fā)送2號 0000000000000000000000010

    return 0;
}

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

#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. 我可以將所有的信號都進行屏蔽,信號不就不會被處理了嗎? 肯定的!9
    sigset_t bset, oset;
    sigemptyset(&bset);
    sigemptyset(&oset);
    for (int i = 1; i <= 31; i++)
    {
        sigaddset(&bset, i); // 屏蔽了所有信號嗎???
    }
    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;
}

信號處理

信號是什么時候被處理的

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

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

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

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

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

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

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

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

信號捕捉

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

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

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

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

實現(xiàn)信號捕捉代碼

#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位圖,什么時候從1->0

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

執(zhí)行信號捕捉方法之前,先清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;
}

當某個信號的處理函數(shù)被調(diào)用時

內(nèi)核自動將當前信號加入進程的信號屏蔽字

信號被處理的時候,對應的信號也會被添加到block表中,防止信號捕捉被嵌套調(diào)用

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

當信號處理函數(shù)返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產(chǎn)生,那么它會被阻塞到當前處理結束為止。 

演示代碼

#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)用信號處理函數(shù)時

除了當前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號

  • 用sa_mask字段說明這些需 要額外屏蔽的信號,當信號處理函數(shù)返回時自動恢復原來的信號屏蔽字。
  • sa_?ags字段包含一些選項,本章的代碼都 把sa_?ags設為0,sa_sigaction是實時信號的處理函數(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ù)

會導致節(jié)點丟失,內(nèi)存泄漏問題

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

像上例這樣,insert函數(shù)被不同的控制流程調(diào)用,有可能在第一次調(diào)用還沒返回時就再次進入該函數(shù),這稱 為重入,insert函數(shù)訪問一個全局鏈表,有可能因為重入而造成錯亂,像這樣的函數(shù)稱為 不可重入函數(shù),反之, 如果一個函數(shù)只訪問自己的局部變量或參數(shù),則稱為可重入(Reentrant) 函數(shù)。想一下,為什么兩個不同的 控制流程調(diào)用同一個函數(shù),訪問它的同一個局部變量或參數(shù)就不會造成錯亂?

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

調(diào)用了malloc或free,因為malloc也是用全局鏈表來管理堆的。

調(diào)用了標準I/O庫函數(shù)。標準I/O庫的很多實現(xiàn)都以不可重入的方式使用全局數(shù)據(jù)結構。

volatile

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

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信號

子進程退出時主動向父進程發(fā)送SIGCHLD(17)信號

#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;
     }
 }
}
 
// 驗證子進程退出,給父進程發(fā)送SIGCHLD
int main()
{
    signal(SIGCHLD, handler);
    // 問題1: 1個子進程,10個呢?
    // 問題2: 10個子進程,6個退出了!
    for (int i = 0; i < 10; i++)
    {
        if (fork() == 0)
        {
            sleep(5);
            std::cout << "子進程退出" << std::endl;
            // 子進程
            exit(0);
        }
    }
 
    while (true)
    {
        sleep(1);
    }
    return 0;
}

如果不用知道子進程退出結構直接這樣即可。

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

17號信號默認SIG_DFL他的默認行為是IGN,所以仍舊會有僵尸進程

總結

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

相關文章

  • linux crm部署代碼詳解

    linux crm部署代碼詳解

    在本篇文章里小編給大家分享了關于linux crm部署流程代碼,需要的朋友們可以學習下。
    2020-01-01
  • linux中tar打包指定路徑文件的實現(xiàn)方法

    linux中tar打包指定路徑文件的實現(xiàn)方法

    下面小編就為大家?guī)硪黄猯inux中tar打包指定路徑文件的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • Linux RabbitMQ 集群搭建流程圖解

    Linux RabbitMQ 集群搭建流程圖解

    在介紹RabbitMQ之前實現(xiàn)要介紹一下MQ,MQ是什么?MQ全稱是Message Queue,可以理解為消息隊列的意思,簡單來說就是消息以管道的方式進行傳遞。RabbitMQ是一個實現(xiàn)了AMQP(Advanced Message Queuing Protocol)高級消息隊列協(xié)議的消息隊列服務,用Erlang語言的
    2021-11-11
  • Apache中偽靜態(tài)Rewrite的使用方法和URL重寫規(guī)則表達式講解

    Apache中偽靜態(tài)Rewrite的使用方法和URL重寫規(guī)則表達式講解

    apache中的mod_rewrite模塊提供了一個基于正則表達式分析器的重寫引擎來實時重寫URL請求,主要的功能就是實現(xiàn)URL的跳轉,它的正則表達式是基于Perl語言。可基于服務器級的(httpd.conf)和目錄級的(.htaccess)兩種方式,允許通過它來改變Apache?Web服務器的配置。
    2022-11-11
  • Linux中fuser命令用法詳解

    Linux中fuser命令用法詳解

    這篇文章主要介紹了Linux中fuser命令用法詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • Linux運維實用腳本集詳解

    Linux運維實用腳本集詳解

    本文分享了作者在實際生產(chǎn)環(huán)境中積累的實用腳本,涵蓋了監(jiān)控、備份、診斷、自動化等多個場景,腳本建議保存在/opt/scripts/目錄,設置權限并添加日志記錄和錯誤處理,作者還提供了一些腳本示例,包括系統(tǒng)監(jiān)控、備份、自動化部署等
    2026-01-01
  • Linux下利用unzip命令如何解壓多個文件詳解

    Linux下利用unzip命令如何解壓多個文件詳解

    這篇文章主要給大家介紹了關于在Linux下利用unzip命令如何解壓多個文件的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-09-09
  • Linux系統(tǒng)中sudo命令的十個技巧總結

    Linux系統(tǒng)中sudo命令的十個技巧總結

    Linux 下使用Sudo 命令,可以讓普通用戶也能執(zhí)行一些或者全部的root命令,下面這篇文章主要給大家介紹了關于Linux系統(tǒng)中sudo命令的十個技巧,需要的朋友可以參考借鑒,下面來一起看看吧。
    2018-04-04
  • Centos7之如何設置定時任務

    Centos7之如何設置定時任務

    這篇文章主要介紹了Centos7之如何設置定時任務問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • tomcat 5.5連接池配置,如何讓工程為默認工程

    tomcat 5.5連接池配置,如何讓工程為默認工程

    把驅動程序拷貝到Tomcat 5.5\common\lib目錄下
    2009-06-06

最新評論

雅江县| 前郭尔| 滦平县| 合江县| 巩义市| 长阳| 岚皋县| 五寨县| 新泰市| 曲靖市| 濉溪县| 昌邑市| 平乡县| 山东省| 图们市| 酉阳| 珠海市| 洛隆县| 黑龙江省| 蕲春县| 望奎县| 巴东县| 弥勒县| 安仁县| 探索| 甘肃省| 芜湖市| 通河县| 屏东市| 玉溪市| 江华| 吉安市| 津南区| 肥乡县| 墨脱县| 固镇县| 商水县| 嵊州市| 图片| 布尔津县| 华坪县|