信号的相关函数

来源:互联网 发布:最优化方法施光燕答案 编辑:程序博客网 时间:2024/04/28 05:52

本文 是一篇学习文章,学习的《unix高级环境编程》

1、信号集,它的处理跟5个函数紧密相关。

  信号集是一个能表示多个信号的数据类型,sigset_t set ;set即一个信号集。

  既然是一个集合,就需要对集合进行添加/删除等操作。

  int sigemptyset(sigset_t *set); 将set集合置空

  int sigfillset(sigset_t *set); 将所有信号加入set集合

  int sigaddset(sigset_t *set,int signo); 将signo信号加入到set集合

  int sigdelset(sigset_t *set,int signo); 从set集合中移除signo信号

  int sigismember(const sigset_t *set,int signo); signo判断信号是否存在于set集合中。

最简单的应用就是下面的一段代码:

#include <stdio.h>

#include <signal.h>

void main()

{

sigset_t set;

sigemptyset(&set);

sigaddset(&set,SIGINT);

if(sigismember(&set,SIGINT))

printf("yes\n");

if(sigismember(&set,SIGTSTP))

printf("yes too\n");

sigdelset(&set,SIGINT);

sigfillset(&set);

if(sigismember(&set,SIGINT))

printf("yes\n");

if(sigismember(&set,SIGTSTP))

printf("yes too\n");

sigemptyset(&set);

if(sigismember(&set,SIGINT))

printf("yes\n");

if(sigismember(&set,SIGTSTP))

printf("yes too\n");

}

输出:

运行的次数可见一般。
好了。这几个函数一般呢不会这么用,我这里做一个测试只是让你知道该怎么用。他们跟后面要说的函数一起使用,威力才够大。不然,就这些,还远远不够。
注意,我们传送的参数都是信号地址集

2、sigprocmask函数(该函数,仅仅为单线程定义,多线程可有另外的函数。)
原型:
#include<signal.h>
int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oset)
整个函数可以检测或更改其信号屏蔽字,或者同时执行这两个操作。
我来解释一下这个函数。
第一个参数,如果set非空,如果不为how不为0,表示怎么修改当前信号屏蔽字。有3中方式:
SIG_BLOCK
该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我
们希望阻塞的附加信号。
SIG_UNBLOCK
该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的补集的交集。set包含了
我们希望解除阻塞的信号.
SIG_SETMASK
该进程新的信号屏蔽是set指向的值

若oset非空,那么进程的当前信号屏蔽字通过oset返回。
比如,典型的用法:
sigset_t oldmask,newmask;
sigemptyset(&newmask);
//添加一个信号,
sigaddset(&newmask,SIGQUIT);
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)
    .....
sigprocmask(SIG_SETMASK,&oldmask,NULL);
上面的解释:我们将进程的原先的屏蔽集放在oldmask中,在newmask中我们添加了一些新的信号,我们想屏蔽这些新的信号。然后调用sigprocmask,将newmask和oldmask的并集改成现在进程的屏蔽字。
然后,我们可以使用后面的将屏蔽字 修改成 原先的值。一般的使用法则。

3、sigpending函数
#include<signal.h>
int sigpending(sigset_t *set);
成功返回0,失败返回-1。set带回来的是未决信号集。
这个根据书上的例子是最好的吧。
#include <signal.h>
#include <stdio.h>
static void sig_quit(int);

void main()
{
sigset_t newmask,oldmask,pendmask;
if(signal(SIGQUIT,sig_quit) == SIG_ERR)
printf("signal error\n");

sigemptyset(&newmask);
sigaddset(&newmask,SIGQUIT);
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)
printf("SIG_BLOCK error\n");

sleep(5);

if(sigpending(&pendmask) < 0)
printf("sigpend error\n");
if(sigismember(&pendmask,SIGQUIT))
printf("SIGWQUIT pending\n");

if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)
printf("SIG_SETMASK error\n");
printf("sigquit unblocked\n");

sleep(5);
exit(0);
}

static void sig_quit(int signo)
{
printf("caught SIGQUIT\n");
if(signal(SIGQUIT,SIG_DFL) == SIG_ERR)
printf("cannot caught SIGQUIT\n");
}
运行结果:
如果我什么都不做,

因为没有未决信号产生,所以,什么都没有做。
如果在sleep中按下ctrl+\。也就是产生sigquit信号,那么结果如下:

我在开始的sleep狂按3次。但是没有反应。说明信号已经被阻塞了。过了5s,发现pendmask有内容了。说明在开始的狂按的信号产生了未决信号,信号产生了但是没有递交。但是在信号解除还原之后,立马信号就被提交了。未决信号得到了处理。于是会运行sig_quit函数。之后,在结束前sleep,我们按下ctrl+\的话,直接退出了,从上图可有看见(因为在处理函数我们又恢复默认)。还有,这也说明,信号没有排队。我狂按了3次,但是只产生了一次信号处理函数。
当然我必须扩展一下,或许也不算扩展,只是我自己不知道罢了。
static void sig_quit(int signo)
{
printf("caught SIGQUIT\n");
if(signal(SIGQUIT,SIG_DFL) == SIG_ERR)
printf("cannot caught SIGQUIT\n");
}
改成
static void sig_quit(int signo)
{
printf("caught SIGQUIT\n");
}
然后运行:

