嵌入式驱动学习第一周——内核的中断机制

前言

   本文介绍中断机制,中断作为需要频繁使用的功能,本文将详细介绍linux内核中的中断机制。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

行文目录

  • 前言
  • 1. 中断机制介绍
    • 1.1 中断概述
    • 1.2 中断的作用:
    • 1.3 中断的产生:
  • 2. 中断实现原理
    • 2.1 中断处理流程
    • 2.2 中断向量表
  • 3. 中断来源
    • 3.1 软件中断
      • 3.1.1 CPU异常
      • 3.1.2 指令中断
    • 3.2 硬件中断
      • 3.2.1 外设中断
      • 3.2.2 处理器间中断
  • 4. 上半部与下半部
    • 4.1 软中断
    • 4.2 tasklet
    • 4.3 工作队列
  • 5. 中断的API
  • 参考资料

1. 中断机制介绍

1.1 中断概述

   当你在刷手机的时候,此时突然npy打电话来了,于是你退出刷手机状态,接听npy电话,此过程即为中断。

   简单来说,中断会让CPU停止正在执行的程序,转而让CPU执行中断处理函数,执行完再返回原程序。

   另外,整个操作系统就是一个中断驱动的死循环,即裸机开发中常写的while(true) {}。其他所有的事情都是由操作系统提前注册的中断机制和其对应的中断处理函数完成。

1.2 中断的作用:

   中断主要有4个用途:外设异步通知CPUCPU间发送消息处理CPU异常实现系统调用

1.3 中断的产生:

   中断信号的产生有以下4个来源:

   1. 外设 :外设产生的中断信号是异步的,一般也叫硬件中断,硬件中断按照是否可以屏蔽分为可屏蔽中断和不可屏蔽中断,例如:网卡、磁盘、定时器都可以产生硬件中断。

   2. CPU:一个CPU向另一个CPU发送中断,叫做IPI(处理器间中断),是一种特殊的硬件中断,也是异步的。

   3. CPU异常:CPU在执行指令的过程中发现异常会向自己发送中断信号,这种中断是同步的,一般也叫做软件中断。

   4. 中断指令:直接用CPU指令来产生中断信号,这种中断和CPU异常一样是同步的,也可以叫做软件中断。例如,中断指令int 0x80可以用来实现系统调用。

2. 中断实现原理

2.1 中断处理流程

   在单片机或裸机开发中,中断的处理方法是:

①、使能中断,初始化相应的寄存器
②、注册中断服务函数,也就是向 irqTable 数组的指定标号处写入中断服务函数
③、中断发生以后进入 IRQ 中断服务函数,在 IRQ 中断服务函数在数组 irqTable 里面查找具体的中断处理函数,找到以后执行相应的中断处理函数。

   中断的执行时间不可以过长,否则会影响对新的中断信号的响应性,所以要尽量缩短中断执行场景的时间,为此对异步中断的处理方法有两种:

   1、立即完全处理

   对于简单好处理的异步中断可以立即进行完全处理。

   2、立即预处理(上半部)+稍后完全处理(下半部)

   对于处理起来耗时的可以采取立即预处理加稍后完全处理的方式来实现中断。 立即预处理只能用直接处理来实现,而稍后完全处理的方法分两类:直接中断后处理有 softirq(软中断)、tasklet(微任务)、线程化中断后处理有workqueue(工作队列)、threaded_irq(中断线程)。

   此处有一个概念:硬件中断、软件中断、硬中断、软中断是不同的概念,前两个是中断来源,后两个是中断处理方式。

2.2 中断向量表

   我们每个人都有各自的身份证,代表每个人的唯一id,这样通过身份证就可以指定唯一的人。中断也是这样的,不同的中断信号有不同的处理方式,那么系统如何区分呢,即通过中断向量号。中断向量号是一个整数,CPU收到一个中断信号会根据这个信号的中断的向量号去查询中断向量表,根据中断向量表调用相应的处理函数。

   中断向量表是一个表,表里面存放的是中断向量。中断服务程序的入口地址或存放中端服务程序的首地址成为中断向量,因此中断向量表是一系列中断服务程序入口地址组成的表。

3. 中断来源

3.1 软件中断

   软件中断主要是两类:CPU异常和指令中断。

3.1.1 CPU异常

   CPU在执行过程中遇到异常就会给自己发送异常信号,但是异常信号不一都是错误。

