linux中断处理程序架构,[原创] Linux 中断子系统 (二)- 通用框架处理

[原创] Linux 中断子系统 (二)- 通用框架处理

背景

Read the fucking source code!

--By 鲁迅

A picture is worth a thousand words.

--By 高尔基

说明:

Kernel 版本: 4.14

ARM64 处理器, Contex-A53, 双核

使用工具: Source Insight 3.5, Visio

1. 概述

[原创] Linux 中断子系统(一)- 中断控制器及驱动分析讲到了底层硬件 GIC 驱动, 以及 Arch-Specific 的中断代码, 本文将研究下通用的中断处理的过程, 属于硬件无关层. 当然, 我还是建议你看一下上篇文章.

这篇文章会解答两个问题:

用户是怎么使用中断的(中断注册)?

外设触发中断信号时, 最终是怎么调用到中断 handler 的(中断处理)?

2. 数据结构分析

先来看一下总的数据结构, 核心是围绕着 struct irq_desc 来展开:

ab7653affab982b574eb7acc55df2e04.gif

Linux 内核的中断处理, 围绕着中断描述符结构 struct irq_desc 展开, 内核提供了两种中断描述符组织形式:

打开 CONFIG_SPARSE_IRQ 宏(中断编号不连续), 中断描述符以 radix-tree 来组织, 用户在初始化时进行动态分配, 然后再插入 radix-tree 中;

关闭 CONFIG_SPARSE_IRQ 宏(中断编号连续), 中断描述符以数组的形式组织, 并且已经分配好;

不管哪种形式, 最终都可以通过 Linux irq 号来找到对应的中断描述符;

图的左侧灰色部分, 主要在中断控制器驱动中进行初始化设置, 包括各个结构中函数指针的指向等, 其中 struct irq_chip 用于对中断控制器的硬件操作, struct irq_domain 与中断控制器对应, 完成的工作是硬件中断号到 Linux irq 的映射;

图的上侧灰色部分, 中断描述符的创建(这里指 CONFIG_SPARSE_IRQ), 主要在获取设备中断信息的过程中完成的, 从而让设备树中的中断能与具体的中断描述符 irq_desc 匹配;

图中剩余部分, 在设备申请注册中断的过程中进行设置, 比如 struct irqaction 中 handler 的设置, 这个用于指向我们设备驱动程序中的中断处理函数了;

中断的处理主要有以下几个功能模块:

硬件中断号到 Linux irq 中断号的映射, 并创建好 irq_desc 中断描述符;

中断注册时, 先获取设备的中断号, 根据中断号找到对应的 irq_desc, 并将设备的中断处理函数添加到 irq_desc 中;

设备触发中断信号时, 根据硬件中断号得到 Linux irq 中断号, 找到对应的 irq_desc, 最终调用到设备的中断处理函数;

上述的描述比较简单, 更详细的过程, 往下看吧.

3. 流程分析

3.1 中断注册

这一次, 让我们以问题的方式来展开:

先来让我们回答第一个问题: 用户是怎么使用中断的?

熟悉设备驱动的同学应该都清楚, 经常会在驱动程序中调用 request_irq()接口或者

request_threaded_irq()

接口来注册设备的中断处理函数;

request_irq()/request_threaded_irq

接口中, 都需要用到 irq, 也就是中断号, 那么这个中断号是从哪里来的呢? 它是 Linux irq, 它又是如何映射到具体的硬件设备的中断号的呢?

先来看第二个问题: 设备硬件中断号到 Linux irq 中断号的映射

ab7653affab982b574eb7acc55df2e04.gif

硬件设备的中断信息都在设备树 device tree 中进行了描述, 在系统启动过程中, 这些信息都已经加载到内存中并得到了解析;

驱动中通常会使用 platform_get_irq 或

irq_of_parse_and_map

接口, 去根据设备树的信息去创建映射关系(硬件中断号到 Linux irq 中断号映射);

[原创] Linux 中断子系统(一)- 中断控制器及驱动分析提到过 struct irq_domain 用于完成映射工作, 因此在

