【Linux驱动】Linux的中断系统 | 中断的重要数据结构

🐱作者:一只大喵咪1201
🐱专栏:《Linux驱动》
🔥格言:你只管努力,剩下的交给时间!
图

目录

  • 🏀Linux系统的中断
    • ⚽中断分类
      • 软中断和硬中断
      • 中断的上半部和下半部
    • ⚽tasklet
    • ⚽工作队列
    • ⚽threaded_irq
  • 🏀Linux中断系统中的重要数据结构
    • ⚽irq_desc数组
    • ⚽irqaction结构体
    • ⚽irq_data结构体
  • 🏀总结

🏀Linux系统的中断

图
如上图所示,本喵使用的IMX6ULL也是ARM架构,中断也是异常的一种,CPU在运行的过程中,会被各种异常打断,包括:

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

导致中断发生的情况有很多,比如:

  • 按键
  • 定时器
  • ADC转化完成
  • UART发生接收中断
  • 等等

这些中断又汇集到中断控制器,由中断控制器选择优先级高的中断通知CPU。

CPU每执行完一条指令后都会检查是否有中断/异常产生,如果有的话就开始处理:

  1. 保存现场

IMUX6ULL中,现场完全是由软件保存的,不会像STM32F103一样硬件帮忙保存R0~R3寄存器的值到栈中。

  1. 处理异常/中断

由硬件分辨出中是中断以后,去异常向量表中寻找中断处理函数,并且跳转执行。

图
如上图所示就是Linux内核或者是u-boot中的异常向量表。每一条指令对应一种异常:

  • 发生复位时,CPU就去执行第 1 条指令:b reset
  • 发生中断时,CPU就去执行ldr pc, _irq这条指令。
    • 无论什么类型的中断,都是去执行这条指令,在_irq中断函数中由软件分辨具体的中断源。

这些指令存放的位置是固定的,比如本喵使用的IMX6ULL芯片,中断向量_irq的入口地址就是0x18,当发生中断时,CPU就强制跳转到0x18处执行代码。

在向量表里,一般放置的都是一条跳转指令,发生异常/中断时,CPU就会执行向量表中的跳转指令,去调用更复杂处理函数。

  • 向量表的位置并不总是从 0 地址开始,很多芯片可以设置某个vector base寄存器,指定向量表在其他位置。

比如设置vector base0x80000000, 指定为内存的某个地址,但是向量表中的各个异常向量的偏移地址是固定的:

  • 复位向量偏移地址是0
  • 中断是 0x18

⚽中断分类

中断中断,中断的是Linux中当前正在运行的进程和线程。

图
如上图所示,对于单核的CPU,此时有进程A和进程B两个进程在运行(Linux内核中认为线程是轻量级进程):

  • 在进程A运行的期间,产生了中断:
    • 保存A的现场
    • 执行中断处理函数
    • 没有更高优先级的进程,恢复A的现场
  • 继续运行A进程。。。,产生了定时器中断(系统的心跳——时钟):
    • 保存A的现场
    • A的时间片没有用完
    • 没有更高优先级的进程,恢复A的现场
  • 继续运行A进程。。。,产生了定时器中断:
    • 保存A的现场
    • A的时间片用完了
    • 找出进程B的现场,恢复B的现场

在Linux中,中断的处理有两个原则:

  • 中断不能嵌套。

假设中断可以嵌套的话会发生什么呢?

  • 假设正在处理1号中断,此时更高优先级的2号中断产生了,在处理2号中断之前,要保存1号中断的现场
  • 开始处理2号中断,此时更高优先级的3号中断产生了,在处理3号中断之前,要保存2号中断的现场

如此嵌套下去,会导致栈空间不足,现场保存出现问题,从而导致系统奔溃,所以为了安全和简便,在Linux不允许中断嵌套

  • 中断越快越好。

如果中断的处理时间较长,Linux中的线程以及进程就无法得到执行,尤其是GUI的进程,这样就会导致整个系统非常卡顿。

软中断和硬中断

Linux 系统把中断的意义扩展了,对于按键中断等硬件产生的中断,称之为硬件中断(hard irq)。每个硬件中断都有对应的处理函数,比如按键中断、 网卡中断,处理它们的中断函数肯定不一样。

如
如上图所示,可以简单认为有一个硬件中断数组,里面存放着不同硬件中断处理函数的指针。

  • 当发生A中断时,对应的irq_function_A函数被调用。

