异常与中断的概念以及处理流程

news/2024/11/15 6:47:48/文章来源:https://www.cnblogs.com/regret20-21/p/18368951

1.CPU理解的中断

image
CPU 在运行的过程中,也会被各种“异常”打断。这些“异常”有:

  • 指令未定义
  • 指令、数据访问异常
  • SWI(软中断)
  • 快中断
  • 中断

中断也是 “异常” 的一种,导致中断发生的情况有

  • 按键
  • 定时器
  • ADC转换完成
  • uart 发送完数据,收到收据
  • 等等
    这些众多的“中断源”,汇集到“中断控制器”,由“中断控制器”选择优先级最高的中断并通知 CPU。

中断的处理流程

arm 对异常(中断)处理过程:
1. 初始化
a.设置中断源,让他可以产生中断
b.设置中断控制器(可以屏蔽某个中断,优先级)
c.设置CPU总开关
2.执行其他程序:正常程序
3.产生中断:比如按下按键------->中断控制器------>cpu
4.cpu 每执行一条指令都会去检查有无中断/异常发生
5.cpu 发现有中断/异常产生,开始执行
对于不同的异常,跳去不同的地址的执行程序。这地址上只是一条跳转指令,跳去执行某个函数(地址),这个就是异常向量。针对以上的 3、4、5 都是硬件做的
6.这些函数做什么事情?
软件做的:
a:保留现场(各种寄存器)
b: 处理异常(中断):分辨中断源,再调用不同的处理函数
c:恢复现场

异常向量表

u-boot 或是 Linux 内核,都有类似如下的代码
image

这就是异常向量表,每一条指令对应一种异常。
发生复位时,CPU 就去 执行第 1 条指令:b reset。
发生中断时,CPU 就去执行“ldr pc, _irq”这条指令。
这些指令存放的位置是固定的,比如对于 ARM9 芯片中断向量的地址是0x18。
当发生中断时,CPU 就强制跳去执行 0x18 处的代码。
在向量表里,一般都是放置一条跳转指令,发生该异常时,CPU 就会执行
向量表中的跳转指令,去调用更复杂的函数。

当然,向量表的位置并不总是从 0 地址开始,很多芯片可以设置某个
vector base 寄存器,指定向量表在其他位置,比如设置 vector base 为
0x80000000,指定为 DDR 的某个地址。但是表中的各个异常向量的偏移地址,
是固定的:复位向量偏移地址是 0,中断是 0x18。

Linux 系统对中断的处理

进程、线程、中断的核心:栈

中断中断,中断谁?
中断当前正在运行的进程、线程。
进程、线程是什么?内核如何切换进程、线程、中断?
要理解这些概念,必须理解栈的作用。

ARM 处理器程序运行的过程

① 对内存只有读、写指令
② 对于数据的运算是在 CPU 内部实现
③ 使用 RISC 指令的 CPU 复杂度小一点,易于设计
比如对于 a=a+b 这样的算式,需要经过下面 4 个步骤才可以实现:
image

细看这几个步骤,有些疑问:
① 读 a,那么 a 的值读出来后保存在 CPU 里面哪里?
② 读 b,那么 b 的值读出来后保存在 CPU 里面哪里?
③ a+b 的结果又保存在哪里?

image

程序被中断时,怎么保存现场

CPU 内部的寄存器很重要,如果要暂停一个程序,中断一个
程序,就需要把这些寄存器的值保存下来:这就称为保存现场。
保存在哪里?内存,这块内存就称之为栈。
程序要继续执行,就先从栈中恢复那些 CPU 内部寄存器的值。
这个场景并不局限于中断,下图可以概括程序 A、B 的切换过程,其他情况
是类似的:
image

① 函数调用:
a) 在函数 A 里调用函数 B,实际就是中断函数 A 的执行。
b) 那么需要把函数 A 调用 B 之前瞬间的 CPU 寄存器的值,保存到栈里;
c) 再去执行函数 B;
d) 函数 B 返回之后,就从栈中恢复函数 A 对应的 CPU 寄存器值,继续执行。

