Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1725755
  • 博文数量: 98
  • 博客积分: 667
  • 博客等级: 上士
  • 技术积分: 1631
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-27 15:59
个人简介

一沙一世界 一树一菩提

文章分类

全部博文(98)

文章存档

2021年(8)

2020年(16)

2019年(8)

2017年(1)

2016年(11)

2015年(17)

2014年(9)

2013年(4)

2012年(19)

2011年(1)

2009年(4)

分类: LINUX

2014-10-22 17:04:01

以前读apue的时候,和信号有关的函数好像都写过验证代码。但是没有记录下来,这几天写服务器代码,考虑到异步信号处理的细节才把信号这部分再拿出来看看,并且记录下来。
本文内容来源三个途径:apue,网络,自己的一些了解,有不对的地方,大家多指出。
信号时一种软件中断,用于处理异步事件,相对于主进程,它是异步的,所以要合理处理信号与主进程的协调关系。
一 信号有三种处理方式:
1 忽略,大多数信号可以采用这种方式进行处理,但是有两种信号不可以:SIGKILL和SIGSTOP。因为这2个信号向超级用户提供使进程终止或停止的可靠方法。另外,如果忽略由硬件异常产生的信号(如内存越界,非法操作内存或除以0),则进程的运行会进入不定态。
2 捕获,为了抓住这个信号,需要布置抓捕现场。这就是signal或者sigaction函数的作用。这2个函数告诉内核,当某个信号到来时,要执行一个用户函数,再用户函数里,可以执行和这个信号有关联的事件。这里SIGKILL和SIGSTOP不能抓捕,也就是说这两个信号只能执行默认动作。

3 默认动作,针对大多数信号的系统默认动作时终止进程。

二 信号与程序启动
     当执行一个程序时,所有信号的状态都是系统默认或忽略。新程序的执行,是fork函数创建新进程,新进程调用exec函数以执行另一个程序。当进程调用exec时,该进程执行的程序完全替换为新程序,新程序则从其main函数开始执行。调用exec并不创建新进程,所以调用exec前后的进程ID不变。exec只是用一个全新的程序替换了当前进程的正文,数据、堆和栈。所以exec执行后,即使原先设置为捕捉的信号也被修改为它们的默认动作。(对于一个进程原先要捕捉的信号,当执行新程序后,信号捕捉函数的地址在所执行的新程序文件当中已无意义,就自然不能捕捉它了)
     当进程调用fork时,其子进程继承父进程的信号处理方式,因为子进程在开始的时候复制了父进程的存储映象,所以信号捕捉函数地址在子进程当中是有意义的。

三 信号与可重入函数
在信号中断函数中使用不可重入函数可能导致不可预见的问题。具体例子不做罗列。不可重入函数大概分以下三类:
1 它们使用静态数据结构
2 它们调用malloc或free
3 它们是标准io函数,标准IO很多实现都使用全局数据结构

另外:
1 信号处理程序中使用printf函数,但是不能保证产生期望的结果,信号处理程序可能中断主程序当中的printf函数。
2 每个线程只有一个errno变量,所以信号处理程序可能会修改errno原先值。所以在信号处理程序中,应该先保存,信号处理函数执行完以前再回复errno。

四 alarm和pause函数
alarm函数设置一个计时器,在将来某个时间会产生SIGALARM信号。系统默认动作是终止调用该alarm函数的进程。
pause函数使调用进程挂起直至捕捉到一个信号。这里注意,只有执行了一个信号处理函数并返回时,pause在会返回。这种情况下,pause返回-1,并置errno为EINTR。下面是一段apue上的代码:
#include
#include

static void sig_alarm(int sig)
{;}
unsigned int sleep1(unsigned int nsecs)
{
    if(signal(SIGALRM, sig_alarm) == SIG_ERR)
        return (nsecs);
    alarm(nsecs);
    pause();
    return (alarm(0))
}
在调用alarm和调用pause之间有一个竞争条件。在一个繁忙的系统中,可能alarm在调用pause之前超时。(这里可以这样理解,在设置了alarm函数以后,程序转去执行一个优先级更高的软件模块,并且这个模块执行时间很长,以至于alarm到期了,这个模块还没有执行完毕。)并调用了信号处理函数。如果发生这种情况,则在调用pause以后,如果没有捕捉到其它信号,那么调用者将永远挂起