除了硬件中断外,还人为的制造了软件中断,每一个软件中断也对应有一个中断处理函数。

图
如上图所示,也可以简单的认为有一个软件中断数组,里面存放着不同软件中断处理函数的指针。

  • 当发生软件中断X时,对应的irq_function_X函数被调用。

软件中断的产生由flag决定,只要在软件中将其置为1就表示发生了该中断。

图
如上图所示,软件中断也有很多类型,我们比较常用的就是TASKLET_SOFTIRQ,它表示中断的下半部。

中断的上半部和下半部

如果一个中断处理必须要耗费比较长的时间来处理呢?比如键盘上的按键中断,它每产生一次后就需要扫描整个键盘,这是比较耗时的。

由于Linux中,中断不能嵌套,所以段时间内,系统是关中断的,此时就不会处理其他中断,再有中断产生时系统就会出问题。

为了遵循中断处理必须快的原则,可以将耗时较长的中断函数分为上半部分和下半部分:

  • 上半部分处理紧急的事情。
  • 下半部分处理不紧急的事情。

图

如上图所示,在中断的上半部中紧做紧急的事情,这个过程的中断是关闭的。比如给键盘发送信号,清除中断标志位,防止它不断向CPU发送中断信号,然后重新开中断。

在中断的下半部中处理那些不紧急的事,此时是开中断的,可以产生其他中断。

  • 在处理完中断上半部时,通过软件触发中断下半部的处理。
  • 中断下半部的处理发生在上半部处理完毕后。

⚽tasklet

当下半部比较耗时,并且处理比较简单时,可以使用tasklet来处理下半部,tasklet是软件中断一种类型。

图
如上图所示代码为执行中断处理的上半部和下半部流程,这样不清晰,画图来说明一下。

图
如上图所示是上半部和下半部的处理流程图,假设有中断A和中断B两个中断:

  • 中断A发生:
  1. 开始处理后,处于关中断状态,让preempt_count++,该值为1。
  2. 执行中断上半部,处理紧急事情。
  3. preempt_count--,该值为0。
  4. 判断preempt_count是否非0,此时是0,不符合条件,执行N分支。
  5. 再让preempt_count++,此时该值为1。
  6. 开中断,允许其他中断产生。
  7. 执行中断下半部,处理不紧急事情。
  8. 处理完毕后关中断。
  9. 再让preempt_count--,此时该值为0。

整个中断流程走完后会重新打开中断。


  • 中断A处理的过程中,再次发生A中断或者中断B发生:
  1. 开始处理后,处于关中断状态,让preempt_count++,该值为1。
  2. 执行中断上半部,处理紧急事情。
  3. preempt_count--,该值为0。
  4. 判断preempt_count是否非0,此时是0,不符合条件,执行N分支。
  5. 再让preempt_count++,此时该值为1。
  6. 开中断,允许其他中断产生。

在中断A执行下半部的时候,被其他中断打了,于是开始处理新的中断:

  1. 开始处理新中断后,处于关中断状态,让preempt_count++,该值为2。
  2. 执行新中断的上半部。
  3. preempt_count--,该值为1。
  4. 判断preempt_count是否非0,此时是1不是0,执行Y分支,直接退出新中断的处理。

新中断退出时重新打开了中断,允许其他中断产生。

  1. 恢复中断A的下半部处理,接着执行中断A的下半部。
  2. 处理完毕后关中断。
  3. 再让preempt_count--,此时该值为0。
  • 由于preempt_count的存在,新中断仅处理了上半部,没有处理下半部就直接退出了。
  • 被打断的中断A下半部没有受到影响,仍然恢复了执行。

中断的上半部和下半部是N : 1的关系,无论产生多少次相同类型的中断,中断的上半部会执行多次,但是中断的下半部只执行一次。

如果在处理中断A的下半部时,产生的是B中断,并且B中断的下半部是另一个软件中断,那么在B中断处理完上半部退出后:

  • 先恢复中断A的下半部继续处理。
  • 中断A的下半部处理完毕后,再去处理中断B的下半部。

⚽工作队列

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

