Linux 中断处理

一、基本概念

1、中断及中断上下文

        中断是一种由硬件设备产生的信号,不同设备产生的中断通过中断号来区分。CPU在接收到中断信号后,根据中断号执行对应的中断处理程序(Interrupt Service Routine) 

内核对异常和中断的处理类似,差别只在于中断是由硬件引起的

异常举例:软中断实现系统调用,缺页异常,除0异常

中断上下文:执行一个中断处理程序时,内核处于中断上下文中

注:在中断上下文中不允许睡眠,这是因为中断上下文没有后备进程,即无法被调度唤醒

2、上半部与下半部

        一般将中断处理分为上半部和下半部

上半部:在接收到中断后只完成有严格时限要求的工作,如对中断应答或者复位硬件,此时中断是被禁止的

下半部:指中断处理流程中推后执行的那一部分,在合适的时机执行,此时允许相应所有的中断

3、下半部和推后执行的工作

         除了对时间敏感且保证不被其他中断打断的部分,剩余的任务考虑在下半部执行,下半部的实现机制有以下几种

下半部机制功能状态
BH(bottom half)一个静态创建、由32个bottom halves组成的链表,上半部通过32整数中的一位来标识出哪个bottom half可以执行,虽然分属不同处理器,也不允许任何两个bottom half同时执行。(不够灵活,简单有性能瓶颈)Linux 2.5移除
任务队列引入任务队列机制来实现工作的推后执行,替代BH机制。驱动程序会将下半部注册到相应的等待队列,等待调用执行。(不够灵活,不能满足性能要求较高的子系统)Linux 2.5移除
软中断(Softirq)一组静态定义的下半部接口,有32个,可以在所有处理器上同时运行,同类型的接口也可以同时执行。tasklet是需要在编译阶段进行静态注册。(针对性能要求较高的子系统)Linux 2.3引入
tasklet一组基于软中断实现的灵活性强、动态创建的下半部实现机制, 不同类型的tasklet可以在不同的处理器上执行, 但类型相同tasklet,不能同时执行。tasklet可以通过代码进行动态注册。(大部分的场景)Linux 2.3引入
工作队列(Work queues)工作队列取代了任务队列Linux 2.3引入

二、中断处理程序

1、注册中断处理程序

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name,void *dev);
irq:要分配的中断号
handler:指向这个中断的处理程序
flag:可以为0,也可以是一个或多个标志的掩码IRQF_DISABLED --- 内核在处理中断处理程序期间,禁止所有的其他中断IRQF_SAMPLE_RANDOM --- 该设备产生的中断对内核熵池有贡献,内核熵池负责从各个随机事件导出真正的随机数IRQF_TIMER --- 为系统定时器的中断处理而准备IRQF_SHARED --- 多个中断处理程序之间共享中断线,同一个中断线的所有的中断处理程序都要指定该标志
name:设备ASCLL文本,会被proc/interrupts文件使用,以便与用户通信
dev:主要用于共享中断线,dev提供唯一标志信息(cookie),用来区分共享一个中断的多个设备,当一个中断处理程序需要释放时,以便从共享中断线的诸多处理程序中删除指定的一个,如果没有共享中断,设置为NULL即可

注:此函数会睡眠,所以不要在中断上下文中调用

2、释放中断处理程序

void free_irq(unsigned int irq, void *dev)

        卸载驱动处理程序时,需要注销相应的中断处理程序,并释放中断线

        如果指定的中断线不是共享的,那么,函数删除处理程序的同时将禁用这条中断线。如果中断线是共享的,则删除dev所对应的处理程序,共享中断线只有在删除了最后一个中断处理程序时才会被禁用。

3、中断处理主程序

static irqreturn_t intr_handler(int irq, void *dev);
irq:处理程序要响应中断的中断号
dev:与传递给request_irq()的dev保持一致,dev将提供唯一的标志信息(cookie),用来区分共享同一个中断处理程序的多个设备

        Linux中断处理程序是不允许嵌套其他中断处理程序,中断上下文的代码应当迅速简洁,中断处理程序没有独立的栈,与所中断进程共享内核栈

