本帖最后由 qiao--- 于 2024-4-7 21:43 编辑
本书的第二章是将Linux内核中断管理的部分,中断是Linux内核很重要的一部分,它能够让操作系统做出实时响应和异常处理,相比普通的轮询机制它还能很大程度上节约操作系统的资源。本书对于初入Linux的我而言,理解起来难度还是有点大,如果有哪里写的不好,请大家见谅。本文会重点介绍中断是如何调用系统API接口注册的。
1.中断控制器检测中断的流程
了解中断流程前,我们先来了解一下中断的状态和中断控制器。
(1)中断的状态
对于每一个中断来说,它支持的状态有不活跃状态、等待状态、活跃状态以及活跃并等待状态。不活跃(inactive)状态是指中断处于无效状态。等待(pending)状态是指中断处于有效状态,但是等待CPU响应该中断。活跃(active)状态指CPU已经响应中断。活跃并等待(active and pending)状态指CPU正在响应中断,但是该中断源又发送中断过来。
(2)中断控制器
中断控制器是计算机系统中的一个硬件组件,简称GIC,用于管理和协调各种中断信号的处理。它负责接收来自外部设备或内部系统的中断请求信号,并将这些中断请求信号传递给处理器,以触发相应的中断处理程序。
(3)中断流程
- 当GIC检测到一个中断发生时,会将该中断标记为 pending状态。
- 对于处于pending状态的中断,仲裁单元会确定目标CPU,将中断请求发送到这个CPU.
- 对于每个CPU,仲裁单元会从众多处于pending状态的中断中选择一个优先级最高的中断,发送到目标CPU的CPU接口模块上。
- CPU接口模块会决定这个中断是否可以发送给CPU。如果该中断的优先级满足要求,GIC会发送一个中断请求信号给该CPU。
- 当一个 CPU进入中断异常后,会读取GICC_IAR来响应该中断(一般由Linux内核的中断处理程序来读寄存器)。寄存器会返回硬件中断号(hardware interrupt ID),对于SGI来说,返回源CPU的ID (source processor ID)。当GIC感知到软件读取了该寄存器后,又分为如下情况。
如果该中断处于 pending状态,那么状态将变成activee
如果该中断又重新产生,那么pending将状态变成active and pending 状态。
如果该中断处于active 状态,将变成active and pending状态。
- 当处理器完成中断服务,必须发送一个完成信号结束中断(End Of Interrupt,EOI)给GIC。
2.中断是如何调用系统API接口注册的
当一个外设中断发生后,内核会执行一个函数来响应该中断,这个函数通常称为中断处理程序(intenupt handler)或中断服务例程。中断处理程序是内核用于响应中断的,并且运行在中断上下文中(和进程上下文不同)。中断处理程序最基本的工作是通知硬件设备中断已经被接收,不同的硬件设备的中断处理程序是不同的,有的常常需要做很多的处理工作,这也是Linux内核把中断处理程序分成上半部和下半部的原因。中断处理程序要求快速完成并且退出中断,但是如果中断处理程序需要完成的任务比较繁重,这两个需求就会有冲突,因此上下半部机制就诞生了。
在Linux内核里request_irq()函数是中断注册函数,但是是比较旧的接口函数,在 Linux 2.6.30内核中新增了线程化的中断注册函数request_threaded_irq()。中断线程化是实时Linux 项目开发的一个新特性,目的是降低中断处理对系统实时延迟的影响。下面是函数的原型:
其中中断标志位在系统中起着重要的作用,帮助系统管理和处理各种中断事件,下面是常用的中断标志位
request_threaded irq()函数中实现的主要操作如下:
在第1821~1824行中,完成一个例行的检查,对干那些共享中断的设备来说,这里强制要求传递一个参数dev_id。如果没有额外参数, 中断处理程序无法识别究竟是哪个外设产生的中断,通常根据dev_id查询设备寄存器来确定是哪个共享外设的中断。
在第1826行中,通过IRQ号获取irq_desc。
在第1830~1832行中,irq_settings_can_request()函数判断是否设置了_IRQ_NOREQUEST标志位,它是系统预留的,外设不可以使用这些中断描述符。另外,设置_IRQ_PER_CPU_DEVID标志位的中断描述符预留给IRQF_PERCPU 类型的中断,因此应该使用request percpu_irqO)函数注册中断。
在第1834~1838行中,主处理程序和 thread_fn 不能同时为NULL。当主处理程序为NULIL时使用默认的处理程序,irq_default_ primary_handler()函数直接返回IRQ_WAKE_THREAD,表示要唤醒中断线程。
在第1840行中,分配一个 irqaction数据结构,填充相应的成员。
在第1850行中,调用_setup_irq()函数继续注册中断。
在第1883行中,返回retval。
3.ARM64底层中断处理
当外设有事情需要报告SoC时,它会通过和SoC连接的中断引脚发送中断信号。根据中断信号类型,发送不同的波形,如上升沿触发、高电平触发等。SoC内部的中断控制器会感知到中断信号,中断控制器里的仲裁单元(distributor)会在众多CPU内核中选择一个,并把该中断分发给CPU内核。GIC和CPU内核之间通过nIRQ信号线来通知 CPU。
ARM64的处理器支持多个异常等级(exception level),其中ELO是用户模式,EL1是内核模式,也称为特权模式。EL2是虚拟化监管模式,EL3则是安全世界的模式。在 ARMv8架构下,异常分为异步异常和同步异常,其中 Linux内核中的异常属于同步异常,而IRQ和FIQ都属于异步异常。
当一个中断发生时,CPU内核感知到异常发生,硬件会自动做如下一些事情。
- 处理器的状态保存在对应的异常等级的SPSR_ ELx中。
- 返回地址保存在对应的异常等级的ELR_ ELx中。
- PSTATE 寄存器里的DAIF 域都设置为1,相当于把调试异常、系统错误(SEror)、IRQ以及FIQ都关闭了。PSTATE寄存器是ARM v8里新增的寄存器。
- 如果是同步异常,那么究竟什么原因导致的呢?具体要看ESR_ELx。
- 设置栈指针,指向对应异常等级里的栈。
- 迁移处理器等级到对应的异常等级,然后跳转到异常向量表里执行。
总结:本期测评就先到这里,这本书中对于一些接口函数和工作流程都讲的十分细致。也许是我基础太差的原因,又获取是我没阅读第一卷原因,我在初次阅读时倍感乏力,所以建议读者需要反复阅读琢磨才能领会其中的真理,有条件的朋友可以同书本系列的第一卷一起阅读效果更佳。