歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

linux系統編程之信號(三) 信號的阻塞與未決

一、信號在內核中的表示

實際執行信號的處理動作稱為信號遞達(Delivery),信號從產生到遞達之間的狀態,稱為信號未決(Pending)。進程可以選擇阻塞(Block)某個信號。被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。注意,阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。信號在內核中的表示可以看作是這樣的:

每個信號都有兩個標志位分別表示阻塞和未決,還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標志,直到信號遞達才清除該標志。在上圖的例子中,

1. SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動作。

2. SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,

因為進程仍有機會改變處理動作之後再解除阻塞。

3. SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函數sighandler。

未決和阻塞標志可以用相同的數據類型sigset_t來存儲,sigset_t稱為信號集,這個類型可以表示每個信號的“有效”或“無效”狀態,,在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處於未決狀態。阻塞信號集也叫做當前進程的信號屏蔽字(Signal Mask),這裡的“屏蔽”應該理解為阻塞而不是忽略。

二、信號集處理函數

sigset_t類型(64bit)對於每種信號用一個bit表示“有效”或“無效”狀態,至於這個類型內部如何存儲這些bit則依賴於系統實現,從使用者的角度是不必關心的,使用者只能調用以下函數來操作sigset_t變量,而不應該對它的內部數據做任何解釋,比如用printf直接打印sigset_t變量是沒有意義的。

#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

int sigismember(const sigset_t *set, int signo);

函數sigemptyset初始化set所指向的信號集,使其中所有信號的對應bit清零,表示該信號集不包含任何有效信號。函數sigfillset初始化set所指向的信號集,使其中所有信號的對應bit置位,表示該信號集的有效信號包括系統支持的所有信號。注意,在使用sigset_t類型的變量之前,一定要調用sigemptyset或sigfillset做初始化,使信號集處於確定的狀態。初始化sigset_t變量之後就可以在調用sigaddset和sigdelset在該信號集中添加或刪除某種有效信號。這四個函數都是成功返回0,出錯返回-1。sigismember是一個布爾函數,用於判斷一個信號集的有效信號中是否包含某種信號,若包含則返回1,不包含則返回0,出錯返回-1。

三、sigprocmask 和 sigpending 函數

1、調用函數sigprocmask可以讀取或更改進程的信號屏蔽字。

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

返回值:若成功則為0,若出錯則為-1

如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是非空指針,則更改進程的信號屏蔽字,參數how指示如何更改。如果oset和set都是非空指針,則先將原來的信號屏蔽字備份到oset裡,然後根據set和how參數更改信號屏蔽字。假設當前的信號屏蔽字為mask,下表說明了how參數的可選值。

2、sigpending讀取當前進程的未決信號集,通過set參數傳出。調用成功則返回0,出錯則返回-1。

#include <signal.h>

int sigpending(sigset_t *set);

示例程序:

/*************************************************************************
> 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);
void printsigset(sigset_t *set)
{
int i;
for (i = 1; i < NSIG; i++)
{
if (sigismember(set, i))
putchar('1');
else
putchar('0');
}
printf("\n");
}
int flag = 0;
int main(int argc, char *argv[])
{
if (signal(SIGINT, handler) == SIG_ERR)
ERR_EXIT("signal error");
if (signal(SIGQUIT, handler) == SIG_ERR)
ERR_EXIT("signal error");
sigset_t pset; // 64bit
sigset_t bset;
sigemptyset(&bset);
sigaddset(&bset, SIGINT);
sigprocmask(SIG_BLOCK, &bset, NULL);
for (; ;)
{
sigpending(&pset);
printsigset(&pset);
sleep(1);
if (flag == 1)
sigprocmask(SIG_UNBLOCK, &bset, NULL);
}
return 0;
}
void handler(int sig)
{
if (sig == SIGINT)
printf("recv a sig=%d\n", sig);
else if (sig == SIGQUIT)
{
printf("rev a sig=%d\n", sig);
sigset_t uset;
sigemptyset(&uset);
sigaddset(&uset, SIGINT);
sigprocmask(SIG_UNBLOCK, &uset, NULL);
flag = 1;
}
}

如果將程序中的37,57,58,75關於flag變量的語句注釋掉,則輸出如下:

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

0000000000000000000000000000000000000000000000000000000000000000

0000000000000000000000000000000000000000000000000000000000000000

...................................................................................

^C0100000000000000000000000000000000000000000000000000000000000000

0100000000000000000000000000000000000000000000000000000000000000

...................................................................................................................................................

^\rev a sig=3

recv a sig=2

0000000000000000000000000000000000000000000000000000000000000000

0000000000000000000000000000000000000000000000000000000000000000

............................................................................................................................................

^C0100000000000000000000000000000000000000000000000000000000000000

0100000000000000000000000000000000000000000000000000000000000000

...........................................................................................................................................

在程序的一開始將SIGINT信號添加進阻塞信號集(即信號屏蔽字),死循環中一直在打印進程的信號未決集,當我們按下ctrl+c,因為信號被阻塞,故處於未決狀態,所以輸出的第二位為1(SIGINT是2號信號),接著當我們按下ctrl+\,即發送SIGQUIT信號,我們在handler中解除了對SIGINT的阻塞,故2號信號被遞達,打印兩行recv語句,此時信號未決集又變成全0。比較讓人疑惑的是我們貌似已經解除了對SIGINT的屏蔽,但當我們再次ctrl+c 時,信號還是處於未決狀態。後來我寫了個測試程序,發現解除阻塞時只是將未決標志pending位清0,而block位一直為1,但還是覺得很不解,難道一個進程運行期間只要阻塞了一個信號,只能每次靠清除pending位讓其遞達,即治標不治本?後來覺得會不會是因為在handler裡進行解除才會這樣呢?於是設置了一個標志位flag,即把前面說的4行代碼補上,則前面的輸出是一樣的,但在主函數中再次解除阻塞後,按下ctrl+c,讓人驚喜的是2號信號順利遞達,如下:

^Crecv a sig=2

0000000000000000000000000000000000000000000000000000000000000000

0000000000000000000000000000000000000000000000000000000000000000

...........................................................................................................................................

我查遍了sigprocmask 的 man手冊,也沒發現說明這一點,但實際測試是這樣的,即如果在信號處理函數中對某個信號進行解除阻塞時,則只是將pending位清0,讓此信號遞達一次,但不會將block位清0,即再次產生此信號時還是會被阻塞,處於未決狀態。

現在使用ctrl+c , ctrl+\ 都終止不了程序了,可以另開個終端kill -9 pid 殺死進程。

Copyright © Linux教程網 All Rights Reserved