首页 > 系统相关 >3.3.1 Linux中断的使能与屏蔽

3.3.1 Linux中断的使能与屏蔽

时间:2024-08-29 14:51:46浏览次数:7  
标签:IRQ 中断 irq 屏蔽 disable 3.3 Linux local desc

点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客

3.3.1 Linux中断的使能与屏蔽

3.3.1.1 中断使能与屏蔽的三重关卡

        本章的主题是hard_local_irq_disable(),它是对中断的关闭操作。为了彻底搞清楚中断关闭的机制,这里先对Linux使能与屏蔽中断的API做详细的分析,然后再分析IPIPE做了哪些针对性地改造。这里分析的范围已经超过了hard_local_irq_disable()。

        硬件控制器的物理中断发生后,到执行中断处理程序IRQ Handler,这中间要经历3重关卡。

3.3.1.2 第一重关卡IMR

        第一重关卡指的是硬件控制器自身的中断屏蔽寄存器IMR。以SPI控制器为例,SPI_IMR[4]为0时代表RX FIFO FULL中断被屏蔽,为1时代表RX FIFO FULL中断被使能。在driver/spi/spi-rockchip.c驱动中,由驱动自行根据需要来管理中断屏蔽位。例如使能RX FIFO FULL中断,则调用:

writel_relaxed(INT_RF_FULL, rs->regs + ROCKCHIP_SPI_IMR);

3.3.1.3 第二重关卡中断控制器的使能bit

        第二重关卡指的是中断控制器的使能bit。以GIC V3为例,每个中断号都有一个enable bit,可以通过GICD_ISENABLER<n>寄存器来set-enable使能中断,通过GICD_ICENABLER<n>寄存器来clear-enable屏蔽中断。在kernel/irq/manage.c,定义了如下接口:

//关闭中断,在非中断处理程序中使用,会等待中断处理程序完成
void disable_irq(unsigned int irq) 

//关闭中断:在中断处理程序中使用,不会等待,避免自己等待自己造成死锁
void disable_irq_nosync(unsigned int irq) 

//使能中断
void enable_irq(unsigned int irq)

        以disable_irq为例,来追踪一下是如何修改中断控制器的使能bit的:

disable_irq <kernel/irq/manage.c>
 ->	__disable_irq_nosync <kernel/irq/manage.c>
	 ->	__disable_irq  <kernel/irq/manage.c>
		 ->	irq_disable <kernel/irq/chip.c>
			 ->	__irq_disable <kernel/irq/chip.c>

        接下来__irq_disable开始了一顿让人迷惑的操作,下面展开说一下。

第1行,第一个入参struct irq_desc *desc是指向中断描述符的指针,为方便讨论,以下简称中断描述符。第二个入参mask涉及到内核的一个精巧的设计。先看一下这个参数是如何传递下来的。

kernel/irq/chip.c:

void irq_disable(struct irq_desc *desc)
{
	__irq_disable(desc, irq_settings_disable_unlazy(desc));
}

kernel/irq/settings.h
static inline bool irq_settings_disable_unlazy(struct irq_desc *desc)
{
	return desc->status_use_accessors & _IRQ_DISABLE_UNLAZY;
}

根据上述代码可以知道,mask的值取决于中断描述符是否设置了标志位_IRQ_DISABLE_UNLAZY。那到底是否定义了呢?在分配sturct irq_desc的过程,desc->status_use_accessors是初始化为0的,所以_IRQ_DISABLE_UNLAZY是没有置位的,入参mask的值为0。为了后续方便讨论,把这种默认情况称为UNLAZY模式。

alloc_desc
->desc_set_defaults
-> irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS) 

#ifndef ARCH_IRQ_INIT_FLAGS
# define ARCH_IRQ_INIT_FLAGS	0
#endif
#define IRQ_DEFAULT_INIT_FLAGS	ARCH_IRQ_INIT_FLAGS

第3行,调用irqd_irq_disabled判断中断描述符是否已经处于IRQD_IRQ_DISABLED。如果是IRQD_IRQ_DISABLED,因为mask默认是0,所以其实什么都不做。如果不是IRQD_IRQ_DISABLED,跳转到第7行。