上面,我在第二次sleep的时候,我按下了ctrl+\。结果,sleep直接结束了,输出了处理函数。我换成了另外的SIGTSTP这个信号,也是这样。同时我在第二个sleep之后再加上了一个sleep。

输入两次ctrl+\,也直接退出,没有默认的quit了。
总结:只要我输入了一个信号,肯定会将sleep函数打断,从sleep函数退出。

4、sigaction函数
这个函数呢,在之前应该已经了解了这么多了。sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作)。他是POSIX的信号接口,而signal()是标准C的信号接口(如果程序必须在非POSIX系统上运行,那么就应该使用这个接口)
表头文件 #include<signal.h>
定义函数 int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
又是3个参数,有必要来一一解释一下。
第一个参数是要检测或修改其动作的信号编号。若act非空,就是要修改其动作。若oldact非空,则系统经由oldact指针返回该信号的上一个动作。
如参数结构sigaction定义如下
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
当更改动作的时候,如果sa_handler字段包含一个信号捕捉函数的地址,则sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号要加入到进程的信号屏蔽字当中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先的值。这样,在调用信号处理程序的时候就能阻塞某些信号。在信号处理程序被调用的时候,操作系统建立的新信号屏蔽字包括正在被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么他会被阻塞到对前一个信号的处理结束为止。
一旦给信号设置了一个动作,在调用sigaction函数显示改变它之前,该设置一直有效。
signal函数实现就是根据sigaction的。
一般来说,如果我们设置sa_handler为处理函数,然后,将信号量设置成signal里面的,那个,其余的默认都可以模拟signal。

5、sigsuspend 函数
#include<signal.h>
int sigsuspend(const sigset_t *sigmask)
将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号返回了,则sigsuspend返回。并且将信号的屏蔽字设置成sigsuspend之前的值。
也就是说,sigsuspend后,进程就挂在那里,等待着开放的信号的唤醒。系统在接收到信号后,马上就把现在的信号集还原为原来的,然后调用处理函数。
该函数没有成功返回值,如果它返回到调用者,总是-1,errno设置成EINTR(错误的情况)。
注意这个函数是一个原子操作
或许你还没有明白,那么就按照书上的例子,我修改修改来说明问题。
#include <signal.h>
#include <stdio.h>

static void sig_int(int);

void main()
{
sigset_t newmask,oldmask,waitmask;
printf("program start:\n");
if(signal(SIGINT,sig_int) == SIG_ERR)
printf("signal error\n");
    //在waitmask中,我们设置了阻塞SIGTSTP(ctrl+Z)
sigemptyset(&waitmask);
sigaddset(&waitmask,SIGTSTP);
     //在newmask我们阻塞了SIGINT
sigemptyset(&newmask);
sigaddset(&newmask,SIGINT);

if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)
printf("sigprocmask error\n");

printf("in critical region\n");
printf("sleep 5 seconds\n");
sleep(5);//第一个5秒
printf("start sigsupend:\n");
if(sigsuspend(&waitmask) != -1)
printf("sigsupend error\n");

printf("after return from sigsupend\n");
if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)
printf("sigSetmask error\n");
printf("change to old and sleep 5 seconds\n");
sleep(5);//第二个5秒
printf("exit\n");
exit(0);
}

static void sig_int(int signo)
{
printf("sig_int :\n");
}
运行结果可以分析一下。(设置5秒的作用是好等待我们输入)在第一个5秒期间,我们如果输入crtl+C,因为阻塞了。也就是一直都是未决信号。然后5秒之后,进入sigsuspend,但是会立马被唤醒,因为之前输入的crtl+c信号来了。然后进入第二个5秒。在这个期间,我们已经恢复了原来的屏蔽字。5秒之后结束。若5秒输入任何信号都会打断sleep,提前结束。
好了,若在第一个5秒,我们没有输入任何信号,然后进程会在start sissupend之后,一直挂起了。等待用户发送信号。如果,我们输入CTRL+Z,是没有任何反应的。因为waitmask屏蔽了这个信号,但是这个信号肯定是产生了,只是被阻塞了。也就是停留在未决信号那个阶段。然后一旦我们输入其他没有屏蔽的信号,sigsuspend返回,第二个5秒会被立马退出,因为在sigsuspend期间产生的信号ctrl+z将提前将sleep结束。
运行结果图:

等待用户输入。我们输入ctrl+Z。肯定没有反应。输入其他信号。ctrl+C

我们来看看ps吧

说明信号传递了。进程挂起了。
注意,ctrl+c只是让sigsuspend返回了,没有影响后面的。
还有一点,sigsuspend函数返回之后,信号屏蔽字恢复成了newmask。这一点我们可以自己可以将
if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)
printf("sigSetmask error\n");
取消,在第二个5秒输入CTRL+C,,发现没有反应。说明被屏蔽了。
好了,根据这个例子,我想,基本上了解了这个函数了。

这个函数是原子操作,所以可以用来保护临界区,不被特定的信号中断。另外一个应用就是等待一个信号处理程序设置一个全局变量。书上例子这里不多说了。不过书上的实现父子进程同步的函数可以好好看看。



 

0 0