3.1.2 指令中断

   指令中断是因为执行指令而产生了中断,指令中断是执行特定指令而发生的中断,设计这些指令的目的就是为了产生中断。其中INT n可以产生任意中断,Linux用int ix80来作为系统调用的指令。

3.2 硬件中断

   硬件中断分为外设中断和处理器间中断(IPI)。

3.2.1 外设中断

   外设中断和软件中断有一个很大的不同,软件中断是CPU自己给自己发送中断,而外设中断是需要外设发送中断给CPU。显然不可能将所有外设都直接连到CPU上,因此需要一个中间设备,替CPU连接到所有外设接受中断信号,这个设备叫中断控制器

   不同的架构有不同的中断控制器,比如STM32这种Cortex-M内核的单片机叫NVIC,Cortex-A中叫GIC,x86上Intel开发的叫APIC。

3.2.2 处理器间中断

4. 上半部与下半部

   上半部希望执行时间快,不会占用很长时间的处理;下半部多用于处理耗时的代码,保证中断函数的快进快出。

   哪部分属于上半部,哪部分属于下半部没有明确规定,可以有一下一些原则:

①、要处理的内容不希望被其他中断打断,可以放入上半部
②、如果要处理的任务对时间敏感,可以放上半部
③、如果要处理的任务与硬件有关,可以放上半部

   上半部的实现直接编写中断处理函数,下半部有多种实现机制,具体下文介绍。

4.1 软中断

   Linux内核中使用结构体softirq_action表示软中断。

/** @description : 注册软中断处理函数* @param-nr    : 要开启的软中断* @param-action: 软中断对应的处理函数* @return      : 无*/
void open_softirq(int nr, void (*action)(struct softirq_action *))

   软中断类型枚举如下:

enum
{HI_SOFTIRQ=0, /* 高优先级软中断 */TIMER_SOFTIRQ, /* 定时器软中断 */NET_TX_SOFTIRQ, /* 网络数据发送软中断 */NET_RX_SOFTIRQ, /* 网络数据接收软中断 */BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, /* tasklet 软中断 */SCHED_SOFTIRQ, /* 调度软中断 */HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */RCU_SOFTIRQ, /* RCU 软中断 */NR_SOFTIRQS
};

   注册好软中断后需要通过raise_softirq() 函数触发

/** @description: 出发软中断* @param-nr   : 要触发的中断* @return     : 无*/
void raise_softirq(unsigned int nr)

4.2 tasklet

   tasklet使用方法简单、灵活,自带有锁机制可以防止多个CPU同时运行,是中断处理下半部分最常用的一种方法,通过执行中断处理程序来快速完成上半部分的工作,接着通过调用tasklet使得下半部分的工作得以完成。tasklet执行过程中是可以被硬件中断所中止的,这样不会影响系统实时性。是一种将任务推后执行的一种机制。

   软中断和tasklet之间,建议使用tasklet,用tasklet_struct结构体表示tasklet

struct tasklet_struct
{struct tasklet_struct *next; /* 下一个 tasklet */unsigned long state; /* tasklet 状态 */atomic_t count; /* 计数器,记录对 tasklet 的引用数 */void (*func)(unsigned long); /* tasklet 执行的函数 */unsigned long data; /* 函数 func 的参数 */
};

   初始化tasklet使用tasklet_init函数:

/** @description: 初始化tasklet* @param-t    : 要初始化的tasklet* @param-func : tasklet的处理函数* @param-data : 要传递给func的参数* @return     : 无*/
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

   也可以使用宏定义来初始化:

#include <linux/interrupt.h>
/** @description: 初始化tasklet* @param-name : 要初始化的tasklet的名字* @param-func : tasklet的处理函数* @param-data : 要传递给func的参数*/
DECLARE_TASKLET(name, func, data)

   如果中途不想使用tasklet,则可以调用该函数释放它,不能在tasklet回调函数调用。和初始化函数作用相反。这个函数首先等待该tasklet执行完毕,然后再将它释放。

/** @description: 释放tasklet* @param-t    : 要释放的tasklet* @return     : 无*/
void tasklet_kill(struct tasklet_struct *t);

   在上半部中,使用tasklet_schedule函数使tasklet在合适的时间运行

/** @description: 上半部中调用使tasklet运行* @param-t    : 要调度的tasklet* @return     : 无*/
void tasklet_schedule(struct tasklet_struct *t)

   tasklet的使用模板:

