嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十四)Linux系统对中断的处理(中)

简介: 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十四)Linux系统对中断的处理

1.2.6 下半部要做的事情太多并且很复杂:工作队列


在中断下半部的执行过程中,虽然是开中断的,期间可以处理各类中断。但是毕竟整个中断的处理还没走完,这期间APP是无法执行的。

假设下半部要执行1、2分钟,在这1、2分钟里APP都是无法响应的。

这谁受得了?

所以,如果中断要做的事情实在太耗时,那就不能用软件中断来做,而应该用内核线程来做:在中断上半部唤醒内核线程。内核线程和APP都一样竞争执行,APP有机会执行,系统不会卡顿。

这个内核线程是系统帮我们创建的,一般是kworker线程,内核中有很多这样的线程:

1670923261091.jpg

kworker线程要去“工作队列”(work queue)上取出一个一个“工作”(work),来执行它里面的函数。

那我们怎么使用work、work queue呢?

a. 创建work:

你得先写出一个函数,然后用这个函数填充一个work结构体。比如:

1670923271424.jpg

b. 要执行这个函数时,把work提交给work queue就可以了:

1670923279437.jpg

上述函数会把work提供给系统默认的work queue:system_wq,它是一个队列。


c. 谁来执行work中的函数?

不用我们管,schedule_work函数不仅仅是把work放入队列,还会把kworker线程唤醒。此线程抢到时间运行时,它就会从队列中取出work,执行里面的函数。


d. 谁把work提交给work queue?

在中断场景中,可以在中断上半部调用schedule_work函数。


总结:

a. 很耗时的中断处理,应该放到线程里去

b. 可以使用work、work queue

c. 在中断上半部调用schedule_work函数,触发work的处理

d. 既然是在线程中运行,那对应的函数可以休眠。


1.2.7 新技术:threaded irq


使用线程来处理中断,并不是什么新鲜事。使用work就可以实现,但是需要定义work、调用schedule_work,好麻烦啊。

太懒了太懒了,就这2步你们都不愿意做。

好,内核是为懒人服务的,再杀出一个函数:

1670923287960.jpg

你可以只提供thread_fn,系统会为这个函数创建一个内核线程。发生中断时,内核线程就会执行这个函数。


说你懒是开玩笑,内核开发者也不会那么在乎懒人。

以前用work来线程化地处理中断,一个worker线程只能由一个CPU执行,多个中断的work都由同一个worker线程来处理,在单CPU系统中也只能忍着了。但是在SMP系统中,明明有那么多CPU空着,你偏偏让多个中断挤在这个CPU上?

新技术threaded irq,为每一个中断都创建一个内核线程;多个中断的内核线程可以分配到多个CPU上执行,这提高了效率。


1.3 Linux中断系统中的重要数据结构


本节内容,可以从request_irq(include/linux/interrupt.h)函数一路分析得到。

能弄清楚下面这个图,对Linux中断系统的掌握也基本到位了。

1670923300517.jpg

最核心的结构体是irq_desc,之前为了易于理解,我们说在Linux内核中有一个中断数组,对于每一个硬件中断,都有一个数组项,这个数组就是irq_desc数组。

注意:如果内核配置了CONFIG_SPARSE_IRQ,那么它就会用基数树(radix tree)来代替irq_desc数组。SPARSE的意思是“稀疏”,假设大小为1000的数组中只用到2个数组项,那不是浪费嘛?所以在中断比较“稀疏”的情况下可以用基数树来代替数组。


1.3.1 irq_desc数组


irq_desc结构体在include/linux/irqdesc.h中定义,主要内容如下图:

1670923315244.jpg

每一个irq_desc数组项中都有一个函数:handle_irq,还有一个action链表。要理解它们,需要先看中断结构图:

1670923323557.jpg

外部设备1、外部设备n共享一个GPIO中断B,多个GPIO中断汇聚到GIC(通用中断控制器)的A号中断,GIC再去中断CPU。那么软件处理时就是反过来,先读取GIC获得中断号A,再细分出GPIO中断B,最后判断是哪一个外部芯片发生了中断。

所以,中断的处理函数来源有三:

① GIC的处理函数:

假设irq_desc[A].handle_irq是XXX_gpio_irq_handler(XXX指厂家),这个函数需要读取芯片的GPIO控制器,细分发生的是哪一个GPIO中断(假设是B),再去调用irq_desc[B]. handle_irq。

注意:irq_desc[A].handle_irq细分出中断后B,调用对应的irq_desc[B].handle_irq。

显然中断A是CPU感受到的顶层的中断,GIC中断CPU时,CPU读取GIC状态得到中断A。


② 模块的中断处理函数:

比如对于GPIO模块向GIC发出的中断B,它的处理函数是irq_desc[B].handle_irq。

BSP开发人员会设置对应的处理函数,一般是handle_level_irq或handle_edge_irq,从名字上看是用来处理电平触发的中断、边沿触发的中断。