② 中断处理
a) 进程 A 正在执行,这时候发生了中断。
b) CPU 强制跳到中断异常向量地址去执行,
c) 这时就需要保存进程 A 被中断瞬间的 CPU 寄存器值,
d) 可以保存在进程 A 的内核态栈,也可以保存在进程 A 的内核结构体中。
e) 中断处理完毕,要继续运行进程 A 之前,恢复这些值。

③ 进程切换
a) 在所谓的多任务操作系统中,我们以为多个程序是同时运行的。
b) 如果我们能感知微秒、纳秒级的事件,可以发现操作系统时让这些程序
依次执行一小段时间,进程 A 的时间用完了,就切换到进程 B。
c) 怎么切换?
d) 切换过程是发生在内核态里的,跟中断的处理类似。
e) 进程 A 的被切换瞬间的 CPU 寄存器值保存在某个地方;
f) 恢复进程 B 之前保存的 CPU 寄存器值,这样就可以运行进程 B 了。

③ 进程切换
a) 在所谓的多任务操作系统中,我们以为多个程序是同时运行的。
b) 如果我们能感知微秒、纳秒级的事件,可以发现操作系统时让这些程序
依次执行一小段时间,进程 A 的时间用完了,就切换到进程 B。
c) 怎么切换?
d) 切换过程是发生在内核态里的,跟中断的处理类似。
e) 进程 A 的被切换瞬间的 CPU 寄存器值保存在某个地方;
f) 恢复进程 B 之前保存的 CPU 寄存器值,这样就可以运行进程 B 了

所以,在中断处理的过程中,伴存着进程的保存现场、恢复现场。进程的调度
也是使用栈来保存、恢复现场:

image

在 Linux 中:资源分配的单位是进程,调度的单位是线程,也就是说,在
一个进程里,可能有多个线程,这些线程共用打开的文件句柄、全局变量等等。
而这些线程,之间是互相独立的,“同时运行”,也就是说:每一个线程,
都有自己的栈。如下图示:

image

Linux 系统对中断处理的演进

,Linux 中断系统的变化并不大。
比较重要的就是引入了 threaded irq:使用内核线程来处理中断。Linux 系统中有硬件中断,也有软件中断。对硬件中断的处理有 2 个原则:

不能嵌套,越快越好。

中断处理函数需要调用 C 函数,这就需要用到栈。

中断处理原则 1:不能嵌套

⚫ 中断 A 正在处理的过程中,假设又发生了中断 B,那么在栈里要保存 A 的现
场,然后处理 B。
⚫ 在处理 B 的过程中又发生了中断 C,那么在栈里要保存 B 的现场,然后处理
C。
如果中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。所以,为了防
止这种情况发生,也是为了简单化中断的处理,在 Linux 系统上中断无法嵌套:
即当前中断 A 没处理完之前,不会响应另一个中断 B(即使它的优先级更高)。

中断处理原则 2:越快越好

在 Linux 系统中使用中断是挺简单的,为某个中断 irq 注册中断处理函数handler,可以使用 request_irq 函数:
image
在 handler 函数中,代码尽可能高效。

但是,处理某个中断要做的事情就是很多,没办法加快。比如对于按键中断,我们需要等待几十毫秒消除机械抖动。难道要在 handler 中等待吗?对于计算机来说,这可是一个段很长的时间。怎么办?

拆分为:上半部、下半部

当一个中断要耗费很多时间来处理时,它的坏处是:在这段时间内,其他
中断无法被处理。换句话说,在这段时间内,系统是关中断的。
如果某个中断就是要做那么多事,我们能不能把它拆分成两部分:紧急的、
不紧急的?
在 handler 函数里只做紧急的事,然后就重新开中断,让系统得以正常运
行;那些不紧急的事,以后再处理,处理时是开中断的。

image

中断下半部的实现有很多种方法,讲 2 种主要的:tasklet(小任务)、
work queue(工作队列)。