/* 定义 taselet */
struct tasklet_struct testtasklet;/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{/* tasklet 具体处理内容 */
}/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{....../* 调度 tasklet */tasklet_schedule(&testtasklet);......
}/* 驱动入口函数 */
static int __init xxxx_init(void)
{....../* 初始化 tasklet */tasklet_init(&testtasklet, testtasklet_func, data);/* 注册中断处理函数 */request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);......
}

   总结:tasklet被调用之后,其绑定的处理函数不会被马上运行,需要在合适的时机去运行!在tasklet被调度以后,只要有机会它就会尽可能早的运行,在它还没有得到运行机会之前,如果一个相同的tasklet又被调度了,那么它仍然只会运行一次。本质上tasklet链表不能存在相同的tasklet对象。

4.3 工作队列

   工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。

tasklet机制是在中断上下文执行,所以在tasklet中不可以执行休眠动作。

关于workqueue与tasklet这两个机制的选择,看具体的工作过程有没有休眠动作

   Linux内核使用work_struct 结构体表示一个工作

struct work_struct {atomic_long_t data; struct list_head entry;work_func_t func; /* 工作队列处理函数 */
};

   在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程基本不用管。我们需要做的是定义一个work_struct 结构体然后使用宏定义来初始化工作:

// _work表示要初始化的工作,_func是工作对应的处理函数
#define INIT_WORK(_work, _func)

   也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化

// n表示定义的工作(work_struct), f表示要处理的函数
#define DECLARE_WORK(n, f)

   和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work

/** @description: 在上半部中对工作的调度* @param-work : 要调度的工作* @return     : 0,成功;其他值,失败*/
bool schedule_work(struct work_struct *work)

   工作队列的使用模板:

/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */void testwork_func_t(struct work_struct *work);
{/* work 具体处理内容 */
}/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{....../* 调度 work */schedule_work(&testwork);......
}/* 驱动入口函数 */
static int __init xxxx_init(void)
{....../* 初始化 work */INIT_WORK(&testwork, testwork_func_t);/* 注册中断处理函数 */request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);......
}

   总结:当调用了schedule_work后很快会执行工作函数,工作函数执行完毕后work对象便自动从工作队列中移除,所以就不需要用户开发中在驱动卸载函数中手动移除work对象了

5. 中断的API

   在Linux内核中想使用某个中断需要申请,可以用request_irq函数,其注册的中断服务函数属于中断处理的上半部,只要中断触发,就会立即执行。

/** @description  : 申请中断向量* @param-irq    : 要申请中断的中断号* @param-handler: 中断处理函数* @param-flags  : 中断标志* @param-name   : 中断名字* @param-dev    : 如果flags是IRQF_SHARED的话,dev用来区分不同的中断,一般情况下将dev设置为设备结构体,dev会传递给中断处理函数的第二个参数* @return       : 0,中断申请成功,其他负值表示中断申请失败,如果返回-EBUSY的话表示中断已经被申请了。*/
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

   中断标志有如下所示几个:

标志描述
IRQF_SHARED多个设备共享一个中断线,共享的所有中断都必须指定此标志。request_irq 函数的 dev 参数就是唯一区分他们的标志
IRQF_ONESHOT单词触发,中断执行一次就结束
IRQF_TRIGGER_NONE无触发
IRQF_TRIGGER_RISING上升沿触发
IRQF_TRIGGER_FALLING下降沿触发
IRQF_TRIGGER_HIGH高电平触发
IRQF_TRIGGER_LOW低电平触发

   表中的标志可以通过 "|" 来实现各种组合。

/** @description  : 释放相应的中断* @param-irq    : 要释放中断的中断号* @param-dev    : 如果flags是IRQF_SHARED的话,dev用来区分不同的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。* @return       : 无*/
void free_irq(unsigned int irq, void *dev);
/** @description : 中断处理函数* @param-first : 中断处理函数要响应的中断号* @param-second: 一个void指针,需要与request_irq函数的dev参数保持一致,用于区分共享中断的不同设备* @return      : 返回irq_handler_t,是一个枚举类型*/
irqreturn_t(*irq_handler_t)(int, void*);

   enable_irq()disable_irq()用于使能和禁止中断,其中disable_irq()要等当前正在执行的中断函数处理函数执行完才返回,因此使用者必须保证不会产生新的中断,并且确保所有已经开始执行的中断处理函数已经全部退出。

/** @description: 中断使能与禁止* @param-irq  : 要禁止的中断号* @return     : 无*/
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

   当要立即返回时,可以使用以下函数:

