转自:https://blog.csdn.net/Lycorisradiata__/article/details/80096203

一. 阻塞信号

1. 信号的常见其他概念
    实际执行信号的处理动作(3种)称为信号递达;
    信号从产生到递达之间的状态,叫做信号未决;
    进程可以选择阻塞某个信号;
    被阻塞的信号产生时,将保持在未决状态,直至进程取消对该信号的阻塞,才执行递达的动作;
注意:阻塞和忽略是不同的。只要信号阻塞就不会被递达;而忽略是信号在递达之后的一种处理方式。
2. 在内核中的表示
    信号在内核中的表示示意图:

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针(handler)表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直至信号递达才清除该标志。操作系统向进程发送信号就是将pending位图中的该信号对应状态位由0变为1。
    如上图,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作;SIGINT信号产生过,但是正在被阻塞,所以暂时递达不了。虽然它的处理动作是忽略,但是未解除阻塞时不能忽略该信号,因为经常仍有机会改变处理动作后再解除阻塞;SIGQUIT信号未产生过,但是它是阻塞的,所以一旦该信号产生它就被阻塞无法递达,它的处理动作也是用户自定义函数。
    在Linux下,如果进程解除某信号的阻塞之前,该信号产生了很多次,它的处理方法是:若是常规信号,在递达之前多次产生只计一次;若是实时信号,在递达之前产生多次则可以放在一个队列里。本文只讨论常规信号,下面提到的信号都是常规信号。

3. 信号集
        
        从上图我们可以知道,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次。同样的,阻塞标志也是这样表示的。所以阻塞和未决标志我们可以采用相同的数据类型sigset_t来存储,sigget_t称为信号集。
        这个类型可以表示每个信号的“有效”、“无效”状态。在未决信号集中,“有效”、“无效”表示该信号是否处于未决状态;在阻塞信号集中,“有效”、“无效”表示该信号是否被阻塞。阻塞信号集也叫做当前进程的信号屏蔽字。

4. 信号集操作函数
        sigget_t类型对每一种信号用一个bit表示“有效”或者“无效”状态,至于这个类型内部是怎样储存这些bit则依赖系统实现,从使用者的角度是不用关心的。使用者只用调用以下函数来对sigget_t变量进行操作,而不用对它的内部数据进行任何解释。

其中,前四个函数都是成功返回0,出错返回-1;最后一个sigismember函数包含返回1,不包含返回0,出错返回-1。
注意:在使用sigget_t类型的变量之前,一定要调用sigemptyset函数或者sigfillset函数做初始化,使信号集处于确定的状态。

5. sigprocmask
调用sigprocmask函数可以读取或更改进程的信号屏蔽字(阻塞信号集)。
(1)函数原型:

(2)参数:
        how:有三个可取值(如下图,假设当前信号屏蔽字为mask)

set:指向一个信号集的指针
    oldset:用于备份原来的信号屏蔽字,不想备份时可设置为NULL

1)若set为非空指针,则根据参数how更改进程的信号屏蔽字;
2)若oldset是非空指针,则读取进程当前的信号屏蔽字通过oldset参数传出;
3)若set、oldset都非空,则将原先的信号屏蔽字备份到oldset中,然后根据set和how参数更改信号屏蔽字

(3)返回值:成功返回0,出错返回-1

说明:若调用sigprocmask解除了若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

6. sigpending

(1)函数原型:

(2)函数功能:读取当前进程的未决信号集,通过set参数传出
(3)参数:输出型参数,传出数据
(4)返回值:成功返回0,出错返回-1

7.利用以上所介绍函数编写代码使用:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void printsigset(sigset_t* set)//打印pending表
{
int i = 0;
for(i=1; i<=32; ++i)
{
if(sigismember(set,i))//当前信号在信号集中
putchar('1');
else//当前信号不在信号集中
putchar('0');
}
puts("");//printf("\n");
}

int main()
{
sigset_t s,p;
sigemptyset(&s);//初始化信号集
sigaddset(&s,2);//将2号信号设置为有效信号
sigprocmask(SIG_BLOCK, &s, NULL);//屏蔽2号信号
int i = 10;
while(i--)
{
sigpending(&p);//获取当前进程的未决信号集
printsigset(&p);//打印未决信号集
if(i==7)
{
raise(2);//向本进程发送2号信号
}
if(i == 5)
{
sigprocmask(SIG_UNBLOCK, &s, NULL);//解除对2号信号的屏蔽
printf("recober block bitmap\n");
}
sleep(1);
}
return 0;
}
运行结果为:

