静安网站建设公司熊岳网站怎么做
news/
2025/9/23 13:02:17/
文章来源:
静安网站建设公司,熊岳网站怎么做,辣条类网站建设规划书,网页设计图片对齐方式文章目录 异常与中断的关系中断的处理流程异常向量表Linux 系统对中断的处理ARM 处理器程序运行的过程程序被中断时#xff0c;怎么保存现场Linux 系统对中断处理的演进Linux 对中断的扩展#xff1a;硬件中断、软件中断硬件中断软件中断 中断处理原则#xff1a;耗时中断的… 文章目录 异常与中断的关系中断的处理流程异常向量表Linux 系统对中断的处理ARM 处理器程序运行的过程程序被中断时怎么保存现场Linux 系统对中断处理的演进Linux 对中断的扩展硬件中断、软件中断硬件中断软件中断 中断处理原则耗时中断的拆分上半部分和下半部分中断下半部分的实现tasklet中断下半部分的实现工作队列 耗时中断的新技术threaded irq线程化的中断 Linux 中断系统中的重要数据结构概述irq_desc数组irqaction结构体irq_domain结构体irq_data结构体irq_chip结构体 在设备树中指定中断、在驱动中获得中断设备树里中断节点的语法设备树里的中断控制器设备树里使用中断 设备树里中断节点的示例在代码中获得中断对于 platform_device对于 I2C 设备、SPI 设备调用 of_irq_get 获得中断号对于 GPIO 异常与中断的关系 异常有
①指令未定义② 指令、数据访问有问题③ SWI(软中断)④ 快中断⑤ 中断
中断也属于一种“异常”导致中断发生的情况有很多比如
按键定时器ADC 转换完成UART 发送完数据、收到数据
众多的“中断源”汇集到“中断控制器”由“中断控制器”选择优先级最高的中断并通知 CPU中断控制器也可以屏蔽中断信号。
中断的处理流程
arm 对异常(中断)处理过程
① 初始化 a) 设置中断源让它可以产生中断b) 设置中断控制器(可以屏蔽某个中断优先级)c) 设置 CPU 总开关(使能中断) ② 执行其他程序正常程序③ 产生中断比如按下按键—中断控制器—CPU④ CPU 每执行完一条指令都会检查有无中断/异常产生⑤ CPU 发现有中断/异常产生开始处理。 对于不同的异常跳去不同的地址执行程序。这地址上只是一条跳转指令跳去执行某个函数(地址)这个就是异常向量。③④⑤都是硬件做的。 ⑥ 这些函数做什么事情软件做的: a) 保存现场(各种寄存器)b) 处理异常(中断):分辨中断源再调用不同的处理函数c) 恢复现场
异常向量表
u-boot 或是 Linux 内核都有类似如下的代码
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq // 发生中断时CPU 跳到这个地址执行该指令 ** 假设地址为 0x18**
ldr pc, _fiq这就是异常向量表每一条指令对应一种异常。
发生复位时CPU 就去 执行第 1 条指令b reset。发生中断时CPU 就去执行“ldr pc, _irq”这条指令。这些指令存放的位置是固定的比如对于 ARM9 芯片中断向量的地址是0x18。当发生中断时CPU 就强制跳去执行 0x18 处的代码。
在向量表里一般都是放置一条跳转指令发生该异常时CPU 就会执行向量表中的跳转指令去调用更复杂的函数。当然向量表的位置并不总是从 0 地址开始很多芯片可以设置某个vector base 寄存器指定向量表在其他位置比如设置 vector base 为0x80000000指定为 DDR 的某个地址。但是表中的各个异常向量的偏移地址是固定的复位向量偏移地址是 0中断是 0x18。
Linux 系统对中断的处理
ARM 处理器程序运行的过程
ARM 芯片属于精简指令集计算机(RISCReduced Instruction SetComputing)它所用的指令比较简单有如下特点
① 对内存只有读、写指令② 对于数据的运算是在 CPU 内部实现③ 使用 RISC 指令的 CPU 复杂度小一点易于设计
比如对于 aab 这样的算式需要经过下面 4 个步骤才可以实现CPU 运行时先去取得指令再执行指令
① 把内存 a 的值读入 CPU 寄存器 R0② 把内存 b 的值读入 CPU 寄存器 R1③ 把 R0、R1 累加存入 R0④ 把 R0 的值写入内存 a
程序被中断时怎么保存现场
如果要暂停一个程序中断一个程序就需要把这些寄存器的值保存下来这就称为保存现场。
寄存器保存在内存中这块内存就称之为栈。程序要继续执行就先从栈中恢复那些 CPU 内部寄存器的值。如图所示概括程序 A、B 的切换过程 这个场景并不局限于A、B 的切换过程也可以中断函数调用等 ① 函数调用
a) 在函数 A 里调用函数 B实际就是中断函数 A 的执行。b) 那么需要把函数 A 调用 B 之前瞬间的 CPU 寄存器的值保存到栈里c) 再去执行函数 Bd) 函数 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 了。
进程的调度也是使用栈来保存、恢复现场在linux中线程才是调度的最小单位这些线程共用打开的文件句柄、全局变量等等。而这些线程之间是互相独立的“同时运行”也就是说每一个线程都有自己的栈那么在同一个进程里面所有的线程共享代码段和数据段同时他们还有各自的栈内存保存CPU寄存器里面的值 Linux 系统对中断处理的演进
Linux 对中断的扩展硬件中断、软件中断
硬件中断
对于按键中断等硬件产生的中断称之为“硬件中断”(hard irq)。每个硬件中断都有对应的处理函数
可以认为对硬件中断的处理是用数组来实现的数组里存放的是函数指针 当硬件触发中断系统会根据中断号调用中断处理函数
软件中断
相对的可以人为地制造中断软件中断(soft irq)
如何触发软中断——由软件决定对于 X 号软件中断只需要把它的 flag 设置为 1 就表示发生了该中断
软件中断何时处理——Linux 系统中各种硬件中断频繁发生例如定时器中断每 10ms 发生一次那将软件中断设计在处理完硬件中断后再去处理软件中断
有哪些软件中断——查内核源码 include/linux/interrupt.h
enum
{HI_SOFTIRQ0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, TASKLET_SOFTIRQ SCHED_SOFTIRQ,
#ifdef CONFIG_HIGH_RES_TIMERSHRTIMER_SOFTIRQ,
#endif
};怎么触发软件中断——最核心的函数是 raise_softirq简单地理解就是设置 softirq_veq[nr]的标记位
extern void raise_softirq(unsigned int nr) //用于引发一个软中断类似普通中断怎么设置软件中断的处理函数
extern void open_softirq(int nr, void (*action) (struct soft_action*));中断处理原则 Linux系统中中断不能嵌套否则会导致栈不够用 中断处理要越快越好即在注册中断 handler 函数中指定中断处理函数代码尽可能高效
在 Linux 系统中使用中断是挺简单的为某个中断 irq 注册中断处理函数handler可以使用 request_irq 函数 如果中断要做的事情就是很多没办法加快则需要将耗时的事情拆分 耗时中断的拆分上半部分和下半部分
当一个中断要耗费很多时间来处理时它的坏处是在这段时间内其他中断无法被处理。换句话说在这段时间内系统是关中断的
耗时的中断中断分为上半部分/下半部分在 handler 函数里只做紧急的事然后就重新开中断让系统得以正常运行那些不紧急的事以后再处理处理时是开中断的。 中断下半部的实现有很多种方法主要讲2种
tasklet(小任务)work queue(工作队列)。
中断下半部分的实现tasklet
当下半部比较耗时但是能忍受并且它的处理比较简单时可以用tasklet 来处理下半部。tasklet 是使用软件中断来实现。 使用流程图简化一下 中断下半部执行时有可能会被多次打断有可能会再次发生同一个中断 中断下半部执行时再次发生同一个中断则同一个中断的上半部、下半部在执行时是多对一的关系。中断下半部执行时如果被多次打断如果在A中断的下半部出现中断则会执行B中断的上半部分然后再进行preemot_count判断时直接进入完成处理之后是恢复整个软件中断的处理包含A中断和B中断的下半部分。【软件中断时定时处理的】
中断下半部分属于软件中断的一种在枚举类型里面对应TASKLET_SOFTIRQ 中断下半部分的实现工作队列
在中断下半部的执行过程中虽然是开中断的期间可以处理各类中断。但是毕竟整个中断的处理还没走完这期间 APP 是无法执行的。在中断上半部唤醒内核线程。内核线程和APP 都一样竞争执行APP 有机会执行系统不会卡顿。
这个内核线程是系统帮我们创建的一般是 kworker 线程内核中有很多这样的线程
kworker 线程要去“工作队列”(work queue)上取出一个一个“工作”(work 结构体)来执行它里面的函数。
使用
①创建 work 你得先写出一个函数然后用这个函数填充一个 work 结构体。比如
static DECLARE_WORK(aer_recover_work,aer_recover_work_fun);
//第一参数是work结构体② 要执行这个函数时把 work 提交给 work queue 就可以了
schedule_work(aer_recover_work);上述函数会把 work 提供给系统默认的 work queuesystem_wq它是一个队列。 在中断上半部调用 schedule_work 函数触发 work 的处理 ③ 谁来执行 work 中的函数 schedule_work 函数不仅仅是把 work 放入队列还会把kworker 线程唤醒。此线程抢到时间运行时它就会从队列中取出 work执行里面的函数。
④ 谁把 work 提交给 work queue 在中断场景中可以在中断上半部调用 schedule_work 函数。 工作队列只是创建一个线程对于多核CPU来说可以处理多个线程所以将中断下部分实现的技术换为threaded irq这样每个中断下半部分为一个线程在任意一个CPU中都可以执行 耗时中断的新技术threaded irq线程化的中断 你可以只提供 thread_fn系统会为这个函数创建一个内核线程。发生中断时内核线程就会执行这个函数。 以前用 work 来线程化地处理中断一个 worker 线程只能由一个 CPU 执行多个中断的 work 都由同一个 worker线程来处理在单 CPU 系统中也只能忍着了。但是在 SMP 系统中明明有那么多 CPU 空着你偏偏让多个中断挤在这个CPU 上新技术threaded irq为每一个中断都创建一个内核线程多个中断的内核线程可以分配到多个 CPU 上执行这提高了效率。 Linux 中断系统中的重要数据结构
概述
本节内容可以从request_irq函数(头文件include/linux/interrupt.h)一路分析得到。 注意如果内核配置了CONFIG_SPARSE_IRQ那么它就会用基数树(radixtree)来代替irq_desc数组。SPARSE的意思是“稀疏”假设大小为1000的数组中只用到2个数组项那不是浪费嘛所以在中断比较“稀疏”的情况下可以用基数树来代替数组。 irq_desc数组
irq_desc结构体在include/linux/irqdesc.h中定义主要内容如下图 每一个irq_desc数组项中都有一个函数handle_irq还有一个action链表。要理解它们需要先看中断结构图硬件中断的信号从左到右函数的处理从右到左函数主要是读取寄存器确定中断的来源。由图可知共享中断会出现两个中断号A号中断和B号中断 在Linux内核中对于每一个硬件中断都有一个数组成员项所以下图有GIC结构体和GPIO结构体每个结构体都有handle_irq还有一个action链表。 首先GIC中断CPU时CPU读取GIC状态得到中断A。
① GIC的处理函数
irq_desc[A].handle_irq是XXX_gpio_irq_handler(XXX指厂家)假设A号中断对应的是GPIO中断这个函数需要读取芯片的GPIO控制器细分发生的是哪一个GPIO中断(假设是B)再去调用irq_desc[B]. handle_irq。
② 模块的中断处理函数
导致GPIO中断B发生的原因很多可能是外部设备1可能是外部设备n可能只是某一个设备也可能是多个设备。所以irq_desc[B].handle_irq会依次调用某个链表action链表里的函数【BSP开发人员会设置对应的处理函数一般是handle_level_irq或handle_edge_irq从名字上看是用来处理电平触发的中断、边沿触发的中断】这些函数由外部设备提供。这些函数自行判断该中断是否自己产生若是则处理。
③ 外部设备提供的处理函数
这里说的“外部设备”可能是芯片也可能总是简单的按键。它们的处理函数由自己驱动程序提供这是最熟悉这个设备的“人”它知道如何判断设备是否发生了中断如何处理中断。
irqaction结构体
irqaction结构体在include/linux/interrupt.h中定义主要内容如下图
当调用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这就是中断上半部、下半部。 在调用reqeust_irq函数时可以传入dev_id作用有2个
① 中断处理函数执行时可以使用dev_id② 卸载中断时要传入dev_id这样才能在action链表中根据dev_id找到对应项所以在共享中断中必须提供dev_id非共享中断可以不提供。
irq_domain结构体
irq_domain结构体在include/linux/irqdomain.h中定义主要内容如下图
在设备树中有这样的属性
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结构体。即每个一GPIO都有自己的irq_domain结构体
irq_domain结构体中有一个irq_domain_ops结构体里面有各种操作函数主要是
① xlate用来解析设备树的中断属性提取出hwirq、type等信息。② map把hwirq转换为irq。即转为为虚拟中断号【软件中断号会保存在platform_diverce】
map函数会完成映射关系会保存在unsigned int linear_revmap[];数组里面 例如读取GPIO寄存器得到hwirq根据hwirq得到之前映射的irq软件中断号B
irq_data结构体
irq_data结构体在include/linux/irq.h中定义主要内容如下图
它就是个中转站里面有irq_chip指针 irq_domain指针都是指向别的结构体。
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。
irq_chip结构体
irq_chip结构体在include/linux/irq.h中定义主要内容如下图
这个结构体跟“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”外设备千变万化内核里可没有对应的清除中断操作
在设备树中指定中断、在驱动中获得中断
可以参考文档内核的Documentation\devicetree\bindings\interrupt-controller\interrupts.txt
设备树里中断节点的语法
设备树里的中断控制器
比如中断的硬件框图如下设备树会将这个硬件图转化为节点
在硬件上“中断控制器”只有 GIC 这一个但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。很多芯片有多个 GPIO 模块比如 GPIO1、GPIO2 等等。所以软件上的“中断控制器”就有多个。各个中断控制器之间有父子关系。
假设 GPIO1 有 32 个中断源但是它把其中的 16 个汇聚起来向 GIC 发出一个中断把另外 16 个汇聚起来向 GIC 发出另一个中断。这就意味着 GPIO1 会用到 GIC 的两个中断会涉及 GIC 里的 2 个 hwirq。这些层级关系、中断号(hwirq)都会在设备树中有所体现。
在设备树中中断控制器节点中必须有一个属性interrupt-controller表明它是“中断控制器”。还必须有一个属性#interrupt-cells表明引用这个中断控制器的话需要多少个 cell。
#interrupt-cells 的值一般有如下取值
#interrupt-cells1 别的节点要使用这个中断控制器时只需要一个 cell 来表明使用“哪一个中断”。#interrupt-cells2 别的节点要使用这个中断控制器时需要一个 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 低电平触发如果中断控制器有级联关系下级的中断控制器还需要表明它的“ interrupt-parent ” 是 谁 用 了 interrupt-parent ” 中的哪一个“interrupts”
设备树里使用中断 一个外设它的中断信号接到哪个“中断控制器”的哪个“中断引脚”这个中断的触发方式是怎样的这 3个问题在设备树里使用中断时都要有所体现。 例如
i2c7000c000 {gpioext: gpio-adnp41 {compatible ad,gpio-adnp;//用哪一个中断控制器里的中断interrupt-parent gpio;//用哪一个中断Interrupts 里要用几个 cell由 interrupt-parent 对应的中断控制器决定interrupts 160 1;gpio-controller;#gpio-cells 1;interrupt-controller;#interrupt-cells 2;//指明子节点的指定中断控制器的话需要多少个 cell};
......
};新的写法interrupts-extended指明了上述的三个问题
一个“interrupts-extended”属性就可以既指定“interrupt-parent”也指定“interrupts”比如
interrupts-extended intc1 5 1, intc2 1 0;设备树里中断节点的示例
从设备树文件里面将有关中断的部分内容抽取出来 对于第一个节点是GIC控制器使用到该控制器需要三个cell因为对于多CPU系统GIC可以发出两种信号SPIPPI而CPU之间可以通过SGI进行通信所以GIC控制器的第一个cell必须要指定是哪种信号SPI或PPI或SGI 从设备树反推 IMX6ULL 的中断体系如下比之前的框图多了一个“GPC INTC” 在代码中获得中断
根据博文哪些设备树节点会被转换为 platform_device提到设备树中的节点有些能被转换为内核里的platform_device有些不能
对于 platform_device
一个节点能被转换为 platform_device如果它的设备树里指定了中断属性那么可以从platform_device 中获得“中断资源”函数如下可以使用下列函数获得 IORESOURCE_IRQ 资源即中断号
/**
* platform_get_resource - get a resource for a device
* dev: platform device
* type: resource type // 取哪类资源IORESOURCE_MEM 、IORESOURCE_REG
* // IORESOURCE_IRQ 等
* num: resource index // 这类资源中的哪一个
*/
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type,unsigned int num);对于 I2C 设备、SPI 设备
对于 I2C 设备节点I2C 总线驱动在处理设备树里的 I2C 子节点时也会处理其中的中断信息。一个 I2C 设备会被转换为一个 i2c_client 结构体中断号会保存在 i2c_client 的 irq 成员里代码如下(drivers/i2c/i2c-core.c) 对于 SPI 设备节点SPI 总线驱动在处理设备树里的 SPI 子节点时也会处理其中的中断信息。一个 SPI 设备会被转换为一个 spi_device 结构体中断号会保存在 spi_device 的 irq 成员里代码如下(drivers/spi/spi.c)
调用 of_irq_get 获得中断号
如果你的设备节点既不能转换为 platform_device它也不是 I2C 设备不是 SPI 设备那么在驱动程序中可以自行调用 of_irq_get 函数去解析设备树得到中断号。
对于 GPIO
参考drivers/input/keyboard/gpio_keys.c可以使用 gpio_to_irq 或 gpiod_to_irq 获得中断号。 举例假设在设备树中有如下节点
gpio-keys {compatible gpio-keys;pinctrl-names default;user {label User Button;gpios gpio5 1 GPIO_ACTIVE_HIGH;gpio-key,wakeup;linux,code KEY_1;};
};那么可以使用下面的函数获得引脚和 flag
button-gpio of_get_gpio_flags(pp, 0, flags);
bdata-gpiod gpio_to_desc(button-gpio);再去使用 gpiod_to_irq 获得中断号
irq gpiod_to_irq(bdata-gpiod);
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/912619.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!