天天看點

請列出三種Linux下的信号處理方式,linux系統程式設計--信号

信号

信号的概念

信号的産生

信号的處理方式

标準信号的不可靠性

信号集

阻塞信号集

未決信号集

信号與系統調用

被中斷的系統調用

信号處理方式的更改

1、signal函數

2、帶參數的信号處理函數

2.1 sigaction函數

2.2 struct sigaction 結構

2.3 sa_sigaction成員

2.4 發送信号時如何攜帶參數

信号的概念

什麼是信号?信号其實是編号從1-64的一組數,用SIG開頭的宏表示。信号是一個軟中斷,可以給我們提供一種停止目前執行流,進而去執行另一部分代碼的方法。其中,1-31是基本信号,32-64是實時信号。實時信号暫不關注。

信号的産生

1、終端按鍵産生。

比如Ctrl+c産生SIGINT、Ctrl+\産生SIGQUIT信号、Ctrl+z産生SIGITSP信号等等。

2、硬體異常産生。

硬體檢測到某些條件發生時,會産生信号。比如除以零、通路非法記憶體。信号由硬體産生通知核心,然後核心再把信号通知程序。

3、指令及函數産生。

shell下的kill指令及編寫代碼時用到的kill函數。

4、軟體産生

檢測到某些軟體條件産生的時候會産生信号。比如網絡連接配接上傳來帶外資料,産生SIGURG信号;往一個讀斷關閉的管道寫資料,産生SIGPIPE信号;程序設定的時鐘逾時,産生SIGALARM信号。

這裡面有幾個相關函數:

1、給自己發送信号

#includeint raise(int signo)

傳回值:成功傳回0,失敗傳回-1。

在指定秒之後給程序自己發送SIGALARM信号。

#includeunsigned int alarm(unsigned int secondes)

傳回值:傳回0或者之前設定的鬧鐘時間的餘留描述

2、給自己或者别的程序發送信号

使用kill給調用者自己發送信号的時候,在kill傳回之前,signo指定的信号或者其他某個未決、非阻塞信号将被遞送至程序。

#includeint kill(pid_t pid, int signo)

pid:指明發送信号給哪個程序或者程序組。

pid > 0 信号發送給程序id為pid的程序。

pid = 0 信号發送給與發送程序同一程序組内的所有程序,前提是調用kill的程序有權限向這些程序發送信号。

pid < 0 信号發送給程序組id等于pid的絕對值,并且發送程序有權限向其發送信号的所有程序。

pid == -1 該信号發送給程序有權發送給的所有程序。

signo:要發送的信号

傳回值: 成功傳回零;出錯傳回-1.

信号的處理方式

linux提供了三種信号處理的方式:

1、忽略此信号。

信号會産生并且遞送給程序,但程序不執行任何動作。

2、執行預設動作。

大多數信号的預設動作是終止程序。

3、捕捉信号。

程序可以為特定的信号注冊信号處理函數。這樣當信号被遞送給程序的時候,程序轉而去執行信号處理函數,從信号處理函數傳回之後再繼續原先的執行流

注意:SIGKILL和SIGSTOP不能被忽略也不能被捕捉。因為它們提供了一種可靠的終止程序的方式。

标準信号的不可靠性

對于标準信号(1-31),在信号處理函數執行期間,如果該信号再産生,會被加入到未決信号集中去,等處理函數傳回後再遞送給程序。但是,如果執行期間該信号産生了多次,隻會遞送一次,其他的都被丢棄了。

信号集

信号集就是信号的集合,用sigset_t類型表示,sigset_t是typedef的,它其實就是一個64位的資料,每一位代表一個信号,分别是1-64,沒有編号為零的信号

信号集有一系列的操作函數,如下:

#includeint sigemptyset(sigset_t *set)

int sigfillset(sigset_t *set)

int sigaddset(sigset_t *set, int signo)

int sigdelset(sigset_t *set, int signo)

傳回值:成功傳回0,失敗傳回-1

int sigismember(const sigset_t *set, int segno)

傳回值:若真,傳回1;若假,傳回0

每個程序都有兩個信号集:阻塞信号集和未決信号集

阻塞信号集

每個程序維護一個阻塞信号集,在該集合内的信号不會被遞送給程序。

可以通過sigprocmask來檢視或者更改阻塞信号集:

#includeint sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oldset);

傳回值:成功傳回0;出錯傳回-1

how有下面幾個選項:

how

作用

SIG_BLOCK

将set中的信号添加到阻塞信号集中去

SIG_UNBLOCK

将set中的信号從阻塞信号集中移除