假设下半部要执行 1~2 分钟,在这 1~2 分钟里进程都是无法响应的,Linux中根本无法忍受这种情况,所以,如果中断下半部要做的事情实在太耗时,那就不能用软件中断来做,而应该用内核线程来做:

  • 在中断上半部唤醒内核线程,用内核线程去处理中断下半部。
  • 此时内核线程和应用层线程都一起竞争CPU资源,系统不会卡顿。
  • 内核线程是Linux系统帮我们创建的,线程名为kworker

kworker线程会去工作队列work queu中取出一个一个工作work,来执行它里面的函数。

所以在使用内核线程处理中断的下半部时:

  1. 创建并填充work结构体:

图
如上图所示,先创建一个work结构体,然后调用DECLARE_WORK把要让内核线程执行的函数指针填充到work结构体中。

  1. work结构体提交给work queue,调用schedule_work实现。
  2. 执行work中的中断下半部函数。
    • 到底由谁执行不用我们管。
    • schedule_work还会把内核线程kworker唤醒。
  • 在中断场景中,可以在中断上半部调用schedule_work函数将work结构体提供给work queue
  • 既然此时中断下半部是在线程中运行,那对应的函数就可以阻塞和休眠,并不会影响其他进程和线程。

⚽threaded_irq

kworker内核线程来处理中断下半部时,一个kworker 线程只能由一个 CPU 执行, 多个中断的work都放在work queue中由同一个kworker线程来处理,在单 CPU 系统中是没有问题的。

但是在多核系统中,明明有那么多 CPU 空着,偏偏让多个中断的下半部挤在一个CPU上处理并不合适。

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

图
如上图所示request_threaded_irq函数,用来为每一个中断下半部创建一个线程。

  • irq:表示哪个中断,后面本喵再详细讲解。
  • handler:中断上半部处理函数,可以为空。
  • thread_fn:中断下半部内核线程处理函数。

其他参数在用到时候再进行说明。

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

图
如上图所示便是Linux系统中最重要的数据结构,弄明白这个图也就了解了Linux的中断系统。

前面说,硬件中断和软件中断的处理函数放在一个数组中,确实是这样,只是这个数组是一个结构体数组,而核心便是irq_desc结构体。

图
如上图所示中断结构图,产生中断时:

  • 外部设备1~外部设备n共享一个GPIO中断B,该中断是GPIO中的某一个引脚。
  • 多个GPIO中断汇聚到GIC(通用中断控制器)的A号中断。
  • GIC再去中断CPU,来处理中断源。

CPU处理中断时:

  • 先读取GIC中的寄存器获得中断号A。
  • 再从GPIO中得到中断号B。
  • 最后判断是哪一个外部设备发生了中断。

⚽irq_desc数组

irq_desc数组中的每一个元素都是irq_desc结构体。

图

如上图所示irq_desc结构体的定义,irq_desc数组中的每一项都有一个函数指针handle_irq,还有一个action链表。

CPU在处理中断时,中断处理函数的来源有三个:

  1. GIC的中断处理函数:
  • CPU根据GIC中的寄存器,确定了中断号A,从而去调用中断处理函数irq_desc[A].handle_irq

  • 该函数会读取GPIO控制器中的寄存器,确定是哪个引脚发生了中断,从而确定中断号B,再去调用irq_desc[B].handle_irq函数。

  • 中断A是CPU感受到的顶层中断。
  1. 模块的中断处理函数:

对于GPIO模块的中断B,BSP开发人员会设置对应的处理函数,一般是:

  • handle_level_irq:电平触发处理函数。
  • handle_edge_irq:边沿触发处理函数。

但是此时中断B是一个共享中断,该引脚上的外部设备1~外部设备n都可能产生中断,可能是一个设备,也可能是多个设备。

所以irq_desc[B].handle_irq需要判断是哪个外部设备产生的中断。

图
如上图所示,此时就会遍历irq_desc[B]结构体中的action链表,链表中的每一项都能代表一个外部设备,并且有外部设备的中断处理函数。

  • 从链表中能找到产生中断的外部设备。进而调用外部设备的中断处理函数。
  • 遍历寻找和调用同样是由BSP开发工程师实现的。
  1. 外部设备提供的中断处理函数:

外部设备可能是芯片,也可能总是简单的按键,它们的中断处理函数由自己的驱动程序提供,因为:

  • 它是最熟悉这个设备的人。
  • 它知道如何判断设备是否发生了中断。
  • 它知道发生了中断后该如何处理。