tasklet

下半部比较耗时但是能忍受,并且它的处理比较简单时,可以用
tasklet 来处理下半部。tasklet 是使用软件中断来实现。

image
假 设 硬 件 中 断 A 的 上 半 部 函 数 为 irq_top_half_A , 下 半 部 为
irq_bottom_half_A。
使用情景化的分析,才能理解上述代码的精华。
⚫ 硬件中断 A 处理过程中,没有其他中断发生:
一开始,preempt_count = 0;
上述流程图①~⑨依次执行,上半部、下半部的代码各执行一次。
⚫ 硬件中断 A 处理过程中,又再次发生了中断 A:
一开始,preempt_count = 0;
执行到第⑥时,一开中断后,中断 A 又再次使得 CPU 跳到中断向量表。
注意:这时 preempt_count 等于 1,并且中断下半部的代码并未执行。
CPU 又从①开始再次执行中断 A 的上半部代码:
在第①步 preempt_count 等于 2;
在第③步 preempt_count 等于 1;
在第④步发现 preempt_count 等于 1,所以直接结束当前第 2 次中断的处
理;
注意:重点来了,第 2 次中断发生后,打断了第一次中断的第⑦步处理。当第
2 次中断处理完毕,CPU 会继续去执行第⑦步。
可以看到,发生 2 次硬件中断 A 时,它的上半部代码执行了 2 次,但是下
半部代码只执行了一次。
所以,同一个中断的上半部、下半部,在执行时是多对一的关系。
⚫ 硬件中断 A 处理过程中,又再次发生了中断 B:
一开始,preempt_count = 0;
执行到第⑥时,一开中断后,中断 B 又再次使得 CPU 跳到中断向量表。
注意:这时 preempt_count 等于 1,并且中断 A 下半部的代码并未执行。
CPU 又从①开始再次执行中断 B 的上半部代码:
在第①步 preempt_count 等于 2;
在第③步 preempt_count 等于 1;
在第④步发现 preempt_count 等于 1,所以直接结束当前第 2 次中断的处
理;
注意:重点来了,第 2 次中断发生后,打断了第一次中断 A 的第⑦步处理。当
第 2 次中断 B 处理完毕,CPU 会继续去执行第⑦步。
在第⑦步里,它会去执行中断 A 的下半部,也会去执行中断 B 的下半部。
所以,多个中断的下半部,是汇集在一起处理的。
总结:
① 中断的处理可以分为上半部,下半部
② 中断上半部,用来处理紧急的事,它是在关中断的状态下执行的
③ 中断下半部,用来处理耗时的、不那么紧急的事,它是在开中断的状态下执
行的
④ 中断下半部执行时,有可能会被多次打断,有可能会再次发生同一个中断
⑤ 中断上半部执行完后,触发中断下半部的处理
⑥ 中断上半部、下半部的执行过程中,不能休眠:中断休眠的话,以后谁来调
度进程啊?

工作队列

在中断下半部的执行过程中,虽然是开中断的,期间可以处理各类中断。
但是毕竟整个中断的处理还没走完,这期间 APP 是无法执行的。
假设下半部要执行 1、2 分钟,在这 1、2 分钟里 APP 都是无法响应的。
这谁受得了?所以,如果中断要做的事情实在太耗时,那就不能用软件中
断来做,而应该用内核线程来做:在中断上半部唤醒内核线程。内核线程和
APP 都一样竞争执行,APP 有机会执行,系统不会卡顿。
这个内核线程是系统帮我们创建的,一般是 kworker 线程,内核中有很多
这样的线程:

image

kworker 线程要去“工作队列”(work queue)上取出一个一个“工作”
(work),来执行它里面的函数。
那我们怎么使用 work、work queue 呢?
① 创建 work:
你得先写出一个函数,然后用这个函数填充一个 work 结构体。比如:
总结:
⚫ 很耗时的中断处理,应该放到线程里去
⚫ 可以使用 work、work queue
⚫ 在中断上半部调用 schedule_work 函数,触发 work 的处理
⚫ 既然是在线程中运行,那对应的函数可以休眠。

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