irq_create_fwspec_mapping

接口中, 会先去找到匹配的 irq domain, 再去回调该 irq domain 中的函数集, 通常 irq domain 都是在中断控制器驱动中初始化的, 以 ARM GICv2 为例, 最终回调到

gic_irq_domain_hierarchy_ops

中的函数;

如果已经创建好了映射, 那么可以直接进行返回 Linux irq 中断号了, 否则的话需要

irq_domain_alloc_irqs

来创建映射关系;

irq_domain_alloc_irqs

完成两个工作:

针对 Linux irq 中断号创建一个 irq_desc 中断描述符;

调用 domain->ops->alloc 函数来完成映射, 在 ARM GICv2 驱动中对应

gic_irq_domain_alloc

函数, 这个函数很关键, 所以下文介绍一下;

gic_irq_domain_alloc 函数如下:

ab7653affab982b574eb7acc55df2e04.gif

gic_irq_domain_translate

: 负责解析出设备树中描述的中断号和中断触发类型(边缘触发, 电平触发等);

gic_irq_domain_map: 将硬件中断号和 Linux 中断号绑定到一个结构中, 也就完成了映射, 此外还绑定了 irq_desc 结构中的其他字段, 最重要的是设置了

irq_desc->handle_irq

的函数指针, 这个最终是中断响应时往上执行的入口, 这个是关键, 下文讲述中断处理过程时还会提到;

根据硬件中断号的范围设置

irq_desc->handle_irq

的指针, 共享中断入口为 handle_fasteoi_irq, 私有中断入口为handle_percpu_devid_irq

;

上述函数执行完成后, 完成了两大工作:

硬件中断号与 Linux 中断号完成映射, 并为 Linux 中断号创建了 irq_desc 中断描述符;

数据结构的绑定及初始化, 关键的地方是设置了中断处理往上执行的入口;

再看第一个问题: 中断是怎么来注册的?

设备驱动中, 获取到了 irq 中断号后, 通常就会采用 request_irq/request_threaded_irq 来注册中断, 其中 request_irq 用于注册普通处理的中断, request_threaded_irq 用于注册线程化处理的中断;

在讲具体的注册流程前, 先看一下主要的中断标志位:#defineIRQF_SHARED0x00000080// 多个设备共享一个中断号, 需要外设硬件支持

#defineIRQF_PROBE_SHARED0x00000100// 中断处理程序允许 sharing mismatch 发生

#define__IRQF_TIMER0x00000200// 时钟中断

#defineIRQF_PERCPU0x00000400// 属于特定 CPU 的中断

#defineIRQF_NOBALANCING0x00000800// 禁止在 CPU 之间进行中断均衡处理

#defineIRQF_IRQPOLL0x00001000// 中断被用作轮训

#defineIRQF_ONESHOT0x00002000// 一次性触发的中断, 不能嵌套, 1)在硬件中断处理完成后才能打开中断; 2)在中断线程化中保持关闭状态, 直到该中断源上的所有 thread_fn 函数都执行完

#defineIRQF_NO_SUSPEND0x00004000// 系统休眠唤醒操作中, 不关闭该中断

#defineIRQF_FORCE_RESUME0x00008000// 系统唤醒过程中必须强制打开该中断

#defineIRQF_NO_THREAD0x00010000// 禁止中断线程化

#defineIRQF_EARLY_RESUME0x00020000// 系统唤醒过程中在 syscore 阶段 resume, 而不用等到设备 resume 阶段

#defineIRQF_COND_SUSPEND0x00040000// 与 NO_SUSPEND 的用户共享中断时, 执行本设备的中断处理函数

ab7653affab982b574eb7acc55df2e04.gif

request_irq 也是调用

request_threaded_irq

, 只是在传参的时候, 线程处理函数 thread_fn 函数设置成 NULL;

由于在硬件中断号和 Linux 中断号完成映射后, irq_desc 已经创建好, 可以通过 irq_to_desc 接口去获取对应的 irq_desc;