/** @description: 调用后立即返回,不会等待当前中断处理程序执行完毕* @param-irq  : 要禁止的中断号* @return     : 无*/
void disable_irq_nosync(unsigned int irq)

   local_irq_enable() 用于使能当前处理器中断系统,local_irq_disable() 用于禁止当前处理器中断系统。

/** @description: 打开和关闭全局中断* @param      : 无* @return     : 无*/
local_irq_enable()
local_irq_disable()

   对于关闭全局中断但是途中又要打开一会,执行完成后又要保持关闭状态的话,可以使用如下函数,执行完会将中断状态恢复到以前的状态。

   local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。local_irq_restore 用于恢复中断,将中断到 flags 状态。

/** @description: 打开和关闭全局中断* @param-flags: 保存中断状态的变量* @return     : 无*/
local_irq_save(flags)
local_irq_restore(flags)

参考资料

[1] 【正点原子】I.MX6U嵌入式Linux驱区动开发指南 第五十一章第十七章

[2] 【操作系统】浅谈 Linux 中的中断机制

[3] Linux内核5. 中断和中断处理

[4] Linux_中断下半部

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

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

相关文章

初始Tomcat(Tomcat的基础介绍)

目录 一、Tomcat的基本介绍 1、Tomcat是什么&#xff1f; 2、Tomcat的配置文件详解 3、Tomcat的构成组件 4、Tomcat的顶层架构 5、Tomcat的核心功能 6、Tomcat的请求过程 一、Tomcat的基本介绍 1、Tomcat是什么&#xff1f; Tomcat 服务器是一个免费的开放源代码的Web …

【数据库管理系统】Mysql 8.0.36入门级安装

下载地址 官方网址&#xff1a;MySQL 注意事项 建议不要安装最新版本&#xff0c;一般找mysql5.0或mysql8.0系列版本即可&#xff1b;mysq1官网有.zip和.msi两种安装形式&#xff1b;zip是压缩包&#xff0c;直接解压缩以后使用的&#xff0c;需要自己配置各种东西&#xff…

912. 排序数组(堆排序)

堆排序&#xff1a; 声明全局堆长度建堆&#xff08;大顶堆&#xff09;从最后一个元素开始向前遍历&#xff0c;进行&#xff1a;1. 交换最后元素和堆顶元素&#xff1b;2. 全局堆长度-1&#xff1b;3. 调整大顶堆&#xff08;从第0个位置开始&#xff09; 建堆&#xff1a;…

【递归搜索回溯专栏】前言与本专栏介绍

本专栏内容为&#xff1a;递归&#xff0c;搜索与回溯算法专栏。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;递归搜索回溯专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代…

分享6个解决msvcp110.dll丢失的方法,全面解析msvcp110.dll文件

msvcp110.dll 是一个动态链接库 (DLL) 文件&#xff0c;属于 Microsoft Visual C 库的一部分&#xff0c;具体来说是 Microsoft Visual C 2012 版本的运行时组件。这个 DLL 文件包含了在 Windows 环境下运行用 C 编写的程序所必需的一些函数和资源。当一个应用程序是使用 Visua…

视频拉流推流技术梳理

概况 视频的整个流程主要分为推流和拉流 摄像头场景&#xff1a; 摄像头捕捉视频画面&#xff0c;推流到服务器&#xff0c;服务器分发到CDN&#xff0c; 客户端从CDN地址拉流&#xff0c;客户端进行播放 直播场景&#xff1a; 主播通过手机&#xff0c;电脑等客户端&…

G8-ACGAN理论

本文为&#x1f517;365天深度学习训练营 中的学习记录博客 原作者&#xff1a;K同学啊|接辅导、项目定制 我的环境&#xff1a; 1.语言&#xff1a;python3.7 2.编译器&#xff1a;pycharm 3.深度学习框架Pytorch 1.8.0cu111 一、对比分析 前面的文章介绍了CGAN&#xf…

java基础(4)注解,集合,

注解 什么是注解&#xff08;Annotation&#xff09;&#xff1f;注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释” // this is a component: Resource("hello") public class Hello {Injectint n;PostConstructpublic void hello(Param String name…

经典文献阅读之--CamMap(基于SLAM地图对不共视相机进行外参标定)

0. 简介 由于多相机之间通常存在有限或无重叠的视场&#xff0c;因此在估计外参相机参数时面临着一定的挑战&#xff0c;为了解决这个问题&#xff0c;本文提出了CamMap&#xff1a;一种新颖的6自由度外参标定流程。根据三个操作规则&#xff0c;使一个多相机系统单独捕捉一些…