可以看到,开始运行时未决信号集中没有有效信号。过3秒后我们向进程发送了一个2号信号,此时未决信号集对应的第二个bit变为1,说明进程有2号信号。到第5秒时,我们解除了对2号信号的屏蔽,此时第三秒产生的2号信号被递达,它的默认处理动作是终止进程,所以此时进程直接终止。

二. 捕捉信号

1. 内核如何实现对信号的捕捉
        在前面的介绍中我们可以知道,当信号的处理动作是自定义函数的时候,在信号递达时就调用这个函数,这称为捕捉信号。内核对信号的捕捉详细过程见下图(进行了四次用户态与内核态的切换):

说明:信号处理函数与main函数使用不同的堆栈空间,它们之间不存在调用与被调用的关系,是两个独立的控制流程。

2. signal函数
(1)函数原型

(2)函数功能:修改signum信号的处理动作为handler指向的函数
(3)参数:
        signum:表示要捕捉的信号序号
        handler:是一个回调函数,若信号signum产生并且没有被阻塞时,当该信号被递达时,就去执行该回调函数,该回调函数有一个整型参数,表示对哪个信号进行处理。
(4)代码实现:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int num)//捕捉信号
{
printf("signo is %d\n",num);
return;
}

int main()
{
signal(2,handler);//捕捉2号信号
signal(3,handler);//捕捉3号信号

while(1)
{
printf("this is youngmay\n");
sleep(1);
}
return 0;
}
运行结果:

可以看到,我们由键盘向进程发送的2号和3号信号都被捕捉,而发送的20号信号并未被捕捉,而是执行了它的默认处理动作,让进程暂停了。

3. sigaction函数

(1)函数原型

(2)函数功能:读取和修改与指定信号相关联的处理动作。如果正在执行该信号的处理动作时,又发来了该信号,系统会自动屏蔽该信号,直到执行结束才解除屏蔽

(3)参数:
        signum:指定信号的编号
        act:若非空,则根据它修改该信号的处理动作
        oldact:若非空,则通过它传出该信号原来的处理动作
说明:act、oldact指针指向sigaction结构体,sigaction结构体如下:
struct sigaction {
void (*sa_handler)(int);//信号的处理动作
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;//当正在执行信号处理动作时,希望屏蔽的信号。当处理结束后,自动解除屏蔽
int sa_flags;//一般为0
void (*sa_restorer)(void);
};
(4)返回值:成功返回0,出错返回-1

(5)说明
        将sahandler赋值为常数SIGIGN传给sigsction表示忽略信号;赋值为常数SIG_DFL表示执行系统默认处理动作;赋值为一个函数指针表示用户自定义函数捕捉信号,或者说向内核注册了一个信号处理函数。
(6)代码实现:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int num)
{
printf("signo is %d\n",num);
return;
}

int main()
{
struct sigaction act,oact;
act.sa_handler = handler;//将信号处理动作设置为信号捕捉
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,3);//屏蔽3号信号
sigaction(2,&act,&oact);//自定义2号信号的处理动作
sigaction(3,&act,&oact);//自定义3号信号的处理动作
int i = 10;
while(i--)
{
printf("this is youngmay\n");
if(i == 5)
{
sigaction(2,&oact,NULL);//恢复2号信号的默认处理动作
oact.sa_handler = SIG_IGN;//将3号信号的处理动作设为忽略
sigaction(3,&oact,NULL);
}
sleep(1);
}
return 0;
}

运行结果:

可以看到,前五秒内,我们可进程发送2号和3号信号都执行了自定义函数,打印出一句话;在五秒之后,因为3号信号的处理动作设置为了忽略,而2号信号的处理动作设为默认处理动作,所以我们再向进程发送3号信号时无任何反应,而发送2号信号执行了2号信号的默认处理动作直接终止了进程。

4. pause函数
(1)函数原型:

(2)函数功能:使进程挂起等待直到有信号递达。
(3)返回值:只有出错的返回值,返回-1
    若信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;若信号的处理动作是忽略,则进程继续处于挂起等待状态,pause不返回;若信号的处理动作是捕捉,则调用了信号处理函数之后,pause返回-1,errno设置为EINTR(该错误码表示“被信号中断”)。
(4)利用pause函数实现sleep
        我们想实现sleep几秒,我们可以先让进程挂起等待,在几秒之后,向进程发送信号使得它递达,从而让pause出错返回-1(这里信号的处理动作必须设置为捕捉信号,否则pause函数不会返回),从而达到sleep的功能。其中,在几秒之后向进程发送信号使得pause函数出错返回,我们需要用到之前的alarm函数,设置一个闹钟,在几秒之后,让它向进程发送SIGALRM信号。所以,实现我们的mysleep函数,具体步骤如下:
   
     1)将SIGALRM信号的处理动作设置为自定义动作;
     2)调用alarm函数设置闹钟;
     3)调用pause函数等待;
     4)alarm(0)取消闹钟(在(3)中pause可能被其他信号唤醒,所以要取消闹钟返回闹钟剩余的秒数);
     5)恢复SIGALRM信号的原有处理动作;
     6)返回剩余的秒数;
实现代码如下:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void handler(int num)
{
;
}

unsigned int mysleep(unsigned int t)
{
struct sigaction act,oact;//act为要设置闹钟信号的相关信息,oact保存闹钟信号>的原有相关信息
act.sa_handler = handler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);//处理闹钟信号时,不屏蔽其他信号
sigaction(SIGALRM,&act,&oact);//捕捉闹钟信号

alarm(t);//t秒之后向进程发送闹钟信号
pause();//使进程挂起等待
int ret = alarm(0);//取消闹钟,返回闹钟剩余秒数
sigaction(SIGALRM,&oact,NULL);//恢复闹钟的默认处理动作
return ret;//返回闹钟剩下的时间
}

int main()
{
while(1)
{
printf("hi,i am youngmay\n");
mysleep(1);
}
return 0;
}
运行结果为:

程序跑起来之后,每隔一秒,都会打印出一句话。说明我们自己实习的mysleep函数实现了sleep函数的功能。
我们可以验证一下mysleep函数的返回值是否为闹钟剩下的秒数,修改代码如下:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int num)
{
printf("signo is %d\n",num);
return;
}

unsigned int mysleep(unsigned int t)
{
struct sigaction act,oact;//act为要设置闹钟信号的相关信息,oact保存闹钟信号>的原有相关信息
act.sa_handler = handler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);//处理闹钟信号时,不屏蔽其他信号
sigaction(SIGALRM,&act,&oact);//捕捉闹钟信号

alarm(t);//t秒之后向进程发送闹钟信号
pause();//使进程挂起等待
int ret = alarm(0);//取消闹钟,返回闹钟剩余秒数
sigaction(SIGALRM,&oact,NULL);//恢复闹钟的默认处理动作
return ret;//返回闹钟剩下的时间
}

int main()
{
signal(2,handler);//将2号信号的处理动作改为自定义函数
printf("hello\n");
unsigned int ret = mysleep(20);
printf("return ret is %d\n",ret);
return 0;
}
运行结果如下:

在程序运行起来过两秒,我们由键盘给进程发送ctrl+c命令,即向进程发送2号信号,可以看到mysleep函数的返回值为18即闹钟剩余的时间。
说明:
(1)在mysleep程序中注册SIGALRM的自定义处理函数,是为了使pause出错返回,使进程从挂起状态醒来。因为如果不注册该函数,SIGALRM信号的默认处理动作是终止进程,所以在几秒后,进程会直接终止而不是醒来继续执行后面的语句。而在处理函数中什么都没做,是因为sleep函数在让进程挂起等待时也什么都没做。

(2)mysleep中再返回前恢复SIGALRM信号的原有处理动作,是因为在mysleep结束后,进程结束前,如果再次发送SIGALRM信号本意是想终止进程,但因为SIGALRM信号是自定义处理动作,导致进程不终止,这样达不到我们的预期。我们也可以将恢复SIGALRM信号的默认处理动作认为是:你借了别人的东西用,在用完之后,你要将东西给人家还回去,不能影响其他人的使用。

(3)mysleep函数的返回值与sleep函数的返回值作用一致。
---------------------
作者:_满舒克的喵
来源:CSDN
原文:https://blog.csdn.net/Lycorisradiata__/article/details/80096203
版权声明:本文为博主原创文章,转载请附上博文链接!

