软中断 简介

2023-10-29

前言

中断服务程序往往实在CPU关中断的条件下执行的,以避免中断嵌套而使控制复杂化。但是CPU关中断的时间不能太长,否则会丢失中断信号。因此Linux将中断服务程序分为“上半部”和“下半部”。前者对时间要求比较严格,必须在中断请求发生后立即(或者一定的时间限制内)完成,为了保证这种处理能原子的完成,上半部通常是在CPU关中断的条件下执行的:从IDT(中断描述符表)中登记的中断入口函数一直到驱动程序注册在中断服务队列中的ISR(中断服务程序)。下半部是上半部根据需要调度执行的,这些处理允许延迟,通常是在CPU开中断的的条件下执行的。
但是,中断下半部有两个缺点:

  1. 同一时刻,只能有一个CPU执行下半部代码,以防止多CPU同时执行而相互干扰,因此下半部代码的执行是“串行化”的。
  2. 下半部函数不允许嵌套。

这两个缺点在单CPU系统中无关紧要,但是在SMP(对称多处理器)系统中,中断下半部的“串行化”执行没有充分利用SMP的多CPU特性。因此,Linux Kernel 2.4扩展了softirq(软中断请求)的机制。

请求机制

Linux softirq的核心思想是“谁触发,谁执行”:触发软中断的CPU负责执行它所触发的软中断,而每个CPU都有自己的软中断触发与控制机制(但是各CPU执行的软中断服务程序是相同的)。

软中断描述符

include/linux/interrupt.h头文件中定义了数据结构softirq_action,描述软中断请求:

struct softirq_action {
	void (*action)(struct softirq_action *);	// 指向软中断请求的服务程序
	void *data;									// 指向服务程序自行解释的数据
};

基于上述软中断描述符,kernel/softirq.c中定义了一个全局数组softirq_vec[32]:

static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;

由此可见,系统定义了32个所有CPU共享软中断描述符。

软中断触发机制

要实现“谁触发,谁执行”,必须为每个CPU定义自己的触发和控制变量。
include/asm- i386/hardirq.h头文件中定义了数据结构irq_cpustat_t,描述一个CPU的中断统计信息:

typedef struct {
	unsigned int __softirq_active;
	unsigned int __softirq_mask;
	unsigned int __local_irq_count;
	unsigned int __local_bh_count;
	unsigned int __syscall_count;
	unsigned int __nmi_count; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;

其中__softirq_active和__softirq_mask就是用于触发和控制软中断的成员变量:

  1. __softirq_active:32位的无符号整数,表示软中断向量0~31的状态。如果bit[i](0≤i≤31)为1,则表示软中断向量i在某个CPU上已经被触发而处于active状态;为0表示处于非活跃状态。
  2. __softirq_mask:32位的无符号整数,软中断向量的屏蔽掩码。如果bit[i](0≤i≤31)为1,则表示使能(enable)软中断向量i,为0表示该软中断向量被禁止(disabled)。

根据系统中当前的CPU个数(NR_CPUS),kernel/softirq.c文件中为每个CPU都定义了它自己的中断统计信息结构:

#if !defined(CONFIG_ARCH_S390)
irq_cpustat_t irq_stat[NR_CPUS];
#endif /* CONFIG_ARCH_S390 */

这样,每个CPU都只操作它自己的中断统计信息结构irq_stat[id],从而使各CPU之间互不影响。这个数组在include/linux/irq_cpustat.h头文件中也作了原型声明。

触发软中断请求的操作函数是__cpu_raise_softirq():

//include/linux/interrupt.h

static inline void __cpu_raise_softirq(int cpu, int nr) 
{
	softirq_active(cpu) |= (1<<nr); 
} 

其通过将相应的__softirq_active成员变量中的相应位设置为1来实现软中断触发。
此外,为保证“原子”性地完成软中断的触发过程,Linux对上述内联函数又作了高层封装raise_softirq(),但在调用之前,先通过 local_irq_save()函数来关闭当前CPU的中断并保存标志寄存器的内容,如下所示:

//include/linux/interrupt.h

static inline void raise_softirq(int nr)
{
	unsigned long flags;
	local_irq_save(flags); 
	__cpu_raise_softirq(smp_processor_id(), nr); 
	local_irq_restore(flags); 
} 

软中断分类

在软中断向量0-31中,Linux内核仅仅使用了软中断向量0-3,其余被留待系统以后扩展。
include/linux/interrupt.h头文件中对软中断向量0-3进行了预定义:

enum 
{ 
	HI_SOFTIRQ=0,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	TASKLET_SOFTIRQ
}
  • HI_SOFTIRQ用于实现高优先级的软中断,如:高优先级的tasklet。
  • NET_TX_SOFTIRQ和NET_RX_SOFTIRQ用于网络数据的发送与接收。
  • TASKLET_SOFTIRQ则用于实现诸如tasklet这样的一般性软中断。

软中断初始化

softirq机制的初始化由函数softirq_init()完成:

void __init softirq_init()
{
	int i;
	for (i = 0; i < 32; i ++)
		tasklet_init(bh_task_vec+i, bh_action, i);

	open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); 
} 