创建 irqaction, 并初始化该结构体中的各个字段, 其中包括传入的中断处理函数赋值给对应的字段;

__setup_irq 用于完成中断的相关设置, 包括中断线程化的处理:

中断线程化用于减少系统关中断的时间, 增强系统的实时性;

ARM64 默认开启了

CONFIG_IRQ_FORCED_THREADING

, 引导参数传入 threadirqs 时, 则除了 IRQF_NO_THREAD 外的中断, 其他的都将强制线程化处理;

中断线程化会为每个中断都创建一个内核线程, 如果中断进行共享, 对应 irqaction 将连接成链表, 每个 irqaction 都有 thread_mask 位图字段, 当所有共享中断都处理完成后才能 unmask 中断, 解除中断屏蔽;

3.2 中断处理

当完成中断的注册后, 所有结构的组织关系都已经建立好, 剩下的工作就是当信号来临时, 进行中断的处理工作.

来回顾一下[原创] Linux 中断子系统(一)- 中断控制器及驱动分析中的 Arch-specific 处理流程:

ab7653affab982b574eb7acc55df2e04.gif

中断收到之后, 首先会跳转到异常向量表的入口处, 进而逐级进行回调处理, 最终调用到 generic_handle_irq 来进行中断处理.

generic_handle_irq 处理如下图:

ab7653affab982b574eb7acc55df2e04.gif

generic_handle_irq 函数最终会调用到 desc->handle_irq(), 这个也就是对应到上文中在建立映射关系的过程中, 调用

irq_domain_set_info

函数, 设置好了函数指针, 也就是 handle_fasteoi_irq 和handle_percpu_devid_irq

;

handle_fasteoi_irq: 处理共享中断, 并且遍历 irqaction 链表, 逐个调用 action->handler()函数, 这个函数正是设备驱动程序调用

request_irq/request_threaded_irq

接口注册的中断处理函数, 此外如果中断线程化处理的话, 还会调用

__irq_wake_thread()

唤醒内核线程;

handle_percpu_devid_irq

: 处理 per-CPU 中断处理, 在这个过程中会分别调用中断控制器的处理函数进行硬件操作, 该函数调用 action->handler()来进行中断处理;

来看看中断线程化处理后的唤醒流程吧__handle_irq_event_percpu->__irq_wake_thread:

ab7653affab982b574eb7acc55df2e04.gif

__handle_irq_event_percpu->__irq_wake_thread

将唤醒 irq_thread 中断内核线程;

irq_thread 内核线程, 将根据是否为强制中断线程化对函数指针 handler_fn 进行初始化, 以便后续进行调用;

irq_thread 内核线程将

while(!irq_wait_for_interrupt)

循环进行中断的处理, 当满足条件时, 执行 handler_fn, 在该函数中最终调用 action->thread_fn, 也就是完成了中断的处理;

irq_wait_for_interrupt

函数, 将会判断中断线程的唤醒条件, 如果满足了, 则将当前任务设置成 TASK_RUNNING 状态, 并返回 0, 这样就能执行中断的处理, 否则就调用 schedule()进行调度, 让出 CPU, 并将任务设置成 TASK_INTERRUPTIBLE 可中断睡眠状态;

3.3 总结

中断的处理, 总体来说可以分为两部分来看:

从上到下: 围绕 irq_desc 中断描述符建立好连接关系, 这个过程就包括: 中断源信息的解析 (设备树), 硬件中断号到 Linux 中断号的映射关系, irq_desc 结构的分配及初始化(内部各个结构的组织关系), 中断的注册(填充 irq_desc 结构, 包括 handler 处理函数) 等, 总而言之, 就是完成静态关系创建, 为中断处理做好准备;

从下到上, 当外设触发中断信号时, 中断控制器接收到信号并发送到处理器, 此时处理器进行异常模式切换, 并逐步从处理器架构相关代码逐级回调. 如果涉及到中断线程化, 则还需要进行中断内核线程的唤醒操作, 最终完成中断处理函数的执行.

来源: https://www.cnblogs.com/LoyenWang/p/13052677.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值