第7行,调用irq_state_set_disabled,将中断描述符设置为IRQD_IRQ_DISABLED。

第8行,判断中断描述符所在的中断控制器是否定义了回调函数desc->irq_data.chip->irq_disable。以drivers/irqchip/irq-gic-v3.c为例,static struct irq_chip gic_chip并没有定义此回调函数,所以第8行的判断不成立,第9~10行不会执行,直接跳转到第11行。

第11行,因为mask为0,所以判断不成立。

综上所述,在默认的UNLAZY模式下,__irq_disable仅仅设置了一个IRQD_IRQ_DISABLED标记,并没有真正的操作中断控制器的使能bit呀?是的,在调用disable_irq <kernel/irq/manage.c>关闭某个中断号后,如果中断发生了,是会触发中断处理流程的,以GIC V3为例,中断处理流程走到irq_desc->handle_irq->handle_fasteoi_irq<kernel/irq/chip.c>时,会检查IRQD_IRQ_DISABLED标记。如果IRQD_IRQ_DISABLED标记置位了,则调用mask_irq(desc)清空中断控制器的使能bit: desc->irq_data.chip->irq_mask(&desc->irq_data)->gic_mask_irq-> gic_poke_irq(d, GICD_ICENABLER)

kernel/irq/chip.c:
void mask_irq(struct irq_desc *desc)
{
	if (irqd_irq_masked(&desc->irq_data))
		return;

	if (desc->irq_data.chip->irq_mask) {
		desc->irq_data.chip->irq_mask(&desc->irq_data);
		irq_state_set_masked(desc);
	}
}

drivers/irqchip/irq-gic-v3.c:
static struct irq_chip gic_chip = {
	.name			= "GICv3",
	.irq_mask		= gic_mask_irq,
	.irq_unmask		= gic_unmask_irq,
……
}

static void gic_mask_irq(struct irq_data *d)
{
	gic_poke_irq(d, GICD_ICENABLER);
}

        绕了这么一大圈,好处是啥?调用disable_irq <kernel/irq/manage.c>关闭某个中断号后,是不一定有中断发生的。如果在调用enable_irq <kernel/irq/manage.c>之前,确实如预测的一样,没有中断发生,那么disable_irq就省掉了一次对中断控制器寄存器(使能bit)的操作。

3.3.1.4 第三重关卡

        第三重关卡指的是CPU core的异常掩码标志。以ARM64为例,DAIF寄存器用来控制异常是否被屏蔽,具体状态通过PSTATE.DAIF查看。

CPU的每个core的DAIF时独立的,所以对DAIF操作的函数都有local_前缀,并且有两对API:

include/linux/irqflags.h:

//第一对API
#define local_irq_enable()	do { raw_local_irq_enable(); } while (0)
#define local_irq_disable()	do { raw_local_irq_disable(); } while (0)

//第二对API
#define local_irq_save(flags)					\
	do {							\
		raw_local_irq_save(flags);			\
	} while (0)
#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)

两对API的区别是什么?一个粗暴,一个温柔。

local_irq_disable()/local_irq_enable是简单粗暴,直接关闭和打开中断。以ARM64为例,最终调用arch_local_irq_disable和arch_local_irq_enable来操作DAIF。

arch/arm64/include/asm/irqflags.h:

static inline void arch_local_irq_enable(void)
{
	asm volatile(
		"msr	daifclr, #2		// arch_local_irq_enable"
		:
		:
		: "memory");
}

static inline void arch_local_irq_disable(void)
{
	asm volatile(
		"msr	daifset, #2		// arch_local_irq_disable"
		:
		:
		: "memory");
}

        简单粗暴是要付出代价的。在调用local_irq_disable()之前,DAIF可能已经处于关闭状态了。当配对使用local_irq_enable时,肯定会直接打开DAIF,这不就破坏了原来DAIF关闭的状态了,无法回到当初的状态了。

       local_irq_save(flags)和local_irq_restore(flags)就显得很温柔了。local_irq_save先保存DAIF的状态到flags,然后再关闭DAIF。当配对使用local_irq_restore(flags)时,把flags保存的状态恢复到DAIF,而不是简单的打开DAIF,以此来保证恢复当初的状态。

