[Linux]内核如何对信号进行捕捉

发布于:2025-07-05 ⋅ 阅读:(19) ⋅ 点赞:(0)

要理解Linux中内核如何对信号进行捕捉,我们需要很多前置知识的理解:

  • 内核态和用户态的区别
  • CPU指令集权限
  • 内核态和用户态之间的切换

由于文章的侧重点不同,上面这些知识我会在这篇文章尽量详细提及,更详细内容还得请大家查看这篇文章:
内核态与用户态详解


一、内核态和用户态的区别

内核态和用户态本质上是对CPU状态的描述。内核态的权限最大,能够执行一切“特权代码”。而用户态则受限制,必须切换到内核态才能执行“特权代码”。

这就意味着内核态能够调度全部硬件资源,本质上就是对权限进行保护。由于硬件资源非常复杂,稍有不慎就会出现很大的问题,所以不是人人都有权限去调度。这样做就是为了安全性和稳定性。


二、CPU指令集权限

具体来说每一条汇编指令都对应一条CPU指令,非常多的CPU指令组成了CPU指令集。指令集则通过CPU实现软件对硬件的调度
同时CPU指令有分级权限。CPU指令能够操作硬件资源,硬件资源是非常复杂的,很容易出问题。所以操作系统直接屏蔽掉用户对CPU指令集的操作

CPU指令分级总共有四级,从高到低依次为:

  • ring0
  • ring1
  • ring2
  • ring3

Linux系统仅采用ring0和ring3这2个权限。用户态的程序工作在3,内核态的程序处于0:

  • ring0权限最高,可以使用所有CPU指令集,有对硬件的所有操作权限
  • ring3权限最低,仅能使用常规CPU指令集,不能使用操作硬件资源的CPU指令集。代码没有对硬件的直接控制权限,也不能直接访问地址的内存,程序是通过调用系统接口(System Call APIs)来达到访问硬件和内存

三、内核态和用户态之间的切换

1.如何理解进程切换?

  • 在当前进程的进程地址空间中的内核空间,找到操作系统的代码和数据。
  • 执行操作系统的代码,将当前进程的代码和数据剥离下来,并换上另一个进程的代码和数据。

注意: 当你访问用户空间时你必须处于用户态,当你访问内核空间时你必须处于内核态

从用户态切换为内核态通常有如下几种情况:

  • 需要进行系统调用时。
  • 当前进程的时间片到了,导致进程切换。
  • 产生异常、中断、陷阱等。

与之相对应,从内核态切换为用户态有如下几种情况:

  • 系统调用返回时。
  • 进程切换完毕。
  • 异常、中断、陷阱等处理完毕。

其中,由用户态切换为内核态我们称之为陷入内核。每当我们需要陷入内核的时,本质上是因为我们需要执行操作系统的代码,比如系统调用函数是由操作系统实现的,我们要进行系统调用就必须先由用户态切换为内核态

2.系统调用

用户态要主动切换到内核态要有统一的入口,它们就是内核提供的系统调用接口,下面是Linux整体架构图:
在这里插入图片描述
我们可以看出来通过系统调用将Linux整个体系分为内核态和用户态,而内核提供了一组通用的访问接口,它们使应用程序能访问到内核的资源,如CPU、内存、I/O,这些接口就叫系统调用


四、内核如何实现信号的捕捉?

当我们在执行主控制流程的时候,可能因为某些情况而陷入内核,当内核处理完毕准备返回用户态时,就需要进行信号pending的检查。(此时仍处于内核态,有权力查看当前进程的pending位图)

在查看pending位图时,如果发现有未决信号,并且该信号没有被阻塞,那么此时就需要该信号进行处理。

1.待处理信号是默认或忽略

如果待处理信号的处理动作是默认或者忽略,则执行该信号的处理动作后清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,从主控制流程中上次被中断的地方继续向下执行即可。
在这里插入图片描述

2.待处理信号是自定义捕捉的

但如果待处理信号是自定义捕捉的,即该信号的处理动作是由用户提供的,那么处理该信号时就需要先返回用户态执行对应的自定义处理动作,执行完后再通过特殊的系统调用sigreturn再次陷入内核并清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,继续执行主控制流程的代码。
在这里插入图片描述
注意: sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。

3.巧记

当待处理信号是自定义捕捉时的情况比较复杂,可以借助下图进行记忆:
在这里插入图片描述
其中,该图形与直线有4个交点就代表在这期间有4次状态切换,而箭头的方向就代表着此次状态切换的方向,图形中间的圆点就代表着检查pending表


当识别到信号的处理动作是自定义时,能直接在内核态执行用户空间的代码吗?不能!!!

  • 理论上来说是可以的,因为内核态是一种权限非常高的状态,但是绝对不能这样设计。
  • 如果允许在内核态直接执行用户空间的代码,那么用户就可以在代码中设计一些非法操作,比如清空数据库等,虽然在用户态时没有足够的权限做到清空数据库,但是如果是在内核态时执行了这种非法代码,那么数据库就真的被清空了,因为内核态是有足够权限清空数据库的。
  • 也就是说,不能让操作系统直接去执行用户的代码,因为操作系统无法保证用户的代码是合法代码,即操作系统不信任任何用户。

特别鸣谢:
Linux进程信号
内核态与用户态详解


网站公告

今日签到

点亮在社区的每一天
去签到