所以对于共享中断B,它的irq_desc[B]结构体中的action链表中就会存放着多个外部设备的中断处理函数。

一旦irq_desc[B].handle_irq中断处理函数确定是哪个外部设备产生了外部中断,就会调用外部设备的中断处理函数。

  • 根据上面分析,虽然GIC中断控制器和GPIO中断控制器有上下级之分,但是它们所包含的中断号都在irq_desc数组中。

⚽irqaction结构体

iqr_desc[B]中的链表action里,每一项都是irqaction结构体变量。

图
如上图所示irqaction结构体的定义,当外部设备的驱动程序调用request_irqrequest_threaded_irq注册中断处理函数时,内核就会构造一个irqaction结构体变量,而且会初始化namedev_id等成员。

  • 只用request_irq注册中断处理函数时,注册的就是上半部,意味着完全由上半部来处理。

最重要的是handlerthread_fn以及thread三个成员:

  • handler:是中断处理函数的上半部,用来处理紧急的事情。
  • thread_fn:是内核线程中断处理函数的下半部,用来处理不紧急且耗时的事情。
  • thread:是用来处理中断下半部的内核线程,当handler执行完毕后,Linux会唤醒该内核线程,执行thread_fn中断下半部处理函数。

在初始化这三个成员时,要注意:

  • 可以不提供handler只提供thread_fn,完全由内核线程来处理中断。
  • 也可以既提供handler也提供thread_fn,这就是中断上半部、下半部。

至于dev_id成员,是在调用request_irq时传入的,该成员有两个作用:

  • 中断处理函数执行时,能够用得上dev_id
  • 卸载中断时要传入dev_id,这样才能在action链表中根据dev_id找到对应项。
    • 所以在共享中断中必须提供dev_id,非共享中断可以不提供。

⚽irq_data结构体

irq_des数组的每个成员,如irq_desc[A]中,除了有struct iqraction类型的链表action外,还有类型是struct iqr_data的成员irq_data

图
如上图所示irq_data结构体的定义,它就是个中转站,里面有irq_chip指针和irq_domain指针,都是指向别的结构体。

  • irq:软件中断号(虚拟中断号)。
  • hirq:硬件中断号。
  • 这里的软件中断号是软件根据硬件中断号映射出来的,和前面的软件中断的中断号不同。
  • 通过软件中断号可以在irq_desc数组中找到相应中断的处理函数,如irq_desc[B].handler_irq

我们在驱动程序中使用request_irqrequest_threaded_irq注册中断处理函数的时候,传入的irq参数就是这个虚拟的软件中断号。

irq_data中的irq_domain成员会建立hirqirq之间的映射关系,将hirq映射为全局的irq

irq_domain结构体:

图
如上图所示,irq_domain结构体中有一个irq_domain_ops结构体成员,里面存放有xlate函数和map函数:

  • xlate:用来解析设备树中的中断属性,提取出hwirqtype等信息。
  • map:把hwirq转化为irq

假设现在有gpio1_5gpio2_5俩个引脚是中断源,此时这两组使用的硬件中断号hirq都是5,只通过hirq是无法区别这两个引脚的。

此时就需要根据gpio1gpio2各自的irq_domain结构体使用xlate来区分了,并且使用map将这两个硬件中断号转化成两个不同的软件中断号。

  • 将转化后的hirqirq的映射关系存放到linear_revmap成员数组中。

此时就能根据硬件中断号hirq直接找到映射出来的软件中断号irq了。

irq_chip结构体:

tu
如上图所示irq_chip结构体的定义,这个结构体跟芯片息息相关,作用就是对GPIO等中断控制器模块中的中断源进行使能等操作。

  • irq_enable:使能中断。
  • irq_disable:使能中断。
  • irq_mask:屏蔽中断。
  • irq_unmask:解除屏蔽。

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

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

但是对于外部设备相关的清中断操作,还是需要我们自己做的。 就像上图里的外部设备 1~外部设备 n,因为外设备千变万化,内核里可没有对应的清除中断操作。

🏀总结

对于Linux系统的中断,要知道有软件中断和硬件中断,并且将比较耗时但处理简单的中断程序分为上半部和下半部:

  • 处理上半部时是关中断的,此时无法产生其他中断。
  • 处理下半部时是开中断的,可以产生其他中断。

