2.3 linux中的信号分析 阻塞、未达
信号的阻塞、未达:
linux中进程1向进程2发送信号,要经过内核,内核会维护一个进程对某个信号的状态,如下图所示:
当进程1向进程2发送信号时,信号的传递过程在内核中是有状态的,内核首先要检查这个信号是不是处于阻塞状态,然后检查这个信号是不是处于未决状态,最后检查是不是忽略该信号。
更详细的信号传递过程如下:
一个信号送到进程2时,先检查这个进程的信号屏蔽字block,如果该信号对应位是1,表示进程把这个信号是屏蔽(阻塞)了,然后内核就将pending状态字的相应位置为1,表示信号未抵达,当我们在进程2中调用一个函数将block中的相应位置为0时,pending中的对应位就会被置为0,这时候刚才未达的信号就可以继续往后走了,然后检查进程2对这个信号是不是忽略,如果不是忽略就调用相应的信号处理函数。
下面介绍几个操作信号集的函数:
int sigemptyset(sigset_t *set) 把信号集(64bit)全部清零
int sigfillset(sigset_t *set) 把信号集全部置为1
int sigaddset(sigset_t *set, int signo) 根据signo,把信号集中的相应位置为1
int sigdelset(sigset_t *set, int signo) 根据signo,把信号集中相应的位清0
int sigismember(const sigset_t *set, int signo) 判断signo是否在信号集中
获取block状态字状态的函数:
int sigprocmask(int how, const sigset_t *set, sigset_t *oset) 读取或者更改进程的信号屏蔽状态字(block)
若成功则返回0,若出错则返回-1
如果oset是非空指针,则读取进程的当前信号屏蔽状态字通过oset传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的状态字备份到oset里,然后根据set和how参数更改信号屏蔽字,假设当前的信号屏蔽字为mask,下表说明了how的可选值。
int sigpending(sigset_t *set) 获取进程没有抵达的状态字
信号阻塞、未达示例程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
if(SIGINT == num)
{
printf("recv signal SIGINT\n");
}
else if(SIGQUIT == num)
{
sigset_t uset;
sigemptyset(&uset);
sigaddset(&uset, SIGINT);
sigprocmask(SIG_UNBLOCK, &uset, NULL);
printf("recv signal num = %d\n", num);
} } void printsigset(sigset_t *set)
{
int i = ;
for(i = ; i < ; i++)
{
if(sigismember(set, i))
{
putchar('');
}
else
{
putchar('');
}
} printf("\n");
} int main()
{
sigset_t bset;
sigset_t pset; sigemptyset(&bset);
sigaddset(&bset, SIGINT); if(signal(SIGINT, my_handler) == SIG_ERR)
{
perror("signal error");
exit();
} if(signal(SIGQUIT, my_handler) == SIG_ERR)
{
perror("signal error");
exit();
} sigprocmask(SIG_BLOCK, &bset, NULL); for(;;)
{
sigpending(&pset);
printf("bset : \n");
printsigset(&bset);
printf("pset : \n");
printsigset(&pset); sleep();
} return ;
}
在主函数中,我们将SIGINT设置为阻塞,执行程序时,当没有按下ctrl+c(产生SIGINT)时,pending状态字为全0,说明没有未达信号,当按下ctrl+c时,由于block中将SIGINT设置为了阻塞,所以当产生SIGINT时,pending中的第2位(SIGINT对应的位,信号从第1位开始算起)被置为了1。执行程序,结果如下:
ctrl+c产生SIGINT, ctrl+\产生SIGQUIT。按下ctrl+\触发信号处理函数,产生SIGQUIT信号,解除对SIGINT的阻塞,而刚刚那个未达的SIGINT被送达,再一次触发信号处理函数,打印出recv signal SIGINT。SIGINT被送达后,相应的pending位被清0了。我们在信号处理函数中将block中的阻塞位清除,但是并没有起作用(原因未知,在信号处理函数中设置block,只是临时起作用,例如:现在有一个未达信号SIGINT,我们产生SIGQUIT进入信号处理函数,临时将block中的阻塞位解除,然后处理这个未达信号,处理完之后,将阻塞位恢复原样,然后继续执行。 从执行结果也可以看出,是先打印出recv signal SIGINT,又打印出recv signal num = 3)。
block中设置SIGINT屏蔽字,按下ctrl+c,pending相应位置为1,按下ctrl+\,SIGQUIT被送达一次,信号处理函数被调用,SIGINT又被送达一次(因为刚才处于pending),SIGQUIT被送达的那次,在信号处理函数中将block中SIGINT位清0,并恢复SIGINT的处理函数为默认。再次按下ctrl+c,应该退出程序,但是没有退出,而是pending中又被置为1,再次按下ctrl+\,则信号处理函数中SIGQUIT相关的打印被输出,然后程序退出。
sigaction注册信号处理函数:
sigaction也用来做信号的安装,该函数功能比signal更强大。函数原型如下:
int sigaction(int signum, const struct sigaction *act, const struct sigaction *old)
该函数的第一个参数为信号的值,可以为除SIGKILL和SIGSTOP外的任何一个有效信号值(为这两个信号定义自己的处理函数,将导致安装错误)。
第二个参数为指向sigaction的一个实例的指针,在结构sigaction中指定了对特定信号的处理,可以为空,进程会以缺省方式对信号进行处理。
第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定为NULL。
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等。
struct sigaction
{
void (*sa_handler)(int) //信号处理程序,不接受额外数据,老的处理函数
void (*sa_sigaction)(int, siginfo_t *, void *) // 信号处理程序,能接受额外数据,和sigqueue配合使用
sigset_t sa_mask;
int sa_flags; //影响信号的行为,SA_SIGINFO表示能接受数据,如果进程想要接收额外数据,则应设置该位
void (*sa_restorer)(void) //废弃
}
sa_handler和sa_sigaction不能同时存在,两个都赋值的话,优先调用sa_sigaction。
示例程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
printf("recv signal num = %d\n", num);
} int main()
{
struct sigaction act = {}; act.sa_handler = my_handler; sigaction(SIGINT, &act, NULL); for(;;)
{
sleep();
} return ;
}
执行结果如下:
赋值sa_sigaction的示例程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
printf("recv signal num = %d\n", num);
} void my_sa_sigaction(int num, siginfo_t *info, void *p)
{
printf("recv sig : %d\n", num);
} int main()
{
struct sigaction act = {}; act.sa_handler = my_handler;
act.sa_sigaction = my_sa_sigaction; sigaction(SIGINT, &act, NULL); for(;;)
{
sleep();
} return ;
}
执行结果如下:
sigqueue函数:
新的发送信号的系统调用,主要是针对实时信号提出的信号带有参数,与函数sigaction()配合使用。比kill函数强大,函数原型如下:
int sigqueue(pid_t pid, int sig, const union sigval value)
第一个参数指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
sigval联合体如下:
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
下面我们编写带有额外数据的信号发送处理函数,先给出信号处理函数中第二个参数siginfo_t的具体定义:
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
printf("recv signal num = %d\n", num);
} void my_sa_sigaction(int num, siginfo_t *info, void *p)
{
int myintnum = ;
myintnum = info->si_value.sival_int;
printf("%d %d \n", myintnum, info->si_int);
} int main()
{
pid_t pid;
struct sigaction act = {}; act.sa_sigaction = my_sa_sigaction;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO; if (sigaction(SIGINT, &act, NULL) < )
{
perror("sigaction error");
exit();
} pid = fork(); if(pid == -)
{
perror("fork error");
} if(pid == )
{
union sigval usig;
usig.sival_int = ;
int n = ;
while(n > )
{
sigqueue(getppid(), SIGINT, usig);
n--;
sleep();
}
exit();
} for(;;)
{
sleep();
} return ;
}
执行结果如下:
如果子进程不睡眠,而是一直发信号,则可能造成信号丢失,因为SIGINT是不可靠信号。
实时信号与非实时信号示例程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_sigaction(int signum, siginfo_t *info, void *p)
{
if(SIGINT == signum)
{
printf("recv SIGINT, num = %d\n", signum);
}
else if(SIGRTMIN == signum)
{
printf("recv SIGRTMIN num = %d\n", signum);
}
else if(SIGUSR1 == signum)
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGRTMIN);
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
else
{
printf("recv else\n");
}
} int main()
{
pid_t pid;
int ret = ;
struct sigaction act = {};
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = my_sigaction; if (sigaction(SIGINT, &act, NULL) < )
{
perror("sigaction error");
exit();
} if(sigaction(SIGRTMIN, &act, NULL) < )
{
perror("sigaction error");
exit();
} if(sigaction(SIGUSR1, &act, NULL) < )
{
perror("sigaction error");
exit();
} sigset_t bset; sigemptyset(&bset);
sigaddset(&bset, SIGINT);
sigaddset(&bset, SIGRTMIN); sigprocmask(SIG_BLOCK, &bset, NULL); pid = fork(); if(pid == -)
{
perror("fork error");
exit();
} if(pid == )
{
int i = ;
union sigval v;
v.sival_int = ; for(i = ; i < ; i++)
{
ret = sigqueue(getppid(), SIGINT, v);
if(ret != )
{
printf("sent SIGINT failed\n");
}
else
{
printf("sent SIGINT success\n");
} } v.sival_int = ;
for(i = ; i < ; i++)
{
ret = sigqueue(getppid(), SIGRTMIN, v);
if(ret != )
{
printf("sent SIGRTMIN failed\n");
}
else
{
printf("sent SIGRTMIN success\n");
}
} v.sival_int = ;
kill(getppid(), SIGUSR1); exit();
} while()
{
sleep();
} printf("end main ...\n"); return ;
}
我们注册了非实时信号SIGINT和实时信号SIGRTMIN,还有一个用户自定义信号SIGUSR1,在本例中负责发送解除命令。它们为同一个处理程序,只是有不同的分支,在子进程中发送了3次SIGINT和三次SIGRTMIN,一开始,这两个信号都是阻塞的,因此发送完成后它们都处于未决状态,当发送SIGUSR1后,临时解除阻塞,未决信号重新被发送,但是非实时信号只被发送了一次,属于不可靠信号。而实时信号发送原来的次数,属于可靠信号。执行结果如下所示:
实时的信号会存储在进程的结构中,所以不会丢失,会缓存到内核中,但是存储的数量也是有上限的,超过缓存上限后,再到来的信号会直接扔掉而不会将之前的冲掉,具体上限可以使用ulimit -a查看,pending signals如下所示:
而非实时信号,linux内核是不缓存的,发送多少也无所谓,最终只缓存一条。
2.3 linux中的信号分析 阻塞、未达的更多相关文章
- 2.2 linux中的信号分析
信号: 信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动. 信号是因为某些错误条件而产生的,比如内存段冲突.浮点处理器错误或者非法指令等. 信号是在软件层次上对中断的一种 ...
- 25 Linux中的信号
Linux中的信号 信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件).每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD.SIGINT等,它们在系统头文件中定义,也可以通 ...
- Linux中多线程信号的处理
1. 博文:Linux多线程中使用信号-1 http://blog.csdn.net/qq276592716/article/details/7325250 2. 博文:Linux多线程信号总结 ...
- linux中的信号简介和trap命令
1.信号 linux通过信号来在运行在系统上的进程之间通信,也可以通过信号来控制shell脚本的运行 主要有一下信号 1 ##进程重新加载配置 2 ##删除进程在内存中的数据 3 ##删除鼠标在内存中 ...
- Linux中的日志分析及管理
日志文件对于诊断和解决系统中的问题很有帮助,因为在Linux系统中运行的程序通常会把系统消息和错误消息写入相应的日志文件,这样系统一旦出现问题就会“有据可查”.此外,当主机遭受攻击时,日志文件还可以帮 ...
- linux中的信号机制
概述 Linux信号机制是在应用软件层次上对中断机制的一种模拟,信号提供了一种处理异步事件的方法,例如,终端用户输入中断键(ctrl+c),则会通过信号机制停止一个程序[1]. 这其实就是向那个程序( ...
- 转:linux中select()函数分析
源地址:http://blog.csdn.net/zi_jin/article/details/4214359 Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱 ...
- [转帖]Linux教程(8)-Linux中的进程和日志㐇、
Linux教程(8)-Linux中的进程和日志 2018-08-20 23:42:23 钱婷婷 阅读数 3554更多 分类专栏: Linux教程与操作 Linux教程与使用 版权声明:本文为博主原 ...
- Java基础(一):I/O多路复用模型及Linux中的应用
IO多路复用模型广泛的应用于各种高并发的中间件中,那么区别于其他模式他的优势是什么.其核心设计思想又是什么.其在Linux中是如何实现的? I/O模型 I/O模型主要有以下五种: 同步阻塞I/O:I/ ...
随机推荐
- 51nod 1232 完美数 数位dp
1232 完美数 题目来源: 胡仁东 基准时间限制:2 秒 空间限制:131072 KB 如果一个数能够被组成它的各个非0数字整除,则称它是完美数.例如:1-9都是完美数,10,11,12,101都 ...
- python json格式转xml格式
import xmltodict #json转xml函数 def jsontoxml(jsonstr): #xmltodict库的unparse()json转xml xmlstr = xmltodic ...
- Qt5.3.2openglVS2010_QSqlField_字段类型
1.本来想通过 QSqlField::typeID() 来找字段类型,但是没找到... 然而看到了 SQL_INTEGER.SQL_SMALLINT等的使用(在“static QVariant::Ty ...
- Qt5.3.2_Oracle驱动
参考网址:http://blog.csdn.net/sdqyhn/article/details/39855847 ZC: 将编译好的 qsqloci.dll和qsqlocid.dll 放到 目录“E ...
- 《剑指offer》第三_二题(不修改数组找出重复的数字)
// 面试题3(二):不修改数组找出重复的数字 // 题目:在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至 // 少有一个数字是重复的.请找出数组中任意一个重复的数字,但不能修改 ...
- MyEclipse快捷键大全【转】
-------------------------------------MyEclipse 快捷键1(CTRL)-------------------------------------Ctrl+1 ...
- rxjs学习
推荐一个好的网站:http://cn.rx.js.org/manual/overview.html#- https://rxjs-cn.github.io/learn-rxjs-operators/o ...
- javascript对象使用总结
javascript对象使用总结 一.总结 一句话总结:js对象的主要知识点是创建对象和继承,并且创建对象和继承的方法都是逐步层层递进的 创建对象 继承 原型 创建对象 1 <script> ...
- English trip V1 - 7.My dream car 我梦想的车 Teacher:Lamb Key: famous for
中华In this lesson you will learn to describe an object(目标). 课上内容(Lesson) famous for 以…著称,闻名 国家(名词) ...
- dp练习(5)——最长严格上升子序列
1576 最长严格上升子序列 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 给一个数组a1, a2 ... ...