歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 關於 Linux 信號詳解

關於 Linux 信號詳解

日期:2017/2/28 13:49:54   编辑:Linux教程

信號的基本概念

每個信號都有一個編號和一個宏定義名稱 ,這些宏定義可以在 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 %d\n",sig); } int main() { signal(2,handler); while(1) { sleep(1); printf("hello\n"); } 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: [email protected] > 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: [email protected] > 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信號, 該信號的默認處理動作是終止當前進程。

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

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

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

Copyright © Linux教程網 All Rights Reserved