值得一看的Linux内核—中断下半部之软中断

news2025/1/11 17:47:13

软中断

软中断(softirq)是中断处理程序在开启中断的情况下执行的部分,可以被硬中断抢占。

内核定义了一张软中断向量表,每种软中断有一个唯一的编号,对应一个softirq_action实例,softirq_action实例的成员action是处理函数。

kernel/softirq.c
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;		/* 软中断向量表 */
 
include/linux/interrupt.h
struct softirq_action
{
	void	(*action)(struct softirq_action *);		/* 软中断处理函数 */
};

1. 软中断的种类

目前内核定义了10种软中断,定义如下:

include/linux/interrupt.h
enum
{
	HI_SOFTIRQ=0,		/* 高优先级的小任务 */
	TIMER_SOFTIRQ,		/* 定时器软中断 */
	NET_TX_SOFTIRQ,		/* 网络栈发送报文的软中断 */
	NET_RX_SOFTIRQ,		/* 网络栈接收报文的软中断 */
	BLOCK_SOFTIRQ,		/* 块设备软中断 */
	IRQ_POLL_SOFTIRQ,	/* 支持IO轮询的块设备软中断 */
	TASKLET_SOFTIRQ,	/* 低优先级的小任务 */
	SCHED_SOFTIRQ,		/* 调度软中断,用于在处理器之间的负载均衡 */
	HRTIMER_SOFTIRQ, 	/* 高精度定时器 */
	RCU_SOFTIRQ,    	/* RCU软中断 */
 
	NR_SOFTIRQS
};

软中断的编号越小优先级越高。

2. 注册软中断的处理函数

函数open_softirq()用来注册软中断的处理函数,在软中断向量表中为指定的软终端编号设置处理函数。

kernel/softirq.c
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

同一种软中断的处理函数可以在多个处理器上同时执行,处理函数必须是可以重入的,需要使用锁保护临界区。

3. 触发软中断

函数raise_softirq用来触发软中断,参数是软中断编号。

void raise_softirq(unsigned int nr);

在已经禁止中断的情况下可以调用函数raise_softirq_irqoff来触发中断。

void raise_softirq_irqoff(unsigned int nr);

函数raise_softirq在当前处理器的待处理软中断位图中为指定的软中断编号设置对应的位,如下所示:

raise_softirq()  ->  raise_softirq_irqoff()  ->  __raise_softirq_irqoff()
 
kernel/softirq.c
void __raise_softirq_irqoff(unsigned int nr)
{
	trace_softirq_raise(nr);
	or_softirq_pending(1UL << nr);
}

把宏or_softirq_pending展开以后是:

irq_stat[smp_processor_id()].__softirq_pending |= (1UL << nr);
【文章福利】小编推荐自己的Linux内核技术交流群:【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议

4. 执行软中断

内核执行软中断的地方如下:

(1)在中断处理程序的后半部分执行软中断,对执行时间有限制:不能超过2毫秒,并且最多执行10次;

(2)每个处理器有一个软中断线程,调度策略是SCHED_NORMAL,优先级是120;

(3)开启软中断的函数local_bd_enable()。

(1)中断处理程序执行软中断

在中断处理程序的后半部分,调用函数irq_exit()以退出中断上下文,处理软中断,其代码如下:

kernel/softirq.c
void irq_exit(void)
{
    ...
	preempt_count_sub(HARDIRQ_OFFSET);
	if (!in_interrupt() && local_softirq_pending())		/* 如果正在处理的硬中断没有抢占正在执行的软中断,没有禁止 */ 
		invoke_softirq();		/* 软中断并且当前处理器待处理软中断位图不是空的,那么调用函数invoke_softirq来处理软中断 */
								/* 如果in_interrupt()为真,表示在不可屏蔽中断、硬中断或软中断上下文,或者禁止软中断 */
    ...
}

函数invoke_softirq()来处理软中断,其主要代码如下:

