掘金 后端 ( ) • 2024-05-06 11:04

关注微信公众号:Linux内核拾遗

文章来源:https://mp.weixin.qq.com/s/nd4vEybTTcSKzHnDV0wyjg

本文将对操作系统的中断机制进行详细的介绍,主要涉及理论知识和相关原理,不包含内核API介绍或者代码演示,可能有些枯燥乏味,但这些知识对后续理解Linux内核中设备驱动中断注册和中断处理有很大帮助。

1 “操作系统是由中断驱动的”

有计算机专业知识背景的同学或许都听说过这样的说法:**==操作系统是由中断驱动的。==**如何理解这句话?

中断是操作系统的核心机制,操作系统是通过中断来响应和处理各种事件和请求,包括硬件设备和软件请求,以响应系统事件、管理系统资源、实现多任务处理和处理异常情况等,并确保系统的稳定性和可靠性。

  1. 事件响应: 操作系统需要及时响应来自硬件设备或软件程序的各种事件和请求,例如键盘输入、定时器触发、网络数据到达、系统调用等。这些事件通过触发中断,引导操作系统进行相应的处理。
  2. 资源管理: 中断机制有助于操作系统有效地管理计算机系统的各种资源,包括处理器、内存、I/O 设备等。操作系统基于中断来处理任务调度、内存分配、设备驱动程序的加载和卸载等操作。
  3. 多任务处理: 中断机制使得操作系统能够实现多任务处理的能力。当一个任务等待外部事件完成时,操作系统可以切换到另一个任务,并在事件发生时立即响应,以提高系统的效率和响应速度。
  4. 异常处理: 中断还用于处理异常情况,如硬件故障、非法操作、系统错误等。操作系统通过中断及时地检测和处理这些异常,以确保系统的稳定性和可靠性。

2 中断 vs 轮询

论及中断,则不得不提到操作系统的另一种事件处理机制:Polling,轮询。

这两种机制的工作原理和特点差异还是很明显的:

  • 工作原理:
    • 中断:只有当设备需要请求CPU进行处理时,它才会向CPU发送中断。CPU在接收到中断请求后,会中断当前正在执行的程序,保存当前状态,并转去执行与该中断相关的中断服务程序(Interrupt Service Routine,ISR)来处理这个事件。
    • 轮询:不论设备是否需要CPU进行处理,CPU都需要定期查询设备的状态,以检查是否有事件发生。如果发现有事件发生,则执行相应的操作;如果没有,则继续查询。
  • 特点:
    • 中断:(1)即时响应:中断允许系统在有事件发生时立即响应,而不是等待CPU的轮询(2)高效利用CPU:中断允许CPU在等待事件发生时执行其他任务,提高了系统的效率(3)复杂性:中断处理需要复杂的硬件和软件支持,包括中断控制器和中断向量表等。
    • 轮询:(1)简单性:轮询相对于中断而言,实现相对简单(2)资源消耗:轮询需要持续消耗CPU时间来检查设备状态,即使设备并没有要处理的数据,可能会导致资源浪费(3)响应时间:对于响应时间敏感的应用程序,轮询可能不够高效。

在选择中断或者轮询机制时,二者并没有绝对的孰优孰劣之分,而是需要根据系统的需求和性能要求进行权衡:

  • 中断通常更适合需要即时响应的场景;
  • 轮询更适合简单的应用或对实时性要求不高的场景。

3 中断 vs 异常

一般都会将中断和异常放在一起讨论。与中断不同,异常与处理器时钟是同步发生的,因此异常也被称为同步中断。异常由处理器在执行指令时产生,响应于编程错误(例如除零)或必须由内核处理的异常情况(例如页面错误)。由于许多处理器架构以类似于中断的方式处理异常,因此内核中处理这两种情况的基础设施是类似的。

可以将两者进行简单的界定:

  • 中断:由硬件生成的异步中断;
  • 异常:由处理器生成的同步中断。

在x86架构中,系统调用(异常的一种类型)通过发出软中断来实现,这将陷入内核并导致执行特殊的系统调用处理程序。中断的工作方式类似,不同之处在于中断是由硬件(而不是软件)发出的。

中断和异常还可以进一步分类。

  • 中断:
    • 可屏蔽中断(Maskable Interrupts):所有I/O设备发出的中断请求(IRQs)都产生可屏蔽中断。可屏蔽中断可以处于两种状态:屏蔽或未屏蔽;当中断处于屏蔽状态时,控制单元将忽略它。
    • 非可屏蔽中断(Non-maskable Interrupts,NMI):只有少数关键事件(如硬件故障)会产生不可屏蔽中断。不可屏蔽中断始终会被CPU识别并处理。
  • 异常:
    • 故障(Faults):例如除零、页错误、分段错误。
    • 陷阱(Traps):在执行陷阱指令后立即报告。例如断点。
    • 终止(Aborts):用于报告严重错误,例如硬件故障和系统表中的无效或不一致值。

