Linux中的workqueue机制

2023-05-16

转载与知乎https://zhuanlan.zhihu.com/p/91106844

任务工厂 - Linux中的workqueue机制 [一]

一、前言

Linux中的workqueue机制是中断底半部的一种实现,同时也是一种通用的任务异步处理的手段。进入workqueue队列处理的任务(work item)在代码中由"work_struct "结构体表示(定义在include/linux/workqueue.h):

struct work_struct {
	struct list_head entry;
	work_func_t func;
	atomic_long_t data;
};

其中,"entry"表示其所挂载的workqueue队列的节点,“func"就是要执行的任务的入口函数。而"data"表示的意义就比较丰富了。最后的4个bits是作为"flags"标志位使用的,中间的4个bits是用于flush功能的"color”。flush功能简单地说就是:等待workqueue队列上的任务都处理完,并清空workqueue队列(由于笔者也没有深入研究过这一块的具体实现原理,在本文的叙述中就不涉及这一部分内容了)。

剩下的bits在不同的场景下有不同的含义(相当于C语言里的"union"),它可以指向work item所在的workqueue队列的地址,由于低8位被挪作他用,因此要求workqueue队列的地址是按照256字节对齐的。它还可以表示处理work item的worker线程所在的pool的ID(关于pool将在本文的后半部分介绍)。

这种在一个C语言变量里塞入不同的类型的数据的方法在Linux的代码实现中还是不难见到的,在目前的workqueue机制中,"flags"和"color"所需的bits都较少,单独使用整形变量去表示确实会增加一定的内存消耗。但这种牺牲可读性的做法也被一些内核开发者认为是比较"ugly"的。

img

为了充分利用locality,通常选择将处理hardirq的CPU作为该hardirq对应的workqueue底半部的执行CPU,在早期Linux的实现中,每个CPU对应一个workqueue队列,并且每个CPU上只有一个worker线程来处理这个workqueue队列,也就是说workqueue队列和worker线程都是per-CPU的,且一一对应。

img

让我们看看这种设计存在什么问题。假设现在一个work item(设为w0)被添加到了workqueue队列上。w0需要运行5ms后休眠10ms,接着再运行5ms。在w0开始运行5ms和10ms后,另外两个work items(设为w1和w2)也分别加入了workqueue队列,w1和w2都是需要运行5ms,然后再休眠10ms(该示例来自内核Documentation/core-api/workqueue.rst文档)。

因为只有1个worker线程,所以即便在执行某个work item的时候休眠,其他的work item也得不到执行,因此将这3个work item执行完毕将总共需要55ms的时间。

img

假设现在一个CPU上有2个worker线程,分别为worker 1和worker 2,那么整个执行时间将缩短到35ms:

img

如果一个CPU上有3个worker线程,执行时间将进一步缩短到25ms:

img

二、cmwq

这种在一个CPU上运行多个worker线程的做法,就是2.6.36版本引入的,也是现在Linux内核所采用的concurrency managed workqueue,简称cmwq。一个CPU上是不可能“同时”运行多个线程的,所以这里的名称是concurrency(并发),而不是parallelism(并行)。

显然,设置合适的worker线程数目是很关键的,多了浪费资源,少了又不能充分利用CPU。大体的原则就是:如果现在一个CPU上的所有worker线程都进入了睡眠状态,但workqueue队列上还有未处理的work item,那么就再启动一个worker线程。

一个CPU上同一优先级的所有worker线程(优先级的概念见下文)共同构成了一个worker pool(此概念由内核v3.8引入),我们可能比较熟悉memory pool,当需要内存时,就从空余的memory pool中去获取,同样地,当workqueue上有work item待处理时,我们就从worker pool里挑选一个空闲的worker线程来服务这个work item。

worker pool在代码中由"worker_pool "结构体表示(定义在kernel/workqueue.c):

struct worker_pool {
	spinlock_t		lock;		/* the pool lock */
	int			cpu;		/* the associated cpu */
	int			id;		/* pool ID */

	struct list_head	idle_list;	/* list of idle workers */
	DECLARE_HASHTABLE(busy_hash, 6);        /* hash of busy workers */
    ...
}