五 信号集的几个函数
   int  sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigaddset(sigset_t *set, ins signo);
    int sigdelset(sigset_t *set, int signo);
    int sigismember(const sigset_t *set, int signo);
前四个函数的返回值,成功返回0, 失败返回-1,最后一个函数的返回值,若真返回1,若假返回0,若出错返货-1。
所有应用程序使用信号集以前,必须要对该信号集调用sigemptyset或者sigfillset函数进行初始化。
    int sigprocmask(int how, const sigset_t *restrict  set, sigset_t *restrict  oset);成功返回0,失败返回-1,这个函数里的restrict是指两个参数没有相关性,可以放心大胆的操作,别那么保守,同样的代码,使用这个限制参数和不使用这个参数,编译出来的执行速度差距很大的。
在调用sigprocmask后,如果有任何未决的,不再阻塞的信号,则在setprocmask返回前,至少会将其中一个信号递送给进程。
还有一点,必须强调一下,siprocmask是仅为单线程的进程定义的,在处理多线程的进程当中的信号屏蔽,使用的是另外的函数。
    sigpending(sigset_t *set);成功返回0,失败返回-1.这个函数用于检查信号,检查什么信号呢,检查当前已经发出的,但是被阻塞不能递送的信号,也就是当前未决信号。下面是一个具体运用,这个例子是apue上的例子,我加以说明:
#include
#include
#include
static void sig_quit(int);

int main(void)
{
sigset_t newmask, oldmask, pendmask;
if(signal(SIGQUIT, sig_quit) == SIG_ERR)
{
printf("can not catch SIGQUIT");
exit(1);
}
sigemptyset(&newmask);//使用前先设置sigset_t结构
sigaddset(&newmask, SIGQUIT);
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)//设置阻塞信号
{
printf("sig_block error");
exit(1);
}
sleep(5);//在5秒内按下键盘上“ctrl” + “\”,会产生SIGQUIT信号,这时候这个信号就是已经发出,但是被阻塞,未决的信号
if(sigpending(&pendmask) < 0)
{
printf("sigpending error");
exit(1);
}
if(sigismember(&pendmask, SIGQUIT))
{
printf("\nSIGQUIT pending\n");//这里会打印信息,说明信号未决
}

if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)//回复默认信号动作,执行这个程序的时候,会发现先打印信号处理函数中的内容,再打印下面的内容,从而证明了上面对sigprocmask函数的说明。
{
printf("SIG_SETMASK error");
exit(1);
}
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("can not reset sigquit\n");
exit(1);
}
}
六 sigsuspend函数
更改信号屏蔽字可以阻塞所选信号,或解除对它们的阻塞。使用这种技术可以保护部希望由信号中断的代码区域。
如果希望对一个信号解决阻塞,然后pause以等待以前被阻塞的信号,如何实现呢?
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
    exit(1);
/* critical region of code*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
    exit(1);

pause();
..........
首先说明,这是个不理想的做法。如果信号发生在阻塞时,等解除阻塞时执行信号处理函数以后,pause返回,然后执行后面的程序。
pause函数:pause函数使调用进程挂起直至捕捉到一个信号,只有执行了一个信号处理函数并从其返回时,pause才返回。
如果在解除阻塞时刻和pause之间发生了信号,就丢失了。这样pause就一直挂在那等信号,这就是早期不可靠信号的机制的一个问题。
为了修正上面问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠,这个功能就是由sigsuspend函数提供的。
int  sigsuspend(const sigset_t *sigmask);
这个函数在一个不可中断的过程当中执行下面过程:
将进程的信号屏蔽字设置为sigmask指向的值,然后挂起,等待特定的信号,然后捕捉到一个信号并且从信号处理程序返回,则sigsuspend返回,
并且将给进程的信号屏蔽字设置为调用sigsuspend之前的值。
这个函数没有成功返回值,它总是返回-1,并置errno为EINTR。下面是几个sigsuspend应用实例:
几个实例等下期再写吧,今天下班。
1  保护临界区不被信号中断



阅读(3086) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~