05 Linux对中断的处理
1.Linux中中断的定义
Linux中将中断划分为为硬件中断和软件中断
CPU在处理中断时,CPU是不能进行调度的。即A中断尚未处理完之前CPU是不会响应B中断的,即使B中断的优先级比A高
中断处理原则:
- 不能嵌套(因为中断会存在上下文的保存和还原)
- 越快越好
中断也分未上半部和下半部
-
上半部
处理紧急的事情,此时会关中断。无法处理其他中断
-
下半部
此时会开中断,处理相对而言不那么紧急的事情。会被其他中断打断。下半部的处理时间相对于上半部的处理时间会偏长一些
中断下半部的实现有多种方法:tasklet(小任务)、work queue(工作队列)、thread irq等
tasklet是使用软件中断来实现的
work queue用于处理特别耗时的工作。tasklet本质上还是软中断,在中断中CPU无法进行其他调度。此时使用work queue将部分工作交由内核线程(kworker)去实现。如此便不会阻塞。
thread irq:只需要向内核提供thread_fn,系统就会为这个函数创建一个内核线程。work queue中一个worker线程只能由一个CPU去执行。当存在多个中断的work由同一个worker线程处理时,对于多核而言就不太合适了。有可能会让有的CPU处于空闲状态。此时引入了thread irq可以为每个中断都创建一个内核线程(避免都使用同一个线程),使内核线程可以分配到个CPU上去执行。
因为开关总中断开关功能的存在,中断上半部处理完后才会处理中断下半部。即多个中断上半部对应中断下半部,中断下半部是汇总到一起来处理的
具体细节以后有时间在去深挖!
2.Linux中对中断的处理
首先对需要对linux中中断处理有一个整体框架行的认知(图片来源百问网)
GIC:通用中断控制器(硬件上只有一个此设备)
GPIO(其他模块):这里可以将GPIO和其他模块成为其对应的中断控制器。不过此控制器为软件概念上的,并无实体
整体工作流程为
上报B中断事件 上报A中断事件
外部设备触发中断事件上报 ----> GPIO中断处理 ----> GIC中断处理 ----> CPU接收中断事件
依据本人对中断的理解,这里对中断的处理简单的划分为两大部分:devicetree配置中断信息,linux注册中断
2.1 devicetree中指定中断资源
2.1.1 interrupt-controller
devicetree指定某个节点为中断控制器,必须包含interrupt-controller。表明此节点为中断控制器
2.1.2 #interrupt-cells
表示别的节点使用此中断控制器需要使用几个cells来表示哪一个中断
eg:
#interrupt-cells=<2> 表明需要两个cells来描述一个中断
一般而言第一个cells用于指定使用哪个中断,而第二个cell用于描述中断的触发类型
第二个cell的bit[3:0]用于表示中断触发类型
1 = low-to-high edge triggered 上升沿触发
2 = high-to-low edge triggered 下降沿触发
4 = active high level-sensitive 高电平触发
8 = active low level-sensitive 低电平触发
2.1.3 interrupt-parent
中断中中断控制器存在层级的关系(详见上图),下级中断控制器需要表明它的上级interrupt-parent是谁
2.1.4 interrupts
interrupt用于指定节点使用的具体中断,描述方法受#interrupt-cells限制
示例
i2c@7000c000 {
gpioext: gpio-adnp@41 {
compatible = "ad,gpio-adnp";
interrupt-parent = <&gpio>;
interrupts = <160 1>;
gpio-controller;
#gpio-cells = <1>;
interrupt-controller;
#interrupt-cells = <2>;
};
......
};
2.1.5 interrupts-extended
此写法为一种比较新的写法,即此写法包含了interrupt-parent和interrupts。
eg:
interrupts-extended = <&intc1 5 1>
2.2 内核中解析设备树的资源并实现中断注册和控制
2.2.1 requset_irq注册中断
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
irq : 中断号
handler : 中断处理函数
flags : 中断处理属性
name : 设备驱动名称
dev : 中断id
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
requset_irq调用requset_threaded_irq注册中断处理函数时,会构造一个irqaction结构体,用于保存name、dev_id、handler、thread_fn、thread
- handler是中断处理的上半部函数
- thread_fn为对应的一个内核线程,当handler处理完毕后内核会唤醒其对应的内核线程,及对应的thread_fn会被调用。中断下半部
- dev_id为中断的检索时使用,当卸载中断时可以根据此索引找到对应的action链表中的对应地址,将其卸载
2.2.2 irq_data
struct irq_data {
u32 mask;
unsigned int irq; //软件中断号
unsigned long hwirq; //硬件中断号
struct irq_common_data *common;
struct irq_chip *chip;
struct irq_domain *domain;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
irq与hwirq之间的映射关系是通过irq_domain来实现的
相同的irq号分属于不同的irq_domain即可以两个irq都为1,也不会存在问题
2.2.3 irq_domain
我们在irq_requset中注册中断使用的是软件中断号irq,一般是基于gpio引脚号,并不意味着实际的hwirq
在实际中我们使用对应域的irq_domain去实现获取设备树的中断属性(xlate),并将hwirq转化为我们所认知的irq(map)
2.2.2 irq_chip
chip的一些操作,在我们从设备树中获取到相关的中断属性后,irq_chip中的函数会帮助我们去使能中断或者清中断等相关操作