如果一个worker正在处理work item,那么它就是busy的状态,将挂载在busy workers组成的6阶的hash表上。既然是hash表,那么就需要key,充当这个key的是正在被处理的work item的内存地址。

如果一个worker没有处理work item,那么它就是idle的状态,将挂载在idle workers组成的链表上。因为空闲的worker线程数目较少,用链表管理就可以了,而busy的worker线程可能较多,所以用hash表来组织,以加快查找的速度。

前面说过,有未处理的work item,内核就会启动一个新的worker线程,以提高效率。有创建就有消亡,当现在空闲的worker线程过多的时候,就需要销毁一部分worker线程,以节省CPU资源。就像一家公司,在项目紧张,人员不足的时候需要招人,在项目不足,人员过剩的时候可能就会裁员。至于保留多少空闲线程可以取得较理想的平衡,则涉及到一个颇为复杂的算法,在此就不展开了。

img

worker线程在代码中由"worker "结构体表示(定义在kernel/workqueue_internal.h):

struct worker {
	struct worker_pool	 *pool;		/* the associated pool */
	union {
		struct list_head  entry;	/* while idle */
		struct hlist_node hentry;	/* while busy */
	};

	struct work_struct	*current_work;	  /* work being processed */
	work_func_t		 current_func;	  /* current_work's fn */
	struct task_struct	*task;		  /* worker task */

	struct pool_workqueue	*current_pwq;     /* current_work's pwq */
        ...
}

其中,"pool"是这个worker线程所在的worker pool,根据worker线程所处的状态,它要么在idle worker组成的空闲链表中,要么在busy worker组成的hash表中。

"current_work"和"current_func"分别是worker线程正在处理的work item和其对应的入口函数。既然worker线程是一个内核线程,那么不管它是idle,还是busy的,都会对应一个task_struct(由"task"表示)。

img

"current_pwq"指向被服务的work item所在的workqueue队列,关于workqueue队列的介绍,以及它与worker pool之间的交互,见下文。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x7K2BeRJ-1634270167751)(C:\Users\TZW\Desktop\v2-12af837f37cb577b2cf1c354879a6719_1440w.jpg)]

在多个worker线程的cmwq模式下,按理一个CPU依然只对应一个workqueue队列,该队列由该CPU的worker pool里的线程共同服务(共享),但别忘了任务是有优先级的,虽不说像完整的系统那样将优先级划分地很细,至少要分成低优先级和高优先级两类吧。为此,目前的设计是一个CPU对应两个workqueue队列,相应地也有两个worker pool,分别服务于这个2个队列。

img

用"ps"命令来看下系统中都有哪些worker线程,worker线程被命名成了"kworker/n:x"的格式,其中n是worker线程所在的CPU的编号,x是其在worker pool中的编号,如果带了"H"后缀,说明这是高优先级的worker pool。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4cpPadLQ-1634270167754)(C:\Users\TZW\Desktop\v2-2dd85ca2f2ab4741ebad22f784b2f436_720w.jpg)]

还有一些带"u"前缀的,它表示"unbound",意思是这个worker线程不和任何的CPU绑定,而是被所有CPU共享,这种设计主要是为了增加灵活性。"u"后面的这个数字也不再表示CPU的编号,而是表示由这些unbound的worker线程组成的worker pool的ID号。

假设一个系统有4个CPU,那么它就对应至多8个"bound"的worker pool(4个普通优先级+4个高优先级)和8个workqueue队列,每个worker pool里有若干个worker线程,每个workqueue队列上有若干个work items。至于"unbound"的worker pool数目,则是由具体的需求决定的。

img

上图的"pwq"就是表示workqueue队列的"pool_workqueue "结构体(定义在kernel/workqueue.c)。这里有个对齐的要求,原因就是上文介绍的"work_struct->data"的低8位被占用的问题。

struct pool_workqueue {
	int	nr_active;	/* nr of active works */
	int	max_active;	/* max active works */

	struct  worker_pool	   *pool;   /* the associated pool */
	struct  workqueue_struct   *wq;	    /* the owning workqueue */
        ..
}__aligned(1 << WORK_STRUCT_FLAG_BITS);
  • "max_active"和"nr_active"分别是该workqueue队列最大允许和实际挂载的work item的数目。最大允许的work item数目也就决定了该workqueue队列所对应的work pool上最多可能的活跃(busy)的worker线程的数目。
  • “pool"指向服务这个workqueue队列的worker pool,至于这个"wq”,它的数据类型是"workqueue_struct",从名字上看这个"workqueue_struct"好像也是表示workqueue队列的,那它和pwq(pool_workqueue)有什么区别呢?

pwq表示的是一个workqueue队列,而"workqueue_struct"表示的是一组同种类型的workqueue队列的集合,具体说来就是普通优先级的workqueue队列构成一个workqueue_struct,高优先级的workqueue队列和unbound的workqueue队列又分别构成两个workqueue_struct。

来看下"workqueue_struct "结构体的定义(代码位于kernel/workqueue.c):

struct workqueue_struct {
	struct list_head	list;		                   /* list of all workqueues */
	struct list_head	pwqs;		           /* all pwqs of this wq */
	struct pool_workqueue  *cpu_pwqs;         /* per-cpu pwqs */
        ...
}

"list"是workqueue_struct自身串接而成的链表,以方便内核管理。

img

"pwqs"是同种类型的pwq组成的链表。

img

三、初始化

至此,有关workqueue机制的5个结构体以及它们之间的相互关系就介绍完了,如果做个类比的话,那么work item就是工件,pwq队列就是这些工件组成的流水线,worker线程就是工人,worker pool就是一个班组的工人构成的集合。

假设一个集团有4个工厂(4个CPU),每个工厂都分了两条流水线,一条是由高级工构成的班组负责的高效流水线,一条是由初级工构成的班组负责的普通流水线,那么这4个工厂的高效流水线都属于同一个技术级别(workqueue_struct),普通流水线则同属于另一个技术级别。

至于unbound的生产班组,就理解为外包吧,它们从组织关系上不属于任何一个工厂,但是可以综合这4个工厂的生产任务的波动,提供对人力资源更机动灵活的配置。

来看一下workqueue机制所需的这一套东西都是怎么创建出来的。负责创建的函数主要是workqueue_init_early(),它的调用关系大致如下:

workqueue_init_early() 
    --> alloc_workqueue() 
        --> alloc_and_link_pwqs()
             --> init_pwq()

在workqueue_init_early()中,初始化了per-CPU的worker pool,并为这些worker pool指定了ID和关联了CPU,工厂是工人劳动的场所,这一步相当于是把工厂的基础设施建好了。

int __init workqueue_init_early(void)
{
	// 创建worker pool 
	for_each_possible_cpu(cpu) {
		struct worker_pool *pool;
		for_each_cpu_worker_pool(pool, cpu) {
                        init_worker_pool(pool);
			pool->cpu = cpu;
                        worker_pool_assign_id(pool);
	...
        }

        // 创建workqueue队列
	system_wq = alloc_workqueue("events", 0, 0);
	system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
	system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE);
        ...				    
}

接下来就是调用alloc_workqueue()创建各种类型的workqueue_struct,基础的是三种:普通优先级的,高优先级的,以及不和CPU绑定的,这一步相当于是把流水线的分类,以及采用自产还是外包的形式确定了。

在alloc_workqueue()中,第3个参数指定了可以并发的worker线程的数目(工厂流水线的容量),最大为512(WQ_MAX_ACTIVE),设为0则表示使用默认值(WQ_MAX_ACTIVE/2=256)。至于为什么是512,仅仅是一种经验的估算而已:

img

在alloc_and_link_pwqs()中,才是创建per-CPU的pwq队列,并把pwq队列和对应的workqueue_struct关联起来。这一步相当于是按之前确定的分类,依次建好了4个工厂对应的流水线。

