当前位置: 首页 > news >正文

[Linux]学习笔记系列 -- [kernel]completion


title: completion
categories:

  • linux
  • kernel
    tags:
  • linux
  • kernel
    abbrlink: 38d6377e
    date: 2025-10-03 09:01:49

文章目录

  • kernel/sched/completion.c 内核同步原语(Kernel Synchronization Primitive) 简洁的任务完成信号量
      • 历史与背景
        • 这项技术是为了解决什么特定问题而诞生的?
        • 它的发展经历了哪些重要的里程碑或版本迭代?
        • 目前该技术的社区活跃度和主流应用情况如何?
      • 核心原理与设计
        • 它的核心工作原理是什么?
        • 它的主要优势体现在哪些方面?
        • 它存在哪些已知的劣势、局-限性或在特定场景下的不适用性?
      • 使用场景
        • 在哪些具体的业务或技术场景下,它是首选解决方案?
        • 是否有不推荐使用该技术的场景?为什么?
      • 对比分析
        • 请将其 与 其他相似技术 进行详细对比。
  • include/linux/completion.h
    • init_completion 初始化完成量
    • DECLARE_COMPLETION_ONSTACK 声明完成量
  • kernel/sched/completion.c
    • wait_for_common 等待完成量
    • wait_for_completion 等待完成量 state TASK_UNINTERRUPTIBLE 不可被中断
    • wait_for_completion_killable 等待完成量
    • complete_with_flags 完成量完成
    • complete_all 完成量释放所有线程
    • complete 完成量释放单个线程
    • completion_done 检查完成量是否有等待者

在这里插入图片描述

https://github.com/wdfk-prog/linux-study

kernel/sched/completion.c 内核同步原语(Kernel Synchronization Primitive) 简洁的任务完成信号量

历史与背景

这项技术是为了解决什么特定问题而诞生的?

这项技术以及它所实现的“完成量”(Completion),是为了提供一个高度简化的、专门用于解决“一个执行单元等待另一个执行单元完成某项工作”这一特定同步场景的内核原语。

  • 简化样板代码:在completion出现之前,要实现这种“等待-通知”的模式,开发者必须手动组合更底层的原语。这通常意味着:
    1. 定义一个wait_queue_head_t(等待队列头)。
    2. 定义一个boolint类型的标志位来表示任务是否完成。
    3. 等待者需要在一个循环中调用wait_event_interruptible(),这个宏会自动处理将任务加入等待队列、睡眠、被唤醒后检查标志位、处理伪唤醒等一系列复杂操作。
    4. 完成者需要设置标志位,然后调用wake_up()来唤醒等待者。
      这个过程虽然强大,但对于一个非常常见的模式来说,显得过于繁琐且容易出错(例如,忘记处理竞态条件)。
  • 解决竞态条件:一个经典的竞态条件是“完成发生在等待之前”(Completion-before-wait)。如果任务在等待者调用wait_event之前就已经完成并发出信号,那么这个信号就会丢失,导致等待者永久睡眠。completion机制从设计上就完美地解决了这个问题。

completion的诞生,就是为了将上述所有复杂性封装成一个极其简单、健壮且易于使用的API,让开发者可以专注于业务逻辑,而不是同步细节。

它的发展经历了哪些重要的里程碑或版本迭代?

completion作为一个基础的同步原语,其核心API非常稳定,其发展主要体现在内部实现的优化上。

  • 引入completion被引入内核,提供了init_completion, wait_for_completion, complete这三个核心API,极大地简化了内核中的同步代码。
  • 性能优化completion内部实现的一个关键优化是针对“完成发生在等待之前”的快速路径。它内部有一个done计数器。当wait_for_completion被调用时,它会首先检查这个计数器。如果计数器大于0,意味着任务已经完成,等待者根本无需睡眠,可以直接递减计数器并立即返回。这避免了进入调度器、上下文切换等昂贵的操作。
  • API扩展:为了支持更广泛的场景,后续加入了complete_all()(唤醒所有等待者,而不仅仅是一个)、reinit_completion()(允许一个completion对象被重用)以及各种可中断、可超时的等待变体(如wait_for_completion_timeout, wait_for_completion_interruptible)。