该函数由内核启动例程start_kernel()所调用,初始化的过程可分为:

  1. 先用一个for循环来初始化用于实现BH(下半部)机制的bh_task_vec[32]数组(注意不是中断描述符)。
  2. 调用open_softirq()函数开启使用软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ,并将它们的软中断服务函数指针分别指向 tasklet_action()和tasklet_hi_action()。函数open_softirq()的主要作用是初始化设置软中断请求描述符softirq_vec[nr](这里才是软中断描述符)。

开启软中断向量

open_softirq()用于开启一个指定的软中断向量nr,主要做两件事情:

  1. 初始化设置软中断向量nr所对应的软中断描述符softirq_vec[nr]。
  2. 将所有CPU的软中断屏蔽掩码变量__softirq_mask中的对应位设置为1,以使能该软中断向量。
// kernel/softirq.c

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
	unsigned long flags;
	int i;
	
	spin_lock_irqsave(&softirq_mask_lock, flags);
	softirq_vec[nr].data = data;
	softirq_vec[nr].action = action;
	for (i = 0; i < NR_CPUS; i++) 
		softirq_mask(i) |= (1 << nr); 
	spin_unlock_irqrestore(&softirq_mask_lock, flags); 
}

执行软中断服务

负责执行数组softirq_vec[32]中设置的软中断服务的函数是do_softirq():

asmlinkage void do_softirq()
{
	int cpu = smp_processor_id();
	__u32 active, mask;

	// 检测当前CPU此次是否已经处于中断服务中
	if (in_interrupt())
		return;

	// 当前CPU中断统计信息的__local_bh_count加1,表示当前CPU处于软中断服务状态
	local_bh_disable();
	
	// 关闭当前CPU的中断
	local_irq_disable();
	
	//  __softirq_active和__softirq_mask中的相应位都为1时,软中断服务函数才能执行
	mask = softirq_mask(cpu);
	active = softirq_active(cpu) & mask;

	if (active) {
		// 软中断向量被触发
		struct softirq_action *h;
restart:
		/* Reset active bitmask before enabling irqs */
		// 将当前CPU的__softirq_active位清零
		softirq_active(cpu) &= ~active;

		// 打开当前CPU的中断
		local_irq_enable();
		
		h = softirq_vec;
		// 将局部变量mask中的相应位清零
		// 目的是让do_softirq()函数内不对同一个软中断向量上的再次软中断请求进行服务,而是将等待下一次do_softirq()调用时服务,从而避免do_sottirq()函数陷入无休止的软中断服务中
		mask &= ~active;
		
		// 执行相应的软中断服务函数
		do {
			if (active & 1)
				h->action(h);
			h++;
			active >>= 1;
		} while (active);
		
		// 关闭当前CPU的中断
		local_irq_disable();
		
		// 检查是否有其他软中断服务被触发
		active = softirq_active(cpu);
		if ((active &= mask) != 0)
			goto retry;
	}

	// 当前CPU退出软中断服务状态
	local_bh_enable();
	
	/* Leave with locally disabled hard irqs. It is critical to close
	* window for infinite recursion, while we help local bh count,
	* it protected us. Now we are defenceless.
	*/
	return;

retry:
	goto restart;
}

do_softirq()函数一开始就检查当前CPU是否已经处于中断服务中,如果是则立即返回。因此,do_softirq()函数在同一个CPU上的执行是串行的,即使有更高级的软中断触发。