Linux信号-信号集&信号屏蔽字&捕捉信号【转】的更多相关文章

  1. APUE学习笔记——10.11~10.13 信号集、信号屏蔽字、未决信号

    如有转载,请注明出处:Windeal专栏 首先简述下几个概念的关系: 我们通过信号集建立信号屏蔽字,使得信号发生阻塞,被阻塞的信号即未决信号. 信号集: 信号集:其实就是一系列的信号.用sigset_ ...

  2. APUE学习笔记5——信号、信号集和进程信号屏蔽字

    1 信号传递过程 当引发信号的事件发生时(如软硬件异常.软件定时.终端产生信号或调用kill函数等等),会产生信号,内核会发送给目标进程. 在信号产生到信号传递给目标进程之间的时间间隔内,称该信号为未 ...

  3. UNIX环境编程学习笔记(24)——信号处理进阶学习之信号集和进程信号屏蔽字

    lienhua342014-11-03 1 信号传递过程 信号源为目标进程产生了一个信号,然后由内核来决定是否要将该信号传递给目标进程.从信号产生到传递给目标进程的流程图如图 1 所示, 图 1: 信 ...

  4. Linux下捕捉信号

    关于 信号signal的知识铺垫 点这里 信号由三种处理方式: 忽略 执行该信号的默认处理动作 捕捉信号 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个自定义函数,这称为捕捉信号. 进程收 ...

  5. Linux 改进捕捉信号机制(sigaction,sigqueue)

    sigaction函数 sigaction函数的功能是用于改变进程接收到特定信号后的行为. int sigaction(int signum, const struct sigaction *act, ...

  6. linux内核剖析(九)进程间通信之-信号signal

    信号及信号来源 什么是信号 信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动.通常信号是由一个错误产生的.但它们还可以作为进程间通信或修改行为的一种方 ...

  7. linux中脚本扑捉(trap)信号问题

    扑捉ctrl+c信号: #!/bin/bash trap ; function trap() { echo "You press Ctrl+C."; echo "Exit ...

  8. linux系统编程之信号(一):中断与信号

    一,什么是中断? 1.中断的基本概念 中断是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序,待处理完毕后又返回原来被 ...

  9. 转:步步LINUX C--进程间通信(二)信号

    源地址:http://blog.csdn.net/jmy5945hh/article/details/7529651 linux间进程通信的方法在前一篇文章中已有详细介绍.http://blog.cs ...

随机推荐

  1. MySQL的复制机制

    MySQL的复制机制 作者:尹正杰  版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL复制介绍 1>.MySQL复制允许将主实例(master)上的数据同步到一个或多个从实例( ...

  2. 解决 Ubuntu 经常 卡死

    ubuntu 的卡死可能与显卡驱动不兼容有关. 这里提供2种方式, 1.禁用原来自带的nouveau显卡驱动sudo gedit /etc/modprobe.d/blacklist.conf在最后一行 ...

  3. z-index设置后导致遮罩层显示跳动问题

    如图,1,3为top,bottom div,2为iscroll,4为遮罩层,如果1设置z-index后,不设置遮挡不住2,遮罩层4弹出会卡顿,既不设置z-index,又能遮挡iscroll的办法是在h ...

  4. ASP.NET Web API 2 媒体类型格式化程序

    Ø  简介 在之前的ASP.NET Web API 2 消息处理管道文章中有提到,在 Web API 的生命周期中,还包含比较中要的一部分,就是媒体类型格式化程序,该程序主要用于处理 Web API ...

  5. ASP.NET Web API 2 消息处理管道

    Ø  前言 ASP.NET 的应用程序都会有自己的消息处理管道和生命周期,比如:ASP.NET Web 应用程序(Web Form).ASP.NET MVC,还有本文将讨论的 ASP.NET Web ...

  6. Groovy 类名称赋值为变量使用(newInstance & new)

    类创建实例一般方式 http://groovy-lang.org/objectorientation.html#_class class Person { String name Integer ag ...

  7. SSH整合方案二(不带hibernate.cfg.xml)

    整体结构: 1.引入相关jar包 2.编写实体类和映射文件 package cn.zqr.domain; public class Customer { private Long cust_id; p ...

  8. mysql 单表更新记录UPDATE

    1.单表更新 (1)mysql> SELECT * FROM users;+----+----------+----------+-----+------+| id   | username | ...

  9. 通过GUI制作一个简单的消息对话框互发消息

    public class LTS extends JFrame { private JPanel contentPane; private JTextField textField; private ...

  10. luogu P3235 [HNOI2014]江南乐

    传送门 这题又是我什么时候做的(挠头) 首先是个和SG函数有关的博弈论,SG=0则先手必败.显然一堆石子就是一个游戏,而若干堆石子的SG值就是每堆SG的异或和,所以算出每堆石子SG就能知道答案 然后怎 ...