目前该技术的社区活跃度和主流应用情况如何?

completion是内核中最基础、最稳定、使用最广泛的同步原语之一。

  • 社区活跃度:其代码库和API极其稳定,处于纯维护状态。它被认为是内核同步工具箱中一个“已解决的问题”。
  • 主流应用:它的应用遍布内核的每一个角落。
    • 驱动程序:一个驱动向硬件发送一个命令后,可以调用wait_for_completion来睡眠,直到硬件完成操作并通过中断处理程序调用complete
    • 内核线程同步:一个内核线程在启动后,可以调用complete来通知其创建者自己已经完成初始化。反之,在线程退出时,也可以用它来等待所有子任务完成。
    • 核心内核vfork()系统调用的实现就用到了completion,父进程用它来等待子进程执行exec()exit()

核心原理与设计

它的核心工作原理是什么?

completion的实现巧妙地封装了wait_queue_head_t,并增加了一个状态计数器来解决竞态问题。
struct completion结构体主要包含两个成员:

  • unsigned int done;:一个计数器,表示“完成”事件发生的次数。
  • wait_queue_head_t wait;:一个标准的等待队列头,用于让任务在此睡眠。

工作流程

  1. 初始化 (init_completion):将done计数器清零,并初始化等待队列头。

  2. 等待 (wait_for_completion)

    • 快速路径:检查done计数器。如果done > 0,说明complete()已经被调用过了。此时,函数会原子地将done减1,然后立即返回,根本不会睡眠。
    • 慢速路径:如果done == 0,说明任务尚未完成。函数会将当前任务加入到wait队列中,并调用调度器使其进入睡眠状态。
  3. 完成 (complete)

    • 原子地将done计数器加1。
    • 检查等待队列wait是否为空。如果不为空,就调用wake_up()唤醒一个正在睡眠的任务。

这个设计的精妙之处在于done计数器。它使得waitcomplete的调用顺序无关紧要。

  • 如果wait先调用,任务会睡眠,等待complete来唤醒它。
  • 如果complete先调用,done会变为1,之后wait被调用时会看到done > 0,直接从快速路径返回。信号绝不会丢失。
它的主要优势体现在哪些方面?
  • API极其简洁:完美地抽象了“等待-通知”模型,隐藏了所有底层复杂性。
  • 健壮:从设计上就避免了“完成先于等待”的竞态条件。
  • 高效:为“完成先于等待”的常见场景提供了无上下文切换的快速路径。
它存在哪些已知的劣势、局-限性或在特定场景下的不适用性?
  • 功能专一:它是一个高度特化的工具,只适用于“任务完成”这种一次性的、单向的信令。
  • 不用于互斥:它绝对不能被用来保护临界区或共享数据。这是互斥锁(mutex)或自旋锁(spinlock)的工作。
  • 不用于资源计数:它不适合用来管理对一组资源的访问。这是信号量(semaphore)的工作。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

当一个执行流需要阻塞自己,直到另一个执行流(或中断上下文)显式地通知它某个一次性事件已经发生时,completion简单、安全且首选的解决方案。

  • 模块卸载:当一个模块被卸载时,它可能需要等待所有正在使用其资源的内核线程都安全退出。卸载代码可以为每个线程调用wait_for_completion,而每个线程在退出前调用complete
  • 同步I/O操作:一个块设备驱动在处理一个同步的read请求时,会将请求加入硬件的命令队列,然后wait_for_completion。当硬件完成读取并通过中断通知驱动时,中断处理程序会调用complete,唤醒等待的进程,使其可以拷贝数据并返回给用户。
是否有不推荐使用该技术的场景?为什么?
  • 保护共享数据:当多个线程需要互斥地访问一个共享变量时,应该使用mutex_lock/mutex_unlockcompletion无法提供互斥保证。
  • 有条件的等待:当一个线程需要等待一个复杂的条件变为真(例如,等待一个队列既不为空也不为满)时,应该使用底层的wait_event()宏,因为它天生就是与条件判断结合在一起的。
  • 生产者-消费者队列:虽然可以用completion来实现简单的生产者-消费者信令,但信号量(ksem)通常是更通用、更合适的工具,因为它能自然地处理队列中的多个“槽位”。

对比分析

请将其 与 其他相似技术 进行详细对比。
特性Completion (完成量)Mutex (互斥锁)Semaphore (信号量)Wait Queue (等待队列)
主要目的信令/同步 (Signaling)。表示一个任务已完成。互斥 (Mutual Exclusion)。保护临界区,保证只有一个执行者能进入。资源计数 (Resource Counting)。控制对N个资源的并发访问。底层等待机制 (Waiting Mechanism)。让任务在某个条件上睡眠。
核心操作wait_for_completion, completemutex_lock, mutex_unlockdown, upwait_event, wake_up
解决的问题“等待工作完成”“保护这段代码不被并发执行”“最多只允许N个线程通过”“当这个条件不满足时,就睡吧”
抽象层级高层。是对Wait Queue的封装。高层高层底层。是其他同步原语的基础。
是否可重入complete可以被多次调用,done计数器会累加。不可重入(常规mutex)。可重入(多次up会增加计数值)。N/A
竞态处理内置。完美处理“完成先于等待”的竞态。N/A (用于解决竞态)需要小心使用以避免竞态。需要手动处理。必须与一个条件变量和正确的锁配合使用。

include/linux/completion.h

init_completion 初始化完成量

/*** init_completion - 初始化动态分配的完成* @x:指向要初始化的完成结构的指针** 此内联函数将初始化动态创建的完成结构。*/
static inline void init_completion(struct completion *x)
{x->done = 0;init_swait_queue_head(&x->wait); // 初始化等待队列头
}

DECLARE_COMPLETION_ONSTACK 声明完成量

#define COMPLETION_INITIALIZER(work) \{ 0, __SWAIT_QUEUE_HEAD_INITIALIZER((work).wait) }/** * DECLARE_COMPLETION - 声明和初始化一个完成结构 * @work: 完成结构的标识符 * * 此宏声明并初始化一个完成结构。一般用于静态声明。对于自动变量,您应该使用_ONSTACK变体。 
*/
#define DECLARE_COMPLETION(work) \struct completion work = COMPLETION_INITIALIZER(work)# define DECLARE_COMPLETION_ONSTACK(work) DECLARE_COMPLETION(work)
/*** reinit_completion - reinitialize a completion structure* @x:  pointer to completion structure that is to be reinitialized** This inline function should be used to reinitialize a completion structure so it can* be reused. This is especially important after complete_all() is used.*/
static inline void reinit_completion(struct completion *x)
{x->done = 0;
}

kernel/sched/completion.c

wait_for_common 等待完成量