kernel/softirq.c
tatic inline void invoke_softirq(void)
{
	if (ksoftirqd_running(local_softirq_pending()))		/* 如果软中断线程处于就绪状态或运行状态,那么让软中断线程执行软中断 */
		return;
 
	if (!force_irqthreads) {		/* 如果没有强制中断线程化,那么调用函数__do_softirq()执行软中断 */
		__do_softirq();
	} else {		/* 如果强制中断线程化,那么唤醒软中断线程执行软中断 */
		wakeup_softirqd();
	}
}

函数__do_softirq是执行软中断的核心函数,其主要代码如下:

kernel/softirq.c
#define MAX_SOFTIRQ_TIME  msecs_to_jiffies(2)
#define MAX_SOFTIRQ_RESTART 10
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
	unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
	unsigned long old_flags = current->flags;
	int max_restart = MAX_SOFTIRQ_RESTART;
	struct softirq_action *h;
	bool in_hardirq;
	__u32 pending;
	int softirq_bit;
 
	/*
	 * Mask out PF_MEMALLOC s current task context is borrowed for the
	 * softirq. A softirq handled such as network RX might set PF_MEMALLOC
	 * again if the socket is related to swap
	 */
	current->flags &= ~PF_MEMALLOC;
 
	pending = local_softirq_pending();		/* 把局部变量pending设置为当前处理器的待处理软中断位图 */
	account_irq_enter_time(current);
 
	__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);		/* 把抢占计数器的软中断计数加1 */
	in_hardirq = lockdep_softirq_start();
 
restart:
	/* Reset the pending bitmask before enabling irqs */
	set_softirq_pending(0);		/* 把当前处理器的待处理软中断位图重新设置为0 */
 
	local_irq_enable();		/* 开启硬中断 */
 
	h = softirq_vec;
 
	while ((softirq_bit = ffs(pending))) {		/* 从低位向高位扫描待处理软中断位图,针对每个设置了对应位的软中断 */
		unsigned int vec_nr;		/* 编号,执行软中断的处理函数 */
		int prev_count;
 
		h += softirq_bit - 1;
 
		vec_nr = h - softirq_vec;
		prev_count = preempt_count();
 
		kstat_incr_softirqs_this_cpu(vec_nr);
 
		trace_softirq_entry(vec_nr);
		h->action(h);
		trace_softirq_exit(vec_nr);
		if (unlikely(prev_count != preempt_count())) {
			pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
			       vec_nr, softirq_to_name[vec_nr], h->action,
			       prev_count, preempt_count());
			preempt_count_set(prev_count);
		}
		h++;
		pending >>= softirq_bit;
	}
 
	rcu_bh_qs();
	local_irq_disable();		/* 禁止硬中断 */
 
	pending = local_softirq_pending();
	if (pending) {		/* 如果软中断的处理函数又触发软中断 */
		if (time_before(jiffies, end) && !need_resched() &&
		    --max_restart)		/* 如果软中断的执行时间小于2毫秒,不需要重现调度进程,并且软中断的执行次数没超过10 */
			goto restart;		/* 那么跳转到restart继续执行软中断 */
 
		wakeup_softirqd();		/* 唤醒软中断线程执行软中断 */
	}
 
	lockdep_softirq_end(in_hardirq);
	account_irq_exit_time(current);
	__local_bh_enable(SOFTIRQ_OFFSET);		/* 把抢占计数器的软中断计数减1 */
	WARN_ON_ONCE(in_interrupt());
	current_restore_flags(old_flags, PF_MEMALLOC);
}

(2)软中断线程

每个处理器有一个软中断线程,名称是“ksoftirqd/”后面跟着处理器编号,调度策略是SCHED_NORMAL,优先级是120。

软中断线程的核心函数是run_ksoftirqd(),其代码如下:

tatic void run_ksoftirqd(unsigned int cpu)
{
	local_irq_disable();		/* 禁止硬中断 */
	if (local_softirq_pending()) {
		/*
		 * We can safely run softirq on inline stack, as we are not deep
		 * in the task stack here.
		 */
		__do_softirq();
		local_irq_enable();
		cond_resched();
		return;
	}
	local_irq_enable();		/* 开启硬中断 */
}

(3)开启软中断时执行软中断

