
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原语明确了使用意图------"等待某个操作完成"。
九、使用注意事项
-
初始化问题:确保在使用completion之前正确初始化,防止使用未初始化的completion。
-
内存管理:动态分配的completion对象应确保在相关活动(complete()或reinit_completion())发生之前不会发生内存解除分配,即使等待函数由于超时或信号触发而过早返回。
-
命名规范:completion的命名应当与正在被同步的事件名一致,提高代码可读性。
-
多次使用:completion支持多次使用(非complete_all场景),每次complete()调用只会唤醒一个等待者。
-
中断上下文:complete()可以在中断上下文中调用,而wait_for_completion()不能在中断上下文中使用。
十、性能考量
completion机制相比信号量的优势在于:
-
语义更清晰,代码意图明确
-
实现更简单,开销更小
-
特别适合"生产者-消费者"模式的单次同步
-
提供多种变体满足不同场景需求
