Linux内核同步方法之完成量

本文详述Linux内核中的完成量(completion)机制,作为信号量的补充,提供一种高效的同步手段。内容包括完成量的定义、相关数据结构与函数接口,如init_completion、wait_for_completion等,并通过实例解析其使用方法。完成量通过等待队列实现,适用于任务间同步通知的场景。

前言

本文主要介绍linux内核同步机制中的完成量,及其使用实例。

一、什么是完成量

完成量(completion)是Linux系统提供的一种比信号量更好的同步机制,是对信号量的一种补充;它用于一个执行单元等待另一个执行单元完成某事;使用完成量等待时,调用进程是以睡眠方式进行等待的,不是忙等待。

二、完成量相关的数据结构和函数接口

1. 完成量相关的数据结构

  • struct completion
  • struct completion代表的就是一个完成量,其在内核源码中的linux/completion.h中定义。如下图所示为struct completion的数据结构定义
    在这里插入图片描述

2. 完成量相关的函数接口

  • 初始化完成量
    init_completion(&my_completion) //初始化完成量my_completion
    DECLARE_COMPLETION(my_completion); //定义和初始化完成量my_completion的快捷方式

说明:
以下两种方式是等效的,都实现了定义和初始化完成量my_completion的功能

  1. 动态方式
    struct completion my_completion; //定义完成量my_completion
    init_completion(&my_completion); //初始化完成量my_completion
  2. 静态方式
    DECLARE_COMPLETION(my_completion); //定义和初始化完成量my_completion
  • 等待完成量
    wait_for_completion(struct completion* complete)
    该函数会阻塞调用进程, 如果所等待的完成量没有被唤醒,那就一直阻塞下去,而且不会被信号打断
    wait_for_completion_interruptible(struct completion* complete)
    该函数等待一个完成量被唤醒;但是它可以被外部信号打断
    wait_for_completion_timeout(struct completion* complete, unsigned long timeout)
    该函数等待一个完成量被唤醒;该函数会阻塞调用进程,如果所等待的完成量没有被唤醒,调用进程也不会一直阻塞下去,而是等待一个指定的超时时间timeout,当超时时间到达时,如果所等待的完成量仍然没有被唤醒,那就返回;并且超时时间timeout以系统的时钟滴答次数jiffies来计算;
    try_wait_for_completion(struct completion* complete)
    该函数尝试等待一个完成量被唤醒;不管所等待的完成量是否被唤醒,该函数都会立即返回
    completion_done(struct completion* complete)
    该函数用于检查是否有执行单元阻塞在完成量complete上(是否已经完成),返回0,表示有执行单元被完成量complete阻塞;相当于wait_for_completion_timeout()中的timeout=0;

说明:

  1. complete->done的值永远大于等于0;
  2. 调用wait_for_completion函数时,如果complete->done的值等于0,那么当前线程会进入睡眠。如果此时complete->done的值大于0,那么wait_for_completion()函数会将complete->done的值减1,然后继续向下执行;
  3. wait_for_completion()函数会执行一个不会被信号中断的等待。如果调用这个函数之后,没有一个线程完成这个完成量,那么执行wait_for_completion()函数的线程会一直等待下去,线程将不可以退出;
  • 唤醒完成量
    complete(struct completion* complete)
    该函数只唤醒一个正在等待完成量complete的执行单元
    complete_all()
    该函数唤醒所有正在等待同一个完成量complete的执行单元

说明:

  1. complete函数会将complete->done的值加1,然后唤醒complete->wait中的一个线程;
  2. complete_all函数会将complete->done的值加1,然后唤醒complete->wait中的所有线程;

三、完成量的使用实例

以下代码实现了一个内核线程调用wait_for_completion()被阻塞住,另一个内核线程调用complete()唤醒之前被阻塞的线程的功能

  1. complete_demo.c
#include <linux/init.h>
#include <linux/module.h>

#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include <linux/delay.h>

struct complete_demo {
    struct completion complete;
    int number;
};

struct complete_demo *comp_test;

static struct task_struct *task1;
static struct task_struct *task2;

static int thread_func_wait(void *data)
{
    struct complete_demo *a = (struct complete_demo *)data;
    printk(KERN_INFO "in wait_complete\n");
    wait_for_completion(&a->complete);
    a->number++;
    printk(KERN_INFO "out wait_complete\n");

    return 0;
}


static int thread_func_wake(void *data)
{
    struct complete_demo *b = (struct complete_demo *)data;
    printk(KERN_INFO "in complete\n");
    complete(&b->complete);
    printk(KERN_INFO "out cpmplete\n");

    return 0;
}

static int hello_init(void)
{

    printk(KERN_INFO "in hello init\n");

    comp_test = kzalloc(sizeof(struct complete_demo), GFP_KERNEL);
    if ( comp_test == NULL) {
        printk(KERN_INFO "kzalloc failed\n");
        return -ENOMEM;
    }

    init_completion(&comp_test->complete);  

    task1 = kthread_run(thread_func_wait, comp_test, "thread%d", 1);
    if (IS_ERR(task1)) {
        printk(KERN_INFO "create thread 1 failed\n");
    }

    msleep(100);

    comp_test->number = 1;

    task2 = kthread_run(thread_func_wake, comp_test, "thread%d", 2);
    if (IS_ERR(task2)) {
        printk(KERN_INFO "create thread 2 failed\n");
    }


    msleep(500);
    printk(KERN_INFO "out hello init\n");
    return 0;
}

static void hello_exit(void)
{

    printk(KERN_INFO "in hello exit\n");
    printk(KERN_INFO "number is %d\n", comp_test->number);
    kfree(comp_test);
}


module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("khl");
MODULE_DESCRIPTION("kernel module demo for completion");
MODULE_LICENSE("GPL");
  1. Makefile文件
obj-m += complete_demo.o

KDIR = /lib/modules/`uname -r`/build

PWD := $(shell pwd)

default:

	#make -C $(KDIR) M=$(PWD) KBUILD_EXTRA_SYMBOLS=$(KDIR)/Module.symvers modules
	make -C $(KDIR) M=$(PWD) modules

clean:
	rm -rf *.o *.cmd *.ko *.mod.c .tmp_versions *.order *.symvers .*.cmd *.mod

总结

  1. 完成量其实和一个被初始化值为0的信号量很类似;
  2. 完成量是对等待队列的封装, 其实质还是等待队列;
  3. 如果在调用wait_for_completion()函数时,如果complete->done的值等于0,那么当前线程会进入睡眠;如果此时complete->done的值大于0(即在调用wait_for_completion之前已经有调用过complete()),那么wait_for_completion()函数会将complete->done的值减1,然后继续向下执行, 不会阻塞;
  4. 在内核中,对于一个任务需要发出信号通知另一个任务发生了某个特定事件的场景,使用完成量是使这两个任务得以同步的一种简单方法;

参考资料

  1. https://blog.csdn.net/huangweiqing80/article/details/83105960
  2. https://blog.csdn.net/ddffyhg/article/details/88801663
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值