/proc/interrupts
cat 这个节点,会打印系统中所有的中断信息,如果是多核CPU,每个核都会打印出来。
包括每个中断的名字、中断号 IRQ number、每个中断的触发次数、在哪个CPU核处理的、是边沿触发还是电平触发,属于哪个中断控制器,都会打印出来。
/proc/irq/…
进入这个目录。会看到以中断号命名的文件夹,每个中断号文件夹下面都有几个节点,存储了这个中断的信息,比如 smp_affinity、affinity_hint、spurious等。smp_affinity 代表中断号核CPU之间的亲缘绑定关系,也就是如果某个中断号绑定了一个CPU核,那么这个中断就会一直在这个CPU上处理。
kernel 2.4 以后的版本才支持把不同的硬件中断请求(IRQs)分配到特定的 CPU 上,这个绑定技术被称为 SMP IRQ Affinity. 更多介绍请参看 Linux 内核源代码自带的文档:linux-4.14/Documentation/IRQ-affinity.txt
/proc/irq/{IRQ}/smp_affinity
/proc/irq/{IRQ}/smp_affinity_list
/proc/irq/{IRQ}/smp_affinity 指定给定的 irq 中断号源允许哪些CPU执行,它是一个掩码位,比如是ff,代表11111111,表示这个中夺冠可以在 8 个 CPU 执行,具体在哪一个 CPU 执行,靠分配器分配。
如果这个 /proc/irq/{IRQ}/smp_affinity 指定为 00000001,代表这个IRQ只能在最后一个CPU核进行处理,其他CPU不允许处理,大家可以测试一下,博主测试是 OK 的(GIC支持,其他中断控制器不一定)。
串口手动赋值的重启以后会消失,可以在代码中调用 irq_set_affinity 函数,指定中断的掩码,来达到某个中断被固定CPU处理的需求。
对于 GIC-V2 而言,SPI 的分发是根据 Distributor 中的 Interrupt Processor Targets Registers 来决定的。对于任何一个 SPI,其都有在某个 GICD_ITARGETSRn 寄存器中有 8 个bit标识送达的 processor,如果只有一个 bit 被 set,那么就很简单了,如果该中断是当前优先级最高的中断,那么 Distributor 就会送到对应的 CPU interface,该中断最终会送达指定的 CPU。
如果该中断对应的 Interrupt Processor Targets Registers 中的那 8 个 bit 有多个 bit 被 set 的话,Distributor 如何处理呢?“依次轮着把产生的中断给各个 CPU,还是说看哪个CPU有空就给哪个CPU来着”,让硬件处理这么复杂的逻辑有些不合适,实际上,GIC 的硬件是不会进行任何判断的,也不会集成任何的算法,它就是根据Interrupt Processor Targets Registers的bit设定情况,忠实的把中断送往指定的一个processor或者多个processors。
大家可以去看看 gic_set_affinity 这个函数,这个函数确保一个中断的 Interrupt Processor Targets Registers 中的那8个bit只有一个bit被设定。
/kernel5.15/drivers/irqchip/irq-gic-v3.c
在 1244 和 1246 行,1246 行就是在 online 的 CPU 中选中一个,1263 行写入到寄存器中,GIC 会读取这个寄存器,是哪个 CPU,然后将中断发给这个CPU。中间的函数很简单,大家可以自己看。
对于 GIC-V2 而言,中断的状态机由 Distributor 维护,每个中断都有一个状态机。
Inactive :中断未激活(未发生)。
Pending:中断到达 GIC ,等待 CPU 的处理。
Active:中断得到 CPU 的应答,中断被CPU处理。
Active and pending :某个中断正在被 CPU 处理,这时候该中断又来了。
来看一个例子:
(a)N 和 M 用来标识两个外设中断,N 的优先级大于 M
(b)两个中断都是 SPI 类型,level trigger,active-high
(c)两个中断被配置为去同一个 CPU
(d)都被配置成 group 0,通过 FIQ 触发中断
时刻 | 事件 |
---|---|
T0时刻 | Distributor检测到M这个interrupt source的有效触发电平 |
T2时刻 | Distributor将M这个interrupt source的状态设定为pending |
T17时刻 | 大约15个clock之后,CPU interface拉低nFIQCPU信号线,向CPU报告M外设的中断请求。这时候,CPU interface的ack寄存器(GICC_IAR)的内容会修改成M interrupt source对应的ID |
T42时刻 | Distributor检测到N这个优先级更高的interrupt source的触发事件 |
T43时刻 | Distributor将N这个interrupt source的状态设定为pending。同时,由于N的优先级更高,因此Distributor会标记当前优先级最高的中断 |
T58时刻 | 大约15个clock之后,CPU interface拉低nFIQCPU信号线,向CPU报告N外设的中断请求。当然,由于T17时刻已经assert CPU了,因此实际的电平信号仍然保持asserted。这时候,CPU interface的ack寄存器(GICC_IAR)的内容会被更新成N interrupt source的ID |
T61时刻 | 软件通过读取 ack 寄存器的内容,获取了当前优先级最高的,并且状态是pending的interrupt ID(也就是N interrupt source对应的ID),通过读该寄存器,CPU也就ack了该interrupt source N。这时候,Distributor将N这个interrupt source的状态设定为pending and active(因为是电平触发,只要外部仍然有asserted的电平信号,那么一定就是pending的,而该中断是正在被CPU处理的中断,因此状态是pending and active)注意:T61标识CPU开始服务该中断 |
T64时刻 | 3个clock之后,由于CPU已经ack了中断,因此GIC中CPU interface模块 deassert nFIQCPU信号线,解除发向该CPU的中断请求 |
T126时刻 | 由于中断服务程序操作了N外设的控制寄存器(ack外设的中断),因此N外设deassert了其interrupt request signal |
T128时刻 | Distributor解除N外设的pending状态,因此N这个interrupt source的状态设定为active |
T131时刻 | 软件操作End of Interrupt寄存器(向GICC_EOIR寄存器写入N对应的interrupt ID),标识中断处理结束。Distributor将N这个interrupt source的状态修改为idle,注意:T61~T131是CPU服务N外设中断的的时间区域,这个期间,如果有高优先级的中断pending,会发生中断的抢占(硬件意义的),这时候CPU interface会向CPU assert 新的中断。 |
T146时刻 | 大约15个clock之后,Distributor向CPU interface报告当前pending且优先级最高的interrupt source,也就是M了。漫长的pending之后,M终于迎来了春天。CPU interface拉低nFIQCPU信号线,向CPU报告M外设的中断请求。这时候,CPU interface的ack寄存器(GICC_IAR)的内容会修改成M interrupt source对应的ID |
T211时刻 | CPU ack M中断(通过读GICC_IAR寄存器),开始处理低优先级的中断 |
GIC 中断控制器支持中断优先级抢占,一个高优先级中断可以抢占一个低优先级且处于active状态的中断,即GIC仲裁单元会记录和比较当前优先级最高的pending状态,然后去抢占当前中断,并且发送这个最高优先级的中断请求给CPU。
从GIC角度看,GIC 会发送高优先级中断请求给CPU。但是CPU不一定响应!!!因为在中断处理过程中,CPU处于关中断状态(关闭本CPU),需要等低优先级中断处理完毕,直到发送 EOI 给GIC,然后CPU才会响应pending状态中优先级最高的中断进行处理。所以 Linux 下:
1、高优先级中断无法抢占正在执行的低优先级中断。
2、同处于 pending 状态的中断,优先响应高优先级中断进行处理。
3、同优先级同是 pending 状态的中断,选择硬件中断号 ID 最小的一个发给CPU。
这样是可以理解的,如果万一中断大量爆发,中断如果允许嵌套的话,栈会越来越大,会爆掉,所以为了防止这种情况发生,Linux中中断不允许嵌套,单CPU中,在一个中断处理完之前,不会相应另外一个中断,哪怕优先级比它高。
FreeRTOS 中是允许高优先级中断抢占正在执行的低优先级中断,不同系统设定不一样。
进程调度是一个复杂的机制, 根据需求的不同,在不同时刻会切换调度机制,CPU会根据进程优先级、时间片等信息,对不同进程进行调度。
中断可以打断进程的运行,任意一个中断的优先级都比所有的进程高。
在中断处理过程中,主要是 GIC 和 CPU 的交互,即便 GIC 支持高优先级中断抢占正在执行的低优先级中断,发信号给 CPU core,但是 CPU core 可以不处理,因为 Linux 中当 CPU core 执行中断处理时,是关中断和关抢占的状态,不再相应中断信号。
也就意味着,在中断优先级这个概念中,只有当 GIC 同时存在多个 pending 的中断,这时候会选择优先级最高的去执行,高优先级会抢占低优先级中断(哪怕低优先级先来)。如果低优先级中断处于 active 状态,是不可以被抢占的,这是前后关系。抢占只存在于同时是pending 状态的时候。
所谓的睡眠,就是调用 schedule 让出 CPU,调度器选择另外个进程继续执行,这个过程涉及进程栈空间的切换。
1、假如中断上下文中调用 schedule ,此时获取的 struct thread info 数据结构是发生中断时该进程栈信息,而不是中断上下文调用 schedule 时任何信息。这就导致再也无法返回中断上下文中调用 schedule 的地方。
2、中断上下文处于关中断中,需要发送个 EOI 通知 GIC 中断处理结束,GIC 和CPUinterface 才会进入下一次中断处理。如果中途 schedule,那么整个系统的中断都会被屏蔽掉。
一般进入中断后,需要关中断,也会关抢占,同时注意不可以调用schedule。
未处理中断和虚假中断
在中断处理的最后,总会有一段代码如下:
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
……
if (!noirqdebug)
note_interrupt(irq, desc, retval);
return retval;
}
note_interrupt就是进行unhandled interrupt和spurious interrupt处理的。对于这类中断,linux kernel有一套复杂的机制来处理,你可以通过command line参数(noirqdebug)来控制开关该功能。
当发生了一个中断,但是没有被处理(有两种可能,一种是根本没有注册的 specific handler,第二种是有 handler,但是 handler 否认是自己对应的设备触发的中断),怎么办?毫无疑问这是一个异常状况,那么 kernel 是否要立刻采取措施将该 IRQ disable 呢?也不太合适,毕竟 interrupt request 信号线是允许共享的,直接 disable 该 IRQ 有可能会下手太狠,kernel 采取了这样的策略:如果该 IRQ 触发了 100,000 次,但是 99,900 次没有处理,在这种条件下,我们就是 disable 这个 interrupt request line。
中断线和中断号是一个意思。
相关的控制数据在中断描述符中,如下:
struct irq_desc {
……
unsigned int irq_count;--------记录发生的中断的次数,每100,000则回滚
unsigned long last_unhandled;-----上一次没有处理的IRQ的时间点
unsigned int irqs_unhandled;------没有处理的次数
……
}
本文分享自 嵌入式Linux系统开发 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有