码迷,mamicode.com
首页 > 其他好文 > 详细

信号(signal)

时间:2016-08-02 06:42:07      阅读:280      评论:0      收藏:0      [点我收藏+]

标签:信号阻塞   信号捕捉 suspend

一 信号的基本概念

信号机制是进程间相互传递消息的一种方法,信号全称软中断信号,也有人称作软中断,从它的命名可以看出,它的使用很像中断,所以,信号是进程控制的一部分。

(1)进程之间可以通过系统调用kill发送软中断信号

(2)内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件

注:信号指示通知给进程发生了什么事,并不给进程传递数据。

为了理解信号,我们从熟悉的场景说起

  1. 用户输入指令,在shell下启动一个前台进程。

  2. 用户按下Ctrl+C,此时硬盘驱动产生一个中断给Linux内核。

  3. 如果cpu当前正在执行这个进程的代码,则该进程的用户空间暂停执行,CPU从用户态切换到内核态处理硬件中断。

  4. 终端驱动程序将Ctrl+C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送一个SIGINT信号给该进程)

  5. 当某个信号从内核返回到该用户空间代码继续执行之前,首先处理PCB中(也可以说发送一个SIGINT信号给该进程)

    kill  -l 命令可查看系统定义的信号列表

    技术分享

信号产生的条件主要有:

1.用户在终端下按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl+C产生SIGINT信号,Ctrl+\产生SIGQUIT信号,Ctrl+Z产生SIGSTOP信号(可使前台进程终止)

2.硬件异常产生信号,这些条件由硬件检测并通知内核,然后内核向当前进程发送适当的信号,例如当前进程执行了除以0的指令,CPU运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给该进程,再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给该进程。

3.一个进程调用kill(2)函数可以发送信号给另一个进程,可以用kill(1)給某个进程,kill(1)也是调用kill(2)函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号默认处理动作是终止进程。当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。如果不想按默认动作处理信号,用户进程可以调用sigaction(2)函数告诉进程应该如何处理某种信号。

可选的信号处理有一下三个动作:

  1. 忽略此信号

  2. 执行信号的默认处理动作

  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号。

产生信号的方式:

  1. 通过终端键产生信号(Ctrl+C等)

  2. 调用系统函数向进程发信号

  3. 由软件条件产生信号(如闹钟alarm函数)

  4. unsigned int alarm(unsigned int seconds);

    调用alarm函数可以设定一个闹钟,也就是告诉内核在second秒之后给当前进程发送SIGALARM信号,该信号默认处理动作是终止当前进程,这个函数返回值是0或者是以前闹钟时间还余下的秒数。

二 阻塞信号

信号在内核中的表示

    信号产生有各种原因,而实际信号的处理动作称为信号的递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞才执行递达动作,注意:阻塞和忽略是不同的,只要信号被阻塞就不会被递达,而忽略是递达之后可选的一种处理动作。

技术分享

每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作,信号产生时,内核在进程控制块中设置信号未决标志,直到信号递达才处理这个标志。

  1. SIGHUP信号未阻塞也未产生过,当它递达是才默认处理动作。

  2. SIGINT信号产生过,但正在被阻塞,所以暂时不能递达,虽然它的处理动作是忽略的,但没有接触阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

  3. SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

如果进程在解除对某信号的阻塞之前这种信号产生过多次,将如何处理?

允许系统递送信号一次或多次,Linux是这样实现的:常规信号在递送之前产生只计一次,而实时信号在递送之前产生多次可依次放在队列里。
从上图看来,每个信号只有一个bit的未决标志,非0即1,不记录信号产生了多少次,阻塞标志也是这样表示的。因此未决和阻塞标志可用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号“有效”或“无效”状态。信号阻塞集也叫作当前进程的信号屏蔽字(Signal Mask).

#include<stdio.h>
#include<signal.h>
void catch()
{}

int my_sleep(int timeout)
{
    signal(SIGALRM,catch);
    alarm(timeout);
    pause();
    int ret = alarm(0);
    signal(SIGALRM,SIG_DFL);
    return ret;

}
int main()
{
    while(1)
    {
        printf("testing...\n");
        my_sleep(1);
    }
    return 0;
}

技术分享设置一个闹钟,使得每一秒输出一次。

现在重新审视“mysleep”程序,设想这样的时序:
1. 注册SIGALRM信号的处理函数。
2. 调用alarm(nsecs)设定闹钟。
3. 内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个
都要 执行很长时间
4. nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。
5. 优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处
理函 数sig_alrm之后再次进入内核。
6. 返回这个进程的主控制流程,alarm(nsecs)返回,调用pause()挂起等待。

7. 可是SIGALRM信号已经处理完了,还等待什么呢?
出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。
虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用
alarm(nsecs)之 后的nsecs秒之内被调用。由于异步事件在任何时候都有可能发生(这里
的异步事件指出现更高优 先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题
而导致错误,这叫做竞态条件 (Race Condition)。
如何解决上述问题呢?读者可能会想到,在调用pause之前屏蔽SIGALRM信号使它不能提前递
达就可 以了。看看以下方法可行吗?

  1. 屏蔽SIGALRM信号;
    2. alarm(nsecs);
    3. 解除对SIGALRM信号的屏蔽;
    4. pause();

  2. 从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要消除这
    个间隙, 我们把解除屏蔽移到pause后面可以吗?
    1. 屏蔽SIGALRM信号;
    2. alarm(nsecs);
    3. pause();
    4. 解除对SIGALRM信号的屏蔽;
    这样更不行了,还没有解除屏蔽就调用pause,pause根本不可能等到SIGALRM信号。要是
    “解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend
    函数的功 能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对
    时序要求严格的场合下都应该调用sigsuspend而不是pause。


三 信号捕捉

测试31种信号哪种可以捕捉?哪种不能捕捉?

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:

1.

信号(signal)

标签:信号阻塞   信号捕捉 suspend

原文地址:http://10798301.blog.51cto.com/10788301/1833279

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!