96SEO 2026-02-19 08:48 9
目录信号入门生活中的信号Linux信号常见信号信号产生通过终端按键产生信号核心转储调用系统函数向进程发信号由软件条件产生信号硬件异常产生信号阻塞信号信号其他相关常见概念信号在内核中的表示sigset_t信号集操作函数sigpendingsigprocmask捕捉信号内核如何实现信号的捕捉sigaction可重入函数volatileSIGCHLD信号总结信号入门

中提供了一种处理异步事件的方法可以很好地在多个进程之间进行同步和简单的数据交互。
注信号和信号是两个东西没有关系信号只是用来通知某个进程发生了什么事情但并不给该进程传递任何数据。
在生活中我们会收到很多信号比如红绿灯、闹钟、转向灯和狼烟等等。
那我们为什么会知道这些生活中的信号呢其实是我们曾经学习过有关这些生活信号的知识并且记住了对应场景下的信号。
有关信号的推论如下
当这些信号产生时我们就能够识别这些信号并且执行相应的动作。
当特定信号没有产生时我们依旧知道应该如何处理这个信号。
当我们收到信号时我们可能不会立即处理这个信号。
当我们无法立即处理信号的时候信号也一定要先被临时地记住。
信号本质是一种通知机制用户或操作系统通过发送一定的信号通知进程某些时间已经发生了进程可以在后续进行信号处理。
进程要处理信号那么进程必须具备信号识别的能力收到信号加上相对应的信号处理动作。
为什么进程能够识别信号呢进程能够识别信号肯定是设计操作系统的程序员将常见的信号及信号处理动作内置到进程的代码和属性中。
信号产生是随机的当信号产生时进程可能正在处理某些任务。
所以信号可能不是立即被进程处理的。
信号会被临时地记录下来方便进程后续进行处理。
那进程会在什么时候处理信号呢合适的时候。
一般而言信号的产生相对于进程而言是异步的。
异步指两个或两个以上的对象或事件不同时存在或发生或多个相关事物的发生无需等待其前一事物的完成。
同步指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系。
注信号也有确定的信号比如定下闹钟的时间时那么闹钟一定会在那个时间点响起来。
信号处理的常见方式
默认进程自带的处理动作该动作是程序员写好的逻辑忽略忽略也是信号处理的一种方式自定义动作捕捉信号
开头。
在头文件siganl.h中你能够这些信号都被定义为正整数称为信息编号。
其中编号
普通信号和实时信号的关系就像分时操作系统和实时操作系统的关系类似分时操作系统是基于时间片轮转调度的而实时操作系统要求要有严格的时序可以认为是一个队列。
将一个任务放入该队列中那么操作系统就尽量快地将该任务处理完。
日常生活中使用最多的就是分时操作系统而实时操作系统常见于特殊的行业如军工领域和自动驾驶领域等等。
那如何理解组合键变成信号呢其实键盘的工作方式是通过中断方式进行的。
键盘是槽位的每个槽位都会对应一个编号。
因为有键盘驱动操作系统是能够识别这些编号的。
只要按下了一些键操作系统立马就能够识别到。
那么当你按下组合键操作系统也是可以识别到的。
操作系统既然都识别到了你按下了组合键那么操作系统给特定的进程发送信号也就是轻而易举的事情了。
既然进程要接收操作系统发送过来的信号那么进程必须要具有保存信号的相关数据结构而该数据结构就是位图unsigned
int使用比特位信息就可以表示操作系统是否有给进程发送信号。
比如最低位比特位为
task_struct。
信号产生的方式有很多种但其发送的本质就是操作系统向目标进程写信号操作系统修改
时操作系统识别到该组合键并解释该组合键然后查找到在前台运行的进程最后操作系统将
对应的信号写入到进程内部的位图结构中就完成了信号发送。
现在进程已经将操作系统发给它的信号记录下来了进程就会在合适的时候处理该信号。
可以同时运行一个前台进程和任意多个后台进程只有前台进程才能接到像
而产生一个信号也就是说该进程的用户空间代码执行到任何地方都有可能收到
信号而终止所以信号相对于进程的控制流程来说是异步(Asynchronous)
catchSignal是自定义捕捉signal(SIGINT,
特定信号的处理动作一般只有一个while(true){cout
函数仅仅是修改进程对特定信号的后续处理动作并不是直接调用对应的处理动作。
而是当进程接收到特定信号时才会去调用对应的处理动作。
如果后续没有产生
Dump。
当一个进程要异常终止时可以选择把进程的用户空间内存数据全部保存到磁盘上文件名通常是
一般而言云服务器生产环境的核心转储功能是关闭的。
程序员写代码的环境称为开发环境测试人员的环境是测试环境测试
版本产品上线后用户可以使用的环境就成为生产环境有对应的服务器。
我们所购买的云服务器是集开发、测试、发布、部署于一体的机器。
作为后缀通常该文件是比较大的。
生产环境一般会关闭核心转储功能是为了防止生成大量的
函数可以给调用该函数的进程发信号raise(sig)等价于kill(getpid(),
}如何理解通过系统调用向进程发信号用户调用系统接口执行操作系统对应的系统调用代码操作系统提取参数或设置特定的数值信号编号和进程
ID操作系统向目标进程写信号修改对应进程的位图结构进程后续处理信号执行相应的处理动作。
学习管道的时候我们说过当管道读端关闭写端一直在写操作系统会自动终止对应的写端进程。
操作系统是通过发送
strlen(send_buffer));sleep(1);}}//
父进程的读端已经关闭子进程的写端再进行写入也没有任何的意义那么操作系统就向子进程发送
号信号SIGPIPE。
像管道的读端关闭写端还在写的这样情况其实就是不符合软件条件管道通信的条件管道也是一种软件那么操作系统就会向不符合软件条件的进程发送特定的信号终止进程。
或者是以前设定的闹钟时间还余下的秒数。
打个比方某人要小睡一觉设定闹钟为
0表示取消以前设定的闹钟函数的返回值仍然是以前设定的闹钟时间还余下的秒数。
和网络传输数据慢导致的。
如果想单纯看看计算的算力可以通过下面的程序。
catchSignal);alarm(1);while(true){count;}return
catchSignal);alarm(1);while(true){count;}return
}以上的代码就简单地实现了定时器的功能每隔一秒钟做指定的一件事。
nullptr);exit(1);}wait(nullptr);
catchSignal);alarm(1);callBacks.push_back(showCount);callBacks.push_back(showLog);callBacks.push_back(logUser);while(true)
如何理解软件条件给进程发送信号操作系统先识别到某种软件条件触发或者不满足然后操作系统构建信号发送给指定的进程。
注闹钟也是结构体操作系统通过特定的数据结构来管理闹钟。
当闹钟超时了操作系统就会给闹钟结构体中存储的进程
}将程序运行起来就会发现程序在死循环打印语句。
那为什么会这样呢如何理解除零呢进行计算的是
内部是有寄存器的其中有一个寄存器是状态寄存器。
该寄存器不进行数值保存它只用来保存
本次计算的状态其结构也是位图有着对应的状态标记位溢出标记位。
当状态寄存器的溢出标记位为
1时操作系统就会意识到有除零错误溢出问题操作系统会找到当前哪个进程在运行向该进程发送
当出现硬件异常时进程不一定会退出一般默认是退出但是我们即使不退出我们也做不了什么那为什么上面的程序会死循环呢虽然我们捕捉了
信号也处理了该信号但是寄存器中的异常一直没有被解决寄存器中的数据是进程的上下文当进行进程切换的时候寄存器的数据也被保存下来了。
当该进程被调度时操作系统又立马就识别到该进程出现了异常所以就一直给进程发送
无论是野指针还是越界访问都必须通过地址来找到目标位置语言层面上的地址全部都是虚拟地址。
当对某个数据进行访问时首先要将虚拟地址转化成物理地址虚拟地址通过页表和
内存管理单元硬件来转换成物理地址当野指针或越界访问时使用的地址都是非法地址那么
报错操作系统就能识别当前进程出现了硬件异常将该硬件异常转化成对应的信号发送给进程。
出现死循环的原因和除零错误出现死循环的原因类似
小总结所有的信号都有它的来源但最终全部都是被操作系统识别、解释并发送给对应的进程的。
实际执行信号的处理动作称为信号递达Delivery信号处理动作有默认、忽略、自定义捕捉。
信号从产生到递达之间的状态,称为信号未决Pending也就是进程收到了一个信号但该信号还未被处理信号被保存在位图Pending
某个信号。
被阻塞的信号产生时将保持在未决状态直到进程解除对此信号的阻塞才执行递达的动作。
注意阻塞和忽略是不同的只要信号被阻塞就不会递达而忽略是在递达之后可选的一种处理动作。
为了表示信号递达、未决和阻塞三个概念那么操作系统就要用一定的结构去表示它们。
操作系统就使用了三张表来表示这三个概念如下图所示
表是函数指针数组数组的下标就是信号编号数组中存的是信号的处理动作block
如果在进程解除对某信号的阻塞之前这种信号产生过多次将如何处理POSIX.1
是这样实现的普通信号在递达之前产生多次只计一次而实时信号在递达之前产生多次可以依次放在一个队列里本篇博客不讨论实时信号。
等。
如果要访问硬件那么语言类的头文件也会包含对应的系统调用接口将系统调用封装起来给我们使用。
sigset_t
类型和使用内置类型和自定义类型没有任何差别。
每个信号只有一个比特位的未决标志非
1不记录该信号产生了多少次阻塞标志也是这样表示的。
因此未决和阻塞标志可以用相同的数据类型
称为信号集这个类型可以表示每个信号的有效或无效状态在阻塞信号集中有效和无效的含义是该信号是否被阻塞而在未决信号集中有效和无效的含义是该信号是否处于未决状态。
阻塞信号集也叫做当前进程的信号屏蔽字Signal
类型对于每种信号用一个比特位表示有效或无效状态至于这个类型内部如何存储这些比特位则依赖于系统实现从使用者的角度是不必关心的使用者只能调用以下函数来操作
所指向的信号集使其中所有信号的对应比特位清零表示该信号集不包含任何有效信号。
函数
1表示该信号集的有效信号包括系统支持的所有信号。
sigaddset
函数可以帮助我们读取或更改进程的信号屏蔽字阻塞信号集调用成功返回
如果我们对所有的信号都进行了信号捕捉那我们是不是就写了一个不会被异常终止或者用户杀掉的进程呢我们通过代码来验证一下
--signal){if(sigismember(pending,
oldset;sigemptyset(set);sigemptyset(oldset);//
pending;sigemptyset(pending);while(true){//
获取当前进程的pending信号集sigpending(pending);//
打印pending信号集showPending(pending);sleep(2);}return
--signal){if(sigismember(pending,
oldset;sigemptyset(set);sigemptyset(oldset);//
pending;sigemptyset(pending);int
获取当前进程的pending信号集sigpending(pending);//
打印pending信号集showPending(pending);sleep(2);count;if(count
默认情况下解除对于2号信号的block的时候,2号信号确实会递达//
--signal){if(sigismember(pending,
oldset;sigemptyset(set);sigemptyset(oldset);//
pending;sigemptyset(pending);int
获取当前进程的pending信号集sigpending(pending);//
打印pending信号集showPending(pending);sleep(2);count;if(count
语句和捕捉的顺序就是一个打印的顺序问题。
所有的信号发送方式都是修改
block我们是不是就写了一个不会被异常终止或者用户杀掉的进程呢我们也通过代码来验证一下
--signal){if(sigismember(pending,
set;sigemptyset(set);sigaddset(set,
pending;while(1){sigpending(pending);showPending(pending);sleep(1);}return
在上面提及到信号产生之后进程可能无法立即处理进程需要在合适的时候去处理信号。
那这个合适的时候是什么呢带着这个问题我们来探究一下信号处理的整个流程
内部属于内核范畴普通用户无法对信号进行检测和处理。
那么要对信号进行处理就需要在内核状态。
当执行系统调用或被系统调度时进程所处的状态就是内核态不执行操作系统的代码时进程所处的状态就是用户态。
现在我们已经知道需要在内核态下进行信号处理那究竟具体是什么时候呢结论在内核态中从内核态返回用户态的时候进行信号的检测和处理如何进入内核态呢进行系统调用或产生异常等。
汇编指令int
是中断编号可以进程进入内核态也就是将代码的执行权限从普通用户转交给操作系统让操作系统去执行注汇编指令int
表示执行系统默认动作赋值为一个函数指针表示用自定义函数捕捉信号或者说向内核注册了一个信号处理函数。
该函数返回值为
int通过参数可以得知当前信号的编号这样就可以用同一个函数处理多种信号。
显然这也是一个回调函数不是被
实时信号的标记位sigemptyset(act.sa_mask);act.sa_handler
}处理信号、执行自定义动作的时候如果在处理信号期间又来了同样的信号操作系统该如何处理呢Linux
的设计方案是在任何时候操作系统只能处理一层信号不允许出现信号正在处理又来信号再被处理的情况。
操作系统无法决定信号什么时候来但可以决定什么时候去处理信号。
接下来要一起探讨的是为什么要有信号屏蔽字
当某个信号的处理函数被调用时内核自动将当前信号加入进程的信号屏蔽字当信号处理函数返回时自动恢复原来的信号屏蔽字这样就保证了在处理某个信号时如果这种信号再次产生那么它会被阻塞到当前处理结束为止。
如果在调用信号处理函数时除了当前信号被自动屏蔽之外还希望自动屏蔽另外一些信号则用
字段说明这些需要额外屏蔽的信号当信号处理函数返回时自动恢复原来的信号屏蔽字。
是实时信号的处理函数本章不详细解释这两个字段有兴趣的伙伴可以再了解一下。
验证2号信号被捕捉期间,再次发送2号信号不会去处理sigset_t
6;while(1){sigpending(pending);showPending(pending);--c;if(!c)
实时信号的标记位sigemptyset(act.sa_mask);act.sa_handler
验证2号信号被捕捉期间,再次发送2号信号不会去处理sigset_t
7;while(1){sigpending(pending);showPending(pending);--c;if(!c)
实时信号的标记位sigemptyset(act.sa_mask);act.sa_handler
7号信号也被blocksigaddset(act.sa_mask,
中插入节点node1插入操作分为两步。
刚做完第一步的时候因为硬件中断使进程切换到内核再次回用户态之前检查到有信号待处理于是切换到
函数中继续往下执行先前做第一步之后被打断现在继续做完第二步。
结果是main
先后向链表中插入两个节点而最后只有一个节点真正插入链表中了。
像上例这样insert
函数被不同的控制流程调用有可能在第一次调用还没返回时就再次进入该函数这称为重入。
insert
函数访问一个全局链表有可能因为重入而造成错乱像这样的函数称为不可重入函数。
反之如果一个函数只访问自己的局部变量或参数则称为可重入(Reentrant)
函数。
想一下为什么两个不同的控制流程调用同一个函数问它的同一个局部变量或参数就不会造成错乱可重入和不可重入是函数的一种特征目前我们用的函数90%
进行了修改也没有办法结束进程。
这是为什么呢正常情况下每次循环通过
进行检测时都需要到内存中去数据但是编译优化编译的时候已经进行了优化后编译器认为
进行修改所以为了提高效率第一次过后就不去内存中取数据了而是直接读取寄存器中的值来进行循环检测。
而实际情况是内存中
函数清理僵尸进程父进程可以阻塞等待子进程结束也可以非阻塞地查询是否有子进程结束等待清理也就是轮询的方式。
采用第一种方式父进程阻塞了就不能处理自己的工作了采用第二种方式父进程在处理自己的工作的同时还要记得时不时地轮询一
信号的处理函数这样父进程只需专心处理自己的工作不必关心子进程了子进程终止时会通知父进程父进程在信号处理函数中调用
出来的子进程在终止时会自动清理掉不会产生僵尸进程也不会通知父进程。
系统默认的忽略动作和用户用
endl;sleep(1);exit(0);}while(true)
getpid());sleep(3);exit(1);}while
如果我们不想等待子进程,并且我们还想让子进程退出之后,自动释放僵尸子进程
endl;sleep(5);exit(0);}while(true){cout
本篇博客主要讲解了什么是信号、信号如何产生、阻塞信号、捕捉信号、可重入函数以及
信号等。
那么以上就是本篇博客的全部内容了如果大家觉得有收获的话可以点个三连支持一下谢谢大家❣️
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback