Linux 内核学习(5) --- Linux 内核底半部机制

目录

      • 中断底半部
      • 软中断
      • tasklet
      • 工作队列
        • 使用工作队列

中断底半部

当产生一个中断时,会进入中断处理程序,但中断处理程序必须快速、异步、简单的对硬件做出迅速响应并完成那些时间要求很严格的操作,因此,对于那些其他的、对时间要求相对宽松的任务,就应该推后到中断被激活以后再去运行

这样,整个中断处理流程就被分为了两个部分:
第一个部分是中断处理程序上半部(top half),内核通过对它的异步执行完成对硬件中断的即时响应(完成清楚中断标志等操作)
下半部(bottom half) 下半部的任务主要是执行与中断相关的工作,这些工作没有被中断服务程序本身完成

下半部并不需要指明一个确切时间,只要把这些任务推迟一点,让它们在系统不太繁忙并且中断恢复后执行就可以了

上半部和下半部的主要区别:

  1. 上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务
  2. 上半部中断不能被相同类型的中断打断,而下半部依然可以被中断打断
  3. 通常下半部在中断处理程序一返回就会马上运行
  4. 上半部分简单快速,执行的时候禁止一些或者全部中断,下半部分稍后执行,而且执行期间可以响应所有的中断

linux 内核中,对中断下半部实现的方式有下面几种:

  • tasklet
  • 软中断
  • 工作队列

软中断

softirq 即软中断,代码位于 kernel/softirq.c 文件中, 每个软中断的处理函数用 softirq_action 表示:

// 软中断处理函数
struct softirq_action
{void (*action)(struct softirq_action *);
};// 软中断处理向量表
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;enum
{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,IRQ_POLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */NR_SOFTIRQS
};

数组的成员数由 NR_SOFTIRQS 决定,是一个枚举常量。
新增一个软中断时,需要在文件 include/linux/interrupt.h 中添加一个枚举常量

软中断使用的几个要点:

  1. 一个软中断不会抢占另外一个软中断。
  2. 惟一可以抢占软中断的是中断处理程序。
  3. 其他的软中断可以在其他处理器上同时执行

注册软中断的接口:

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

使用 open_softirq 即注册对应类型的处理函数到全局数组 softirq_vec

触发软件中断的接口:

void raise_softirq(unsigned int nr);

实际上即以软中断类型 nr 作为偏移量会置位 irq_stat[cpu_id]的成员变量 __softirq_pending.
__softirq_pending字段中的每一个 bit,对应着某一个软中断,某个 bit 被置位,说明有相应的软中断等待处理
这也是同一类型软中断可以在多个 cpu 上并行运行的根本原因

可以看到,使用软中断是需要修改内核,添加一个枚举的,有些繁琐
所以,通常我们不建议擅自增加软中断的数量,如果需要新的软中断,尽可能把它们实现为基于软中断的 tasklet 形式

软中断在内核中的处理是通过专门的内核线程来完成的,这些内核线程通常与 CPU 核心绑定,并且在一个称为ksoftirqd的线程中运行。
每个 CPU 核心都有一个对应的 ksoftirqd 线程,例如,对于 CPU 核心 0,线程名为 ksoftirqd/0,对于核心1,则是ksoftirqd/1,以此类推

当软中断被触发时,它会被标记为待处理,并在适当的时候由 ksoftirqd 线程进行处理。这些线程会周期性地检查是否有待处理的软中断,并执行相应的处理函数

软中断的处理函数通常是原子的,并且不能睡眠。这意味着它们不能调用任何可能导致当前线程睡眠的函数,例如那些可能引起阻塞的 API,注意这里的软中断和系统调用产生的软件中断不是一个含义

tasklet

tasklet 是利用软中断实现的一种下半部机制,关于软中断的和基于软中断实现的 tasklet 的选择:通常你应该用 tasklet,就像我们在前面看到的,软中断资源有限,也麻烦,而且软中断的使用者屈指可数,它只在那些执行频率很高和连续性要求很高的情况下才需要
tasklet 却有更广泛的用途,大多数情况下用tasklet 效果都不错,而且它们还非常容易使用
因为 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_struct 结构,当 tasklet 被调度之后,对应的函数就会执行,参数由 data 给出
这两个宏之间的区别是引用计数的初始值不同,前一个将引用计数初始值设置 0,tasklet 处于激活状态,
另一个设置为 1,tasklet 处于禁止状态

tasklet struct 的 定义如下图所示:

// include/linux/interrupts.hstruct tasklet_struct {struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};

还可以通过指针的方式动态创建一个 tasklet:

struct tasklet_struct {struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};

tasklet 的调度方式: 使用 tasklet_shedule 的方式调度 tasklet

static inline void tasklet_schedule(struct tasklet_struct *t);

下面是使用 tasklet 的一个实例:

// tasklet bottom half handle function
void btn_tasklet_func(unsigned long data) 
{printk("tasklet: btn interrupt handle\n");wake_up_interruptible(&btn_wq);
}DECLARE_TASKLET(btn_tasklet, btn_tasklet_func, 0)static irqreturn_t btn_interrupt(int irq, void *dev)
{struct pin_t *p = (struct pin_t*)dev;key_values = p-> key_val;tasklet_shedule(&btn_tasklet);return IRQ_HANDLED;
}

先使用 DECLARE_TASKLET 静态声明一个 tasklet,指定其下半部函数为 btn_tasklet_func,在中断服务函数(上半部)获取按键值后,调用 tasklet_schedule调度

工作队列

work queue 即工作队列,也是中断下半部的一种, work queue 将下半部工作推迟给一个内核线程去执行 ==> work 总是会在进程的上下文执行,重要的是 workqueue 允许重新调度甚至睡眠

两个关键的点:

  1. 如果推迟的工作需要睡眠,则使用 work queues,否则使用 softirqtasklets
  2. work queues 适用于需要分配大量的内存,获得一个信号量,或者执行阻塞的I/O的情况

工作队列创建的内核线程称为 worker 线程(work thread),工作队列子系统创建了一个缺省的工作者线程来处理这些推后的工作,一般都是使用缺省的工作线程

workqueue 允许在两个主要类型的线程中执行工作:

  • 普通的内核线程:这些线程可以执行任何类型的工作,并且可以在多个CPU上并行执行。它们适用于通用的、非CPU亲和性的工作。
  • 绑定到特定CPU的内核线程:这些线程与特定的CPU核心绑定,并专门用于在该核心上执行工作。这适用于需要与特定硬件交互或者需要保持数据局部性的任务

内核中存在两种类型的工作者线程:
默认工作队列(default workqueue): 这是最常见的工作队列,它由内核自动创建和管理工作线程。默认工作队列中的工作可以在系统的任何一个CPU上执行
自定义工作队列(custom workqueue): 用户可以创建自己的工作队列,并指定它们的工作线程是否绑定到特定的CPU核心上,如果绑定到特定的CPU,那么工作只会在这个CPU上执行,这有助于提高缓存亲和性

对于默认工作队列,内核会为每个CPU核心创建一个工作者线程,这些线程的名字通常以 kworker 开头,后面跟着CPU编号,例如,如果一个系统有4个CPU核心,那么可能会有名为 kworker/0:0kworker/1:0kworker/2:0kworker/3:0的线程

对于自定义工作队列,如果它们是绑定到CPU的,那么线程的名字会反映这一点,例如 kworker/u2:0 表示这是一个绑定到CPU核心0的用户创建的工作队列的工作者线程。

使用工作队列
// include/linux/workqueue.h n for name f for function
#define DECLARE_WORK(n, f) struct work_struct n = __WORK_INITIALIZER(n, f)
DECLARE_WORK(name, void (*func)(void* ));

DECLARE_WORK 会静态的创建一个名称为 name,处理函数为 funcwork_struct 结构,这个 work_struct 函数每个工作队列的成员都会带一个(创建 work_struct 结构)

也可以在运行时通过指针创建一个 work,传入的 work_struct 的指针

函数原型: INIT_WORK(struct work_struct *work, void(*func)(void*))

#define INIT_WORK(_work, _func)	__INIT_WORK((_work), (_func), 0)

这样会动态的初始化一个 work 指向的工作,处理函数为 func

工作队列的处理函数原型:

void work_handler(void *data)

这样的函数会有一个工作者线程执行,默认的情况下,允许响应中断,并且不持有任何的锁,如何需要,函数可以睡眠

对工作队列进行调度:

static inline bool schedule_work(struct work_struct *work);
static inline bool schedule_delayed_work(struct delayed_work *dwork,unsigned long delay);

schedule_work 把给定的处理函数提交给缺省 events 的工作线程,work 马上会进行调度,一旦其所在处理器上的工作者线程被唤醒,它就会执行
schedule_delayed_work 经过一段时间后延时执行

刷新工作队列:
排入工作队列的工作会在 work thread 下一次被唤醒时执行,有时,在下一步工作之前,必须保证一些操作已经执行完毕了,卸载之前,有可能需要调用下面的函数,在内核的其他部分,为了防止竞争条件的出现,也有可能需要确保不再有待处理的工作:

static inline void flush_scheduled_work(void);

函数会一直等待,直到队列中所有的对象都被执行以后才会返回,在等待所有待处理的工作执行的时候,该函数会进入休眠状态,所以 只能在进程上下文使用

也可以创建新的工作队列,不使用缺省的工作队列函数:

// 返回一个 workqueue_struct 结构
#define create_workqueue(name)	alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))static inline bool queue_work(struct workqueue_struct *wq, struct work_struct *work)static inline bool queue_delayed_work(struct workqueue_struct *wq,struct delayed_work *dwork, unsigned long delay);// 刷新指定工作队列的函数
void flush_workqueue(struct workqueue_struct *wq);  

参考代码:

struct drm_atomic_state {......struct work_struct commit_work;
}// 在 drm_atomic_helper.c 中实现的 commit workqueue
INIT_WORK(&state->commit_work, commit_work);static void commit_work(struct work_struct *work)
{struct drm_atomic_state *state = container_of(work,struct drm_atomic_state,commit_work);commit_tail(state);
}if (nonblock)queue_work(system_unbound_wq, &state->commit_work);
elsecommit_tail(state);

任务队列和工作队列的区别:

任务队列(tasklet):

  1. 任务队列是基于软中断的机制,它们是轻量级的,用于处理短小的、不需要睡眠的底半部任务。

  2. 任务队列保证在任意时刻在同一个CPU核心上只能执行一个特定的任务队列,因此它们不需要处理并发执行的问题,简化了同步

  3. 任务队列的执行上下文是中断上下文,这意味着任务队列中的代码不能睡眠

注意 tasklet 虽然是在中断上下文执行,但是不是在中断中执行的

工作队列(workqueue):

工作队列是用于处理需要较长时间或者可以睡眠的底半部任务的机制。

  1. 工作队列中的任务会在内核线程中执行,这意味着它们可以睡眠,可以调用可能导致阻塞的函数,如 kmalloc、msleep等

  2. 工作队列提供了更多的灵活性,可以创建自定义的队列,并且可以控制任务的并发性和执行顺序

  3. 工作队列通常用于处理那些对延迟不太敏感的底半部任务,或者需要较长时间处理的任务

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

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

相关文章

汽车OEMs一般出于什么目的来自定义Autosar CP一些内容

汽车OEMs在使用AUTOSAR CP(Classic Platform)协议时,可能会根据自身的特定需求对标准协议进行修改,形成自己的企业标准(企标)。这种修改通常是为了满足特定的硬件平台、功能需求、安全要求或优化性能。以下是一些常见的修改场景和例子: 1. 硬件平台适配 企业可能会根据…

基于语义-拓扑-度量表征引导的大语言模型推理的空中视觉语言导航

1. 摘要翻译及主要贡献点 摘要: 空中视觉语言导航(VLN)是一项新兴任务,它使无人机能够通过自然语言指令和视觉线索在户外环境中导航。由于户外空中场景中复杂的空间关系,这项任务仍然具有挑战性。本文提出了一种端到…

HTML-新浪新闻-实现标题-样式1

用css进行样式控制 css引入方式: --行内样式:写在标签的style属性中(不推荐) --内嵌样式:写在style标签中(可以写在页面任何位置,但通常约定写在head标签中) --外联样式&#xf…

LongLoRA:高效扩展大语言模型上下文长度的微调方法

论文地址:https://arxiv.org/abs/2309.12307 github地址:https://github.com/dvlab-research/LongLoRA 1. 背景与挑战 大语言模型(LLMs)通常在预定义的上下文长度下进行训练,例如 LLaMA 的 2048 个 token 和 Llama2 的…

.NET9增强OpenAPI规范,不再内置swagger

ASP.NETCore in .NET 9.0 OpenAPI官方文档ASP.NET Core API 应用中的 OpenAPI 支持概述 | Microsoft Learnhttps://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/openapi/overview?viewaspnetcore-9.0https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/ope…

景联文科技加入AIIA联盟数据标注分委会

2025年1月16日,中国人工智能产业发展联盟(简称AIIA)数据委员会数据标注分委会(以下简称“分委会”)正式成立。景联文科技成为第一批AIIA联盟数据标注分委会委员单位。 数据标注分委会的成立旨在搭建数据标注领域产学研…

SQL Server 建立每日自动log备份的维护计划

SQLServer数据库可以使用维护计划完成数据库的自动备份,下面以在SQL Server 2012为例说明具体配置方法。 1.启动SQL Server Management Studio,在【对象资源管理器】窗格中选择数据库实例,然后依次选择【管理】→【维护计划】选项&#xff0…