image

在硬件上,“中断控制器”只有 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),都会在设备树中有所体现。
在设备树中,中断控制器节点中必须有一个属性: interruptcontroller,表明它是“中断控制器”。
还必须有一个属性:#interrupt-cells,表明引用这个中断控制器的话需
要多少个 cell。

interrupt-cells 的值一般有如下取值:
#interrupt-cells=<1>

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

#interrupt-cells=<2>

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

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

示例如下:
image

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

设备树里使用中断

一个外设,它的中断信号接到哪个“中断控制器” 的 哪个“中断引脚”,这个中断的触发方式是怎样的?
这3个问题,在设备树里使用中断时,都要有所体现。

⚫ interrupt-parent=<&XXXX>
你要用哪一个中断控制器里的中断?
⚫ interrupts
你要用哪一个中断?
Interrupts 里要用几个 cell,由 interrupt-parent 对应的中断控制器
决定。在中断控制器里有“#interrupt-cells”属性,它指明了要用几个
cell 来描述中断。

比如:

image

新写法:interrupts-extended
一个“interrupts-extended”属性就可以既指定“interrupt-parent”,
也指定“interrupts”,比如:

interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;

设备树里中断节点的示例

以 100ASK_IMX6ULL 开发板为例,在 arch/arm/boot/dts 目录下可以看
到 2 个文件:imx6ull.dtsi、100ask_imx6ull-14x14.dts,把里面有关中
断的部分内容抽取出来。
image

在代码中获得中断

设备树中的节点有些能被转换为内核里的platform_device,有些不能,回顾如下:
① 根节点下含有 compatile 属性的子节点,会转换为 platform_device
② 含有特定 compatile 属性的节点的子节点,会转换为 platform_device
如果一个节点的 compatile 属性,它的值是这 4 者之一:"simplebus","simple-mfd","isa","arm,amba-bus",
③ 总线 I2C、SPI 节点下的子节点:不转换为 platform_device
某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该
被转换为 platform_device。

1 对于 platform_device

一个节点能被转换为 platform_device,如果它的设备树里指定了中断属
性,那么可以从 platform_device 中获得“中断资源”,函数如下,可以使用
下列函数获得 IORESOURCE_IRQ 资源,即中断号:
image

2 对于 I2C 设备、SPI 设备

对于I2C设备节点,I2C总线驱动在处理设备树里的I2C子节点时,也会
处理其中的中断信息。一个 I2C 设备会被转换为一个 i2c_client 结构体,中
断号会保存在 i2c_client 的 irq 成员里,代码如下(drivers/i2c/i2c-core.c):

image

对于SPI设备节点,SPI总线驱动在处理设备树里的SPI子节点时,也会处理其中的中断信息。一个SPI设备会被转换为一个spi_device结构体,中断号会保存在spi_device的irq成员里,代码如下(drivers/spi/spi.c):

image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/784245.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wifi基础(一):无线电波与WIFI信号干扰、衰减

liwen01 2024.08.18 前言 无论是在产品开发还是在日常生活中,在使用无线网络的时候,都会经常遇到一些信号不好的问题,也会产生不少疑问:为什么我们在高速移动的高铁上网络会变慢? 为什么 5G WiFi 的穿墙能力没有 2.4G 的好? 为什么在对 WiFi 进行 iperf 拉距测试的时候,…

监理单位项目管理系统:选择前你必须知道的事

国内外主流的 10 款监理单位项目管理系统对比:PingCode、Worktile、Primavera P6、Microsoft Project、Wrike、Asana、Trello、红圈、泛微项目协同工具、广联达。在寻找适合监理单位的项目管理系统时,许多专业人士面临着复杂性和成本效益的双重挑战。一个好的系统不仅需要具备…

多任务进程与线程

