Linux - 信號

信號,將內核和進程聯繫起來,是操作系統內部交互的機制。

通過kill -l指令查看所有信號

注意,這裏沒有32,33號信號,也是從這裏將信號分爲兩類,1~31位普通信號,34~64爲實時信號

信號產生原因 
  • 通過鍵盤按鍵產生信號
ctrl-c:2號信號(SIGINT),終止當前運行進程,注意:ctrl-c 產生的信號只發給前臺進程
ctrl-\:3號信號(SIGQUIT),終止進程並且產生coredump文件

  • 通過硬件異常產生信號
CPU :0作爲除數時,CPU運算單元會產生異常,產生8號信號(SIGFPE)
MMU:訪問內存越界或者非法的時候,MMU發現當前內存不合法,就會通知CPU向該進程發送一個11號信號(SIGSEGV),使終止進程(段錯誤)

  • 系統調用
kill 給某個進程發送某個信號

  • 軟件條件產生信號
管道讀端關閉,嘗試寫,會觸發13號信號(SIGPIPE)
alarm,若干秒後,操作系統會發送14號鬧鐘信號(SIGALRM)

關於信號處理的三種方式
  • 忽略 利用signal給信號註冊一個函數
  • 默認 通常情況下是讓信號異常終止
  • 捕捉 利用signal,有一個函數指針,在函數指針中進行處理(無窮大符號,用戶和內核如何切換)
//代碼功能:利用MyHnadler函數將產生的SIGINT(ctrl-c)信號捕捉,一旦產生SIGINT信號,就打印對應的值
#include <stdio.h>
#include <signal.h>

void MyHandler(int sig)
{
    printf("sig = %d\n",sig);
}

int main()
{
    signal(SIGINT,MyHandler);
    while(1)
    {}
    return 0;
}

下圖展示信號捕捉的時候,用戶和內核的幾次交互

  • 執行順序,一個正無窮符號。
  • main和MyHandler函數有各自的執行流,各自有各自的調用棧。
  • MyHandler函數結束以前,main函數需等待,這也限制了信號捕捉的使用場景,比如7*24小時的服務器程序時不允許main函數停止的。
  • 本次過程,內核態和用戶態共交互4次,途中紅圈表示。

信號的阻塞

PCB中維護了兩個位圖,分別表示已經收到但是沒來得及處理的信號(未決信號集),和要去阻塞的信號(信號屏蔽字)
信號集操作函數

對於未決信號集和信號屏蔽字的操作其實就是在操作一個位圖,所以我們需要一個位圖結構體(sigset_t)
每個信號只用一個bit位表示,非0即1,不表示多少,只表示信號的有效無效狀態。
在未決信號集中,‘有效’和‘無效’狀態分別表示是否處於未決狀態。
在信號屏蔽字中,‘有效’和‘無效’狀態分別表示信號是否被阻塞。

int sigemptyset(sigset_t *set); //初始化位圖,並將所有位置爲0
int sigfillset(sigset_t *set); //將所有位置爲1
int sigaddset (sigset_t *set, int signo);//給位圖中某一位置爲1 
int sigdelset(sigset_t *set, int signo); //給位圖中某一位置爲0
int sigismember(const sigset_t *set, int signo);//判斷某一位是0/1

返回值 
    前四個函數,成功返回0,失敗返回-1;
    sigismember函數,若包含返回1,不包含返回0,失敗返回-1

sigpromask - 讀取或更改進程的信號屏蔽字,指定某一位信號被阻塞

int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
參數
    how : 將哪一位進行改變
    set : 輸入型參數,通過設置set中的位
    oset : 輸出型參數,設置新的信號屏蔽字之前把舊的備份下來,以便於恢復

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

sigpending - 讀取未決信號集

int sigpending(sigset_t* set)
參數
    通過set參數傳出。
返回值
調⽤成功則返回0,出錯則返回-1。 下⾯⽤剛學的⼏個函數做個 實驗。程序如下:

代碼演示
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void MyHandler(int sig)
{
    printf("sig = %d\n",sig);
}

void PrintSigSet(sigset_t* set)
{
    int i = 1;
    for(; i < 32; ++i) //只考慮普通信號
    {
        if(sigismember(set,i))
        {
            printf("1");
        }
        else
        {
            printf("0");
        }
    }
    printf("\n");
}

int main()
{
    //1.捕捉 SIGINT 信號
    signal(SIGINT,MyHandler);
    //2.把 SIGINT 信號屏蔽掉
    sigset_t set;
    sigset_t oset;
    sigemptyset(&set);
    sigaddset(&set,SIGINT);
    sigprocmask(SIG_BLOCK,&set,&oset);
    //3.循環讀取未決信號集
    int count = 0;
    while(1)
    {
        ++count;
        if(count >= 5)
        {
            count = 0;
            //解除信號屏蔽字,再設置上
            printf("解除信號屏蔽字\n");
            sigprocmask(SIG_SETMASK,&oset,NULL);
            sleep(1);
            printf("再次設置信號屏蔽字\n");
            sigprocmask(SIG_BLOCK,&set,&oset);
        }
        sigset_t pending_set;
        sigpending(&pending_set);
        PrintSigSet(&pending_set);
        sleep(1);
    }
    //循環若干次,自動解除屏蔽
    return 0;
}

結果演示:

起始時,未決信號集中沒有未決信號,所以全0
當鍵入ctrl-c時,未決信號集的2號位置(從1開始)被設置爲1
到了第五秒的時候,程序自動解除信號屏蔽字
這時SIGINT信號被我們所定義的MyHandler函數所捕捉,打印了一句 sig = %d
回到main函數的時候,重新將信號屏蔽字集置爲全0







發佈了77 篇原創文章 · 獲贊 50 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章