嵌入式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”,请看下一小节。

相关文章
|
9月前
|
Ubuntu Linux Anolis
Linux系统禁用swap
本文介绍了在新版本Linux系统(如Ubuntu 20.04+、CentOS Stream、openEuler等)中禁用swap的两种方法。传统通过注释/etc/fstab中swap行的方式已失效,现需使用systemd管理swap.target服务或在/etc/fstab中添加noauto参数实现禁用。方法1通过屏蔽swap.target适用于新版系统,方法2通过修改fstab挂载选项更通用,兼容所有系统。
858 3
Linux系统禁用swap
|
9月前
|
Linux
Linux系统修改网卡名为eth0、eth1
在Linux系统中,可通过修改GRUB配置和创建Udev规则或使用systemd链接文件,将网卡名改为`eth0`、`eth1`等传统命名方式,适用于多种发行版并支持多网卡配置。
1438 3
|
10月前
|
Ubuntu Linux
计算机基础知识:linux系统怎么安装?
在虚拟机软件中创建一个新的虚拟机,并选择相应操作系统类型和硬盘空间大小等参数。将下载的 ISO 镜像文件加载到虚拟机中。启动虚拟机,进入安装界面,并按照步骤进行安装。安装完成后,可以在虚拟机中使用 Linux 系统。
|
10月前
|
存储 Ubuntu Linux
「正点原子Linux连载」第二章Ubuntu系统入门
在图2.8.2.4中,我们使用命令umount卸载了U盘,卸载以后当我们再去访问文件夹/mnt/tmp的时候发现里面没有任何文件了,说明我们卸载成功了。
|
Ubuntu Linux 网络安全
Linux系统初始化脚本
一款支持Rocky、CentOS、Ubuntu、Debian、openEuler等主流Linux发行版的系统初始化Shell脚本,涵盖网络配置、主机名设置、镜像源更换、安全加固等多项功能,适配单/双网卡环境,支持UEFI引导,提供多版本下载与持续更新。
925 3
Linux系统初始化脚本
|
10月前
|
运维 Linux 开发者
Linux系统中使用Python的ping3库进行网络连通性测试
以上步骤展示了如何利用 Python 的 `ping3` 库来检测网络连通性,并且提供了基本错误处理方法以确保程序能够优雅地处理各种意外情形。通过简洁明快、易读易懂、实操性强等特点使得该方法非常适合开发者或系统管理员快速集成至自动化工具链之内进行日常运维任务之需求满足。
675 18
|
9月前
|
安全 Linux Shell
Linux系统提权方式全面总结:从基础到高级攻防技术
本文全面总结Linux系统提权技术,涵盖权限体系、配置错误、漏洞利用、密码攻击等方法,帮助安全研究人员掌握攻防技术,提升系统防护能力。
1076 1
|
9月前
|
监控 安全 Linux
Linux系统提权之计划任务(Cron Jobs)提权
在Linux系统中,计划任务(Cron Jobs)常用于定时执行脚本或命令。若配置不当,攻击者可利用其提权至root权限。常见漏洞包括可写的Cron脚本、目录、通配符注入及PATH变量劫持。攻击者通过修改脚本、创建恶意任务或注入命令实现提权。系统管理员应遵循最小权限原则、使用绝对路径、避免通配符、设置安全PATH并定期审计,以防范此类攻击。
1368 1
|
10月前
|
缓存 监控 Linux
Linux系统清理缓存(buff/cache)的有效方法。
总结而言,在大多数情形下你不必担心Linux中buffer与cache占用过多内存在影响到其他程序运行;因为当程序请求更多内存在没有足够可用资源时,Linux会自行调整其占有量。只有当你明确知道当前环境与需求并希望立即回收这部分资源给即将运行重负载任务之前才考虑上述方法去主动干预。
2544 10
|
10月前
|
安全 Linux 数据安全/隐私保护
为Linux系统的普通账户授予sudo访问权限的过程
完成上述步骤后,你提升的用户就能够使用 `sudo`命令来执行管理员级别的操作,而无需切换到root用户。这是一种更加安全和便捷的权限管理方式,因为它能够留下完整的权限使用记录,并以最小权限的方式工作。需要注意的是,随意授予sudo权限可能会使系统暴露在风险之中,尤其是在用户不了解其所执行命令可能带来的后果的情况下。所以在配置sudo权限时,必须谨慎行事。
1780 0