注意:导致GPIO中断B发生的原因很多,可能是外部设备1,可能是外部设备n,可能只是某一个设备,也可能是多个设备。所以irq_desc[B].handle_irq会调用某个链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生,若是则处理。


③ 外部设备提供的处理函数:

这里说的“外部设备”可能是芯片,也可能总是简单的按键。它们的处理函数由自己驱动程序提供,这是最熟悉这个设备的“人”:它知道如何判断设备是否发生了中断,如何处理中断。

对于共享中断,比如GPIO中断B,它的中断来源可能有多个,每个中断源对应一个中断处理函数。所以irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数。

一旦程序确定发生了GPIO中断B,那么就会从链表里把那些函数取出来,一一执行。

这个链表就是action链表。

对于我们举的这个例子来说,irq_desc数组如下:

1670923336842.jpg


1.3.2 irqaction结构体


irqaction结构体在include/linux/interrupt.h中定义,主要内容如下图:

1670923348633.jpg

当调用request_irq、request_threaded_irq注册中断处理函数时,内核就会构造一个irqaction结构体。在里面保存name、dev_id等,最重要的是handler、thread_fn、thread。

handler是中断处理的上半部函数,用来处理紧急的事情。

thread_fn对应一个内核线程thread,当handler执行完毕,Linux内核会唤醒对应的内核线程。在内核线程里,会调用thread_fn函数。

可以提供handler而不提供thread_fn,就退化为一般的request_irq函数。

可以不提供handler只提供thread_fn,完全由内核线程来处理中断。

也可以既提供handler也提供thread_fn,这就是中断上半部、下半部。


里面还有一个名为sedondary的irqaction结构体,它的作用以后再分析。

在reqeust_irq时可以传入dev_id,为何需要dev_id?作用有2:

① 中断处理函数执行时,可以使用dev_id

② 卸载中断时要传入dev_id,这样才能在action链表中根据dev_id找到对应项

所以在共享中断中必须提供dev_id,非共享中断可以不提供。


1.3.3 irq_data结构体


irq_data结构体在include/linux/irq.h中定义,主要内容如下图:

1670923362886.jpg

它就是个中转站,里面有irq_chip指针 irq_domain指针,都是指向别的结构体。

比较有意思的是irq、hwirq,irq是软件中断号,hwirq是硬件中断号。比如上面我们举的例子,在GPIO中断B是软件中断号,可以找到irq_desc[B]这个数组项;GPIO里的第x号中断,这就是hwirq。

谁来建立irq、hwirq之间的联系呢?由irq_domain来建立。irq_domain会把本地的hwirq映射为全局的irq,什么意思?比如GPIO控制器里有第1号中断,UART模块里也有第1号中断,这两个“第1号中断”是不一样的,它们属于不同的“域”──irq_domain。


1.3.4 irq_domain结构体


irq_domain结构体在include/linux/irqdomain.h中定义,主要内容如下图:

1670923376149.jpg

当我们后面从设备树讲起,如何在设备树中指定中断,设备树的中断如何被转换为irq时,irq_domain将会起到极大的作为。

这里基于入门的解度简单讲讲,在设备树中你会看到这样的属性:

interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;

它表示要使用gpio1里的第5号中断,hwirq就是5。

但是我们在驱动中会使用request_irq(irq, handler)这样的函数来注册中断,irq是什么?它是软件中断号,它应该从“gpio1的第5号中断”转换得来。

谁把hwirq转换为irq?由gpio1的相关数据结构,就是gpio1对应的irq_domain结构体。

irq_domain结构体中有一个irq_domain_ops结构体,里面有各种操作函数,主要是:

① xlate

用来解析设备树的中断属性,提取出hwirq、type等信息。

② map

把hwirq转换为irq。


1.3.5 irq_chip结构体


irq_chip结构体在include/linux/irq.h中定义,主要内容如下图:

1670923401616.jpg

这个结构体跟“chip”即芯片相关,里面各成员的作用在头文件中也列得很清楚,摘录部分如下:

@irq_startup: start up the interrupt (defaults to ->enable if NULL)
@irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
@irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
@irq_disable: disable the interrupt
@irq_ack: start of a new interrupt
@irq_mask: mask an interrupt source
@irq_mask_ack: ack and mask an interrupt source
@irq_unmask: unmask an interrupt source
@irq_eoi: end of interrupt

我们在request_irq后,并不需要手工去使能中断,原因就是系统调用对应的irq_chip里的函数帮我们使能了中断。

我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用irq_chip中的相关函数。

但是对于外部设备相关的清中断操作,还是需要我们自己做的。

就像上面图里的“外部设备1“、“外部设备n”,外设备千变万化,内核里可没有对应的清除中断操作


1.4 在设备树中指定中断_在代码中获得中断


1.4.1 设备树里中断节点的语法


参考文档:

内核Documentation\devicetree\bindings\interrupt-controller\interrupts.txt


1.4.1.1 设备树里的中断控制器


中断的硬件框图如下:

1670923446539.jpg

在硬件上,“中断控制器”只有GIC这一个,但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。很多芯片有多个GPIO模块,比如GPIO1、GPIO2等等。所以软件上的“中断控制器”就有很多个:GIC、GPIO1、GPIO2等等。

GPIO1连接到GIC,GPIO2连接到GIC,所以GPIO1的父亲是GIC,GPIO2的父亲是GIC。

假设GPIO1有32个中断源,但是它把其中的16个汇聚起来向GIC发出一个中断,把另外16个汇聚起来向GIC发出另一个中断。这就意味着GPIO1会用到GIC的两个中断,会涉及GIC里的2个hwirq。


这些层级关系、中断号(hwirq),都会在设备树中有所体现。


在设备树中,中断控制器节点中必须有一个属性:interrupt-controller,表明它是“中断控制器”。

还必须有一个属性:#interrupt-cells,表明引用这个中断控制器的话需要多少个cell。

#interrupt-cells的值一般有如下取值:

① #interrupt-cells=<1>

别的节点要使用这个中断控制器时,只需要一个cell来表明使用“哪一个中断”。

② #interrupt-cells=<2>

别的节点要使用这个中断控制器时,需要一个cell来表明使用“哪一个中断”;

还需要另一个cell来描述中断,一般是表明触发类型:

第2个cell的bits[3:0] 用来表示中断触发类型(trigger type and level flags):

1 = low-to-high edge triggered,上升沿触发
2 = high-to-low edge triggered,下降沿触发
4 = active high level-sensitive,高电平触发
8 = active low level-sensitive,低电平触发

示例如下:

vic: intc@10140000 {
compatible = “arm,versatile-vic”;
interrupt-controller;
#interrupt-cells = <1>;
reg = <0x10140000 0x1000>;
};


如果中断控制器有级联关系,下级的中断控制器还需要表明它的“interrupt-parent”是谁,用了interrupt-parent”中的哪一个“interrupts”,请看下一小节。

相关文章
|
1天前
|
API 数据安全/隐私保护 UED
探索鸿蒙的蓝牙A2DP与访问API:从学习到实现的开发之旅
在掌握了鸿蒙系统的开发基础后,我挑战了蓝牙功能的开发。通过Bluetooth A2DP和Access API,实现了蓝牙音频流传输、设备连接和权限管理。具体步骤包括:理解API作用、配置环境与权限、扫描并连接设备、实现音频流控制及动态切换设备。最终,我构建了一个简单的蓝牙音频播放器,具备设备扫描、连接、音频播放与停止、切换输出设备等功能。这次开发让我对蓝牙技术有了更深的理解,也为未来的复杂项目打下了坚实的基础。
77 58
探索鸿蒙的蓝牙A2DP与访问API:从学习到实现的开发之旅
|
2天前
【HarmonyOS Next开发】:ListItemGroup使用
通过使用ListItemGroup和AlphabetIndexer两种类型组件,实现带标题分类和右侧导航栏的页面
83 61
|
2天前
|
API 容器
【HarmonyOS Next开发】Navigation使用
Navigation是路由容器组件,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。适用于模块内和跨模块的路由切换。 在页面跳转时,应该使用页面路由router,在页面内的页面跳转时,建议使用Navigation达到更好的转场动效场景。
32 8
|
2天前
|
API
【HarmonyOS Next开发】Tabs使用封装
在写Tabs时,会使用很多个TabContent来实现不同页面的展示内容,但是如果TabContent数量很多时,会导致Tabs代码量大而且很臃肿,因此想着尝试去封装Tabs的使用,可以让界面整洁和对内容界面的解耦。 主要依托于wrapBuilder:封装全局@Builder的方法使用。需要注意从API 11 才开始支持使用
18 6
|
自然语言处理 JavaScript 前端开发
一文了解HarmonyOS系统架构
HarmonyOS是一款面向 万物互联时代的、全新分布式操作系统。在传统的单设备系统能力基础上,HarmonyOS提出了基于`同一套系统能力`、`适配多种终端形态`的分布式理念。能够支持手机、平板、智能穿戴、智慧屏、车机等多种终端设备,提供全场景(移动办公、运动健康、社交通信、媒体娱乐等)业务能力。
2408 0
一文了解HarmonyOS系统架构
|
3天前
|
人工智能 文字识别 算法
|
3天前
|
安全 Java 开发者
|
2天前
|
安全 数据安全/隐私保护
鸿蒙开发:一文了解软键盘相关
软键盘最主要的就是合理的进行避让,不能遮挡可输入组件,再有多个输入框的时候,需要动态的进行设置高度,这一点需要注意。
鸿蒙开发:一文了解软键盘相关
|
2天前
鸿蒙开发:一个轻盈的上拉下拉刷新组件
在和可滑动组件使用的时候,记得一定要和nestedScroll属性配合使用,用于解决滑动冲突,除此之外,还需要传递滑动组件的scroller属性,用于手势操作。
鸿蒙开发:一个轻盈的上拉下拉刷新组件

热门文章

最新文章