总结

  • Linux内核的bh是串行化的,为了解决这个问题,增加了softirq的机制。
  • 几个概念的区别:
    • 硬中断:由外设发起的对CPU的中断。
    • 软中断:硬中断的服务程序发起的对内核的中断。
    • 信号:是由内核或进程发起的对其他进程的中断。
  • bh,softirq,tasklet之间的关系:
    • tasklet和softirq的区别:tasklet只能在一个CPU上执行。
    • tasklet和bh的区别:不同的tasklet可以同时在不同的CPU上执行。
    • tasklet是在softirq机制中限制并发的一种特例

(bh和tasklet有空再整理)


参考:

  1. 阿里云开发者社区.内核代码阅读 - softirq和bottom half.https://developer.aliyun.com/article/801039
  2. Bin Watson
    .Linux内核中断系统结构——软中断.https://blog.csdn.net/Bin_Watson/article/details/125860446
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

软中断 简介 的相关文章

  • Linux、ARM:为什么仅当启动时存在 I2C GPIO 扩展器时才创建 gpiochip

    在 imx6sx 硬件平台 NXP 嵌入式 ARM 上使用 Linux 3 14 52 问题是设备树中指定的 PCF8575 I2C GPIO 扩展器不会实例化为 sys class gpio 结构中的设备 除非它们在内核启动期间存在 这些
  • 在 shell 脚本中查找和替换

    是否可以使用 shell 在文件中搜索然后替换值 当我安装服务时 我希望能够在配置文件中搜索变量 然后在该值中替换 插入我自己的设置 当然 您可以使用 sed 或 awk 来完成此操作 sed 示例 sed i s Andrew James
  • 如何确保 numpy BLAS 库可用作动态加载库?

    The theano安装文档 http www deeplearning net software theano install html troubleshooting make sure you have a blas library指
  • Linux 上的 Python 3.6 tkinter 窗口图标错误

    我正在从 Python GUI 编程手册 学习 Python GUI 某项任务要求我通过将以下代码添加到我的配方中来更改窗口图标 Change the main windows icon win iconbitmap r C Python3
  • 如何从 Linux 命令行获取视频文件的分辨率(宽度和高度)?

    我一直在挖掘 mplayer mencoder 和 ffmpeg 文档 但我似乎无法想出anything 我对输出格式不是特别挑剔 因为我可以使用正则表达式将其拉出来 我只是似乎无法首先获取数据 Use ffprobe https ffmp
  • 套接字:监听积压并接受

    listen sock backlog 在我看来 参数backlog限制连接数量 这是我的测试代码 server initialize the sockaddr of server server sin family AF INET ser
  • 如何调用位于其他目录的Makefile?

    我正在尝试这样做 我想打电话给 make Makefile存在于其他目录中 abc可以使用位于不同目录中的 shell 脚本的路径 我该怎么做呢 由于 shell 脚本不允许我cd进入Makefile目录并执行make 我怎样才能编写she
  • numpy 未定义符号:PyFPE_jbuf

    我正在尝试使用一百万首歌曲数据集 为此我必须安装 python 表 numpy cython hdf5 numexpr 等 昨天我设法安装了我需要的所有内容 在使用 hdf5 遇到一些麻烦之后 我下载了预编译的二进制包并将它们保存在我的 b
  • 如何用X11复制到剪贴板?

    使用 OS X 上的框架 我可以使用以下命令将 PNG 复制到粘贴板 在 C 中 显然我可以将 NSPasteboard 与 Cocoa 一起使用 include
  • 标准头文件中的 C 编译器错误 - 未定义的 C++ 定义

    我正在尝试编译 C 程序 但收到许多错误 这些错误是在标准 C 头文件 inttypes h stdio h stat h 等 中遇到的 错误的来源是以下未定义的常量 BEGIN DECLS END DECLS BEGIN NAMESPAC
  • 如何让“grep”从文件中读取模式?

    假设有一个很大的文本文件 我只想打印与某些模式不匹配的行 显然 我可以使用egrep v patter1 pattern2 pattern3 现在 如果所有这些模式都在一个文本文件中怎么办 最好的制作方法是什么egrep从文件中读取模式 g
  • 从多线程程序中调用 system()

    我们正在开发一个用 C 编写的多线程内存消耗应用程序 我们必须执行大量的 shellscript linux 命令 并获取返回码 读完之后article http www linuxprogrammingblog com threads a
  • 无需 cron 在后台发送邮件

    我想知道是否有一种方法可以运行 PHP 循环 以便在后台向订阅者发送几百封电子邮件 我的目标是格式化新闻通讯 单击发送 然后关闭浏览器或更改页面 当然 发送电子邮件的实际过程将在后台运行 不会因浏览器关闭而中断 我知道这可以通过 cron
  • 劫持系统调用

    我正在编写一个内核模块 我需要劫持 包装一些系统调用 我正在暴力破解 sys call table 地址 并使用 cr0 来禁用 启用页面保护 到目前为止一切顺利 一旦完成 我将公开整个代码 因此如果有人愿意 我可以更新这个问题 无论如何
  • Composer 安装要求

    我正在尝试将 Composer 安装到 Laravel 项目中 当我做的时候sudo composer install在项目目录中它显示了两个错误 Problem 1 Installation request for simplesoftw
  • Linux 桌面快捷方式和安装图标

    我需要添加什么到我的 spec文件来创建桌面快捷方式并在安装过程中为快捷方式分配一个图标 rpm 如果需要脚本 一个示例将非常有帮助 您在 Linux 下使用 desktop 文件作为图标 图标放置的位置取决于您使用的发行版和桌面环境 由于
  • 归档文件系统或格式

    我正在寻找一种文件类型来存储已退役系统的档案 目前 我们主要使用 tar gz 但从 200GB tar gz 存档中查找并提取几个文件是很麻烦的 因为 tar gz 不支持任何类型的随机访问读取规定 在你明白之前 使用 FUSE 安装 t
  • 在 /dev/input/eventX 中写入事件需要哪些命令?

    我正在开发一个android需要将触摸事件发送到 dev input eventX 的应用程序 我知道C执行此类操作的代码结构如下 struct input event struct timeval time unsigned short
  • 在 Linux 中禁用历史记录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 要在 Linux 环境中禁用历史记录 我执行了以下命令 export HISTFILESIZE 0 export HISTSIZE 0 u
  • QFileDialog::getSaveFileName 和默认的 selectedFilter

    我有 getSaveFileName 和一些过滤器 我希望当用户打开 保存 对话框时选择其中之一 Qt 文档说明如下 可以通过将 selectedFilter 设置为所需的值来选择默认过滤器 我尝试以下变体 QString selFilte

