歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux系統編程之信號(一) 信號概述

linux系統編程之信號(一) 信號概述

日期:2017/3/3 16:24:18   编辑:關於Linux

一、為了理解信號,先從我們最熟悉的場景說起:

1. 用戶輸入命令,在Shell下啟動一個前台進程。

2. 用戶按下Ctrl-C,這個鍵盤輸入產生一個硬件中斷。

3. 如果CPU當前正在執行這個進程的代碼,則該進程的用戶空間代碼暫停執行,CPU從用戶態切換到內核態處理硬件中斷。

4. 終端驅動程序將Ctrl-C解釋成一個SIGINT信號,記在該進程的PCB中(也可以說發送了一個SIGINT信號給該進程)。

5. 當某個時刻要從內核返回到該進程的用戶空間代碼繼續執行之前,首先處理PCB中記錄的信號,發現有一個SIGINT信號待處理,而這個信號的默認處理動作是終止進程,所以直接終止進程而不再返回它的用戶空間代碼執行。

用kill -l命令可以察看系統定義的信號列表:

每個信號都有一個編號和一個宏定義名稱,這些宏定義可以在signal.h中找到,例如其中有定義#define SIGINT 2。編號34以上的是實時信號,這些信號各自在什麼條件下產生,默認的處理動作是什麼(Term表示終止當前進程,Core表示終止當前進程並且Core Dump,Ign表示忽略該信號,Stop表示停止當前進程,Cont表示繼續執行先前停止的進程),在signal(7)中都有詳細說明。

0~31 不可靠信號,多個信號不會排隊只保留一個,即信號可能丟失。

34~64 可靠(實時信號),支持排隊信號不會丟失,可使用sigqueue發送信號,不像0~31有缺省的定義。

二、產生信號的條件主要有:

1、用戶在終端按下某些鍵時,終端驅動程序會發送信號給前台進程,例如Ctrl-C產生SIGINT信號,Ctrl-\產生SIGQUIT信號,Ctrl-Z產生SIGTSTP信號。

2、硬件異常產生信號,這些條件由硬件檢測到並通知內核,然後內核向當前進程發送適當的信號。例如當前進程執行了除以0的指令,CPU的運算單元會產生異常,內核將這個異常解釋為SIGFPE信號發送給進程。

3、再比如當前進程訪問了非法內存地址,MMU會產生異常,內核將這個異常解釋為SIGSEGV信號發送給進程。

4、一個進程調用kill(2)函數可以發送信號給另一個進程。

5、可以用kill(1)命令發送信號給某個進程,kill(1)命令也是調用kill(2)函數實現的,如果不明確指定信號則發送SIGTERM信號,該信號的默認處理動作是終止進程。

6、raise:給自己發送信號。raise(sig)等價於kill(getpid(), sig);

7、killpg:給進程組發送信號。killpg(pgrp, sig)等價於kill(-pgrp, sig);

8、sigqueue:給進程發送信號,支持排隊,可以附帶信息。

9、當內核檢測到某種軟件條件發生時也可以通過信號通知進程,例如鬧鐘超時產生SIGALRM信號,向讀端已關閉的管道寫數據時產生SIGPIPE信號。

三、用戶程序可以調用signal(2) / sigaction(2)函數告訴內核如何處理某種信號(若未注冊則按缺省處理),可選的處理動作有三種:

1. 忽略此信號。有兩個信號不能被忽略:SIGKILL和SIGSTOP。

2. 執行該信號的默認處理動作。

3. 提供一個信號處理函數,要求內核在處理該信號時切換到用戶態執行這個處理函數,這種方式稱為捕捉(Catch)一個信號。

四、信號與中斷的區別

信號與中斷的相似點:

(1)采用了相同的異步通信方式;

(2)當檢測出有信號或中斷請求時,都暫停正在執行的程序而轉去執行相應的處理程序;

(3)都在處理完畢後返回到原來的斷點;

(4)對信號或中斷都可進行屏蔽。

信號與中斷的區別:

(1)中斷有優先級,而信號沒有優先級,所有的信號都是平等的;

(2)信號處理程序是在用戶態下運行的,而中斷處理程序是在核心態下運行;

(3)中斷響應是及時的,而信號響應通常都有較大的時間延遲。

五、signal(2) 信號注冊函數

typedef void (*__sighandler_t) (int);

#define SIG_ERR ((__sighandler_t) -1)

#define SIG_DFL ((__sighandler_t) 0)

#define SIG_IGN ((__sighandler_t) 1)

函數原型:

__sighandler_t signal(int signum, __sighandler_t handler);

參數

signal是一個帶signum和handler兩個參數的函數,准備捕捉或屏蔽的信號由參數signum給出,接收到指定信號時將要調用的函數由handler給出,handler這個函數必須有一個int類型的參數(即接收到的信號代碼),它本身的類型是void

handler也可以是兩個特殊值:SIG_IGN屏蔽該信號;SIG_DFL 恢復默認行為

RETURN VALUE

signal() returns the previous value of the signal handler, or SIG_ERR on error.

示例小程序:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void handler(int sig);
int main(int argc, char *argv[])
{
__sighandler_t oldhandler;
oldhandler = signal(SIGINT, handler); // 返回值是先前的信號處理程序函數指針
if (oldhandler == SIG_ERR)
ERR_EXIT("signal error");
while (getchar() != '\n') ;
/* signal(SIGINT, SIGDFL) */
if (signal(SIGINT, oldhandler) == SIG_ERR)
ERR_EXIT("signal error");
for (; ;) ;
return 0;
}
void handler(int sig)
{
printf("recv a sig=%d\n", sig);
}

測試輸出如下:

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./signal

^Crecv a sig=2

^Crecv a sig=2

^Crecv a sig=2

^C

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$

程序執行開始注冊了SIGINT信號的處理函數,故我們按下ctrl+c 並不會像往常一樣終止程序,只是打印了recv a sig = 2。接著按下回車,重新注冊了SIGINT的默認處理,此時再ctrl+c 程序就被終止了。

將程序中的 32 ~37 行 換成如下的表述:

for (; ;)
{
    pause(); //使進程掛起直到一個信號被捕獲(信號處理函數完成後返回)
    //且調用schedule()使系統調度其他程序運行,
    //這樣比完全的死循環的好處是讓出cpu
    printf("pause return\n");
}

調用pause函數:將進程置為可中斷睡眠狀態。然後它調用schedule(),使linux進程調度器找到另一個進程來運行。pause使調用者進程掛起,直到一個信號被捕獲處理後函數才返回。調用pause 的好處是在等待信號的時候讓出cpu,讓系統調度其他進程運行,而不是完全的死循環,當然這樣ctrl+c 就是始終終止不了程序,我們可以使用 ctrl+ \產生SIGQUIT信號終止程序。

事實上根據man手冊,signal 函數可移植性並不是很好,最好只是用在SIG_DFL, SIG_IGN 上,注冊信號處理函數用sigaction 比較好。

Copyright © Linux教程網 All Rights Reserved