Linux kernel completion(完成量)10分钟讲清楚

Linux内核中的completion(完成量)是一种轻量级同步机制,专门用于一个执行单元等待另一个执行单元完成某项任务的场景。与信号量相比,completion语义更清晰,开销更小,特别适合单次触发的同步场景。

一、completion核心原理

completion机制基于Linux调度器的等待队列和唤醒基础设施实现。线程等待的事件被简化为struct completion中的一个简单标志"done"。其工作流程遵循"初始化-等待-完成"三步模型。

二、数据结构

cpp 复制代码
struct completion {
    unsigned int done;            // 完成状态计数器
    struct swait_queue_head wait; // 等待队列头
};
  • done字段:0表示未完成,非0表示已完成

  • wait字段:等待该事件完成的进程队列

三、初始化方法

1. 静态初始化

cpp 复制代码
DECLARE_COMPLETION(my_completion);
DECLARE_COMPLETION_ONSTACK(my_completion); // 栈上分配版本

2. 动态初始化

cpp 复制代码
struct completion my_completion;
init_completion(&my_completion);

动态分配的完成对象最好嵌入到数据结构中,以确保在函数/驱动的生命周期内存活,防止与异步complete()调用发生竞争。

四、等待函数API

基础等待函数

cpp 复制代码
void wait_for_completion(struct completion *x);

不可中断的等待,如果没有人完成这个任务,则会成为不可杀死的进程。

带超时的等待

cpp 复制代码
unsigned long wait_for_completion_timeout(struct completion *x, 
                                         unsigned long timeout);

返回剩余时间,单位为jiffies。

可中断的等待

cpp 复制代码
int wait_for_completion_interruptible(struct completion *x);
int wait_for_completion_killable(struct completion *x);

这些函数可以被信号中断,返回-ERESTARTSYS表示等待被信号中断。

非阻塞检查

cpp 复制代码
bool try_wait_for_completion(struct completion *x);
bool completion_done(struct completion *x);

IO等待统计

cpp 复制代码
long wait_for_completion_io(struct completion *x);
long wait_for_completion_io_timeout(struct completion *x, unsigned long timeout);

正确统计IO等待时间。

五、完成函数API

唤醒单个等待者

cpp 复制代码
void complete(struct completion *x);

唤醒所有等待者

cpp 复制代码
void complete_all(struct completion *x);

调用complete_all()时,done会被设置为UINT_MAX,表示永久完成状态。

优化CPU局部性

cpp 复制代码
void complete_on_current_cpu(struct completion *x);

六、重新初始化

cpp 复制代码
void reinit_completion(struct completion *x);

将done字段重置为0,不触及等待队列。调用者必须确保没有wait_for_completion()调用在并行进行。

七、实际应用示例

1. 设备驱动中的DMA传输同步

cpp 复制代码
struct my_device {
    struct completion transfer_complete;
    // ...
};

// 中断处理程序
static irqreturn_t my_device_irq(int irq, void *dev_id)
{
    struct my_device *dev = dev_id;
    // 处理中断...
    complete(&dev->transfer_complete);
    return IRQ_HANDLED;
}

// 用户请求处理
static ssize_t my_device_write(struct file *file, const char __user *buf,
                               size_t count, loff_t *ppos)
{
    struct my_device *dev = file->private_data;
    
    // 发起DMA传输
    start_dma_transfer(dev, buf, count);
    
    // 等待完成
    wait_for_completion(&dev->transfer_complete);
    
    return count;
}

2. 内核线程同步示例(公交司机和售票员)

cpp 复制代码
struct completion door_closed;
struct completion car_stopped;

static int thread_driver(void *p)
{
    printk("Driver: I am waiting for saleman to close the door\n");
    wait_for_completion(&door_closed);
    printk("Driver: Ok, Let's go! Now~\n");
    printk("Driver: Arrive the station. stopped car!\n");
    complete(&car_stopped);
    return 0;
}

static int thread_saleman(void *p)
{
    printk("Saleman: the door is closed!\n");
    complete(&door_closed);
    printk("Saleman: you can go now!\n");
    wait_for_completion(&car_stopped);
    printk("Saleman: Ok, the door be opened!\n");
    return 0;
}

3. 模块加载/卸载同步

cpp 复制代码
static DECLARE_COMPLETION(module_unload_done);

static int __unlink_module(void)
{
    // ... 卸载模块 ...
    complete(&module_unload_done);
    return 0;
}

static void wait_for_module_unload(void)
{
    wait_for_completion(&module_unload_done);
}

八、completion与信号量的区别

特性 completion 信号量
默认行为 wait_for_completion()默认阻塞 down()在无法获取时才阻塞
语义 明确表示"等待某个操作完成" 主要用于互斥访问
适用场景 单次触发的同步 资源计数和互斥
优先级反转 不易发生 容易发生
实现复杂度 更简单,开销更小 相对复杂

completion与信号量的关键区别在于:wait_for_completion()默认阻塞,而信号量的down()操作在无法获取时才阻塞。更重要的是,completion原语明确了使用意图------"等待某个操作完成"。

九、使用注意事项

  1. 初始化问题:确保在使用completion之前正确初始化,防止使用未初始化的completion。

  2. 内存管理:动态分配的completion对象应确保在相关活动(complete()或reinit_completion())发生之前不会发生内存解除分配,即使等待函数由于超时或信号触发而过早返回。

  3. 命名规范:completion的命名应当与正在被同步的事件名一致,提高代码可读性。

  4. 多次使用:completion支持多次使用(非complete_all场景),每次complete()调用只会唤醒一个等待者。

  5. 中断上下文:complete()可以在中断上下文中调用,而wait_for_completion()不能在中断上下文中使用。

十、性能考量

completion机制相比信号量的优势在于:

  • 语义更清晰,代码意图明确

  • 实现更简单,开销更小

  • 特别适合"生产者-消费者"模式的单次同步

  • 提供多种变体满足不同场景需求

相关推荐
Sakuyu434683 小时前
sed和awk
linux
码农多耕地呗3 小时前
VMware创建虚拟机
linux·运维·服务器
wggmrlee3 小时前
性能压测-单机
linux
youyudexiaowangzi3 小时前
ubuntu 1604安装组件报错
linux·运维·ubuntu
muls13 小时前
java面试宝典
java·linux·服务器·网络·算法·操作系统
Eric.Lee20214 小时前
python实现pdf转图片png
linux·python·pdf
剑锋所指,所向披靡!4 小时前
linux的目录结构
linux·运维·服务器
我爱学习好爱好爱4 小时前
Ansible变量介绍 vars变量 inventory针对主机设置变量
linux·自动化·ansible
结衣结衣.4 小时前
【Linux】命名管道的妙用:实现进程控制与实时字符交互
linux·运维·开发语言·学习·操作系统·交互