4、中断控制

函数说明
local_irq_disable()禁止本地中断传递
local_irq_enable()激活本地中断传递
local_irq_save(unsigned long flags)保存本地中断传递的当前状态,然后禁止本地中断传递
local_irq_restore(unsigned long flags)恢复本地中断传递到给定的状态
disable_irq(unsigned int irq)禁止给定中断线,并确保该函数返回之前在该中断线上没有处理程序在运行
disable_irq_nosync(unsigned int irq)禁止给定中断线,不会等待当前中断处理程序执行完毕
enable_irq(unsigned int irq)激活给定中断线
synchronize_irq(unsigned int irq)等待一个特定的中断处理程序退出,才会返回
irqs_disabled()如果本地处理器上的中断系统被禁止,则返回非0;否则返回0
in_interrupt()如果在中断上下文中(包括执行中断处理程序和正在执行下半部处理程序), 则返回非0, 如果在进程上下文中,则返回0
in_irq()如果当前正在执行中断处理程序, 则返回非0;否则返回0

三、中断绑定

1、查看中断号

cat proc/interruptsCPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7       11:      17280      12860       7073       8441       7254      10057      11085      10031     GICv3  27 Level     arch_timer13:          0          0          0          0          0          0          0          0     GICv3  38 Level     arch_mem_timer15:          0          0          0          0          0          0          0          0     GICv3  23 Level     arm-pmu16:          0          0          0          0          0          0          0          0     GICv3 960 Edge      gh_msgq_tx17:          9          0          0          0          0          0          0          0     GICv3 961 Edge      gh_msgq_rx18:      13799       1329          0          0          0          0          0          0     GICv3 261 Level     ipcc_019:         62          0          0          0          0          0          0          0     GICv3  94 Level     qcom_cpucp1、irq逻辑中断号
2、中断在各CPU发生的次数
3、中断所属设备类名称
4、硬件中断号
5、中断触发方式
6、中断处理函数

2、将中断绑定到CPU 

smp_affinity:通过 bitmask 算法绑定CPU  echo 0xf > /proc/irq/45/smp_affinitysmp_affinity_list:通过数字指定CPU编号  echo 0-3 > /proc/irq/45/smp_affinity_list //作用是将中断号45的设备中断处理(包括软中断和硬中断)均摊绑定到0、1、2、3各个CPU

四、下半部机制

1、软中断

        软中断是在编译期间静态分配的,软中断由softirq_action结构表示。软中断保留给系统中对时间要求最严格以及最重要的下半部使用

struct softirq_action
{void (*action)(struct softirq_action *);
};

        注册的软中断,都在这个数组里面,每个被注册的软中断都占据该数组的一项

static struct softirq_action softirq_vec[NR_SOFTIRQS]
(1)软中断处理程序

        同一个处理器,一个软中断不会抢占另外一个软中断,但中断处理程序可以抢占软中断。其他软中断(包括相同类型的软中断)可以在其他处理器上同时执行 

void softirq_handler(struct softirq_action *);
(2)软中断执行程序

        每个处理器都有一个ksoftirqd/n线程,他们会通过softirq_pending()发现是否有待处理的软中断,如果发现,就会调用do_softirq() 循环遍历,调用相应的处理程序