【Linux进程】进程状态(运行阻塞挂起)

目录 前言 1. 进程状态 2. 运行状态 3. 阻塞状态 4. 挂起状态 5. Linux中具体的状态 总结 前言 在Linux操作系统中&#xff0c;进程状态非常重要&#xff0c;它可以帮助我们了解进程在系统中的运行情况&#xff0c;从而更好地管理和优化系统资源&#xff0c;在Linux系统中&am…

【Python笔记-设计模式】迭代器模式

一、说明 迭代器模式是一种行为设计模式&#xff0c;让你能在不暴露集合底层表现形式&#xff08;列表、栈和树等&#xff09;的情况下遍历集合中所有的元素。 (一) 解决问题 遍历聚合对象中的元素&#xff0c;而不需要暴露该对象的内部表示 (二) 使用场景 需要对聚合对象…

SpringBoot实现短链跳转

目录 1.背景介绍 2.短链跳转的意义 3.SpringBoot中的代码实现 1.建议短链-长链的数据库表&#xff1a;t_url_map: 2.映射实体 3.Dao层实现 4.Service层实现 5.Controller层实现 3.结果测试 4.问题 1.背景介绍 短链跳转是一种通过将长链接转换为短链接的方式&…

南方电网的能源棋局上,蔚来换电扮演什么角色?

2 月 26 日&#xff0c;南网储能科技与蔚来能源签署协议&#xff0c;将充换电站、储能站、可调负载等聚合资源连接到虚拟电厂平台&#xff0c;推动换电站作为分布式储能在虚拟电厂项目上的应用。 蔚来换电站是国内首个智慧微电网型分布式换电设施&#xff0c;可透过换电订单预…

软考-系统集成项目管理中级-信息系统建设与设计

本章重点考点 1.信息系统的生命周期 信息系统建设的内容主要包括设备采购、系统集成、软件开发和运维服务等。信息系统的生命周期可以分为四个阶段:立项、开发、运维和消亡。 2.信息系统开发方法 信息系统常用的开发方法有结构化方法、原型法、面向对象方法等 1)结构化方法 …

AI智能分析网关V4:抽烟/打电话/玩手机行为AI算法及场景应用

抽烟、打电话、玩手机是人们在日常生活中常见的行为&#xff0c;但这些行为在某些场合下可能会带来安全风险。因此&#xff0c;对于这些行为的检测技术及应用就变得尤为重要。今天来给大家介绍一下TSINGSEE青犀AI智能分析网关V4抽烟/打电话/玩手机检测算法及其应用场景。 将监控…

java项目打包运行报异常:xxxxx-1.0-SNAPSHOT.jar中没有主清单属性

pom.xml中加入这段话即可 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.4</version><executions><execution><…

安泰ATA-7050高压放大器在微流控细胞分选中的应用

微流控细胞分选是一种用于分离和鉴定生物样本中特定类型细胞的技术&#xff0c;其原理基于将生物细胞通过微通道进行操纵和区分。微流控细胞分选的原理主要基于流体力学、电气学、光学和热力学等多学科的交叉应用。通过设计具有特定尺寸和性质的微通道网络&#xff0c;可实现对…

RV1126芯片概述

RV1126芯片概述 前言1 主要特性2 详细参数 前言 1 主要特性 四核 ARM Cortex-A7 and RISC-V MCU250ms快速开机2.0Tops NPU14M ISP with 3帧 HDR支持3个摄像头同时输入4K H.264/H.265 视频编码和解码 2 详细参数

万人在线直播:构建高效稳定的音视频架构

万人在线大型直播音视频架构解析 随着网络技术的发展,大型直播已成为人们生活中不可或缺的一部分。万人在线直播音视频架构是实现高清、流畅直播的关键。本文将深入探讨这一架构的核心组成部分及其运作机制。 直播客户端作为架构的基石,负责音视频数据的采集、编码、推流、…

永磁同步电机无感FOC(龙伯格观测器)算法技术总结-仿真篇

文章目录 1、观测器的引入2、β轴向下的电机观测器数学模型3、β轴向下的转子点角度及速度观测4、Simulink仿真模型搭建4.1模型总览4.2 Luenberger观测器模块4.2.1 I_alpha观测4.2.2 I_beta观测4.2.3 e_alpha、e_beta观测4.2.4 锁相环 4.3 速度设定4.4 速度观测结果4.5 电角度观…