Level DB --- TableBuilder

TableBuilder是Level DB里面重要的类和模块,它描述了数据如何序列化到文件中,以及数据里面的格式逻辑。它里面包含了之前介绍的多个模块和类。 data block、filter block和index block block格式,之前已经介绍过Level DB --- BlockBuilder-…

【esp32-uniapp小程序】uniapp小程序篇02——Hbuilder利用git连接远程仓库

一、安装git Git - Downloading Package 下载所需的安装包,点击安装,一路跟着安装指示就行。 二、安装ToriseGit Download – TortoiseGit – Windows Shell Interface to Git 语言包可下载可不下载,软件默认语言是英语。 如果下载了语言…

Java Web-Tomcat Servlet

Web服务器-Tomcat Web服务器简介 Web 服务器是一种软件程序,它主要用于在网络上接收和处理客户端(如浏览器)发送的 HTTP 请求,并返回相应的网页内容或数据。以下是关于 Web 服务器的详细介绍: 功能 接收请求&#…

MiniMax-01中Lightning Attention的由来(线性注意力进化史)

目录 引言原始注意力线性注意力因果模型存在的问题累加求和操作的限制Lightning AttentionLightning Attention-1Lightning Attention-2 备注 引言 MiniMax-01: Scaling Foundation Models with Lightning Attention表明自己是第一个将线性注意力应用到如此大规模的模型&#…

Linux 内核进程调度

一、进程的分类 在CPU的角度看进程行为的话,可以分为两类: CPU消耗型:此类进程就是一直占用CPU计算,CPU利用率很高。IO消耗型:此类进程会涉及到IO,需要和用户交互,比如键盘输入,占用…

BLE透传方案,IoT短距无线通信的“中坚力量”

在物联网(IoT)短距无线通信生态系统中,低功耗蓝牙(BLE)数据透传是一种无需任何网络或基础设施即可完成双向通信的技术。其主要通过简单操作串口的方式进行无线数据传输,最高能满足2Mbps的数据传输速率&…

Linux 入门 常用指令 详细版

欢迎来到指令小仓库!! 宝剑锋从磨砺出,梅花香自苦寒来 什么是指令? 指令和可执行程序都是可以被执行的-->指令就是可执行程序。 指令一定是在系统的每一个位置存在的。 1.ls指令 语法: ls [选项][目…

Node.js下载安装及环境配置

目录 一、下载 1. 查看电脑版本,下载对应的安装包 2. 下载路径下载 | Node.js 中文网 二、安装步骤 1. 双击安装包 2. 点击Next下一步 3. 选择安装路径 4. 这里我选择默认配置,继续Next下一步(大家按需选择) 5. 最后inst…

为什么在编程中cast有强制类型转换的意思?

C语言或C在编程时,常常遇到“XXX without a cast”的警告信息,意思是 XXX 没有进行显示的强制类似转换,那么cast为什么会有强制类型转换的意思呢? 从英语的本义来看,cast 有“塑造、铸造”的意思。引申到编程中&#…

Spring Boot(6)解决ruoyi框架连续快速发送post请求时,弹出“数据正在处理,请勿重复提交”提醒的问题

一、整个前言 在基于 Ruoyi 框架进行系统开发的过程中,我们常常会遇到各种有趣且具有挑战性的问题。今天,我们就来深入探讨一个在实际开发中较为常见的问题:当连续快速发送 Post 请求时,前端会弹出 “数据正在处理,请…

瑞芯微方案:RV1126定制开发板方案定制

产品简介 RV1126 核心板是常州海图电子科技有限公司推出的一款以瑞芯微 RV1126处理器为核心的通用产品,其丰富的设计资源、稳定的产品性能、强力的设计支持,为客户二次开发快速转化产品提供强有力的技术保障。RV1126 核心板集多种优势于一身&#xff0c…

VB6.0 显示越南语字符

近期接到客户咨询,说是VB6.0写软件界面上显示越南语乱码,需要看看怎样解决。 我在自己电脑上也试了下,确实显示越南语结果是乱码。编辑器里乱码,运行起来界面上也是乱码。 经过一天的折腾,算是解决了问题&#xff0c…

理解C++中的右值引用

右值引用,顾名思义,就是对一个右值进行引用,或者说给右值一个别名。右值引用的规则和左值一用一模一样,都是对一个值或者对象起个别名。 1. 右值引用和左值引用一样,在定义的同时必须立即赋值,如果不立即赋…