信号的机制——信号的发送与处理

对于硬件触发的,无论是中断,还是信号,肯定是先到内核的,然后内核对于中断和信号处理方式不同。一个是完全在内核里面处理完毕,一个是将信号放在对应的进程 task_struct 里信号相关的数据结构里面,然后等待进程在用户态去处理。当然有些严重的信号,内核会把进程干掉。但是,这也能看出来,中断和信号的严重程度不一样,信号影响的往往是某一个进程,处理慢了,甚至错了,也不过这个进程被干掉,而中断影响的是整个系统。一旦中断处理中有了 bug,可能整个 Linux 都挂了。

有时候,内核在某些情况下,也会给进程发送信号。例如,向读端已关闭的管道写数据时产生 SIGPIPE 信号,当子进程退出时,我们要给父进程发送 SIG_CHLD 信号等。

最直接的发送信号的方法就是,通过命令 kill 来发送信号了。例如,我们都知道的 kill -9 pid 可以发送信号给一个进程,杀死它。

另外,我们还可以通过 kill 或者 sigqueue 系统调用,发送信号给某个进程,也可以通过 tkill 或者 tgkill 发送信号给某个线程。虽然方式多种多样,但是最终都是调用了 do_send_sig_info 函数,将信号放在相应的 task_struct 的信号数据结构中。

  • kill->kill_something_info->kill_pid_info->group_send_sig_info->do_send_sig_info
  • tkill->do_tkill->do_send_specific->do_send_sig_info
  • tgkill->do_tkill->do_send_specific->do_send_sig_info
  • rt_sigqueueinfo->do_rt_sigqueueinfo->kill_proc_info->kill_pid_info->group_send_sig_info->do_send_sig_info

当发现一个进程应该被调度的时候,我们并不直接把它赶下来,而是设置一个标识位 TIF_NEED_RESCHED,表示等待调度,然后等待系统调用结束或者中断处理结束,从内核态返回用户态的时候,调用 schedule 函数进行调度。信号也是类似的,当信号来的时候,我们并不直接处理这个信号,而是设置一个标识位 TIF_SIGPENDING,来表示已经有信号等待处理。同样等待系统调用结束,或者中断处理结束,从内核态返回用户态的时候,再进行信号的处理。

signal_wake_up_state 的第二件事情,就是试图唤醒这个进程或者线程。wake_up_state 会调用 try_to_wake_up 方法。这个函数我们讲进程的时候讲过,就是将这个进程或者线程设置为 TASK_RUNNING,然后放在运行队列中,这个时候,当随着时钟不断的滴答,迟早会被调用。如果 wake_up_state 返回 0,说明进程或者线程已经是 TASK_RUNNING 状态了,如果它在另外一个 CPU 上运行,则调用 kick_process 发送一个处理器间中断,强制那个进程或者线程重新调度,重新调度完毕后,会返回用户态运行。这是一个时机会检查 TIF_SIGPENDING 标识位。

一个信号中断系统调用的典型逻辑。

首先,我们把当前进程或者线程的状态设置为 TASK_INTERRUPTIBLE,这样才能使这个系统调用可以被中断。

其次,可以被中断的系统调用往往是比较慢的调用,并且会因为数据不就绪而通过 schedule 让出 CPU 进入等待状态。在发送信号的时候,我们除了设置这个进程和线程的 _TIF_SIGPENDING 标识位之外,还试图唤醒这个进程或者线程,也就是将它从等待状态中设置为 TASK_RUNNING。

当这个进程或者线程再次运行的时候,我们根据进程调度第一定律,从 schedule 函数中返回,然后再次进入 while 循环。由于这个进程或者线程是由信号唤醒的,而不是因为数据来了而唤醒的,因而是读不到数据的,但是在 signal_pending 函数中,我们检测到了 _TIF_SIGPENDING 标识位,这说明系统调用没有真的做完,于是返回一个错误 ERESTARTSYS,然后带着这个错误从系统调用返回。

然后,我们到了 exit_to_usermode_loop->do_signal->handle_signal。在这里面,当发现出现错误 ERESTARTSYS 的时候,我们就知道这是从一个没有调用完的系统调用返回的,设置系统调用错误码 EINTR。