SIG_SETMASK

新的阻塞信号集是set指向的集合

調用sigprocmask後如果有任何未決的、不再阻塞的信号,則在sigprocmask傳回前至少将其中之一遞送給程序

未決信号集

何為未決?信号産生了但是還未遞送給程序,稱為未決的。如果為程序産生了一個信号,并且對信号的動作是捕捉或者預設動作,那麼該信号就會被加入到未決信号集,直到程序對該信号解除阻塞,或者信号的動作更改為忽略;在别的程序運作期間産生的信号,也會被加入到未決信号集。

可以使用sigpending來檢視未決信号集。

int sigpending(sigset_t *set)

傳回值:成功傳回0;出錯傳回-1。

信号與系統調用

如果程序在執行一個低速系統調用而阻塞期間捕捉到一個信号,則該系統調用就被中斷,如果該系統調用是可自動重新開機動的,則重新執行該系統調用。否則該系統調用傳回出錯,并将errno設定為EINTR.

低速系統調用:可能會是系統永遠阻塞的一類系統調用。

如管道、終端、網絡裝置的資料不存在時,則讀操作就有可能永遠阻塞。

被中斷的系統調用

當程序執行低速系統調用而阻塞的時候,程序的狀态是TASK_INTERRUPTIBLE,

但是,當程序收到一個信号,并且該信号不在阻塞集中時,核心會把程序的狀态改成TASK_RUNNING,即使系統調用請求的資源沒有準備好。這樣當程序被排程運作的時候從系統調用傳回前會檢查有沒有未處理的信号,進而去執行信号處理函數。

信号處理方式的更改

1、signal函數

#define SIG_ERR (void (*)())-1

#define SIG_DFL (void (*)())0

#define SIG_IGN (void (*)())1

#includetypedef void Sigfunc (int);

Sigfunc *signal(int signo, Sigfunc *func)

func:沒有傳回值且隻有一個int型參數的函數指針,指向信号處理函數。也可以是SIG_IGN或者SIG_DFL。

傳回值:成功傳回之前的信号處理配置,失敗傳回SIG_ERR

signal的缺點:

1、被signal注冊的信号處理函數中斷的系統調用預設是自動重新開機動的,當該類型的系統調用被中斷之後,不會傳回錯誤,會重新執行下該系統調用。

2、在某個信号處理函數執行期間不能阻塞其他信号。

3、不能給信号處理函數傳遞額外參數。

下面的sigaction函數可以解決上面的問題。

2、帶參數的信号處理函數

2.1 sigaction函數

#includeint sigaction(int signo, const struct sigaction *restrict act,

struct sigaction *oact)

傳回值:成功傳回0,失敗傳回-1.

2.2 struct sigaction 結構

struct sigaction{

void (*sa_handler)(int)

sigset_t sa_mask;

int sa_flags

void (*sa_sigaction)(int, siginfo_t * void *)

}

sa_mask:當信号的動作是捕捉的時候,在信号處理函數傳回之前,這一信号集指定的信号會被加到信号屏蔽字當中去,從信号處理函數傳回時屏蔽字回複為原先值。同時,信号處理函數執行期間,新信号屏蔽字包含正被遞送的信号。

sa_flags:選項有很多,主要列出如下常用幾個

SA_INTERRUPT:由此信号中斷的系統調用不重新啟動。

SA_RESTART:由此信号中斷的系統調用自動重新開機動。

SA_SIGINFO:此選項對信号處理程式提供附加資訊。并不是說指定了該選項之後隻能使用sa_sigaction函數,也可以使用sa_handler函數,隻不過這麼使用沒什麼意義。

2.3 sa_sigaction成員

void sa_sigaction(int signo, siginfo_t *info,void *context)

signo:信号

info:要傳遞的資訊

context:它表示發送程序在發送信号時的上下文,這個參數暫時不關心。

siginfo_t 實際上是一個結構體,如下:

typedef struct siginfo

{

int si_signo;

int si_errno;

int si_code;

pid_t si_pid;

uid_t si_uid;

void *si_addr;

int si_status;

union sigval si_value;

}siginfo_t;

union sigval

{

int sival_int;

void *sigval_ptr;

}

傳遞參數給信号處理函數的時候,sa_flags中的SA_SIGINFO要置位

示例:

2.4 發送信号時如何攜帶參數

使用kill函數無法發送攜帶額外資料的信号。linux提供了另外一個函數:sigqueue

int sigqueue(pid_t pid, int signo, const union sigval value)

傳回值:成功傳回0;出錯傳回-1.