信号保存
信号相干的概念
信号递达:指 操纵体系 将一个信号(Signal)从内核传递到目的进程 的过程。它是 信号处置处罚机制 中的关键步调。
信号未决:信号从产生到递达之间的状态
信号壅闭 进程或线程可以暂时屏蔽某些信号,使它们在壅闭期间不会递达和处置处罚。一旦解除壅闭,信号会被递达并处置处罚。
被壅闭的信号将保持未决状态,直到进程解除对此信号的壅闭,才能实验递达的动作。
留意:壅闭信号和忽略信号差异,壅闭信号表现信号没有递达,但是忽略信号表现信号已经抵达了,但是我们的处置处罚方式是忽略处置处罚。
信号是怎样保存的呢?
在task_struct中有三张表,分别是下面三张:
前两张表是位图,第三张表是函数指针表。
我们从左到右提及:
第一张表是位图,比特位的位置是信号编号,比特位的含义是是否壅闭信号,1表现壅闭当前编号的信号,0表现为壅闭当前信号。
第二张表也是位图,比特位的位置是信号编号,比特位的含义是是否收到信号,1表现收到信号,0表现未收到信号。
第三张表是函数指针表,表现每个信号对应方法,当收到信号之后,我们将拿着信号编号,然后在handler表中查找对应信号的方法。
内核中三个表的布局,如上图。
有关信号保存的体系调用
sigprocmask
这个函数的作用是获取大概设置当前进程的block表。
第一个参数表现我们用这个函数干嘛,有以下三种选项:
三种选项的作用也有批注。
在讲第二个参数之前我们先讲讲sigset_t这个类型:
sigset_t是一个用于表现 信号集合的数据类型,定义在 <signal.h> 头文件中。它通常用于 壅闭信号、解除信号壅闭 和 检查信号 等操纵。
第二个参数是新的信号集,是我们修改后的信号集,而第三个参数是旧的信号集,是修改之前的信号集,方便我们修改之后方便恢复。
信号的增删查改
上面五个函数是增删查改,第一个函数是将一个信号集置为零,第二个函数是将信号集全部设置为1,第三个函数是添加新的信号到信号集当中,第四个函数表现在信号集中删除指定信号,第五个函数是在指定信号集中查找指定信号。
检察pending表
这个函数很简朴,参数是输出参数,可以将当前进程的pending表输出出来,但是为什么没有设置pending表的呢?由于产生信号的方式有许多种,我们在上一章中讲到过,我们拿一个作为例子,比如说下令kill就可以产生信号。
我们讲完上面的接口,我们来写一个简朴的代码,来验证一下,这些接口是否有屏蔽信号的功能。
验证接口
代码
- #include <iostream>
- #include <signal.h>
- using namespace std;
- void Printf(const sigset_t& pending)
- {
- for(int i = 31;i > 0;i--)
- {
- if(sigismember(&pending,i) == 1)
- {
- cout<<1;
- }
- else cout<<0;
- }
- cout<<endl;
- }
- int main()
- {
- sigset_t block,oblock;
- //重置两个block表
- sigemptyset(&block);
- sigemptyset(&oblock);
- //设置屏蔽信号
- sigaddset(&block,2);
- //将信号设置进内核
- sigprocmask(SIG_BLOCK,&block,&oblock);
- sigset_t pending;
- while(true)
- {
- //打印pending表
- sigpending(&pending);
- Printf(pending);
- sleep(1);
- }
- return 0;
- }
复制代码 运行结果
为什么当我们输入ctrl+c的时候,为什么在pending表中会不停存在2号信号呢?由于我们之前做了壅闭,当收到2号信号的时候,将其壅闭,以是pending表中会不停受到信号。以是怎样解决这种情况呢?
我们可以定义一个计数器,当计数器走到10的时候将2号信号进入递达状态。
代码:
- #include <iostream>
- #include <signal.h>
- using namespace std;
- void Printf(const sigset_t& pending)
- {
- for(int i = 31;i > 0;i--)
- {
- if(sigismember(&pending,i) == 1)
- {
- cout<<1;
- }
- else cout<<0;
- }
- cout<<endl;
- }
- int main()
- {
- sigset_t block,oblock;
- //重置两个block表
- sigemptyset(&block);
- sigemptyset(&oblock);
- //设置屏蔽信号
- sigaddset(&block,2);
- //将信号设置进内核
- sigprocmask(SIG_BLOCK,&block,&oblock);
- sigset_t pending;
- int count = 0;
- while(true)
- {
- //打印pending表
- sigpending(&pending);
- Printf(pending);
- if(count == 10)
- {
- sigprocmask(SIG_SETMASK,&oblock,nullptr);
- cout<<"Unblock signal"<<endl;
- }
- count++;
- sleep(1);
- }
- return 0;
- }
复制代码 结果
由于ctrl+c默认处置处罚方式就是竣事进程以是这里,我们看到的是竣事,没有看到pending表的变化,我们参加signal函数进行信号捕获,进行我们的自定义方法,不竣事进程,检察pending表的变化状态:
可以瞥见,当信号从屏蔽字中去除的时候,实验自定义方法,然后pending表中2号信号消失。
信号捕获
用户态与内核态
在操纵体系中,CPU 告急运行在 用户态(User Mode) 或 内核态(Kernel Mode)。这两种模式是 操纵体系的特权级别,用于保护体系的安全和稳定性。
- 用户态(User Mode):
- 权限受限,不能直接访问硬件(如磁盘、网络、内存管理等)。
- 运行用户进程(如应用步调)。
- 只能通过体系调用哀求内核提供服务(比如 read()、write()、open())。
- 内核态(Kernel Mode):
- 最高权限,可以直接访问所有资源(如 CPU、内存、I/O 装备)。
- 运行操纵体系内核代码(进程管理、文件体系、装备驱动等)。
- 可以直接操纵硬件,比如修改页表、控制装备中断。
CPU 通过 模式位(mode bit) 来区分这两种状态:
- 0 表现 内核态(特权模式)。
- 1 表现 用户态(受限模式)。
信号捕获流程
之前讲到处置处罚信号是在符合的时候处置处罚的,什么是符合的时候呢?
进程从内核态切换到用户态的时候,操纵体系检测当前进程的pending表&&block表,决定是否处置处罚handler表处置处罚信号
假如我们写了一个代码,当我们进行某些体系调用的时候,会出现中断,中断之后会进入内核态,当进入内核态之后,会处置处罚当前进程中可以传递信号,如果信号的处置处罚方式是自定义处置处罚方式会直接返回用户态调用自定义方法,处置处罚完后返回内核态,从上次中断的地方继承实验。
总结
通过本文的探究,我们深入了解了Linux中进程信号的保存和捕获机制。信号作为进程间通信的一种告急方式,能够有用地处置处罚异步事件和异常情况。我们学习了信号的根本概念、信号的保存方式(如信号掩码和未决信号集),以及怎样通过信号处置处罚函数来捕获和处置处罚信号。
在现实应用中,公道地利用信号机制可以大大提高步调的结实性和响应能力。然而,信号处置处罚也必要留意一些细节,例如信号处置处罚函数的可重入性、信号竞争条件的避免等。把握这些知识点,能够资助我们在编写多进程、多线程步调时更加得心应手。
希望本文的内容能够资助你更好地明确Linux信号机制,并在现实开发中灵活运用。如果你有任何题目或发起,接待在评论区留言讨论!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |