APUE 3 -- 信号 (signal)<II>: 可靠信号
一个事件可以使一个信号发送给一个进程,这个事件可以是硬件异常,可以是软件条件触发,可以是终端产生信号,也可以是一个kill函数调用。当信号产生后,内核通常会在进程表中设置某种形式的标志(flag)。我们可以认为当进程中的信号处理函数被触发的时候认为信号下达到了(delivered)这个进程。从信号产生到信号下达到进程这段期间,信号被认为是挂起状态(pending)。进程拥有阻塞信号下达的选项。如果一个阻塞信号要发送给一个进程,而且信号的处理方式是默认处理或者被进程捕获,那么这个信号将一直处于挂起状态(pending)直到这个进程将信号设置成非阻塞状态或将信号的处理方式改为忽略。操作系统在信号下达(delivered)的时候决定如何处理这个阻塞信号,而不是在信号产生的时候。这样做允许进程在信号下达前更改信号的处理方式。
如果一个阻塞信号在进程解除它的阻塞前产生多次,那么unix内核也仅仅会向进程下发一次这个信号。POSIX并没有规定信号下发到进程的顺序,然而与进程当前状态相关的信号会较先到达进程。
每个进程都有一个信号掩码(signal mask),它定义了一个当前被阻塞发送到该进程的信号的集合。我们可以认为这个掩码对于每个可能下发到该进程的信号有一个与之对应的bit位,对于一个给定的信号来说,如果与之对应的bit位是打开状态,那么意味着这个信号当前应处于阻塞状态。进程可以通过sigprocmask来检查并更改他当前的信号掩码。因为信号的数量是有可能超过一个整数正bit位位数的,所有POSIX规范定义了一个叫做sigset_t的数据类型,它包含了所有信号集。信号掩码就是存储在这其中一个信号集中的。
发送信号
kill & raise
可以使用kill函数向一个进程或进程组发送信号。raise函数允许进程给他自己发送信号。
#include <signal.h> /* return 0 if OK, -1 on error */
int kill(pid_t pid, int signo); /* return 0 if OK, -1 on error */
int raise(int signo);
函数调用 raise(signo); 等同于 kill(getpid(), signo); 。
对于kill函数,pid有以下四种不同的选择:
- pid > 0: 信号被发送给进程号为pid的进程
- pid == 0: 信号被发送给所有进程组Id等于发送者进程组的进程(即发送给发送进程所属进程组下的所有进程),前提是发送进程有权限将信号发送给该进程并且该进程不是系统进程。
- pid < 0: 信号被发送给进程组Id为pid绝对值的进程组下的所有进程,前提是发送进程有权限发送信号到该进程并且该进程不是系统进程。
- pid == -1: 信号被发送给发送进程有权限发送的所有进程,但不包含系统进程。
正如之前我们提到的,进程需要足够的权限才能向另一个进程发送信号,超级用户可以向任何进程发送信号。对于其他用户,可以发送信号的基本准则是:发送进程的真实用户Id(real user Id)或有效用户Id(effective user Id)必须等于接收进程的真实用户Id或有效用户Id。如果实现支持 _POSIX_SAVED_IDS的话,系统会检查接收进程的saved set-user-ID而不是有效用户Id。关于发送信号权限的一个特殊情景是:如果待发送信号是SIGCONT,那么发送进程可以将信号发送给他所属会话下的任何进程。
POSIX规范定义信号值为0的信号为空信号(null signal)。它可以用于通过kill函数来检测某一进程是否存在。kill函数在收到值为0的信号后会进程正常的错误检查,但是不会发送此信号。因此我们可以通过 kill(pid, 0) 来判断进程id为pid的进程是否存在。然而, UNIX系统在一定时间后会循环使用进程 IDs,所以通过pid检查出来的进程未必真的是你认为的那个进程(即函数调用时,通过pid查询到的进程未必会是你认为的那个进程)。另外kill函数不是原子的,当kill函数返回时,有可能被发送信号的进程已经结束。
alarm & pause
alarm函数允许我们设置一个定时器,这个定时器在未来某个时间点触发,这个定时器触发后会产生一个SIGALRM信号,此信号的默认处理方式是结束进程。
#include <unistd.h> /* 如果之前没有设置alarm返回0,否则返回之前
设置的alarm所剩余的秒数 */
unsigned int alarm(unsigned int seconds);
一旦到了alarm所设置的时间点,内核就会发送alarm信号,而由于处理器调度延时进程此时可能还无法获取到此信号处理的控制权。每个进程中只有一个alarm时钟。当我们调用alarm时,如果当前进程之前注册的闹钟还未到期,那么此函数返回之前闹钟距到期剩余的秒数,并且之前注册的闹钟会被这个新的闹钟值取代。另外,如果之前注册的闹钟还未到期并且新注册的闹钟值为0的话,那么之前注册的闹钟会被取消。
pause函数的调用会阻塞调用进程直到调用进程捕获到一个信号。
#include <unistd.h> /*Returns: -1 with errno set to EINTR*/
int pause(void);
信号集
像我们之前提到的,不同信号的数量可能会超过一个整数的bit位所能表示的信号数量。POSIX规范定义了sigset_t类型用来表示信号集,并使用下面的5个函数来管理信号集:
#include <unistd.h> /*All four return 0 if OK, -1 on error*/ /*清空set信号集中的所有信号*/
int sigemptyset(sigset_t* set);
/*使set包含所有信号*/
int sigfillset(sigset_t* set);
/*将信号signo加入到信号集set中*/
int sigaddset(sigset_t* set, int signo);
/*从信号集set中删除signo*/
int sigdelset(sigset_t* set, int signo); /*Returns 1 if true, 0 if false, -1 on error*/
int sigismember(const sigset_t* set, int signo);
sigprocmask
一个进程的信号掩码是指被阻止下发到此进程的所有信号的集合。进程可以检查并更改他的信号掩码。
#include <signal.h> /*
如果oset不为空,那么此进程信号掩码之前的值会被复制到oset中。
如果set为空,那么此进程的信号掩码不会被更改,而信号掩码的当前值
也不会复制到oset中。
*/
int sigprocmask(int how, const sigset_t* restrict set,
sigset_t* restrict oset);
how参数的值指明如何修改信号掩码:
- SIG_BLOCK: set包含另外的我们想阻塞的信号
- SIG_UNBLOCK:set包含我们想要解除阻塞的信号
- SIG_SETMASK:使用set替代进程当前信号掩码
sigprocmask 不支持多线程环境。
sigpending
#include <unistd.h> /*通过set返回发送给当前进程但被阻塞的信号*/
int sigpending(sigset_t* set);
sigaction
我们可以通过sigaction方法检查并修改特定信号的处理方式(action)。他是早期sinal函数的取代版本。
#include <unistd.h> /*若oact不为空,函数通过oact返回当前signo的action
* 若act不为空,则修改signo的当前action*/
int sigaction(int signo, const struct sigaction* restrict act,
struct sigaction* restrict oact); struct sigaction{
void (*sa_handler)(int); /*addr of signal handler,
or SIG_IGN or SIG_DFL */
sigset_t sa_mask; /*additional signals to block*/
int sa_flag; /*signal options*/ /*alternate handler*/
void (*sa_sigaction)(int,siginfo_t *, void *);
};
当使用sigaction改变signo的action时,如果sa_handler指向了一个信号处理函数(SIG_IGN和SIG_DFL除外),sigaction函数会将sa_mask指向的信号集合在这个信号处理函数(sa_handler)被调用前加入到当前进程掩码中,当信号处理函数返回时,进程的信号掩码会恢复为他原来的值。这样,使我们能够在信号处理函数被调用是阻塞一部分信号的到达。一旦我们为一个信号安装了action, 那么对于这个信号这个action将一直处于安装状态,除非我们使用sigaction方法明确的更改可它。
sa_flags:
sigsuspend
等待信号到达的一个整洁而可靠地方式是先阻塞这个信号然后使用sigsuspend。
#include <signal.h> /*
将当前进程信号掩码设置为sigmask,
函数返回后将进程掩码恢复为调用前
的值, 该函数总是返回-1,并设置
errno 为 -1
*/
int sigsuspend(const sigset_t* sigmask);
sigsuspend 可用于等待指定信号的到达,他的常用用法如下:
sigset_t mask, oldmask; … /* Set up the mask of signals to temporarily block. */
sigemptyset (&mask);
sigaddset (&mask, SIGUSR1); … /* Wait for a signal to arrive. */
sigprocmask (SIG_BLOCK, &mask, &oldmask);
while (!usr_interrupt)
sigsuspend (&oldmask);
sigprocmask (SIG_UNBLOCK, &mask, NULL);
通过user_interrupt 判断是否等待的SIGUSR1信号已到达,sigsuspend再返回时将进程信号掩码设置为他被调用前的值,因此我们最后需要将添加mask移除掉。
Signal Names and Numbers
数组sys_siglist可以帮助我们匹配信号与信号名:
extern char* sys_siglist[]; 数组索引为信号值, 数组元素值为信号名。
信号与信号名的转换:
#include <signal.h> /* 如果msg不为空,则向stderr 输出msg紧跟一个冒号加一个空着在加信号描述;如果msg为空则只向stderr输出信号描述*/
void psignal(int signo, const char* msg); void psiginfo(const siginfo_t info, const char* msg); /*获取信号描述*/
char* strsignal(int signo); void sig2str(int signo, char* str);
void str2sig(const char* str, int* signop);
总结
信号通常用于一些相对复杂的程序, 理解如何及为何处理信号对于UNIX高级编程是必要的。
APUE 3 -- 信号 (signal)<II>: 可靠信号的更多相关文章
- xenomai内核解析之信号signal(一)---Linux信号机制
版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1. Linux信号 1.1注册信号处理函数 ...
- linux可靠信号和非可靠信号测试样例
不可靠信号(在执行自定义函数其间会丢失同类信号) 可靠信号(在执行自定义函数其间不会丢失同类信号) 不可靠信号用一次以后,就恢复其默认处理吗? 至少在ubuntu 12.04上,已经是一次绑定,永远使 ...
- Linux信号实践(4) --可靠信号
Sigaction #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct si ...
- 练习--LINUX进程间通信之信号SIGNAL
同样的,信号也不要太迷信可靠信号及不及靠信号,实时或非实时信号. 但必须要了解这些信号之间的差异,函数升级及参数,才能熟练运用. ~~~~~~~~~~~~~~~~ 信号本质 信号是在软件层次上对中断机 ...
- 非常好的一篇对linux信号(signal)的解析 (转载)【转】
转自:https://blog.csdn.net/return_cc/article/details/78845346 Linux信号(signal) 机制分析 转载至:https://www.cnb ...
- linux内核剖析(九)进程间通信之-信号signal
信号及信号来源 什么是信号 信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动.通常信号是由一个错误产生的.但它们还可以作为进程间通信或修改行为的一种方 ...
- xenomai内核解析之信号signal(二)---xenomai信号处理机制
xenomai信号 上篇文章讲了linux的信号在内核的发送与处理流程,现在加入了cobalt核,Cobalt内核为xenomai线程提供了信号机制.下面一一解析xenomai内核的信号处理机制. 1 ...
- Django中信号signal针对model的使用
Django中实现对数据库操作的记录除了使用[开源插件]还可以使用信号signal独立实现 信号机制-观察者模式-发布与订阅:signal - 配置 # 文件路径:Django/myapps/__in ...
- APUE学习笔记——10.可靠信号与不可靠信号
首先说明:现在大部分Unix系系统如Linux都已经实现可靠信号. 1~31信号与SIGRTMIN-SIGRTMAX之间并不是可靠信号与不可靠信号的区别,在大多数系统下他们都是可靠信号. 只不过: 1 ...
随机推荐
- Hibernate——hibernate的配置测试
Hibernate Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自 ...
- [Linux] Desktop Management
xrandr --output Virtual1 --mode 1360x768 Extend Display Size
- 【Spring】JDBC事务管理XML配置
将spring事务管理与spirng-mybatis分离开了: <?xml version="1.0" encoding="UTF-8"?> < ...
- Kickstart Round D 2017 problem A sightseeing 一道DP
这是现场完整做出来的唯一一道题Orz..而且还调了很久的bug.还是太弱了. Problem When you travel, you like to spend time sightseeing i ...
- Django 创建admin账户
Django版本 1.11.4 安装参考:http://blog.csdn.net/a_little_snail/article/details/76933868 问题:创建admin账户密码 解决: ...
- d3.js多个x轴y轴canvas柱状图
最终效果图镇楼: 本文通过三个步骤来介绍d3.js. 1.简单的柱状图: 2.多个x轴的柱状图: 3.多个x轴.y轴的柱状图: 学习心得: d3.js入门相对比较困难,一旦掌握了核心思想,不断熟悉AP ...
- 网络唤醒原理浅析(Wake On LAN)
之前我的一篇文章<网络唤醒全攻略(Wake On Lan)>介绍过如何设置远程唤醒电脑,着重于使用,这篇主要从原理方面解析一下当中的奥妙: 原理 将唤醒魔术包发送的被唤醒机器的网卡上,魔术 ...
- 关于C#开发 windows服务进程
最近在做一个物联网项目,其中有一个模块是需要实现热水工程的自动化补水和回水功能 实现的方式有多种,我选用了VS C#的Windows服务方式. 首先是创建一个windows服务项目(名称随你喜欢的规范 ...
- switch case异常处理机制
public class T3{ public static void main(String[] args) { try{ String kc=""; System.out.pr ...
- 常用的十大Python开发工具
据权威机构统计,Python人才需求量每日高达5000+,但目前市场上会 Python 的程序员少之又少, 竞争小,很容易快速高薪就业.可能你并不太了解常用的十大Python开发工具都有哪些,现在告诉 ...