(3)软中断类型

        下面是按照优先级排列的 

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ highfrequency threaded job scheduling. For almost all the purposestasklets are more than enough. F.e. all serial device BHs etal. should be converted to tasklets, not to softirqs.*/enum
{HI_SOFTIRQ=0,     /*优先级较高的tasklet*/TIMER_SOFTIRQ,    /*定时器的下半部*/NET_TX_SOFTIRQ,   /*发送网络数据包*/NET_RX_SOFTIRQ,   /*接收网络数据包*/BLOCK_SOFTIRQ,    /*block装置*/IRQ_POLL_SOFTIRQ, TASKLET_SOFTIRQ,  /*正常优先权的tasklet*/SCHED_SOFTIRQ,    HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the numbering. Sigh! */RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */NR_SOFTIRQS
};
(4)使用软中断
a.注册软中断
open_softirq(NET_TX_SOFTIRQ,net_tx_action);
open_softirq(NET_RX_SOFTIRQ,net_rx_action);b.触发软中断
//将软中断设置成挂起状态,下次调用do_softirq()函数时投入运行。
raise_softirq(NET_TX_SOFTIRQ);
//中断已经被禁止,调用下面指令
raise_softirq_irqoff(NET_TX_SOFIRQ);

2、tasklet

        tasklet是一种利用软中断实现的一种下半部机制, 但是,它的接口更简单,锁保护也要求较低

struct tasklet_struct
{struct tasklet_struct *next;//链表,指向下一个taskletunsigned long state;//tasklet状态, 0/准备执行/正在运行atomic_t count;//引用计数,为0才被激活void (*func)(unsigned long);//tasklet处理函数unsigned long data;//给tasklet处理函数的参数
};
(1)调度tasklet

        已经通过tasklet_schedule()完成调度,等待执行的tasklet会被存放在两个链表tasklet_vec和tasklet_hi_vec,将软中断TASKLET_SOFTIRQ或HI_SOFTIRQ设置成挂起状态,等待下一次调用do_softirq()就会执行

tasklet_vec:存放普通的tasklet,由tasklet_schedule()进行调度,使用的是TASKLET_SOFTIRQ软中断。
tasklet_hi_vec:存放高优先级的tasklet,由tasklet_hi_schedule()进行调度,使用的是HI_SOFTIRQ软中断

//将软中断TASKLET_SOFTIRQ设置成挂起状态,等待do_softirq()调用raise_softirq_irqoff(softirq_nr);
(2)使用tasklet

静态创建一个tasklet结构

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

通过传入一个利用tasklet_init()函数动态创建tasklet结构

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
{t->next = NULL;t->state = 0;atomic_set(&t->count, 0);t->func = func;t->data = data;
}
EXPORT_SYMBOL(tasklet_init);

调度你创建的tasklet

tasklet_schedule(&my_tasklet)

3、工作队列

        在中断处理中,经常用到工作队列,这样便能缩短中断处理时的时间。工作队列可以睡眠,比如需要获取大量内存或者需要获取信号量

         Linux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程,方便了用户的编程

(1)常用函数
a.INIT_WORK()

        通过此函数将work_struct与函数建立联系

#ifdef CONFIG_LOCKDEP
#define __INIT_WORK_KEY(_work, _func, _onstack, _key)			\do {								\__init_work((_work), _onstack);				\(_work)->data = (atomic_long_t) WORK_DATA_INIT();	\lockdep_init_map(&(_work)->lockdep_map, "(work_completion)"#_work, (_key), 0); \INIT_LIST_HEAD(&(_work)->entry);			\(_work)->func = (_func);				\} while (0)
#else
#define __INIT_WORK_KEY(_work, _func, _onstack, _key)			\do {								\__init_work((_work), _onstack);				\(_work)->data = (atomic_long_t) WORK_DATA_INIT();	\INIT_LIST_HEAD(&(_work)->entry);			\(_work)->func = (_func);				\} while (0)
#endif#define __INIT_WORK(_work, _func, _onstack)				\do {								\static __maybe_unused struct lock_class_key __key;	\\__INIT_WORK_KEY(_work, _func, _onstack, &__key);	\} while (0)#define INIT_WORK(_work, _func)						\__INIT_WORK((_work), (_func), 0)work:work_struct结构体
func:函数名
struct work_struct {atomic_long_t data;             /*工作处理函数func的参数*/                         struct list_head entry;         /*连接工作的指针*/            work_func_t func;               /*函数指针,指向func函数*/
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};
b.schedule_work(work)
static inline bool schedule_work(struct work_struct *work)
{return queue_work(system_wq, work);
}

        当中断来了,立马调用schedule_work(work),然后退出,此函数一般在中断上半部。

        中断结束后,内核线程会自动调用work结构体对应的func函数

static inline bool schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)
{return queue_delayed_work(system_wq, dwork, delay);
}

         有时并不希望work立即执行,而是希望过一段delay时间后再执行。工作队列是没有优先级的,基本按照FIFO的方式进行处理。

c.cancel_work_sync(work);
/*取消work结构体对应的func函数,但会等待其执行完,一般在exit中使用*/
bool cancel_work_sync(struct work_struct *work)
{return __cancel_work_timer(work, false);
}
EXPORT_SYMBOL_GPL(cancel_work_sync);
d.queue_work(workqueue, work)
/*调度执行一个指定workqueue中的任务*/
static inline bool queue_work(struct workqueue_struct *wq,struct work_struct *work)
{return queue_work_on(WORK_CPU_UNBOUND, wq, work);
}        
e.flush_work(work)
/*等待工作队列完成执行工作*/
bool flush_work(struct work_struct *work)
{return __flush_work(work, false);
}
EXPORT_SYMBOL_GPL(flush_work);

【参考博客】

[1] Linux 内核设计与实现

[2] Linux内核 | 中断机制 - 世至其美

[3] INIT_WORK()工作队列使用-CSDN博客

[4] https://www.cnblogs.com/oceanding/p/7595738.html

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

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

相关文章

Linux mount用法

在Linux系统中,系统自动挂载了以下挂载点: /: xfs文件系统,根文件系统, 所有其他文件系统的挂载点。 /sys: sysfs文件系统,提供内核对象的信息和接口。 /proc: proc文件系统,提供进程和系统信息。 /dev: devtmpfs文件系…

qlabel 跑马灯

就是简单的定时器➕qlabel实现,思路:每一秒钟,把label上最右边的文字切下来,然后放在最左边,这样不断切,就相当于是文字轮转了

localhost 与 127.0.0.1 在本地开发中有什么区别

在进行前端开发时,我们经常使用localhost,只需运行npm run就能在浏览器中打开网页,地址栏显示类似于http://localhost:xxx/index.html,但是很多人并不了解更深层次的原理,不知道 localhost 与 127.0.0.1 到底有什么区别…

C++_priority_queue的学习

1.概述 1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的 2. 类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素 ( 优先队列中位于顶部的元 素 ) 3. 优先队列被实现为容器…

2024年商家争相入驻视频号小店,究竟有什么吸引力,新契机?

大家好,我是电商花花。 直播电商作为一种直观、生动的媒体形式,已经深入人心。 通过达人直播带货,短视频带货,商家可以更直观地向消费者展示产品,提升销售效果。 同时,视频号小店的开通,使得…

前端三剑客 —— JavaScript (第七节)

内容回顾 DOM编程 document对象 有属性 有方法 节点类型 元素节点 属性节点 文本节点 操作DOM属性 DOM对象.属性名称 DOM对象[属性名称] 调用DOM对象的API 操作DOM样式 获取有单位的样式值 标签对象.style.样式名称,这种方式只能操作行内样式。 使用getComputedSty…

vue3中的复制功能怎么做

很简单,比如想要复制p标签里面的1111,给他一个点击事件,然后加上navigator.clipboard.writeText即可完成。 说明: ​​​​​​​ navigator.clipboard 是 Web API 中的一个对象,它提供了对剪贴板的访问权限&…

创业公式(保罗·格雷厄姆)