多任务进程与线程 一、多任务介绍 ​ 我们生活中有很多事情是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的;用程序来模拟: from time import sleepdef sing():for i in range(3):print("正在唱歌...%d"%i)sleep(1)def dance():…

生产工时管理系统:提高效率的秘诀

国内外主流的10款工时管理平台对比:.Teambition; 2.Tower; 3.蓝凌OA; 4.ClockShark; 5.Hubstaff; 6.TimeClock Plus; 7.Jibble; 8.MISys Labor Tracking;9.PingCode;10.Worktile。在选择合适的工时管理平台时,你是否感到挑战重重?市场上的各种选项似乎都声称能够提…

ArgoWorkflow教程(二)---快速构建流水线:Workflow Template 概念

上一篇我们部署了 ArgoWorkflow,并创建了一个简单的流水线做了个 Demo。本篇主要分析 ArgoWorkflow 中流水线相关的概念,了解概念后才能更好使用 ArgoWorkflow。本文主要分析以下问题:1)如何创建流水线? Workflow 中各参数含义 2)WorkflowTemplate 流水线模版如何使用, …

神经网络之卷积篇:详解单层卷积网络(One layer of a convolutional network)

详解单层卷积网络 如何构建卷积神经网络的卷积层,下面来看个例子。已经写了如何通过两个过滤器卷积处理一个三维图像,并输出两个不同的44矩阵。假设使用第一个过滤器进行卷积,得到第一个44矩阵。使用第二个过滤器进行卷积得到另外一个44矩阵。最终各自形成一个卷积神经网络层…

小小的引用计数,大大的性能考究

本文基于 Netty 4.1.56.Final 版本进行讨论在上篇文章《聊一聊 Netty 数据搬运工 ByteBuf 体系的设计与实现》 中,笔者详细地为大家介绍了 ByteBuf 整个体系的设计,其中笔者觉得 Netty 对于引用计数的设计非常精彩,因此将这部分设计内容专门独立出来。Netty 为 ByteBuf 引入…

【生化代谢基础笔记】RNA 合成

第一节 原核生物转录的模板和酶⚠️ RNA合成需要:DNA Template,NTP,RNA pol,其他蛋白质因子,$Mg^{2+}$一、原核生物转录模板模板链(Template strand) VS 编码链(Coding strand)模板链为合成模板另一股单链为编码链,mRNA 碱基序列与编码链一致二、RNA 聚合酶催化 RNA …

暑假集训CSP提高模拟 25

暑假集训CSP提高模拟 25 组题人: @KafuuChinocpp | @H_Kaguya\(T1\) P235.可持久化线段树 \(0pts\)弱化版: SP11470 TTM - To the moon标记永久化主席树板子。点击查看代码 const ll p=998244353; ll a[100010]; struct PDS_SMT {ll root[100010],rt_sum;struct SegmentTree{…

[Flink] Flink 序列化器

Flink 序列化器依赖包及版本信息org.apache.kafka:kafka-clients:${kafka-clients.version=2.4.1}org.apache.flink:flink-java:${flink.version=1.12.6} org.apache.flink:flink-clients_${scala.version=2.11}:${flink.version} org.apache.flink:flink-streaming-java_${sca…

怎么在pycharm里面写.md文件

一、插件安装 如果不清楚自己的PyCharm是否自带Markdown,可以在File - settings - Plugins - installed中查看是否有“Markdown”插件。 如果没有安装,可以在File - settings - Plugins - Marketplace中搜索“Markdown”安装。二、创建Markdown文件 在Pycharm中,Markdown(.m…

香城档案利用 NocoBase 快速实现智能档案管理

探索香城档案如何通过 NocoBase 革新档案管理,通过智能系统和强大的自动化技术提高效率。关于档案管理行业 档案管理历史悠久,最早可追溯至周朝。周文王姬昌非常重视档案管理,他命令手下的管理者将这些文献和档案进行整理和分类,然后存放在专门的档案馆中。这些档案馆也被称…