Linux信号机制

理论部分

简单来说

信号是用来通知进程发生了异步事件

比如,在终端运行程序,按下ctrl+c就产生一个中端信号,大概过程是这样的:

  • 用户从shell下启动一个进程;
  • 用户按下ctrl+c,产生一个硬件中端信号;
  • cpu从用户态切换到进程态,处理中端;(这个过程当年读书的时候在《计算机组成原理》看过)
  • 终端驱动程序将ctrl+c解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
  • 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

前台进程在运行过程中用户随时可能按下ctrl+c而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步的。

kill -l 可以查看系统定义的信号,这些定义是在signal.h中。

1.png

也可以通过man 7 signal来查看。

2.png

其中Action代表默认处理动作,

Term : 终止当前进程
Core : 终止并且Core Dump
Ing   : 忽略
Stop : 停止当前进程
Cont: 继续执行先前停止的进程

注:当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。

产生信号的条件主要是:

  • 终端的按键,如ctrl+c产生SIGINT信号,ctrl+\产生SIGQUIT信号,ctrl+z产生SIGTSTP信号。
  • 硬件异常产生信号。
  • 一个进程调用kill (man 2 kill) 可以发送信号给另一个进程。
  • 可以调用kill (man 1 kill)。
  • 当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。

用户程序可以调用sigaction(man 2 sigaction)函数告诉内核如何处理某种信号,可选的操作有

  • 忽略
  • 执行默认动作
  • 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号。

发送信号可以用:

#include <signal.h> 
int kill(pid_t pid, int signo);//向其他进程发信号
int raise(int signo);//向自己发信号

#include <stdlib.h>
void abort(void);//使当前进程接收到SIGABRT信号而异常终止

#include <unistd.h>
unsigned int alarm(unsigned int seconds);//内核在seconds秒之后给当前进程发SIGALRM信号

信号从产生到递达之间的状态,称为信号未决。
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作

信号集操作函数

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

#include<signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。注意,在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

#include<signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);//读取或更改进程的信号屏蔽字
3.png
#include<signal.h>
int sigpending(sigset_t *set);//读取当前进程的未决信号集,通过set参数传出

信号补捉过程

4.png
#include<signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);//读取和修改与指定信号相关联的处理动作

act和oact指向sigaction结构体

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_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字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

#include<unistd.h>
int pause(void);//使调用进程挂起直到有信号递达

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

理解部分

关于信号,之前接触的比较多的是kill这个命令,现在看来,发送信号给进程是一种交互方式。当进程中实现了补捉信号以及处理的函数时,就可以与用户进行简单的交互,之所以简单是因为信号数量很少。所以一般用做进程的启动,重启,停止,错误处理等等。若使用其他的信息传递方式我想也是可以实现的,就是麻烦了一些。
下面来实现一个简单的例子,后台跑一个进程,打印出发送给该进程的信号。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void show_handler(int sig)
{
    printf("I got signal %d\n", sig);
}

int main(void)
{
    int i = 0;
    struct sigaction act, oldact;
    act.sa_handler = show_handler;
    sigaddset(&act.sa_mask, SIGQUIT);
    act.sa_flags = 0;
    sigaction(SIGINT, &act, &oldact);
    sigaction(SIGCONT, &act, &oldact);
    while(1) {
        sleep(1);
        i++;
    }
}

贴上运行图

s.png

注:sigaction 是不能接收SIGKILL 和 SIGSTOP的信号

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 进程之间可以通过信号传递信息,信号是一种软中断机制,通过信号用来通知进程发生了异步事件。进程之间可以互相通过系统调...
    lintong阅读 411评论 0 2
  • 信号(signal)是Linux进程间通信的一种机制,全称为软中断信号,也被称为软中断。信号本质上是在软件层次上对...
    小小小小诺阅读 3,784评论 0 3
  • 对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件...
    故事狗阅读 84,924评论 2 62
  • 文 | 季风无忌 目录: 《缺失的爱》第 1 卷 简单的爱(1)《缺失的爱》第 1 卷 简单的爱(2)《缺失的爱》...
    季风无忌阅读 3,920评论 0 4
  • 谁知道哪里有微信支付的相关知识的测试问卷啊
    昵称ok阅读 300评论 0 0