arch/arm64/include/asm/irqflags.h:

static inline unsigned long arch_local_irq_save(void)
{
	unsigned long flags;
	asm volatile(
		"mrs	%0, daif		// arch_local_irq_save\n"
		"msr	daifset, #2"
		: "=r" (flags)
		:
		: "memory");
	return flags;
}

static inline void arch_local_irq_restore(unsigned long flags)
{
	asm volatile(
		"msr	daif, %0		// arch_local_irq_restore"
	:
	: "r" (flags)
	: "memory");
}

点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客

原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!

标签:IRQ,中断,irq,屏蔽,disable,3.3,Linux,local,desc
From: https://blog.csdn.net/aspirestro/article/details/141678040

相关文章

  • 关于linux 中断的嵌套
    在Linux系统中,关于中断嵌套的问题,我们可以从以下几个方面进行说明:###一、Linux中断处理的基本机制Linux系统中的中断是一种异步事件处理机制,用于处理硬件设备或其他系统事件引起的中断请求。中断处理程序(InterruptServiceRoutine,ISR)是操作系统内核中用于响应和处理这些中断......
  • Linux日志查看命令,大日志文件排查问题
    查询关键日志行号,再根据行号查询 cat-ncatalina.out|grep15153294092 cat-ncatalina.out|tail-n+3230539|head-n10 tail-n+3230539表示查询3230539行之后的日志 head-n10则表示在前面的查询结果里再查前10条记录 查看指定时间段内的日志 grep'06-2512:08'......
  • Linux--GFS分布式文件系统
    ​ ......
  • Linux-centos7目录结构
    目录说明1./根目录2./bin/ 可执行二进制文件的目录,如常用的命令ls,tar,mv,cat等.3./boot/  开机引导目录,包括Linux内核文件与开机所需要的文件.  建议单独分区,避免根                 ......
  • 3.3 switch语句
    1.switch语句C语言中,除了if语句外,还有switch语句也可用来实现分支结构。switch语句是一种特殊形式的if……else结构,用于判断条件有多个结果的情况,把多重的elseif改成更易用、可读性更好的形式。switch-开关switch (expression){      casevalue1:statement......
  • 超详细 Linux 安装
    centos下载地址:可以去官网下载最新版本:Download以下针对各个版本的ISO镜像文件,进行一一说明:CentOS-7.0-x86_64-DVD-1503-01.iso :标准安装版,一般下载这个就可以了(推荐)CentOS-7.0-x86_64-NetInstall-1503-01.iso :网络安装镜像(从网络安装或者救援系统)CentOS-7.0-x86_64......
  • Linux 账户管理
    每个文件和目录都有『拥有人(UserID,简称UID)与拥有群组(GroupID,简称GID)』配置文件登录过程在/etc/passwd找UID和GID根据UID在/etc/shadow中找密码根据GID在/etc/group中找密码/etc/passwdhead-n2/etc/passwdroot:x:0:0:root:/root:/bin/bash#等一下做为底......
  • Linux三剑客之grep命令详解
    grep是Linux中最常用的文本搜索工具,用于在文件或文本输出中查找与指定模式匹配的行。它支持基本正则表达式、扩展正则表达式、多文件搜索、递归搜索等多种功能,非常适合过滤、搜索和提取文本内容。1.grep的基本语法grep[选项]模式[文件...]模式:搜索的文本模式,可......
  • 【Linux网络编程】Reactor模式与Proactor模式
    【Linux网络编程】Reactor模式与Proactor模式Reactor模式Reactor模式是指主线程即IO处理单元只负责监听文件描述符上是否有事件发生,有则立刻将该事件通知给工作线程即逻辑单元,除此之外,主线程不做任何其它实质性的动作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完......
  • Linux监控&性能调优分析-perf(中)监控应用程序性能及剖析内存访问
    5用perf调查繁忙的CPU在调查系统性能问题时,可以使用perf工具来识别和监控最繁忙的CPU,以便集中精力。5.1用perfstat显示哪些CPU事件被计数通过禁用CPU计数聚合,您可以使用perfstat显示哪些CPU事件被计数。要使用此功能,必须使用-a标志在全系统模式下统计事件。#p......