首页 > 其他分享 >异常与中断的概念以及处理流程

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

时间:2024-08-20 13:27:05浏览次数:6  
标签:中断 流程 处理 线程 半部 执行 异常 CPU

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

标签:中断,流程,处理,线程,半部,执行,异常,CPU
From: https://www.cnblogs.com/regret20-21/p/18368951

相关文章

  • 5.现场正式操作流程-《街舞比赛展示管理系统》
    第1步:数据清零操作点击控制台菜单[赛前操作>所有轮次成绩清零]或[赛前操作>当前轮次成绩清零],见下方示意图: ,此操作只是把以前的表决数据清零,其它的非表决信息都在。由于前期测试时,里面已有相关表决数据,所以在正式展示之前,最好再[清零]一下,保证初始状态是干净状态。 第2步......
  • 流程控制语句
    第一章流程控制语句在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的。所以,我们必须清楚每条语句的执行流程。而且,很多时候要通过控制语句的执行顺序来实现我们想要的功能。1.1流程控制语句分类顺序结构判断和选择结构(if,switch)循环结构(for,while......
  • [Python学习日记-10] Python中的流程控制(if...else...)
    简介        假如把写程序比做走路,那我们到现在为止,一直走的都是直路,还没遇到过分叉口,想象现实中,你遇到了分叉口,然后你决定往哪拐必然是有所动作的。你要判断那条岔路是你真正要走的路,如果我们想让程序也能处理这样的判断怎么办?很简单,只需要在程序里预设一些条件判断......
  • 算法备案流程中的痛点攻克指南
    主体信息填报的难点主要包括以下几个方面:1.《落实算法安全主体责任基本情况》的填写:需要详细描述企业在算法安全方面的组织架构、专职机构设置、以及相关责任人的职责分配。2.算法安全责任人工作证明:必须提供算法安全责任人的身份证明和工作职责证明,这可能需要企业内部的详......
  • Node-RED开源流程网络工具
    文章目录Node-RED开源流程网络工具Node-RED介绍特点和设计理念Node-RDE安装Windows安装Docker安装基础功能示列HTTP发送请求MQTT-示列TCP-示列MQTT-Modbus示列Node-RED综合示列示列Node-RED开源流程网络工具Node-RED介绍Node-RED是一个基于Node.js的开源流程......
  • 王苏安说钢材@309s不锈钢无缝管的工艺流程详细介绍
    309S不锈钢无缝管的工艺流程一共分为五步,分别是①切割、②弯曲、③成型、④焊接、⑤表面处理  ①切割:309s不锈钢无缝管的切割方法有剪切、火焰切割、机械切割、等离子切割等,其中薄型材料可以用剪切或切割机进行,厚型材料可以用火焰切割和等离子切割。  ②弯曲:冷弯能够让3......
  • 【面试】介绍几种常见的进程调度算法及其流程
    面试模拟场景面试官:你能介绍一下几种常见的进程调度算法及其流程吗?参考回答示例进程调度是操作系统管理进程的核心功能,负责在多任务环境中分配CPU时间给各个进程。常见的进程调度算法包括先来先服务(FCFS)、短作业优先(SJF)、优先级调度、轮转调度(RR)以及多级反馈队列调度等......
  • 工作一年多,准备重新缕一下Java全流程(JDK8和JDK17,搭建环境)
    在重新学习的过程中哥们会吧一些理解不深的有疑问的记录在此系列中有好兄弟想一起学习,可以一起打卡记录一下一搭建环境今天下载了一下jdk17,因为工作中用8所以配置了一些兼容性的东西给大伙分析一些首先我们可以去官网下载jdk17,下载的话走默认路径就可以JavaDownloads|......
  • Xfce漫游(2) - Xfce的启动流程
    本文探讨从Linux启动直到进入xfce4会话之前的过程;和Xfce相关的内容需要单独进行说明。从按下电源键到进入登录界面不关心这些,不过可以参考这篇Arch的启动流程或者bootup(7)。在这个过程中systemd启动时,还会启动各种服务诸如D-bus等,可以阅读systemd.service(5)。systemd启动的目......
  • Java异常处理
    Java异常处理java:Compilationfailed:internaljavacompilererrorjava:Compilationfailed:internaljavacompilererror原因:idea的jdk版本和项目配置的不同。比对idea中三处关于jdk版本配置:setting-Build,Execution,Deployment-Compiler-JavaCompilerProj......