static inline long __sched
do_wait_for_common(struct completion *x,long (*action)(long), long timeout, int state)
{/*  x->done 为 0,表示条件尚未完成,需要进入等待逻辑 */if (!x->done) {/* 使用 DECLARE_SWAITQUEUE(wait) 声明一个等待队列条目 wait,用于将当前线程加入到 x->wait 的等待队列中 */DECLARE_SWAITQUEUE(wait);do {/* 检查当前线程是否有挂起的信号 */if (signal_pending_state(state, current)) {timeout = -ERESTARTSYS;break;}/* 将当前线程添加到 x->wait 的等待队列中,并设置线程的状态为 state */__prepare_to_swait(&x->wait, &wait);__set_current_state(state);raw_spin_unlock_irq(&x->wait.lock);/* 执行传入的回调函数(通常是一个等待操作,如 schedule_timeout),进入睡眠并更新超时时间 */timeout = action(timeout);raw_spin_lock_irq(&x->wait.lock);} while (!x->done && timeout);/* 调用 __finish_swait 将当前线程从等待队列中移除 */__finish_swait(&x->wait, &wait);if (!x->done)return timeout;}/* 如果 x->done 不等于 UINT_MAX(表示条件未被永久完成),则将其递减,表示一个等待线程已完成 有多个线程在等待同一个完成事件,x->done 的值可以大于 1,表示该事件可以唤醒多个线程。每当一个线程被唤醒并处理完成事件时,x->done 会递减,直到其值为 0,表示事件已被完全消耗。*/if (x->done != UINT_MAX)x->done--;return timeout ?: 1;
}static inline long __sched
__wait_for_common(struct completion *x,long (*action)(long), long timeout, int state)
{might_sleep();complete_acquire(x);raw_spin_lock_irq(&x->wait.lock);timeout = do_wait_for_common(x, action, timeout, state);raw_spin_unlock_irq(&x->wait.lock);complete_release(x);return timeout;
}static long __sched
wait_for_common(struct completion *x, long timeout, int state)
{return __wait_for_common(x, schedule_timeout, timeout, state);
}

wait_for_completion 等待完成量 state TASK_UNINTERRUPTIBLE 不可被中断

/*** 等待完成: - 等待任务的完成 * * @x: 保存此特定完成的状态 * * 这将等待特定任务完成的信号。它不可被打断,并且没有超时。 * * 另见类似例程(即 wait_for_completion_timeout()),它具有超时和可中断能力。另见 complete()。*/
void __sched wait_for_completion(struct completion *x)
{wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion);

wait_for_completion_killable 等待完成量

/* wait_for_completion_killable: - 等待一个任务的完成(可被杀死) * @x: 保存此特定完成的状态 * * 这会等待特定任务完成的信号。可以被杀死信号中断。 * * 返回:-ERESTARTSYS 如果被中断,0 如果已完成。 
*/
int __sched wait_for_completion_killable(struct completion *x)
{/* #define TASK_KILLABLE			(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE) */long t = wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_KILLABLE);if (t == -ERESTARTSYS)return t;return 0;
}
EXPORT_SYMBOL(wait_for_completion_killable);

complete_with_flags 完成量完成

/* * 通用等待完成的处理程序; ** 它与信号量不同,信号量的默认情况相反,wait_for_completion默认是阻塞的,而信号量默认是非阻塞的。这个接口还使得‘完成’多个等待线程变得简单,这对于信号量并不是完全自然的。 ** 但更重要的是,这个原语记录了用法。信号量通常用于互斥,这会导致优先级反转。等待完成通常是一个同步点,而不是一个互斥点。 */static void complete_with_flags(struct completion *x, int wake_flags)
{unsigned long flags;raw_spin_lock_irqsave(&x->wait.lock, flags);if (x->done != UINT_MAX)x->done++;/* swake 链表为空不执行唤醒,有数据尝试唤醒第一个任务,之后删除该任务 */swake_up_locked(&x->wait, wake_flags);raw_spin_unlock_irqrestore(&x->wait.lock, flags);
}

complete_all 完成量释放所有线程