当进程调用函数local_bd_enable()开启软中断的时候,如果是开启最外层的软中断,并且当前处理器的待处理软中断位图不是空的,那么执行软中断。

local_bh_enable()  ->  __local_bd_enable_ip()
 
kernel/softirq.c
void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
{
	...
	preempt_count_sub(cnt - 1);
 
	if (unlikely(!in_interrupt() && local_softirq_pending())) {
		do_softirq();
	}
 
	preempt_count_dec();
    ...
}

5. 抢占计数器

每个进程的thread_info结构体中有一个抢占计数器:int interrupt_count,它用来表示当前进程能不能被抢占。

抢占是指当进程在内核模式下运行的时候可以被其他进程抢占,如果优先级更高的进程处于就绪状态,强行剥夺当前进程的处理器使用权。

内核按照各种场景对抢占计数器的位进行了划分:

其中0~7位表示抢占计数;

第8~15位是软中断计数;

第16~19位是硬中断计数;

第20为是不可屏蔽中断计数。

include/linux/preempt.h
/*
 *         PREEMPT_MASK:	0x000000ff
 *         SOFTIRQ_MASK:	0x0000ff00
 *         HARDIRQ_MASK:	0x000f0000
 *             NMI_MASK:	0x00100000
 * PREEMPT_NEED_RESCHED:	0x80000000
 */
#define PREEMPT_BITS	8
#define SOFTIRQ_BITS	8
#define HARDIRQ_BITS	4
#define NMI_BITS	1

各种场景分别利用各自的位禁止或开启抢占:

(1)普通场景(PREEMPT_MASK):对应函数preempt_disable()和preempt_enable();

(2)软中段场景(SOFTIRQ_MASK):对应函数local_bd_disable()和local_bh_enable();

(3)硬中断场景(HARDIRQ_MASK):对应函数__irq_enter()和__irq_exit()

(4)不可屏蔽中断场景(NMI_MASK):对应函数nmi_enter()和nmi_exit()

反过来也可以通过抢占计数器的值判断当前处在什么场景:

include/linux/preempt.h
#define in_irq()		(hardirq_count())
#define in_softirq()		(softirq_count())
#define in_interrupt()		(irq_count())
#define in_serving_softirq()	(softirq_count() & SOFTIRQ_OFFSET)
#define in_nmi()		(preempt_count() & NMI_MASK)
#define in_task()		(!(preempt_count() & \
				   (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET)))
 
#define hardirq_count()	(preempt_count() & HARDIRQ_MASK)
#define softirq_count()	(preempt_count() & SOFTIRQ_MASK)
#define irq_count()	(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
				 | NMI_MASK))

in_irq()表示硬中断场景,也就是正在执行硬中断。

in_softirq()表示软中断场景,包括禁止软中断和正在执行软中断。

in_interrupt()表示正在执行不可屏蔽中断、硬中断或软中断、或者禁止软中断。

in_servicing_softirq()表示正在执行软中断。

in_nmi()表示不可屏蔽中断场景。

in_task()表示普通场景,也就是进程上下文。

6.禁止/开启软中断

禁止软中断的函数是local_bd_disable(),注意:这个函数只能禁止本处理器的软中断,不能禁止其他处理器的软中断。该函数把抢占计数器的软中断计数加2其他代码如下:

include/linux/bottom_half.h
static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
	preempt_count_add(cnt);
	barrier();
}
 
static inline void local_bh_disable(void)
{
	__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
 
include/linux/preempt.h
#define SOFTIRQ_DISABLE_OFFSET	(2 * SOFTIRQ_OFFSET)

开启软中断的函数是local_bd_enable(),该函数把抢占计数器的软中断计数减2。

为什么禁止软中断的函数local_bd_disable()把抢占计数器的软中断计数加2,而不是加1呢?目的是区分禁止软中断和正在执行软中断这两种情况。__do_softirq()把抢占计数器的软中断计数加1,。如果软中断计数是奇数,可以确定正在执行软中断。

 

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

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

相关文章

b站黑马JavaScript的Node.js案例代码——考试成绩整理案例

目录 目标效果&#xff1a; 重点原理&#xff1a; 1.js中split方法——转换字符串为数组 2.js中forEach方法——遍历数组中每个对象 3.js数组操作中push方法——添加1/多个元素去数组的末尾 4.js数组操作中replace方法——在字符串中用一些字符替换另一些字符 5.js数组操…

ATtiny13与Proteus仿真-8位通用定时器/计数器与PWM仿真

8位通用定时器/计数器与PWM 1、8位通用定时器介绍 ATtiny13的8位通用定时器/计数器有两个独立的输出比较单元,并支持PWM。这意味着,可以通过8位通用定时器/计数器生产PWM信号。关于PWM的介绍,在这里就展开介绍,请参考相关资料。 ATtiny13的8位通用定时器/计数器具有如下…

备战一年,终于斩获腾讯T3,老子一定有美好的未来...

我就是那个从25岁躺平&#xff0c;30岁开始醒悟的“中年秃头大叔”&#xff0c;这人一到了中年&#xff0c;思考问题的方向确实不一样了。以前我不想结婚不想养育后代&#xff0c;天天公司摸鱼&#xff0c;总觉得自己赚钱自己花就挺好&#xff0c;25岁赚一万&#xff0c;30岁还…

[ 数据结构 ] 排序算法--------七大内排,看完还不会写来揍我

0 前言 1.1 排序分类 内部排序和外部排序,前者数据加载到内存,后者数据量大需借助外部文件. 内部排序包含: 插入排序:直接插入排序,希尔排序 选择排序:简单选择排序,堆排序 交换排序:冒泡排序,快速排序 归并排序 基数排序 1.2 复杂度 1)度量一个程序时间有两种方法,事后统…

tp3.2实现websocket

首先从单服务器实现开始 我的系统是centos系统&#xff0c;lnmp搭建的环境&#xff0c;php5.6 1&#xff1a;首先检查环境是否支持 curl -Ss http://www.workerman.net/check.php | php PHP Version > 5.3.3 [OK] Extension pcntl check [OK] Extension posix check [OK] 2.…

Metal每日分享,均值模糊滤镜效果

本案例的目的是理解如何用Metal实现均值模糊效果滤镜&#xff0c;均值模糊原理其实很简单通过多个纹理叠加&#xff0c;每个纹理偏移量设置不同达到一点重影效果来实现模糊; Demo HarbethDemo地址 实操代码 // 均值模糊效果滤镜 let filter C7MeanBlur.init(radius: 0.5)//…

清除浏览器缓存

清除浏览器的缓存知识调用前言引入具体操作知识调用 文章中可能用到的知识点前端学习&#xff1a;浏览器缓存方式有哪些&#xff08;http协议 websql indexDB cookie localstorage sessionstorage&#xff09;如何查看Chrome浏览器的页面缓存内容【详细教程】 前言引入 上期文…

基于汇编的.NET高级调试

一:背景 1. 简介 .NET 高级调试要想玩的好,看懂汇编是基本功,但看懂汇编和能写点汇编又完全是两回事,所以有时候看的多,总手痒痒想写一点,在 Windows 平台上搭建汇编环境不是那么容易,大多还是用微软的 MASM + DosBox 搭一个 8086 的环境,这玩意距今快 50 年了。 在…

Node.js Event Loop 处理的几大周期介绍

Node.js Event Loop 处理的几大周期如下图所示&#xff1a; Timer&#xff1a;通过 setTimeout() 或 setInterval() 安排的一切都将在这里处理。 IO 回调&#xff1a;这里将处理大部分回调。 由于 Node.js 中的所有用户态代码基本上都在回调中&#xff08;例如&#xff0c;对传…

深入理解机器学习——概率图模型(Probabilistic Graphical Model):马尔可夫随机场(Markov Random Field,MRF)

分类目录&#xff1a;《深入理解机器学习》总目录 马尔可夫随机场&#xff08;Markov Random Field&#xff0c;MRF&#xff09;是典型的马尔可夫网&#xff0c;这是一种著名的无向图模型&#xff0c;图中每个结点表示一个或一组变量&#xff0c;结点之间的边表示两个变量之间的…