int alloc_and_link_pwqs(struct workqueue_struct *wq)
{
	bool highpri = wq->flags & WQ_HIGHPRI;

	if (!(wq->flags & WQ_UNBOUND)) {
		wq->cpu_pwqs = alloc_percpu(struct pool_workqueue);
		for_each_possible_cpu(cpu) {
			init_pwq(pwq, wq, &cpu_pools[highpri]);
	...
}

然后就是各个工厂根据市场上来的订单(产生的work items)确立生产任务的要求,并根据订单生产所需的技能级别,招募高级工或者初级工,安排到对应的流水线上。在这个过程中,还需要根据订单量的变化,动态地调整工人的数目,以实现效益的最大化(CPU资源最充分的利用)。

四、参考

http://jake.dothome.co.kr/workqueue-1/

http://jake.dothome.co.kr/workqueue-2/

https://chasinglulu.github.io/2019/07/16/%E4%B8%AD%E6%96%AD%E5%BB%B6%E8%BF%9F%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6%E3%80%8Cinterrupt-delay-processing%E3%80%8D/

.kr/workqueue-1/

http://jake.dothome.co.kr/workqueue-2/

https://chasinglulu.github.io/2019/07/16/%E4%B8%AD%E6%96%AD%E5%BB%B6%E8%BF%9F%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6%E3%80%8Cinterrupt-delay-processing%E3%80%8D/

http://kernel.meizu.com/linux-workqueue.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux中的workqueue机制 的相关文章

  • 删除 Git 存储库,但保留所有文件

    在我使用 Linux 的过程中的某个时刻 我决定将我的主目录中的所有内容都放入源代码管理中是个好主意 我不是在问这是否是一个好主意 我是在问如何撤销它 删除存储库的原因是我最近安装了 Oh My Zsh 而且我非常喜欢它 问题是我的主目录有
  • MySQL 中的创建/写入权限

    我的设备遇到一些权限问题SELECT INTO OUTFILE陈述 当我登录数据库并执行简单的导出命令时 例如 mysql gt select from XYZ into outfile home mropa Photos Desktop
  • 如何减去两个 gettimeofday 实例?

    我想减去两个 gettimeofday 实例 并以毫秒为单位给出答案 这个想法是 static struct timeval tv gettimeofday tv NULL static struct timeval tv2 gettime
  • waitpid() 的作用是什么?

    有什么用waitpid 它通常用于等待特定进程完成 或者如果您使用特殊标志则更改状态 基于其进程 ID 也称为pid 它还可用于等待一组子进程中的任何一个 无论是来自特定进程组的子进程还是当前进程的任何子进程 See here http l
  • 我如何知道 C 程序的可执行文件是在前台还是后台运行?

    在我的 C 程序中 我想知道我的可执行文件是否像这样在前台运行 a out 或者像这样 a out 如果你是前台工作 getpgrp tcgetpgrp STDOUT FILENO or STDIN FILENO or STDERR FIL
  • Windows CE 与嵌入式 Linux [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 现在我确信我们都清楚 Linux 与 Windows 桌面的相对优点 然而 我对嵌入式开发世界的了解却少得多 我主要对行业解决方案感兴
  • 高效的内存屏障

    我有一个多线程应用程序 其中每个线程都有一个整数类型的变量 这些变量在程序执行期间递增 在代码中的某些点 线程将其计数变量与其他线程的计数变量进行比较 现在 我们知道在多核上运行的线程可能会无序执行 一个线程可能无法读取其他线程的预期计数器
  • 监控子进程的内存使用情况

    我有一个 Linux 守护进程 它分叉几个子进程并监视它们是否崩溃 根据需要重新启动 如果父进程可以监视子进程的内存使用情况 以检测内存泄漏并在超出一定大小时重新启动子进程 那就太好了 我怎样才能做到这一点 您应该能够从 proc PID
  • 为什么C Clock()返回0

    我有这样的事情 clock t start end start clock something else end clock printf nClock cycles are d d n start end 我总是得到输出 时钟周期是 0
  • 无法在 Perl 中找到 DBI.pm 模块

    我使用的是 CentOS 并且已经安装了 Perl 5 20 并且默认情况下存在 Perl 5 10 我正在使用 Perl 5 20 版本来执行 Perl 代码 我尝试使用 DBI 模块并收到此错误 root localhost perl
  • Mcrt1.o和Scrt1.o有什么用?

    我坚持使用以下两个文件 即 Mcrt1 o 和 Scrt1 o 谁能帮我知道这两个文件的用途 如何使用它 我们以 gcrt1 o 为例 在使用 pg 选项编译进行性能测试时非常有用 谢谢 表格的文件 crt o总是 C 运行时启动代码 大部
  • Linux 上的 RTLD_LOCAL 和dynamic_cast

    我们有一个由应用程序中的一些共享库构成的插件 我们需要在应用程序运行时更新它 出于性能原因 我们在卸载旧插件之前加载并开始使用新插件 并且只有当所有线程都使用旧插件完成后 我们才卸载它 由于新插件和旧插件的库具有相同的符号 我们dlopen
  • 嵌入式linux编写AT命令

    我在向 GSM 模块写入 AT 命令时遇到问题 当我使用 minicom b 115200 D dev ttySP0 term vt100 时它工作完美 但我不知道如何在 C 代码中做同样的事情 我没有收到任何错误 但模块对命令没有反应 有
  • linux下写入后崩溃

    如果我使用 write 将一些数据写入磁盘上的文件会发生什么 但我的应用程序在刷新之前崩溃了 如果没有系统故障 是否可以保证我的数据最终会刷新到磁盘 如果您正在使用write 并不是fwrite or std ostream write 那
  • 在 Linux 上访问 main 之外的主要参数

    是否可以访问参数main在外面main 即在共享库构造函数中 在 Linux 上除了通过解析之外 proc self cmdline 您可以通过将构造函数放入 init array部分 功能在 init array 不像 init 使用相同
  • 从c调用汇编函数

    我试图从 c 调用汇编函数 但我不断收到错误 text globl integrate type integrate function integrate push ebp mov esp ebp mov 0 edi start loop
  • 推荐用于小型站点的 IRC 服务器 (ircd)? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 情况 我想使用 IRC 机器人作为我正在研究的其他代码的通用通信接口 服务器硬件陈旧且内存不足 但运行在相对最新的 Debian GNU
  • dlopen 或 dlclose 未调用信号处理程序

    我在随机时间内收到分段错误 我注册了信号 但发生分段错误时未调用信号处理程序 include
  • 这种 bash 文件名提取技术有何用途?

    我有一部分 bash 脚本正在获取不带扩展名的文件名 但我试图了解这里到底发生了什么 是做什么用的 有人可以详细说明 bash 在幕后做了什么吗 如何在一般基础上使用该技术 bin bash for src in tif do txt sr
  • Linux“屏幕”的 Windows 等效项还是其他替代方案?

    我正在寻找一种在 Windows 环境中控制程序的方法 我希望它与 Linux 软件有点相似 screen 我搜索的原因是我需要使用标识符启动一个程序 在 Windows 上 这样我以后就能够关闭该特定程序 而无需关闭其他任何程序 即使实际

随机推荐

  • 中国交通标志检测数据集

    版权声明 xff1a 本文为转发问 xff0c 原文见博客 https blog csdn net dong ma article details 84339007 中国交通标志检测数据集 xff08 CCTSDB xff09 来源于 A
  • CentOS 修改ls目录的颜色

    修改ls目录的颜色 linux系统默认目录颜色是蓝色的 xff0c 在黑背景下看不清楚 xff0c 可以通过以下2种方法修改ls查看的颜色 bash profile文件在用的根目录下 xff0c ls al可以看到 方法一 xff1a 1
  • Tiny210(S5PV210) U-BOOT(六)----DDR内存配置

    上次讲完了Nand Flash的低级初始化 xff0c 然后Nand Flash的操作主要是在board init f nand xff0c 中 xff0c 涉及到将代码从Nand Flash中copy到DDR中 xff0c 这个放到后面实
  • NAND FLASH命名规则

    基于网络的一个修订版 三星的pure nandflash xff08 就是不带其他模块只是nandflash存储芯片 xff09 的命名规则如下 xff1a 1 Memory K 2 NANDFlash 9 3 Small Classifi
  • s3c6410 DMA

    S3C6410中DMA操作步骤 xff1a 1 决定使用安全DMAC SDMAC 还是通用DMAC DMAC xff1b 2 开始相应DMAC的系统时钟 xff0c 并关闭另外一组的时钟 xff08 系统默认开启SDMA时钟 xff09 x
  • Visual Studio和VS Code的区别

    1 Visual Studio简介 xff1a 是一个集成开发环境 IDE xff0c 安装完成后就能直接用 xff0c 编译工具 xff0c 调试工具 xff0c 各个语言的开发工具 xff0c 都是已经配置好的 xff0c 开箱即用 适
  • 博客转移

    由于CSDN 文章不间断的会丢失图片 xff0c 然后逼格也不够高 xff0c 导致几年都没有写博客 xff0c 全部是记录至印象笔记中 xff0c 但是久了也不太好 xff0c 所以最近搞了一个自己的个人博客 xff0c 以后文章全部写至
  • 安装qt-everywhere-opensource-src-4.8.6

    1 下载 qt everywhere opensource src 4 8 6 http mirrors hust edu cn qtproject official releases qt 4 8 4 8 6 qt everywhere
  • CentOS6.5上安装qt-creator-opensource-linux-x86-3.1.2.run

    1 qt creator opensource linux x86 3 1 2 run的下载 wget http mirrors hustunique com qt official releases qtcreator 3 1 3 1 2
  • atoi()函数

    atoi 函数 原型 xff1a int atoi xff08 const char nptr xff09 用法 xff1a include lt stdlib h gt 功能 xff1a 将字符串转换成整型数 xff1b atoi 会扫描
  • ubuntu中printk打印信息

    1 设置vmware添加seria port 使用文件作为串口 2 启动ubuntu xff0c 修改 etc default grub GRUB CMDLINE LINUX DEFAULT 61 34 34 GRUB CMDLINE LI
  • 静态库、共享库、动态库概念?

    通常库分为 xff1a 静态库 共享库 xff0c 动态加载库 下面分别介绍 一 静态库 xff1a 1 概念 xff1a 静态库就是一些目标文件的集合 xff0c 以 a结尾 静态库在程序链接的时候使用 xff0c 链接器会将程序中使用
  • 链表——怎么写出正确的链表?

    链表 相比数组 xff0c 链表不需要一块连续的内存空间 xff0c 而是通过指针将一组零散的内存块串联起来使用 xff0c 而这里的内存块就叫做节点 xff0c 一般节点除了保存data还会保存下一个节点的地址也就是指针 单链表 头节点
  • 【STM32】STM32 变量存储在片内FLASH的指定位置

    在这里以STM32L4R5为例 xff08 官方出的DEMO板 xff09 xff0c 将变量存储在指定的片内FLASH地址 xff08 0x081F8000 xff09 一 MDK Keil软件操作 uint8 t version spa
  • 【STM32】 利用paho MQTT&WIFI 连接阿里云

    ST联合阿里云推出了云接入的相关培训 xff08 基于STM32的端到端物联网全栈开发 xff09 xff0c 所采用的的板卡为NUCLEO L4R5ZI板 xff0c 实现的主要功能为采集温湿度传感器上传到阿里云物联网平台 xff0c 并
  • 【STM32 】通过ST-LINK utility 实现片外FLASH的烧写

    目录 前言 一 例程参考及讲解 1 1 Loader Src c文件 1 2 Dev Inf c文件 二 程序修改 三 实测 参考 前言 在单片机的实际应用中 xff0c 通常会搭载一些片外FLASH芯片 xff0c 用于存储系统的一些配置
  • 基于FFmpeg的推流器(UDP推流)

    一 推流器对于输入输出文件无要求 1 输入文件可为网络流URL xff0c 可以实现转流器 2 将输入的文件改为回调函数 xff08 内存读取 xff09 的形式 xff0c 可以推送内存中的视频数据 3 将输入文件改为系统设备 xff08
  • 十进制转十六进制的C语言实现

    include lt stdio h gt include lt stdlib h gt include lt string h gt void reversestr char source char target unsigned int
  • Linux中断处理的“下半部”机制

    前言 中断分为硬件中断 xff0c 软件中断 中断的处理原则主要有两个 xff1a 一个是不能嵌套 xff0c 另外一个是越快越好 在Linux中 xff0c 分为中断处理采用 上半部 和 下半部 处理机制 一 中断处理 下半部 机制 中断
  • Linux中的workqueue机制

    转载与知乎https zhuanlan zhihu com p 91106844 一 前言 Linux中的workqueue机制是中断底半部的一种实现 xff0c 同时也是一种通用的任务异步处理的手段 进入workqueue队列处理的任务