一个事件可以使一个信号发送给一个进程,这个事件可以是硬件异常,可以是软件条件触发,可以是终端产生信号,也可以是一个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有以下四种不同的选择:

  1. pid > 0: 信号被发送给进程号为pid的进程
  2. pid == 0: 信号被发送给所有进程组Id等于发送者进程组的进程(即发送给发送进程所属进程组下的所有进程),前提是发送进程有权限将信号发送给该进程并且该进程不是系统进程。
  3. pid < 0: 信号被发送给进程组Id为pid绝对值的进程组下的所有进程,前提是发送进程有权限发送信号到该进程并且该进程不是系统进程。
  4. 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>: 可靠信号的更多相关文章

  1. xenomai内核解析之信号signal(一)---Linux信号机制

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1. Linux信号 1.1注册信号处理函数 ...

  2. linux可靠信号和非可靠信号测试样例

    不可靠信号(在执行自定义函数其间会丢失同类信号) 可靠信号(在执行自定义函数其间不会丢失同类信号) 不可靠信号用一次以后,就恢复其默认处理吗? 至少在ubuntu 12.04上,已经是一次绑定,永远使 ...

  3. Linux信号实践(4) --可靠信号

    Sigaction #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct si ...

  4. 练习--LINUX进程间通信之信号SIGNAL

    同样的,信号也不要太迷信可靠信号及不及靠信号,实时或非实时信号. 但必须要了解这些信号之间的差异,函数升级及参数,才能熟练运用. ~~~~~~~~~~~~~~~~ 信号本质 信号是在软件层次上对中断机 ...

  5. 非常好的一篇对linux信号(signal)的解析 (转载)【转】

    转自:https://blog.csdn.net/return_cc/article/details/78845346 Linux信号(signal) 机制分析 转载至:https://www.cnb ...

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

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

  7. xenomai内核解析之信号signal(二)---xenomai信号处理机制

    xenomai信号 上篇文章讲了linux的信号在内核的发送与处理流程,现在加入了cobalt核,Cobalt内核为xenomai线程提供了信号机制.下面一一解析xenomai内核的信号处理机制. 1 ...

  8. Django中信号signal针对model的使用

    Django中实现对数据库操作的记录除了使用[开源插件]还可以使用信号signal独立实现 信号机制-观察者模式-发布与订阅:signal - 配置 # 文件路径:Django/myapps/__in ...

  9. APUE学习笔记——10.可靠信号与不可靠信号

    首先说明:现在大部分Unix系系统如Linux都已经实现可靠信号. 1~31信号与SIGRTMIN-SIGRTMAX之间并不是可靠信号与不可靠信号的区别,在大多数系统下他们都是可靠信号. 只不过: 1 ...

随机推荐

  1. Python 文档 涉及词汇

    Python  is an interpreted, interactive, object-oriented programming language that combines remarkabl ...

  2. ASP.NET CORE 2.0 不小心踩得坑

    前言 我是跟着 https://github.com/FQLin/Docs 学习asp.net core 2.0 的 1.EF迁移 EF 的迁移方式有两种: Command-line interfac ...

  3. ID3算法(2)

    今天,我来讲解的是决策树.对于决策树来说,主要有两种算法:ID3算法和C4.5算法.C4.5算法是 对ID3算法的改进.今天主要先讲ID3算法,之后会讲C4.5算法和随机森林等. Contents   ...

  4. 【Java学习笔记之三十一】详解Java8 lambda表达式

    Java 8 发布日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Java 8之前 ...

  5. fzu1969 GCD Extreme 类似于uva10561

    Description Given the value of N, you will have to find the value of G. The meaning of G is given in ...

  6. Html5笔记之第六天

    Canvas元素 <canvas> 标签定义图形,比如图表和其他图像,您必须使用脚本来绘制图形. 在画布上(Canvas)画一个红色矩形,渐变矩形,彩色矩形,和一些彩色的文字. <c ...

  7. Mvc分页组件MvcSimplePager代码重构及使用

    1 Mvc分页组件MvcSimplePager代码重构 1.1 Intro 1.2 MvcSimplePager 代码优化 1.3 MvcSimplePager 使用 1.4 End Mvc分页组件M ...

  8. 【详细资料】ICN6202:MIPI DSI转LVDS芯片简介

    ICN6202功能MIPI DSI转LVDS,分辨率1920*1200,封装QFN40

  9. UEditor Flash文件上传-crossdomain.xml文件配置

    在使用UEditor富文本时,如果客户端的浏览器是低版本浏览器,如IE7.IE8等,UEditor的文件上传方式将会使用flash方式上传而不是html5,而flash在跨域时唯一的限制策略就是cro ...

  10. 二、nginx搭建图片服务器

    接上篇:Nginx安装手册 cd /usr/local/nginx/conf/ 配置图片服务器 方法一.在配置文件server{}中location /{} 修改配置: #默认请求 location ...