Linux 信號

信號的基本概念

每一個信號都有一個編號和一個宏定義名稱 ,這些宏定義能夠在 signal.h 中找到。html

使用kill -l命令查看系統中定義的信號列表: 1-31是普通訊號; 34-64是實時信號  
全部的信號都由操做系統來發!

對信號的三種處理方式

  1. 忽略此信號:大多數信號均可使用這種方式進行處理,但有兩種信號卻決不能被忽略。它們是:SIGKILL和SIGSTOP。這兩種信號不能被忽略的,緣由是:它們向超級用戶提供一種使進程終止或中止的可靠方法。另外,若是忽略某些由硬件異常產生的信號(例如非法存儲訪問或除以0),則進程的行爲是示定義的。
  2. 直接執行進程對於該信號的默認動做 :對大多數信號的系統默認動做是終止該進程。
  3. 捕捉信號:執行自定義動做(使用signal函數),爲了作到這一點要通知內核在某種信號發生時,調用一個用戶函數handler。在用戶函數中,可執行用戶但願對這種事件進行的處理。注意,不能捕捉SIGKILL和SIGSTOP信號。
#include <signal.h>
typedef void( *sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

signal函數的做用:給某一個進程的某一個特定信號(標號爲signum)註冊一個相應的處理函數,即對該信號的默認處理動做進行修改,修改成handler函數所指向的方式。bash

  • 第一個參數是信號的標號
  • 第二個參數,sighandler_t是一個typedef來的,原型是void (*)(int)函數指針,int的參數會被設置成signum

舉個代碼例子:函數

#include<stdio.h>
#include<signal.h>
void handler(int sig)
{
    printf("get a sig,num is %d\n",sig);
}

int main()
{
     signal(2,handler);
     while(1)
     {
         sleep(1);
         printf("hello\n");
      }
      return 0;
}

  修改了2號信號(Ctrl-c)的默認處理動做爲handler函數的內容,則當該程序在前臺運行時,鍵入Ctrl-c後不會執行它的默認處理動做(終止該進程)spa

信號的處理過程:

進程收到一個信號後不會被當即處理,而是在恰當 時機進行處理!什麼是適當的時候呢?好比說中斷返回的時候,或者內核態返回用戶態的時候(這個狀況出現的比較多)。操作系統

信號不必定會被當即處理,操做系統不會爲了處理一個信號而把當前正在運行的進程掛起(切換進程),掛起(進程切換)的話消耗太大了,若是不是緊急信號,是不會當即處理的。操做系統多選擇在內核態切換回用戶態的時候處理信號,這樣就利用二者的切換來處理了(不用單獨進行進程切換以避免浪費時間)。.net

總歸是不能避免的,由於頗有可能在睡眠的進程就接收到信號,操做系統確定不肯意切換當前正在運行的進程,因而就得把信號儲存在進程惟一的PCB(task_struct)當中。指針

產生信號的條件

1.用戶在終端按下某些鍵時,終端驅動程序會發送信號給前臺程序。
     例如:Ctrl-c產生SIGINT信號,Ctrl-\產生SIGQUIT信號,Ctrl-z產生SIGTSTP信號
2.硬件異常產生信號。
     這類信號由硬件檢測到並通知內核,而後內核向當前進程發送適當的信號。
     例如:當前進程執行除以0的指令,CPU的運算單元會產生異常,內核將這個進程解釋爲SIGFPE信號發送給當前進程。
               當前進程訪問了非法內存地址,MMU會產生異常,內核將這個異常解釋爲SIGSEGV信號發送給進程。
3.一個進程調用kill(2)函數能夠發送信號給另外一個進程。
     能夠用kill(1)命令發送信號給某個進程,kill(1)命令也是調用kill(2)函數實現的,若是不明確指定信號則發送SIGTERM信號,該信號的默認處理動做是終止進程。

 信號的產生

1. 經過終端按鍵產生信號
舉個栗子:寫一個死循環,前臺運行這個程序,而後在終端鍵入Ctrl-c
  當CPU正在執行這個進程的代碼 , 終端驅動程序發送了一 個 SIGINT 信號給該進程,記錄在該進程的 PCB中,則該進程的用戶空間代碼暫停執行 ,CPU從用戶態 切換到內核態處理硬件中斷。
  從內核態回到用戶態以前, 會先處理 PCB中記錄的信號 ,發現有一個 SIGINT 信號待處理, 而這個信號的默認處理動做是終止進程,因此直接終止進程而再也不返回它的用戶空間代碼執行。
 2. 調用系統函數向進程發信號
/*************************************************************************
 > File Name: test.c
 > Author:Lynn-Zhang
 > Mail: iynu17@yeah.net
 > Created Time: Fri 15 Jul 2016 03:03:57 PM CST
 ************************************************************************/
 
#include<stdio.h>
int main()
{
    printf("get pid :%d circle ...\n",getpid());
    while(1);
    return 0;
}
寫一個上面的程序在後臺執行死循環,並獲取該進程的id,而後用kill命令給它發送SIGSEGV信號,可使進程終止。也可使用kill -11 5796,11是信號SIGSEGV的編號。
打開終端1,運行程序:
 利用終端2,給進程發送信號
 終端1 顯示進程被core了:

kill命令是調用kill函數實現的。kill函數能夠給一個指定的進程發送指定信號。htm

raise函數可 以給當前進程發送指定的信號 (本身給本身發信號 )對象

#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
這兩個函數都是成功返回0,錯誤返回-1.
除此以外,abort函數使當前進程接收到SIGABRT信號而異常終止。
#include<stdlib.h>
void abort(void);
就像 exit函數同樣 ,abort 函數老是會成功的 ,因此沒有返回值。
3. 由軟件條件產生信號
/*************************************************************************
 > File Name: alarm.c
 > Author:Lynn-Zhang
 > Mail: iynu17@yeah.net
 > Created Time: Fri 15 Jul 2016 08:52:02 PM CST
 ************************************************************************/

#include<stdio.h>

int main()
{
    int count=0;
    alarm(1);
    while(1)
    {
        printf("%d\n",count);
        count++;
    }
    return 0;
}

 經過實現以上代碼,調用alarm函數能夠設定一個鬧鐘,告訴內核在seconds秒以後給當前進程發SIGALRM信號, 該信號的默認處理動做是終止當前進程。blog

   該程序會在1秒鐘以內不停地數數,並打印計數器,1秒鐘到了就被SIGALRM信號終止。因爲電腦配置等的不一樣,每臺電腦一秒鐘以內計數值是不一樣的通常是不一樣的。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

  alarm函數的返回值是0或上次設置鬧鐘剩餘的時間。

阻塞信號

 1.信號在內核中的表示:

信號遞達delivery:實際執行信號處理信號的動做
信號未決pending:信號從產生到抵達之間的狀態,信號產生了可是未處理
忽略:抵達以後的一種 動做
阻塞block:收到信號不當即處理      被阻塞的信號將保持未決狀態,直到進程解除對此信號的阻塞,才執行抵達動做
 
信號產生和阻塞沒有直接關係 抵達和解除阻塞沒有直接關係!
進程收到一個信號後,不會當即處理,它會在恰當的時機被處理。

每一個信號都由兩個標誌位分別表示阻塞和未決,以及一個函數指針表示信號的處理動做。

在上圖的例子中,

  1. SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動做。
  2. SIGINT信號產生過,但正在被阻塞,因此暫時不能遞達。雖然它的處理動做是忽略,但在沒 有解除阻塞以前不能忽略這個信號,由於進程仍有機會改變處理動做以後再解除阻塞。
  3. SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動做是用戶自定義函數sighandler。阻塞信號集也叫做信號屏蔽字。

信號產生可是不當即處理,前提條件是要把它保存在pending表中,代表信號已經產生。

2.信號集操做函數

#include <signal.h>
int sigemptyset(sigset_t *set);      //初始化set所指向的信號集,使全部信號的對應位清0
int sigfillset(sigset_t *set);       //初始化set所指向的信號集,表示該信號集的有效信號包括系統支持的全部信號
int sigaddset(sigset_t *set, int signo);   //在該信號集中添加有效信號
int sigdelset(sigset_t *set, int signo);    //在該信號集中刪除有效信號
int sigismember(const sigset_t *set, int signo);   //用於判斷一個信號集的有效信號中是否包含某種信號

參數解析:

  • sigset_t結構體的參數表示信號集,信號操做的時候都是以信號集合的方式進行操做,須要事先建立一個該結構體的對象,而後把想要操做的信號添加到信號集合對象當中去
  • signo就是信號的標號了

3.調用函數sigprocmask能夠讀取或更改進程的信號屏蔽字(阻塞信號集)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

   一個進程的信號屏蔽字規定了當前阻塞而不能遞送給該進程的信號集。調用函數sigprocmask能夠檢測或更改(或二者)進程的信號屏蔽字。若是調用sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中 一個信號遞達。

參數解析:

 how,有三個宏

  • SIG_BLOCK      添加到block表當中去
  • SIG_UNBLOCK  從block表中刪除
  • SIG_SETMASK  設置block表 設置當前信號屏蔽字爲set所指向的值

 set表示新設置的信號屏蔽字,oset表示當前信號屏蔽字

處理方式:

  • set 非空, oset 爲NULL :按照how指示的方法更改set指向信號集的信號屏蔽字。
  • set 爲NULL,oset 非空:讀取oset指向信號集的信號屏蔽字,經過oset參數傳出。
  • set 和 oset 都非空 :現將原來的信號屏蔽字備份到oset裏,而後根據set和how參數更改信號屏蔽字。

4. sigpending讀取當前進程的未決信號集,經過set參數傳出

#include <signal.h>
int sigpending(sigset_t *set);

這是一個輸出型參數,會把當前進程的pending表打印到傳入的set集中。 

實例驗證上面幾個函數:

 

一開始沒有任何信號,因此pending表中全是0,我經過Ctrl+C傳入2號信號,看到pending表中有2號被置位了,通過10秒取消阻塞,2號信號被處理(通過我自定義的函數)

 

相關文章
相關標籤/搜索