Zookeper报错:Will not attempt to authenticate using SASL (unknown error)|防火墙的问题

先放一张debug成功的图吧~ 之前一直报这个错&#xff0c;不知道为什么&#xff0c;非常迷惑&#xff0c;然后试了多方法&#xff0c;就是防火墙的问题。我是Hadoop2.5和centos6&#xff0c;因此没法用systemtcl&#xff0c;就使用serive命令。 方式一&#xff1a;Linux命令来…

Kubernetes单主集群的部署(一)

目录 一、k8s单主架构集群的部署 1.操作系统初始化配置 2.部署 etcd 集群 3.部署docker引擎 4.部署 Master 组件 5.部署 Worker Node 组件 6.部署网络组件&#xff08;使用 flannel&#xff09; 一、k8s单主架构集群的部署 k8s集群master01&#xff1a;192.168.116.1…

纯手写2022年最新JVM调优实战手册,看完让你精通JVM调优

很多程序员不重视 JVM 内存调优&#xff0c;写出来的代码经常出现 OOM 等内存问题。而且&#xff0c;面试求职者中&#xff0c;很多求职者一旦遇到JVM 或者 JVM 调优方面的问题&#xff0c;往往不知如何回答&#xff0c;才能充分展现自己的能力。 jvm OOM问题实战分析 说说问题…

JS 原生面经从初级到高级【近1.5W字】

前言 是时候撸一波 JS 基础啦,撸熟了,银十速拿 offer; 本文不从传统的问答方式梳理,而是从知识维度梳理,以便形成知识网络; 包括函数,数组,对象,数据结构,算法,设计模式和 http. 1. 函数 1.1函数的3种定义方法 1.1.1 函数声明 //ES5 function getSum(){} function (){}//匿名…

【MATLAB教程案例56】VGG16网络的MATLAB编程学习和实现,以步态识别为例进行仿真分析

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 目录 1.软件版本 2.VGG16理论概述

ADI Blackfin DSP处理器-BF533的开发详解12:Memory DMA的使用详解(含源代码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 接口功能介绍 MDMA 全称是 memoryDMA &#xff0c;是内存到内存搬运数据的 DMA。在 DSP 做算法时&#xff0c;经常会遇到数据重组或者搬移&#…

谁说菜鸟不会数据分析 | 学习笔记(全)

一.前言 数据分析是为了提取有用信息和形成结论而对数据加以详细研究和概括总结的过程 数据分析的目的是把隐藏在一大批看似杂乱无章的数据背后的信息集中和提炼出来&#xff0c;总结出所研究对象的内在规律。在实际工作中&#xff0c;数据分析能够帮助管理者进行判断和决策&…

Linux | 进程信号 | 信号的产生 | 进程处理信号过程 | 进程pending,block,handler设置 | 用户态、内核态

文章目录信号的概念Ctrl C信号Linux中的信号信号产生前由终端产生信号由软件条件产生信号硬件异常产生信号core dump信号产生时sigset_tsigprocmask信号产生后用户态和内核态sigaction信号的概念 在日常生活中&#xff0c;我们看到绿灯选择过马路&#xff0c;看到红灯选择等待…

浅谈Java Web经典三层架构和MVC框架模式

从前端到后端整体架构做一个分析&#xff0c;通过这个分析更清楚的了解一下具体的前后端架构。以下三个架构属于三种架构&#xff0c;有共同点&#xff0c;但是并不是从属关系。 01 MVC模型 MVC是一种模型概念&#xff0c;绝大多数架构都是根据他来实现的&#xff0c;但是并不…

使用Docker+Jenkins+Gitee自动化部署Vue+ElementUI项目

参考本文章并打算跟着步骤进行构建部署的朋友们&#xff0c;建议直接先看踩坑总结&#xff0c;看看自己是否存在对应的问题&#xff0c;免得构建完才发现出错了&#xff0c;毕竟构建一次过程还挺长的。也可以自己走一遍后再参考如何解决啦。 1、Docker安装Jenkins 关于如何安…