六、信号未决(pending)和信号屏蔽字(signal mask)
当产生信号,并将信号传递给进程,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称为信号是未决的。进程可以选用信号递送阻塞。如果为进程产生了一个选择为阻塞的信号,则改进程将此信号保持为未决状态,直到该进程解除对此信号的阻塞或者将此信号的动作更改为忽略。内核在递送一个被阻塞的信号给进程时(非产生信号时),才决定对他的处理方式。于是进程在处理他之前都可以改变对该信号的动作。
如果进程在解除对某个信号的阻塞之前,这种信号发生了多次,会怎么样?答案是:如果系统允许递送多次的,将对这些信号进行排队,如果系统不支持递送多次的,那么内核将只递送这种信号一次。
如果进程在阻塞一个信号时产生了其他不阻塞的信号,又会怎么样?答案是:阻塞的信号在解除之前永远不能递送,不阻塞的信号会被正常递送并处理,两者互不影响。
每一个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。对于每一种信号,该屏蔽字都有一位与之对应。对于某种信号,若其对应位已经设置,则他当前是被阻塞的。信号数量可能会超过整型所包含的二进制数(32个),因此posix定义了一个新数据类型sigset_t,用于保存一个信号集。
以上都会在后续中详细解说相关函数.
七、信号发送相关函数
1、kill和raise函数:
kill函数将信号发送给进程或者进程组。raise函数则允许向进程本身发送信号。
#include <signal.h>
int kill(pid_t pid,int signo);
int raise(int signo); //两个函数的返回值:若成功返回0,出错返回-1
调用raise(signo);等价于调用kill(getpid(),signo);
kill函数的pid参数有四种不同的情况:
1、pid > 0 将信号发送给进程ID为pid的进程
2、pid == 0 将信号发送给与发送进程属于同一进程组的所有进程。而且发送进程具有给这些进程发送信号的权限。
3、pid < 0 将信号发送给其进程组ID等于pid的绝对值,而且发送进程具有给这些进程发送信号的权限。
4、pid == -1 将信号发送给发送进程有权限向他们发送信号的系统上的所有进程。
posix将编号为0的信号定义为空信号。由于在kill函数执行时会对权限等进行测试。如果signo参数是0,那么kill仍然进行正常的错误检查,但不发送信号。这常常被用来确定一个特定进程是否仍旧存在。如果向一个并不存在的进程发送空信号,kill会返回-1,并将errno设置为ESRCH,不过由于这种测试不是原子操作,返回后可能该进程已经更改正太,所以并不完全具备价值。kill函数产生的信号在kill返回之前就会被递送。
2、alarm和pause函数:
alarm函数可以设置一个计时器,在将来某个指定的时间该计时器会超时。此时会产生SIGALRM信号。其默认动作是终止调用进程
#include <unistd.h>
unsigned int alarm(unsigned int seconds);//参数seconds是计时器秒数。返回值:0或者以前设置的闹钟的余留秒数
每一个进程只能有一个闹钟时钟,如果在调用alarm时,此前已经设置了闹钟,而且该闹钟还没有超时,则将此前闹钟的剩余秒数当做返回值返回,以前的闹钟会被新的闹钟重新替换。
如果此前的闹钟已经设置而且并未超时,而且本次调用alarm时参数seconds是0,则取消以前的闹钟,剩余秒数仍然返回,注意此时不会产生SIGALRM信号。
#include <unistd.h>
int pause();//返回值:-1.并将errno设置为EINTR
pause函数使调用进程挂起直到捕捉一个信号。注意,只有执行了一个信号处理程序并从其返回,pause才会返回,并非产生一个信号就返回,一个信号被阻塞了pause是不会返回的。
八、信号集
我们在六中谈过了信号集,并且知道了有个sigset_t数据类型表示一个信号集。相关的函数如下:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set);
int sigdelset(sigset_t *set); //以上四个函数返回值:成功返回0,出错返回-1
int sigismember(const sigset_t *set,int signo); //返回值:若真返回1,若假返回0,出错返回-1
sigemptyset函数是初始化set指向的信号集,清除所有信号位,即所有信号不阻塞;
sigfillset函数也是初始化set指向的信号集,设置所有信号位,即阻塞所有信号。应用程序在使用信号集前,都要用sigemptyset或则sigfillset初始化一次。
sigaddset和sigdelset函数是添加或则删除(解除)某个信号进入信号屏蔽字。
sigismember是测试某个信号集里面的signo信号是否被设置了。
注意以上的函数都是设置信号集这个数据结构的对应位,并还未设置进程对这些信号的处理动作,真正的设置处理动作在下面的函数中
#include <signal.h>
int sigprocmask(int how,const siget_t *set,sigset_t *oset);//成功返回0,出错返回-1
该函数功能是进程设置关于自己信号集的信号屏蔽字
set和oset都可以为NULL;
当oset为非空时,进程此前的信号屏蔽字通过oset返回;
当set为非空时,进程使用how指示如何修改当前的信号屏蔽字;
how的值有三种情况:
1、SIG_BLOCK 该进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集的并集(按位或)
2、SIG_UNBLOCK 该进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集补集的交集;
3、SIG_SETMASK 该进程新的信号屏蔽字将被set指向的信号集的值替代。
如果set是空,则how的值也无意义,通常被用来获取当前的信号屏蔽字,不过有专门的函数提供。
sigprocmask后如果有未决的,不再阻塞的信号,会在该函数赶回前被递送至该进程。
#include <signal.h>
int sigpending(sigset_t *set);//成功返回0,出错返回-1
该函数通过set返回进程当前的信号集。
例子:
#inlcude <stdio.h>
#include <signal.h>
static void sig_quit(int signo)
{
printf("caught sigquit\n");
if(signal(SIGOUT,SIG_DFL) == SIG_ERR)
printf("can not reset sigquit\n");
}
int main()
{
sigset_t newmask,oldmask,pendmask;
if(signal(SIGQUIT,sig_quit) == SIG_ERR)
printf("can not set handle func\n");
sigemptyset(&newmask);
sigaddset(&newmask,SIGQUIT);
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)
printf("set sigquit block err\n");
sleep(5);
if(sigpending(&pendmask) < 0)
printf("pending err\n");
if(sigismember(&pendmask,SIGQUIT))
printf("sigquit block\n");
if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)
printf("set sigquit unblock err\n");
printf("sigquit unblock\n");
sleep(1);
exit(0);
}
以上小程序和证明:1、阻塞信号在未决期间不管产生多少个都会被阻塞;
2、非阻塞信号可以正常递送至进程,互不影响;
3、阻塞信号在未决期间产生多个,在解除阻塞时之后递送一次。