关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

【linux】信号的保存和递达处理(二)

发布时间:2023-06-29 14:00:38
  四个交点(四次身份切换)        在用户态中因为一些原因陷入内核,执行系统调用后,在内核态中再进行信号的检测过程,再由内核态切换到用户态执行方法,完毕后再切换身份回到内核态,通过信号检测结束后,再身份切换,回到进程执行流中上次中断的地方。 三、sigset_t 信号集                我们知道信号是在进程的pcb中,即内核中。所以用户级操作难免会困难一些。所以sigset_t 信号集就是为了更好的在用户级操作信号所产生的类型。sigset_t 信号集包括 pending信号集、信号屏蔽字(block信号集)。sigset_t 底层就是一个大数组实现的位图结构。 信号集操作函数: #include int sigemptyset(sigset_t *set);                  //初始化set信号集 int sigfillset(sigset_t *set);                        //将信号集set全部设置为1 int sigaddset (sigset_t *set, int signo);     //往set信号集添加信号 int sigdelset(sigset_t *set, int signo);       //删除set信号集中的信号 这四个函数都是成功返回0,出错返回-1 。 int sigismember(const sigset_t *set, int signo);        //判断信号是否在set中 sigismember 是一个布尔函数 , 用于判断一个信号集的有效信号中是否包含某种信号, 若包含则返回 1, 不包含则返回 0, 出错返回 -1 。 sigprocmask 调用函数 sigprocmask 可以读取或更改进程的信号屏蔽字 ( 阻塞信号集) #include int sigprocmask(int how, const sigset_t *set, sigset_t *oset); how就是下面的几种方式: 返回值 : 若成功则为 0, 若出错则为 -1 sigpending #include int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 返回值 : 若成功则为 0, 若出错则为 -1 读取当前进程的未决信号集 , 通过 set 参数传出。调用成功则返回 0, 出错则返回 -1。 下面我们利用上面所学,来实现一个观察pending信号集,通过信号屏蔽子来观察pending信号集的变化: #include #include #include #include // #define BLOCK_SIGNAL 2 #define MAX_SIGNUM 31 using namespace std; static vector sigarr = {2}; //输出pending信号集 static void show_pending(const sigset_t &pending) { for(int signo = MAX_SIGNUM; signo >= 1; signo--) { if(sigismember(&pending, signo)) { cout << "1"; } else cout << "0"; } cout << "\n"; } //递达自定义动作 static void myhandler(int signo) { cout << signo << " 号信号已经被递达!!" << endl; } int main() { for(const auto &sig : sigarr) signal(sig, myhandler); // 1. 先尝试屏蔽指定的信号 sigset_t block, oblock, pending; // 1.1 初始化 sigemptyset(&block); sigemptyset(&oblock); sigemptyset(&pending); // 1.2 添加要屏蔽的信号 for(const auto &sig : sigarr) sigaddset(&block, sig); //批量化屏蔽 // 1.3 开始屏蔽,设置进内核(进程) sigprocmask(SIG_SETMASK, &block, &oblock); // 2. 遍历打印pengding信号集 int cnt = 10; while(true) { // 2.1 初始化 sigemptyset(&pending); // 2.2 获取它 sigpending(&pending); // 2.3 打印它 show_pending(pending); // 3. 慢一点 sleep(1); if(cnt-- == 0) { sigprocmask(SIG_SETMASK, &oblock, &block); // 一旦对特定信号进行解除屏蔽,一般OS要至少立马递达一个信号! cout << "恢复对信号的屏蔽,不屏蔽任何信号\n"; } } } 四、信号的处理细节 4.1 对于同类型信号的处理        当我们正在递达一个信号期间,同类型的信号无法被递达!(信号的处理细节)        当信号正在被递达中,又来了同类型的信号,此时当前信号会被加入到进程的信号屏蔽字,且会将pending中该信号对应的那一位由0变为1。(因为该信号被递达前,会将pending中对应的那一位由1改为0),若结束递达后,同类型仍发送,则会继续重复上面的动作。但若结束递达后,同类型的信号没有发送了,进程就只会再捕捉一次,将pending中的1改为0。递达后则继续检其他信号进行递达。        进程处理信号的原则是穿行的处理同类型的信号,不允许递归处理! 4.2 可重入函数和不可重入函数        举例说明:       在main执行流中,没有头结点的单链表进行头插,如上图所示:在执行到第一步时,此时被信号中断,结果导致main中还没有执行完又进入insert()中,最后回到main执行流中,再执行完剩下的代码结果导致内存泄漏等问题。        1.一般而言,main执行流和信号捕捉执行流是两个执行流!        2.如果在main中,和在handler中,该函数被反复进入:1出现问题的就是不可重入函数;2.没有出现问题的就是可重入函数。当然可重入和不可重入只是他们的特性,没有好坏之分。 4.3 volatile关键字        我们在读取变量的值时,一般会从内存中读取,但是由于编译器的优化,就会将内存中的值加载到cpu的寄存器中,从而之后访问该变量的值只会从寄存器中读取,如果这个变量的值被修改了,自然而然内存上的值也被修改了,但是寄存器中的值仍然没有变化,还是修改之前的值,所以为了避免这种优化产生的后果,我们就会在变量前加上volatile,意为一直从内存中读取值! 总结:        我们了解了信号的保存原来是通过进程pcb中的pending、block位图,handler函数指针数组来进行保存,从而信号递达。  

/template/Home/leiyu/PC/Static