4 中断处理机制

中断是由硬件设备产生的电子信号,发送到中断控制器的输入引脚。这个中断控制器是一个简单的芯片,将多个中断线路复用为单一线路,然后发送到处理器。

中断的处理过程如下:

  1. 当接收到中断时,中断控制器向处理器发送信号。
  2. 处理器检测到该信号后,中断当前的执行来处理该中断。
  3. 处理器随后通知操作系统发生了中断,由于不同设备使用不同的唯一值关联到不同的中断,从而使操作系统能够区分是哪个硬件设备引起的该中断,并为每个中断调用相应的处理程序。

中断处理作为内核执行的最敏感任务之一,它必须满足以下要求:

  • 硬件设备异步生成中断(相对于处理器时钟)。这意味着中断可以随时发生。
  • 因为中断可以随时发生,内核可能正在处理其中一个中断时又发生了另一个中断(类型不同)。
  • 内核代码中存在一些关键区域,必须禁用中断。这些关键区域必须尽可能地受限。

5 中断服务例程

对于设备的每一个中断,相对应的设备驱动程序都必须注册一个中断处理程序(Interrupt Handler)。

中断处理程序,或者说中断服务例程(Interrupt Service Routine,ISR),它是内核响应特定中断时执行的函数:

  • 每个生成中断的设备都有一个关联的中断处理程序。
  • 设备的中断处理程序是设备驱动程序的一部分。

在Linux中,需要将中断处理程序与其他内核函数区分开来。对于中断处理程序,内核以对中断做出响应的方式调用它们,并且它们在称为中断上下文的特殊上下文中运行。这种特殊的上下文有时被称为原子上下文,因为在此上下文中执行的代码无法阻塞。

中断可以在任何时候发生,因此中断处理程序可能在任何时候执行。处理程序的运行必须尽可能快,以尽快恢复被中断的代码的执行。对于硬件来说:

  • 操作系统必须立即响应和服务中断。
  • 对于系统的其他部分来说,中断处理程序的执行时间应尽可能短。

6 中断上下文 vs 进程上下文

操作系统内核通过进程上下文和中断上下文的组合来完成任务的处理。

服务于用户程序发出的系统调用的内核代码,是代表相应的应用程序进程运行的,并且是在进程上下文中执行。而中断处理程序则在中断上下文中异步运行。进程上下文与任何中断上下文都没有直接联系,反之亦然。

在进程上下文中运行的内核代码是可抢占的;而中断上下文则始终运行到完成,并且不能被抢占。因此中断上下文中执行的代码不能执行以下的操作:

  • 休眠或者释放处理器。
  • 获取互斥锁(mutex)。
  • 执行耗时任务。
  • 访问用户空间虚拟内存。

7 中断上半部和下半部

前面讲到,中断服务例程不应该执行耗时任务。但是在实际的任务中,不可避免会碰到需要在收到中断时完成大量的工作,例如在DeviceMapper dm-crypt模块中,它在收到磁盘设备数据读取完成的中断请求后,需要对磁盘数据执行耗时的解密操作。

当发生这种情况时,往往会带来以下的问题:

  • 当最高优先级的中断服务例程正在运行时,它不允许响应和处理其他中断。
  • 相同类型的中断将被忽略。

为了解决这些问题,Linux内核中将中断的处理过程划分成两部分或者说两个阶段:上半部(Top halves)和下半部(Bottom halves)。

7.1 中断上半部

前面所说的中断服务例程(ISR)则为上半部。中断上半部将在接收到中断后立即运行,并且仅执行那些时间敏感的工作,例如确认接收到中断或复位硬件。中断上半部执行时是需要屏蔽相对应中断的,在执行完成后重新打开中断。

7.2 中断下半部

中断下半部用于处理数据,使得中断上半部能够继续处理新到达的中断。中断下半部会在将来合适的时间被内核调度执行,期间中断是处于打开状态的。

中断下半部并不是必须的,如果ISR能够很快地(例如几微秒)响应和处理中断请求,则没必要划分中断上下部分。

Linux内核提供了4种中断下半部处理机制:Workqueue、Threaded IRQs、Softirq和Tasklets。

后续将会对这几种处理机制进行详细的介绍。

关注微信公众号:Linux内核拾遗

文章来源:https://mp.weixin.qq.com/s/nd4vEybTTcSKzHnDV0wyjg