保罗格雷厄姆有一套完整的创业哲学,他的创业公式是: (1)搭建原型 (2)上线运营(别管bug) (3)收集反馈 (4)调整产品 (5&…

【ZZULIOJ】1050: 阶乘的累加和(Java)

目录 题目描述 输入 输出 样例输入 Copy 样例输出 Copy code 题目描述 求1! 2! ……n! 输入 输入一个整数n,你可以假定n不大于10。 输出 输出一个整数,即阶乘累加的结果,单独占一行。 样例输入 Copy 4 样例输出 Copy 33 co…

基于51单片机的DAC0832锯齿波产生设计

**单片机设计介绍, 基于51单片机的DAC0832锯齿波产生设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的DAC0832锯齿波产生设计是一个结合了硬件与软件技术的项目,旨在利用51单片机和DAC08…

【机器学习】决策树(Decision Tree,DT)算法介绍:原理与案例实现

前言 决策树算法是机器学习领域中的一种重要分类方法,它通过树状结构来进行决策分析。决策树凭借其直观易懂、易于解释的特点,在分类问题中得到了广泛的应用。本文将介绍决策树的基本原理,包括熵和信息熵的相关概念,以及几种经典的…

如何申请公派访问学者?(全过程解析)

申请成为访问学者,无疑是增强个人学术实力与拓宽国际视野的绝佳机会。然而,对于初涉此道者,资金往往成为横亘在出国深造之路上的巨大障碍。幸运的是,CSC(国家留学基金管理委员会)的资助项目,为众…

现代深度学习模型和技术

Transformer模型的理解和应用 Transformer模型自2017年由Vaswani等人在论文《Attention is All You Need》中提出以来,已经彻底改变了自然语言处理(NLP)领域的面貌。Transformer的核心是自注意力(Self-Attention)机制…

liunx系统发布.net core项目

liunx系统发布.net core项目 准备.net6程序运行环境部署nginx,通过一个地址既能访问web api,又能访问web项目有一个客户把web api放到docker中,想通过nginx转发,nginx也支持配置多个程序api接口的其它 liunx系统:cento…

惠海H5031 降压恒流芯片IC 支持36V48V60V80V转9V12V5A方案 爆闪 高低亮,远近光

降压恒流芯片IC是一种电子元件,用于将较高的电压转换为较低的电压,并保持电流的稳定。这种芯片IC广泛应用于各种电子设备中,如LED灯、汽车灯光等。 对于您提到的支持36V48V60V80V转9V12V5A方案的降压恒流芯片IC,它可以将36V至80V…

【设计模式】笔记篇

目录标题 OO设计原则策略模式 - Strategy定义案例分析需求思路分析核心代码展示进一步优化UML 图 观察者模式 - Observe定义案例分析需求UML图内置的Java观察者模式核心代码 总结 装饰者模式 - Decorator定义案例分析需求UML图分析核心代码 总结 工厂模式 - Abstract Method/Fa…

突如其来:OpenAI分家的Anthropic公司悄悄地释放出他们的秘密武器——Claude3

突如其来的消息,OpenAI分家的Anthropic公司悄悄地释放出他们的秘密武器——Claude3 这货居然在默默无闻中一举超越了GPT-4的地位。没发布会,没吹牛逼,就发了一帖子。 字少,事大。 Claude3独挡一面的推理能力 Anthropic推出了三款…

【springboot开发】Gradle VS Maven

前言: java构建工具的主要作用是依赖管理和构建的生命周期管理。gradle和maven是目前java中最流行的两个构建工具,springboot常用maven,Android studio使用gradle。 目录 1. 简介2. Maven2.1 安装2.2 依赖管理2.3 构建生命周期管理 3. Gradle…

【noVNC】使用noVNC实现浏览器网页访问vnc(基于web的远程桌面)

1.VNC本身提供的http连接方式,可传输文件,画面有卡顿,需要安装jre 2.noVNC访问方式,不可传输文件,画面较为流畅,不用安装插件运行环境 一、noVNC 是什么 Web 端的Vnc软件,通过noVNC&#xff0…

tsc --init 报错

运行 tsc --init 报错, 全局安装 ts 也不行 通过 npx tsc --init 就可以解决