进程对信号的响应

进程可以通过三种方式来响应一个信号:

1、忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;

2、捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;

3、执行缺省操作,Linux对每种信号都规定了默认操作。注意,进程对实时信号的缺省反应是进程终止。

Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。

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

用户程序注册了SIGQUIT信号的处理函数sighandler。

当前正在执行main函数,这时发生中断或异常切换到内核态。

在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。

内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。

sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。

如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

1、 sigaction

#include <signal.h>

int sigaction(int signo, const structsigaction *act, struct sigaction *oact);

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回-1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:

struct sigaction {
void (*sa_handler)(int); /* addr of signal handler, */
/* orSIG_IGN, or SIG_DFL */
sigset_t sa_mask; /*additional signals to block */
int sa_flags; /* signal options, Figure 10.16*/ /*alternate handler */
void (*sa_sigaction)(int,siginfo_t *, void *);
};

将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,本章不详细解释这两个字段,有兴趣的读者参考[APUE2e]。

2、pause

#include <unistd.h>

int pause(void);

pause函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR,所以pause只有出错的返回值(想想以前还学过什么函数只有出错返回值?)。错误码EINTR表示“被信号中断”。

下面我们用alarm和pause实现sleep(3)函数,称为mysleep。

#include <unistd.h>
#include <signal.h>
#include <stdio.h> void sig_alrm(int signo)
{
/*nothing to do */
} unsigned int mysleep(unsigned int nsecs)
{
structsigaction newact, oldact;
unsignedint unslept; newact.sa_handler= sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags= 0;
sigaction(SIGALRM,&newact, &oldact); alarm(nsecs);
pause(); unslept= alarm(0);
sigaction(SIGALRM,&oldact, NULL); returnunslept;
} int main(void)
{
while(1){
mysleep(2);
printf("Twoseconds passed\n");
}
return0;
}

1、 main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。

2、调用alarm(nsecs)设定闹钟。

3、调用pause等待,内核切换到别的进程运行。

4、nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。

5、从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。

6、切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽,从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。

7、pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。

3、可重入函数与不可重入函数

 

当捕捉到信号时,不论进程的主控制流程当前执行到哪儿,都会先跳到信号处理函数中执行,从信号处理函数返回后再继续执行主控制流程。信号处理函数是一个单独的控制流程,因为它和主控制流程是异步的,二者不存在调用和被调用的关系,并且使用不同的堆栈空间。引入了信号处理函数使得一个进程具有多个控制流程,如果这些控制流程访问相同的全局资源(全局变量、硬件资源等),就有可能出现冲突。

如果一个函数符合以下条件之一则是不可重入的:

1、调用了malloc或free,因为malloc也是用全局链表来管理堆的。

2、调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

4、竞态条件与sigsuspend函数

现在重新审视上面的例子“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();

从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要消除这个间隙,我们把解除屏蔽移到pause后面可以吗?

1、屏蔽SIGALRM信号;

2、alarm(nsecs);

3、pause();

4、解除对SIGALRM信号的屏蔽;

这样更不行了,还没有解除屏蔽就调用pause,pause根本不可能等到SIGALRM信号。要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend函数的功能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

#include <signal.h>

int sigsuspend(const sigset_t *sigmask);

和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。

调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

以下用sigsuspend重新实现mysleep函数:

unsigned int mysleep(unsigned int nsecs)
{
structsigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsignedint unslept; /*set our handler, save previous information */
newact.sa_handler= sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags= 0;
sigaction(SIGALRM,&newact, &oldact); /*block SIGALRM and save current signal mask */
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask, &oldmask); alarm(nsecs); suspmask= oldmask;
sigdelset(&suspmask,SIGALRM); /* make sure SIGALRM isn'tblocked */
sigsuspend(&suspmask); /* wait for any signal to be caught*/ /*some signal has been caught, SIGALRM isnow blocked */ unslept= alarm(0);
sigaction(SIGALRM,&oldact, NULL); /* reset previousaction */ /*reset signal mask, which unblocks SIGALRM */
sigprocmask(SIG_SETMASK,&oldmask, NULL);
return(unslept);
}

1、如果在调用mysleep函数时SIGALRM信号没有屏蔽:

2、调用sigprocmask(SIG_BLOCK,&newmask, &oldmask);时屏蔽SIGALRM。