/** * complete_all: - 信号所有等待这个完成的线程 * @x:  持有这个特定完成的状态 * * 这将唤醒所有等待这个特定完成事件的线程。 * * 如果这个函数唤醒了一个任务,它将在访问任务状态之前执行完整的内存屏障。 * * 由于 complete_all() 将 @x 的完成状态永久设为完成,以允许多个等待者结束,如果 @x 需要再次使用,则必须在 @x 上调用 reinit_completion()。 代码必须确保在重新初始化 @x 之前,所有的等待者都已经被唤醒并完成。同时需要注意,函数 completion_done() 不能用来判断在调用 complete_all() 后是否还有等待者。 
*/
void complete_all(struct completion *x)
{unsigned long flags;lockdep_assert_RT_in_threaded_ctx();raw_spin_lock_irqsave(&x->wait.lock, flags);x->done = UINT_MAX;swake_up_all_locked(&x->wait);raw_spin_unlock_irqrestore(&x->wait.lock, flags);
}
EXPORT_SYMBOL(complete_all);

complete 完成量释放单个线程

/** * complete: - 表示有一个线程在等待这个完成 * @x:  保存这个特定完成的状态 * * 这将唤醒一个在此完成上等待的线程。线程将按照它们被排队的顺序被唤醒。 * * 另见 complete_all()、wait_for_completion() 和相关的例程。 * * 如果此函数唤醒了一个任务,它在访问任务状态之前会执行完整的内存屏障。 */
void complete(struct completion *x)
{complete_with_flags(x, 0);
}
EXPORT_SYMBOL(complete);

completion_done 检查完成量是否有等待者

/***	completion_done - 测试完成操作是否有等待者*	@x:	完成结构体**	返回值: 如果有等待者(正在执行 wait_for_completion()),返回 0;*		 如果没有等待者,返回 1。**	注意,如果对 @x 调用了 complete_all(),此函数将始终返回 true。*/
bool completion_done(struct completion *x)
{unsigned long flags;if (!READ_ONCE(x->done))return false;/** 如果 ->done,我们需要等待 complete() 释放 ->wait.lock,* 否则可能会在 complete() 完成引用之前释放该完成对象。*/raw_spin_lock_irqsave(&x->wait.lock, flags);raw_spin_unlock_irqrestore(&x->wait.lock, flags);return true;
}
EXPORT_SYMBOL(completion_done);
http://www.dtcms.com/a/582251.html

相关文章:

  • 如何创建一个自己的Docker镜像(Dockerfile)
  • 从一个问题深入解析C++字符串处理中的栈损坏
  • 成都市做网站的公司建设网站的心得
  • 爱下手机站建设学院实验网站的作用
  • afsim-2.9.0升级Qt5.15.2
  • 网站域名实名认证通知最新国际军事新闻
  • 潍坊 营销型网站建设企业网站推广最有效的方法
  • 泰坦科技网站建设wordpress权限说明
  • [AI]关系论
  • 直通车推广计划方案seo关于网站搜索排名关键词的标准评定
  • 网络协议之传统DNS存在的问题以及httpdns
  • Linux——9
  • 广西网站建设证件查询安装wordpress到服务器
  • 电子电气架构 --- 高阶智能辅助驾驶浅析
  • GPT-4o与GPT-5存在七项零点击攻击漏洞
  • 医院信息化建设会议安排网站县级网站
  • 网站建设方案文库wordpress的标签页
  • 17zwd一起做网站官网wordpress开发视频网站模板
  • 仓颉语言:全栈开发新利器,从服务端到鸿蒙的深度解析与实践
  • GitPuk零基础学习,使用GitPuk + Arbess进行CICD自动化部署
  • 部署基于 LNMP 的 Discuz! 论坛服务器
  • Cordova 开发鸿蒙应用完全指南
  • HarmonyOS开发-系统AI视觉能力-图片识别
  • YAML语言
  • ChatBox AI 中配置阿里云百炼模型实现聊天对话
  • 基于 GitCode 云端环境的 CANN ops-math 算子库深度测评:Ascend NPU 上的数学引擎解析
  • php网站本地搭建做采集网站赚钱
  • 03 Model组件及其使用技巧
  • 指针深入第四弹--sizeof和strlen的对比、数组和指针笔试题解析、指针运算笔试题解析
  • 做刷单的网站网站关键词优化遇到的情况和解决方法