【Linux】一篇文章彻底搞定信号!
信号
- 1.信号是什么?
- 2.信号的种类
- 3.信号的产生
- 3.1硬件产生
- 3.2软件产生
- 4.信号的注册
- 4.1非可靠信号的注册
- 4.2可靠信号的注册
- 5.信号的注销
- 5.1非可靠信号的注销
- 5.2可靠信号的注销
- 6.信号阻塞
- 6.1信号是怎样阻塞的?
- 6.2sigprocmask
- 7.信号未决
- 7.1未决概念
- 7.2sigpending
- 8.信号的处理方式
- 8.1signal函数
- 8.2sigaction函数
- 8.3自定义信号处理的流程
- 9.信号的捕捉
- 9.1信号捕捉的条件
- 9.2信号捕捉流程
- 10.常用信号集操作函数
- 11.SIGCHLD信号
1.信号是什么?
信号其实就是一个软件中断。
例:
- 输入命令,在Shell下启动一个前台进程。
- 用户按下Ctrl-C,键盘输入产生一个硬件中断。
- 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行, CPU从用户
态切换到内核态处理硬件中断。- 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一
个SIGINT信号给该进程)。- 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的
信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接
终止进程而不再返回它的用户空间代码执行。
在这个例子中,由ctrl+c产生的硬件中断就是一个信号。Ctrl+C产生的信号只能发送给前台进程,命令后加&就可放到后台运行。
Shell可同时运行一个前台进程和任意多个后台进程,只有前台进程才能接受到像CTRL+C这种控制键产生的信号。
2.信号的种类
使用命令查看:kill -l
非可靠信号:1~31号信号,信号可能会丢失
可靠信号:34~64号信号,信号不可能丢失
SIGHUP:1号信号,Hangup detected on controlling terminal or death of controlling process(在控制终端上挂起信号,或让进程结束),ation:term
SIGINT:2号信号,Interrupt from keyboard(键盘输入中断,ctrl + c
),action:term
SIGQUIT:3号信号,Quit from keyboard(键盘输入退出,ctrl+ |
),action:core,产生core dump文件
SIGABRT:6号信号,Abort signal from abort(3)(非正常终止,double free
),action:core
SIGKILL:9号信号,Kill signal(杀死进程信号),action:term,该信号不能被阻塞、忽略、自定义处理
SIGSEGV:11号信号,Invalid memory reference(无效的内存引用,解引用空指针、内存越界访问),action:core
SIGPIPE:13号信号,Broken pipe: write to pipe with no readers(管道中止: 写入无人读取的管道,会导致管道破裂),action:term
SIGCHLD:17号信号,Child stopped or terminated(子进程发送给父进程的信号,但该信号为忽略处理的)
SIGSTOP:19号信号,Stop process(停止进程),action:stop
SIGTSTP:20号信号,Stop typed at terminal(终端上发出的停止信号,ctrl + z
),action:stop
具体的信号采取的动作和详细信息可查看:man 7 signal
3.信号的产生
3.1硬件产生
硬件产生即通过终端按键产生的信号:
- ctrl + c:SIGINT(2),发送给前台进程,& 进程放到后台运行,fg 把刚刚放到后台的进程,再放到前台来运行
- ctrl + z:SIGTSTP(20),一般不用,除非有特定场景
- ctrl + | :SIGQUIT(3),产生core dump文件
产生core dump文件的条件:
- 当前OS一定不要限制core dump文件的大小,ulimit -a
- 磁盘空间要足够
- 如何产生:
3.1 解引用空指针,收到11号信号,产生core dump文件
3.2 内存访问越界,程序一旦崩溃,就会收到11号信号,也就会产生core dump文件
3.3 double free,收到6号信号,并产生core dump。
3.4 free(NULL),不会崩溃
3.2软件产生
软件产生即调用系统函数向进程发信号
- kill函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数解释:
pid:进程号
sig:要发送的信号值
返回值:成功返回0,失败返回-1,并设置错误
- kill命令:
kill -[信号] pid
, - abort:
void abort(void);
,收到6号信号,谁调用该函数,谁就收到信号 - alarm:
unsigned int alarm(unsigned int seconds);
,收到14号信号,告诉内核在seconds秒后给进程发送SIGALRM信号,该信号默认处理动作为终止当前进程。
4.信号的注册
信号注册又分为可靠信号的注册和非可靠信号的注册。
信号注册实际上是一个位图和一个sigqueue队列。
4.1非可靠信号的注册
当进程收到非可靠信号时:
- 将非可靠信号对应的比特位置为1
- 添加sigqueue节点到sigqueue队列当中,但是,在添加sigqueue节点的时候,队列当中已然有了该信号的sigqueue节点,则不添加
4.2可靠信号的注册
当进程所受到可靠信号时:
- 在sig位图中更改信号对应的比特位为1
- 不论之前sigqueue队列中是否存在该信号的sigqueue节点,都再次添加sigqueue节点到sigqueue队列当中去
5.信号的注销
5.1非可靠信号的注销
- 信号对应的比特位从1置为0
- 将该信号的sigqueue节点从sigqueue队列当中进行出队操作
5.2可靠信号的注销
- 将该信号的sigqueue节点从sigqueue队列当中进行出队操作
- 需要判断sigqueue队列当中是否还有相同的sigqueue节点:
①没有了:信号比特位从1置为0
②还有:不会更改sig位图中的比特位
6.信号阻塞
6.1信号是怎样阻塞的?
- 信号的阻塞,并不会干扰信号的注册。信号能注册,但不能被立即处理,
- 将block位图中对应的信号比特位置为1,表示阻塞该信号
- 进程收到该信号,还是一如既往的注册
- 当进程进入到内核空间,准备返回用户空间的时候,调用do_signal函数,就不会立即去处理该信号了
- 当该信号不被阻塞后,就可以进行处理了
6.2sigprocmask
函数原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数解释:
- how,该做什么样的操作
SIG_BLOCK:设置信号为阻塞
SIG_UNBLOCK:解除信号阻塞
SIG_SETMASK:替换阻塞位图- set:用来设置阻塞位图
SIG_BLOCK:设置某个信号为阻塞,block(new) = block(old) | set
SIG_UNBLOCK:解除某个信号阻塞,block(new)= block(old) & (~set)
SIG_SETMASK:替换阻塞位图,block(new)= set- oldset:原来的阻塞位图
例:下述例子,信号全部被阻塞,采用kill -9,将该进程结束掉
结果: 此时发送信号是不会有作用的,采用kill -9强杀掉
代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void signcallback(int signumber)
{printf("change the signal %d\n",signumber);
}int main()
{sigset_t set;sigset_t oldset;sigfillset(&set);//所有比特位全置为1,则信号全部会被阻塞sigprocmask(SIG_BLOCK,&set,&oldset);while(1){sleep(1);}return 0;
}
7.信号未决
7.1未决概念
实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是、在递达之后可选的一种处理动作。
7.2sigpending
函数原型:int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。调用成功返回0,出错返回-1.
例:
结果:
代码:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void signalcallback(int signumber)
{printf("chang signumber %d\n",signumber);
}
void printsigset(sigset_t *set)
{int i = 0;for(;i < 32;i++){if(sigismember(set,i)){putchar('1');}else{putchar('0');}}
}int main()
{signal(2,signalcallback);signal(10,signalcallback);sigset_t set;sigset_t oldset;sigset_t pending;sigfillset(&set);//所有比特位全部置为1,则信号会全部被阻塞sigprocmask(SIG_BLOCK,&set,&oldset);while(1){sigpending(&pending);printsigset(&pending);sleep(1);}return 0;
}
8.信号的处理方式
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。
在上述例子中:
- SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
- SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
- SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
8.1signal函数
该函数可以更改信号的处理动作。
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数解释:
- signum:更改的信号值
- handler:函数指针,要更改的动作是什么
实际上,该函数内部也调用了sigaction函数。
8.2sigaction函数
读取和修改与指定信号相关联的处理动作。
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数解释:
- signum:待更改的信号值
struct sigaction结构体:
void (*sa_handler)(int);//函数指针,保存了内核对信号的处理方式
void (*sa_sigaction)(int, siginfo_t *, void *);//
sigset_t sa_mask;//保存的是当进程在处理信号的时候,收到的信号
int sa_flags;//SA_SIGINFO,OS在处理信号的时候,调用的就是sa_sigaction函数指针当中
//保存的值0,在处理信号的时候,调用sa_handler保存的函数
void (*sa_restorer)(void);
例:
结果:
代码:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void signcallback(int signumber)
{printf("change signumber %d\n",signumber);
}int main()
{struct sigaction act;//act为入参sigemptyset(&act.sa_mask);act.sa_flags = 0;act.sa_handler = signcallback;struct sigaction oldact;//oldact为出参sigaction(3,&act,&oldact);while(1){sleep(1);}return 0;
}
8.3自定义信号处理的流程
task_struct
结构体中有一个struct sighand_struct
结构体。struct sighand_struct
结构体有一个struct k_sigaction action[_NSIG]
结构体数组。- 该数组中,其中的
_sighandler_t sa_handler
保存的是信号的处理方式,通过改变其指向,可以实现我们对自定义信号的处理。
9.信号的捕捉
9.1信号捕捉的条件
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这就称为信号捕捉。
9.2信号捕捉流程
内核态返回用户态会调用do_signal函数,两种情况:
- 无信号:sys_return函数,返回用户态
- 有信号:先处理信号,信号返回,再调用do_signal函数
例:
- 程序注册了SIGQUIT信号的处理函数sighandler。
- 当前正在执行main函数,这时发生中断或异常切换到内核态。
- 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
- 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数, sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
- sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
- 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
10.常用信号集操作函数
int sigemptyset(sigset_t *set);://将比特位图全置为0int sigfillset(sigset_t *set);//将比特位图全置为1int sigaddset(sigset_t *set, int signum);//将该set位图,多少号信号置为1int sigdelset(sigset_t *set, int signum);//将该set位图,多少号信号置为0int sigismember(const sigset_t *set, int signum);//信号signum是否是set位图中的信号
11.SIGCHLD信号
该信号是子进程在结束是发送给父进程的信号,但是该信号的处理方式是默认处理的。
父进程对子进程发送过来的SIGCHLD信号进行了忽略处理,就会导致子进程成为僵尸进程。
可以自定义该信号的处理方式:
指令查看后台:ps aux | grep ./fork
代码:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>void signcallback(int signumber)
{printf("change signal %d\n",signumber);wait(NULL);
}int main()
{signal(17,signcallback);pid_t pid = fork();if(pid < 0){perror("fork");return -1;}else if(pid == 0){printf("I am child\n");sleep(1);exit(12);}else{while(1){sleep(1);}}return 0;
}
码字不易,点个赞是对博主最大的支持!!
转载请注明出处!
【Linux】一篇文章彻底搞定信号!相关推荐
- linux脚本pend什么意思,【Linux】一篇文章彻底搞定信号!
信号 1.信号是什么? 2.信号的种类 3.信号的产生 3.1硬件产生 3.2软件产生 4.信号的注册 4.1非可靠信号的注册 4.2可靠信号的注册 5.信号的注销 5.1非可靠信号的注销 5.2可靠 ...
- 搭建简易个人博客(一篇文章就搞定!)
搭建我的个人博客这个想法很早就有了,之前仅仅了解到在CSDN和博客园上可以创建我自己的主页,但感觉很没有趣味性,也就没有实施.偶然在B站上看到搭建动态和静态博客的教程,维护和建设自己亲手搭建好的网 ...
- Xposed精品连载 | 一篇文章彻底搞定安卓刷机与Root
前言 adb与fastboot 开启开发者选项 开启USB调试 Bootloader解锁 如何进入Bootloader模式 刷机 Root 安卓是基于Linux内核的一个移动操作系统.在Linux这种 ...
- git使用教程-一篇文章全搞定哦
Git使用教程 Git是什么 文章转载自 代码飞:https://code.bywind.cn/2018/07/14/170/ Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项 ...
- 一篇文章轻松搞定SpringSecurity权限框架!
目录 前言 一.引入依赖 二.提供正常的业务接口 三.自定义用户认证 3.1 编写配置类 3.2 编写UserDetailsService实现类 3.3 启动项目,完成认证功能的验证 3.4 小说明 ...
- ununtu20.04系统中如何划词翻译_如何高效阅读PDF外文文献,这一篇文章全搞定
对于很多初学者而言,阅读外文文献时非常痛苦的.句型复杂.生词多以及读者本身对外文文献的排斥感,都是造成阅读障碍的主要因素. 当然,如果你学会以下小编分享的一些技巧,可能并不觉得阅读那么难熬. 首先:建 ...
- 如何高效阅读PDF外文文献,这一篇文章全搞定!
对于很多初学者而言,阅读外文文献时非常痛苦的.句型复杂.生词多以及读者本身对外文文献的排斥感,都是造成阅读障碍的主要因素. 当然,如果你学会以下小编分享的一些技巧,可能并不觉得阅读那么难熬. 首先:建 ...
- 一篇文章彻底搞懂Android事件分发机制
本文讲的是一篇文章彻底搞懂Android事件分发机制,在android开发中会经常遇到滑动冲突(比如ScrollView或是SliddingMenu与ListView的嵌套)的问题,需要我们深入的了解 ...
- linux shell find depth,搞定 Linux Shell 文本处理工具,看完这篇集锦就够了
原标题:搞定 Linux Shell 文本处理工具,看完这篇集锦就够了 Linux Shell是一种基本功,由于怪异的语法加之较差的可读性,通常被Python等脚本代替.既然是基本功,那就需要掌握,毕 ...
最新文章
- Pandas基础用法合集(中文官档)
- erdas叠加显示_ERDAS操作技巧
- flutter 移动通知_Flutter开发之动画
- 转:前端js、jQuery实现日期格式化、字符串格式化
- 阶段3 2.Spring_07.银行转账案例_7 代理的分析
- 【转】vue双向绑定原理分析
- 微型计算机工作适宜的温度,应用电脑(1)第一章 第一节 计算机的基本组成...
- 树莓派学习(二):摄像头模块的安装和使用
- 基于3D人像复原技术的试衣平台
- [Azure - VNet] 解决办法:Azure P2S VNet无法建立网络连接。“parameter is incorrect“ (Error 87 参数错误)
- Python学习(一) 准备工作
- [ZT]完全用Linux工作,摈弃Windows
- idea如何配置或者创建mybatis的xml文件 idea如何配置或者创建mybatis的配置文件
- 桥接,NAT,Host Only的区别
- linux下文件与Windows下文件格式的区别与转换
- web项目开发一般流程
- 转载的,给大家分享,,希望有用
- 记一次redis数据库RDB内存事故排查处理
- 【C++】类和对象---什么是类?
- 3.7 倒计时计时器——全部代码
热门文章
- anc降噪是什么意思
- PCIe TLP详解
- Django Web 官方 中文文档 开发手册
- JS生成 GUID 或 UUID 四种方法
- [RK3288][Android6.0] USB OTG模式及切换
- 基站信号强度和位置变化详解(可获得其他系统信息)
- 1875. 贝茜的报复
- html背景图片 纵向拉伸,背景图片拉伸(CSS方法)_html/css_WEB-ITnose
- GPT分区安装Linux双系统,UEFI+GPT双硬盘安装Win10+Ubuntu16.04双系统
- NAACL 2022 | 机器翻译SOTA模型的蒸馏