3、调用sigsuspend(&suspmask);时解除对SIGALRM的屏蔽,然后挂起等待待。

4、SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。

5、调用sigprocmask(SIG_SETMASK,&oldmask, NULL);时再次解除对SIGALRM的屏蔽。

Linux系统编程(22)——响应信号的更多相关文章

  1. Linux系统编程——进程间通信:信号中断处理

    什么是信号? 信号是 Linux 进程间通信的最古老的方式.信号是url=474nN303T2Oe2ehYZjkrggeXCaJPDSrmM5Unoh4TTuty4wSgS0nl4-vl43AGMFb ...

  2. Linux系统编程之----》信号

    "===信号========================================================================================= ...

  3. [linux] C语言Linux系统编程-捕获进程信号

    typedef void( *sighandler_t)(int); 1.用typedef给类型起一个别名. 2.为函数指针类型定义别名, 3.函数指针(指向函数的指针) sighandler_t s ...

  4. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

  5. linux系统编程之信号(七)

    今天继续学习信号,主要是学习关于时间和定时器相关的函数的使用,关于这个实际上有很多内容,这里先简要进行说明,等之后再慢慢进行相关深入,也主要是为接下来要做的一个综合linux系统编程的例子做准备,好了 ...

  6. LINUX系统编程 由REDIS的持久化机制联想到的子进程退出的相关问题

    19:22:01 2014-08-27 引言: 以前对wait waitpid 以及exit这几个函数只是大致上了解,但是看REDIS的AOF和RDB 2种持久化时 均要处理子进程运行完成退出和父进程 ...

  7. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  8. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  9. linux系统编程(一)概述

    glibc库封装了linux系统调用,并提供c语言接口 所以学习linux系统编程,主要参考glibc库系统调用相关api 一.进程控制: fork 创建一个新进程 clone 按指定条件创建子进程 ...

  10. Linux 系统编程 学习:00-有关概念

    Linux 系统编程 学习:00-有关概念 背景 系统编程其实就是利用系统中被支持的调度API进行开发的一个过程. 从这一讲开始,我们来介绍有关Linux 系统编程的学习. 知识 在进行Linux系统 ...

随机推荐

  1. 【转】ubuntu12.04下安装chrome浏览器

    原文网址:http://blog.163.com/zhou_411424/blog/static/197362156201331931313549 下载google chrome deb包 32位:h ...

  2. Controller 中Action 返回值类型 及其 页面跳转的用法

        •Controller 中Action 返回值类型 View – 返回  ViewResult,相当于返回一个View 页面. -------------------------------- ...

  3. MySQL的备份和还原

    MySQL的备份和还原 备份:副本    RAID1,RAID10:保证硬件损坏而不会业务中止:        DROP TABLE mydb.tb1; 备份类型:        热备份.温备份和冷备 ...

  4. 【转】[总结]FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

  5. handsontable常规配置的中文API

    常规配置: 1.固定行列位置 fixedRowsTop:行数 //固定顶部多少行不能垂直滚动 fixedColumnsLeft:列数 //固定左侧多少列不能水平滚动 2.拖拽行头或列头改变行或列的大小 ...

  6. eclipse配置maven + 创建maven项目

        登录|注册     努力+坚持,而且还很年轻   目录(?)[+] 在现实的企业中,以低成本.高效率.高质量的完成项目,不仅仅需要技术大牛,企业更加需要管理大牛,管理者只懂技术是远远不够的.当 ...

  7. Subsequence(两个单调队列)

    Subsequence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Tota ...

  8. Afianl加载网络图片(延续)

    上一页"已经谈到了如何使用Afianl网络负载的图片和下载文件,本文将继续介绍使用Afinal使用网络负载图片,主绑定listview采用: 看效果图: listview在滑动过程中没用明显 ...

  9. H5移动端性能优化

    概述 1. PC优化手段在Mobile侧同样适用 2. 在Mobile侧我们提出三秒种渲染完成首屏指标 3. 基于第二点,首屏加载3秒完成或使用Loading 4. 基于联通3G网络平均338KB/s ...

  10. [转]Asp.Net调用前台js调用后台代码分享

    1.C#前台js调用后台代码 前台js <script type="text/javascript" language="javascript"> ...