信号的发送与处理是一个复杂的过程,这里来总结一下。

  1. 假设我们有一个进程 A,main 函数里面调用系统调用进入内核。
  2. 按照系统调用的原理,会将用户态栈的信息保存在 pt_regs 里面,也即记住原来用户态是运行到了 line A 的地方。
  3. 在内核中执行系统调用读取数据。
  4. 当发现没有什么数据可读取的时候,只好进入睡眠状态,并且调用 schedule 让出 CPU,这是进程调度第一定律。
  5. 将进程状态设置为 TASK_INTERRUPTIBLE,可中断的睡眠状态,也即如果有信号来的话,是可以唤醒它的。
  6. 其他的进程或者 shell 发送一个信号,有四个函数可以调用 kill、tkill、tgkill、rt_sigqueueinfo。
  7. 四个发送信号的函数,在内核中最终都是调用 do_send_sig_info。
  8. do_send_sig_info 调用 send_signal 给进程 A 发送一个信号,其实就是找到进程 A 的 task_struct,或者加入信号集合,为不可靠信号,或者加入信号链表,为可靠信号。
  9. do_send_sig_info 调用 signal_wake_up 唤醒进程 A。
  10. 进程 A 重新进入运行状态 TASK_RUNNING,根据进程调度第一定律,一定会接着 schedule 运行。
  11. 进程 A 被唤醒后,检查是否有信号到来,如果没有,重新循环到一开始,尝试再次读取数据,如果还是没有数据,再次进入 TASK_INTERRUPTIBLE,即可中断的睡眠状态。
  12. 当发现有信号到来的时候,就返回当前正在执行的系统调用,并返回一个错误表示系统调用被中断了。
  13. 系统调用返回的时候,会调用 exit_to_usermode_loop。这是一个处理信号的时机。
  14. 调用 do_signal 开始处理信号。
  15. 根据信号,得到信号处理函数 sa_handler,然后修改 pt_regs 中的用户态栈的信息,让 pt_regs 指向 sa_handler。同时修改用户态的栈,插入一个栈帧 sa_restorer,里面保存了原来的指向 line A 的 pt_regs,并且设置让 sa_handler 运行完毕后,跳到 sa_restorer 运行。
  16. 返回用户态,由于 pt_regs 已经设置为 sa_handler,则返回用户态执行 sa_handler。
  17. sa_handler 执行完毕后,信号处理函数就执行完了,接着根据第 15 步对于用户态栈帧的修改,会跳到sa_restorer 运行。
  18. sa_restorer 会调用系统调用 rt_sigreturn 再次进入内核。
  19. 在内核中,rt_sigreturn 恢复原来的 pt_regs,重新指向 line A。
  20. 从 rt_sigreturn 返回用户态,还是调用 exit_to_usermode_loop。
  21. 这次因为 pt_regs 已经指向 line A 了,于是就到了进程 A 中,接着系统调用之后运行,当然这个系统调用返回的是它被中断了,没有执行完的错误。

此文章为11月Day18学习笔记,内容来源于极客时间《趣谈Linux操作系统》,推荐该课程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/192030.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索NLP中的核心架构:编码器与解码器的区别

❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️ 👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…

2023年【危险化学品经营单位安全管理人员】考试题及危险化学品经营单位安全管理人员模拟试题

题库来源:安全生产模拟考试一点通公众号小程序 危险化学品经营单位安全管理人员考试题是安全生产模拟考试一点通总题库中生成的一套危险化学品经营单位安全管理人员模拟试题,安全生产模拟考试一点通上危险化学品经营单位安全管理人员作业手机同步练习。…

三级分类,递归查询组织树

签名:但行好事,莫问前程。 文章目录 前言一、数据库表设计二、递归查询实现组织树1、返回结果类2、递归方法实现3、组织树最终数据展示 三、友情链接总结 前言 公司最近有个需求,涉及到商品的三级类目,需要做成组织树的形式展示&…

Spring Boot 中使用 ResourceLoader 加载资源的完整示例

ResourceLoader 是 Spring 框架中用于加载资源的接口。它定义了一系列用于获取资源的方法,可以处理各种资源,包括类路径资源、文件系统资源、URL 资源等。 以下是 ResourceLoader 接口的主要方法: Resource getResource(String location)&am…

什么是java反射机制?

类的正常加载 反射概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为jav…

2023年道路运输企业主要负责人证考试题库及道路运输企业主要负责人试题解析

题库来源:安全生产模拟考试一点通公众号小程序 2023年道路运输企业主要负责人证考试题库及道路运输企业主要负责人试题解析是安全生产模拟考试一点通结合(安监局)特种作业人员操作证考试大纲和(质检局)特种设备作业人…

FDM(傅里叶分解)

代码的使用教程 傅里叶分解(FDM) 代码原理 FDM (Frequency Division Multiplexing)是一种调制技术,将信号分成多个不同的频带进行传输,从而实现多路复用的通信方式。FDM分解原理是将不同频率的信号分解成不同的频带(子…

Flutter笔记:Matrix4矩阵变换与案例

Flutter笔记 Matrix4矩阵变换及其案例 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/134474764 【简介…

C++初阶 日期类的实现(下)

目录 一、输入输出(>>,<<)重载的实现 1.1初始版 1.2友元并修改 1.2.1简单介绍下友元 1.2.2修改 1.3>>重载 二、条件判断操作符的实现 2.1操作符的实现 2.2!操作符的实现 2.3>操作符的实现 2.4>,<,<操作符的实现 三、日期-日期的实现 …

Windows下安装RabbitMQ

1.安装Erlang 因为RabbitMQ是用Erlang语言编写的&#xff0c;所以在安装RabbitMQ之前需要先安装Erlang。 如果还未安装Erlang&#xff0c;官方下载安装包&#xff0c;点击Download Windows installer下载Erlang Downloads - Erlang/OTP 下载Erlang/OTP后&#xff0c;双击otp的…

【入门篇】1.5 redis 客户端Jedis和Lettuce对比详解

文章目录 0. 前言1. Lettuce 与Jedis 对比2.详解2.1 线程安全2.2 阻塞/非阻塞2.3 集群支持2.4 PUB/SUB模型2.5 二进制协议 3. 参考资料 0. 前言 对于Java开发者来说&#xff0c;Jedis和Lettuce是两种非常常见的Redis客户端&#xff0c;他们可以帮助开发者更容易地在Java应用中…

提升 Python 执行速度:Codon、C/C++、Rust、Numba(JIT)、Taichi、Nuitka、MatxScript

几种流行的 Python 性能加速方案对比&#xff1a;https://zhuanlan.zhihu.com/p/604519817 对于一般通用场景用户&#xff0c;对性能没有那么强烈的诉求&#xff0c;紧跟官方步伐&#xff0c;升级到最新版本的 Python 既可&#xff0c;或者使用 PyPy。Numba、Codon、Taichi 等这…