对于中断下半部,又分为三种处理方式:

  • 软件中断tasklet
  • 内核线程kworker,只有一个内核线程去处理多个中断下半部。
  • threaded_irq,为每一个中断的下半部创建一个内核线程。

要了解Linux中是如何描述和处理中断的,清除irq_desc数组的大致构成和工作原理。

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

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

相关文章

STM32--基于STM32F103的MAX30102心率血氧测量

本文介绍基于STM32F103ZET6MAX30102心率血氧测量0.96寸OLED(7针)显示(完整程序代码见文末链接) 一、简介 MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器…

如何将远程桌面全屏显示在3台显示器中的2台

cmd运行: mstsc /l编辑*.rdp文件 screen mode id:i:2 use multimon:i:1 selectedmonitors:s:1,4 desktopwidth:i:1920 desktopheight:i:1080 session bpp:i:32 winposstr:s:0,0,1920,0,3840,1080

Matlab 使用 DH table 建立的 robot 和实际不符

机器人仿真 想借助 matlab robotics toolbox 来仿真机器人,但是直接输入自己的 DH table 显示出来的 robot 和实际不情况不符。 DH table 建立 robot Build Manipulator Robot Using Kinematic DH Parameters 主要使用 setFixedTransform,DH table 中…

谷歌开发者账号关联的主要原因有哪些?应如何解决或避免?

众所周知,谷歌对账号的风控非常严格,不允许一个开发者拥有多个开发者账号,且相信大多数开发者都收到过谷歌发来的账号终止的邮件,甚至同时连续被封多个账号。 大多数开发者账号被封的的主要原因是谷歌认为或检测出账号存在高风险行…

Inis博客系统本地部署结合内网穿透实现远程访问本地站点

文章目录 前言1. Inis博客网站搭建1.1. Inis博客网站下载和安装1.2 Inis博客网站测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2 Cpolar稳定隧道(云端设置)2.3.Cpolar稳定隧道(本地设置) 3. 公网访问测试总…

龙芯3A5000上使用腾讯会议

原文链接:龙芯3A5000上使用腾讯会议 hello,大家好啊!今天我要给大家介绍的是在龙芯3A5000处理器上安装使用腾讯会议的经验分享。随着远程工作和在线会议的普及,腾讯会议成为了许多人日常工作不可或缺的工具。而对于使用龙芯3A5000…

JetCache源码解析——缓存处理

在Java技术体系中,如果想要在不改变已有代码逻辑的情况下,对已有的函数进行功能增强,一般可以使用两种方式,如AOP(Aspect Oriented Programming),即面向切面编程,以及代理模式&#…

MacOS访问某局域网域名存在问题,但是ip可以正常访问的问题解决方案

背景: 公司服务器部署wiki,之前macos访问wiki服务都是通过ip:8090形式访问,后来给wiki服务配置了域名wiki:8090,后macos浏览器连接同样的网络却一直没办法正常域名访问wiki,但可以ip访问wiki,但是其他同事…

用通俗易懂的方式讲解:对 embedding 模型进行微调,我的大模型召回效果提升了太多了

QA对话目前是大语言模型的一大应用场景,在QA对话中,由于大语言模型信息的滞后性以及不包含业务知识的特点,我们经常需要外挂知识库来协助大模型解决一些问题。 在外挂知识库的过程中,embedding模型的召回效果直接影响到大模型的回…

四、C++运算符(5)逻辑运算符

作用&#xff1a;用于根据表达式的值返回真值或假值 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> using namespace std; int main() {//逻辑运算符 非&#xff01;int a 10;int b 20;//在c中除了0都是真cout << !a << end…

小红书种草类型有哪些,小红书营销攻略

我们都知道小红书是个内容平台。用户来这可以看到各种类型的笔记&#xff0c;从笔记中获取自己想要了解的内容。这也就意味着平台上有着许多种不同的笔记类型。今天我们和大家分享下小红书种草类型有哪些&#xff0c;小红书营销攻略&#xff01; 1. 明星带货类 顾名思义&#x…

职称为什么要提前报名?⬇️ ⬇️

评职称需要一堆的材料&#xff0c;比如&#xff1a; 论文发表&#xff0c;从写作到选期刊到发表&#xff0c;需要3-12个月的时间继续教育&#xff0c;职称评审对于继续教育是有学时要求的&#xff0c;这一点申报职称的人想必都清楚&#xff0c;但具体学时要求,就要根据当时的要…