首頁 > 軟體

關於 Linux 信號詳解

2020-06-16 17:35:22

信號的基本概念

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

使用kill -l命令檢視系統中定義的信號列表: 1-31是普通訊號; 34-64是實時信號 
所有的信號都由作業系統來發!

對信號的三種處理方式

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

signal函數的作用:給某一個進程的某一個特定信號(標號為signum)註冊一個相應的處理常式,即對該信號的預設處理動作進行修改,修改為handler函數所指向的方式。

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

舉個程式碼例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
#include<signal.h>
void handler(int sig)
{
    printf("get a sig,num is %dn",sig);
}
 
int main()
{
     signal(2,handler);
     while(1)
     {
         sleep(1);
         printf("hellon");
      }
      return 0;
}

  修改了2號信號(Ctrl-c)的預設處理動作為handler函數的內容,則當該程式在前台執行時,鍵入Ctrl-c後不會執行它的預設處理動作(終止該進程)

信號的處理過程:

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

信號不一定會被立即處理,作業系統不會為了處理一個信號而把當前正在執行的進程掛起(切換進程),掛起(進程切換)的話消耗太大了,如果不是緊急信號,是不會立即處理的。作業系統多選擇在核心態切換回使用者態的時候處理信號,這樣就利用兩者的切換來處理了(不用單獨進行進程切換以免浪費時間)。

總歸是不能避免的,因為很有可能在睡眠的進程就接收到信號,作業系統肯定不願意切換當前正在執行的進程,於是就得把信號儲存在進程唯一的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.呼叫系統函數向進程發信號
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*************************************************************************
 > 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函數可以給一個指定的進程傳送指定信號。

raise函數可 以給當前進程傳送指定的信號 (自己給自己發信號 )

 
1
2
3
#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
這兩個函數都是成功返回0,錯誤返回-1.
除此之外,abort函數使當前進程接收到SIGABRT信號而異常終止。
 
1
2
#include<stdlib.h>
void abort(void);
就像 exit函數一樣 ,abort 函數總是會成功的 ,所以沒有返回值。
3.由軟體條件產生信號
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*************************************************************************
 > 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("%dn",count);
        count++;
    }
    return 0;
}

 通過實現以上程式碼,呼叫alarm函數可以設定一個鬧鐘,告訴核心在seconds秒之後給當前進程發SIGALRM信號, 該信號的預設處理動作是終止當前進程。

   該程式會在1秒鐘之內不停地數數,並列印計數器,1秒鐘到了就被SIGALRM信號終止。由於電腦設定等的不同,每台電腦一秒鐘之內計數值是不同的一般是不同的。

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

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

更多詳情見請繼續閱讀下一頁的精彩內容http://www.linuxidc.com/Linux/2016-08/134303p2.htm


IT145.com E-mail:sddin#qq.com