随机推荐

  • gpio上拉下拉区别

    gpio上拉下拉区别 GPIO是一颗芯片 MCU 必须具备的最基本外设功能 GPIO通常有三种状态 高电平 低电平和高阻态 高阻态换句话说就是断开状态或浮空态 因此上拉和下拉其中一个强大的理由就是为了防止输入端悬空 使其有确定的状态 减弱外
  • 【经典】修改SpringBoot的默认服务器Tomcat,替换Tomcat

    以下将介绍如何替换掉SpringBoot默认服务器Tomcat 我们将从两个案例 替换为Jetty和替换为UnderTow Tomcat是目前较流行的web容器 但过于臃肿 Jetty是个内嵌WEB容器 支持长连接 如聊天等长时间保持连接
  • 图论----同构图(详解)

    图论当中的术语 假设G V E 和G1 V1 E1 是两个图 如果存在一个双射m V V1 使得对所有的x y V均有xy E等价于m x m y E1 则称G和G1是同构的 这样的一个映射m称之为一个同构 如果G G1 则称他为一个自同构
  • JS:各种遍历方式总结

    js的遍历方式真的是有很多 有用于遍历数组的 也有用于遍历对象的 各种方式有什么样的应用场景 如何选择恰当的遍历方式 很容易就让人迷糊 所以做一下总结吧 第一种 普通for循环 直接遍历出的是索引 注意每次遍历都需要获取一次arr的长度 f
  • c#运算符

    一运算符 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号 C 有丰富的内置运算符 分类如下 1 算术运算符 下表显示了 C 支持的所有算术运算符 假设变量 A 的值为 10 变量 B 的值为 20 则 例如 假如A 21 B 10 i
  • 网址与域名的区别

    目录 一 网址与域名的区别 二 主域名与子域名 一 网址与域名的区别 以网址https www baidu com为例 网址由协议加域名组成所以协议是https 域名 www baidu com 区别 1 包含与被包含的关系 网址包含域名
  • 如何判断某个值更改就让按钮可用_【教程】 如何创建自己的 NFT? 这里有份教程, 请收下!...

    AtomicHub 提供了 NFT 创建工具 让任何人都可以创建自己的 NFT 非同质代币 喜欢 NFT 的小伙伴们 一起搞起来吧 除了 WAX 之外 目前 AtomicAssets 也支持了 EOS 区块链 所以 两条链上的朋友都可以参考
  • pandas的定义以及pandas的DataFrame的初步使用(二)

    补充 Series自动对齐 当多个series对象之间进行运算的时候 如果不同series之间具有不同的索引值 那么运算会自动对齐不同索引值的数据 如果某个series没有某个索引值 那么最终结果会赋值为NaN 示例 DataFrame对象
  • 计算机网络c类网络划分子网介绍,IP地址的子网划分详解

    原标题 IP地址的子网划分详解 来源 今日头条北京炫亿时代 一 子网划分基础 1 子网划分的若干个好处 减少网络流量 提高网络性能 简化管理 可以更为灵活的形成大覆盖范围的网络 2 你最好遵循以下步骤来进行子网划分 确认所需要的网络ID数
  • 文件分片上传demo

    知识点 File File 接口也继承了 Blob 接口的属性 File 接口没有定义任何方法 但是它从 Blob 接口继承了以下方法 Blob slice start end contentType new File 字符串数组 file
  • 【C语言】错题本(4)

    一 题目及选项 答案解析 知识点 字符型在内存中的数据存储 char类型数据在内存中的图示 unsigned char类型数据在内存中的图示 二 题目及选项 答案解析 A B C D 三 题目及选项 答案解析 数据在计算机中是先转换成补码
  • @ApiImplicitParam注解使用说明

    ApiImplicitParam注解使用说明 ApiImplicitParam是Swagger框架中的一个注解 用于描述请求参数的详细信息 它可以帮助开发人员生成API文档 并提供给用户更清晰的接口信息 以下是对 ApiImplicitPa
  • 别再乱写了,Controller 层代码这样写才足够规范!

    前言 本篇主要要介绍的就是controller层的处理 一个完整的后端请求由4部分组成 接口地址 也就是URL地址 请求方式 一般就是get set 当然还有put delete 请求数据 request 有head跟body 响应数据 r
  • Deep learning:四十一(Dropout简单理解)

    前言 训练神经网络模型时 如果训练样本较少 为了防止模型过拟合 Dropout可以作为一种trikc供选择 Dropout是hintion最近2年提出的 源于其文章Improving neural networks by preventin
  • 马哥:linux云计算从入门到精通笔记

    前言 Linux可安装在各种计算机硬件设备中 比如手机 平板电脑 路由器 视频游戏控制台 台式计算机 大型机和超级计算机 互联网Linux运维工作 以服务为中心 以稳定 安全 高效为三个基本点 确保公司的互联网业务能够7 24小时为用户提供
  • 密码复杂度(四选三)

    密码复杂度 四选三 数字 大写字母 小写字母 特殊字符 四种里边任选其三 0 9 lt gt A Z W a z W 0 9a z A Z0 9 0 9 lt gt a zA Z a zA Z0 9 lt gt 8 32
  • java 有序列表_Java 中的 List —— 有序序列

    List 在 java 中是个有序序列 一 容量 ArrayList 中有一个容量概念 表示基础数组的大小 无参时默认为 10 在需要的时候 比如 add操作 会自动增加其容量 LinkedList 没有这个概念 TreeMap 也有容量
  • SpringBoot忽略HTTPS请求的SSL证书

    报错如下 java security cert CertificateException No subject alternative names present 解决方案 1 创建SslUtil工具类 import java securi
  • FreeRTOS移植到STM32F103

    1 下载FreeRTOS源码 官网 FreeRTOS官网 下载第一个带有示例的 2 在基础工程文件中创建文件夹FreeRTOS 3 打开下载好的源码 将FreeRTOSv202212 01 FreeRTOS Source 里面的两个文件夹和
  • 软中断 简介

    前言 中断服务程序往往实在CPU关中断的条件下执行的 以避免中断嵌套而使控制复杂化 但是CPU关中断的时间不能太长 否则会丢失中断信号 因此Linux将中断服务程序分为 上半部 和 下半部 前者对时间要求比较严格 必须在中断请求发生后立即