先决条件
在示例部分中,我使用kthread解释了此完成过程。如果您不知道什么是kthread以及如何使用它,那么我建议您使用下面的链接进行探索。
Completion
Completion, 和名字本身说的一样。当我们想在完成某些工作时通知或唤醒某个线程或某些东西时,可以使用完成。我们将采取一种情况。我们要等待一个线程运行。在此之前,该线程必须休眠。该过程完成后,我们需要唤醒正在休眠的线程。我们可以通过使用没有竞争条件的完成来做到这一点。
这些完成是一种同步机制,在上述情况下是一种很好的方法,而不是使用不正确的锁/信号量和繁忙循环。
Completion in Linux Device Driver
在Linux内核中,完成是通过使用waitqueue开发的。
使用Completion的优点是它们具有定义明确,目标明确的目的,可以很容易地看到代码的意图,但是由于所有线程都可以继续执行直到实际需要结果,因此它们还可以提高代码效率。使用低级调度程序的睡眠/唤醒功能,等待和信令均非常高效。
Completion中有5个重要步骤。
- 初始化completion
- 重新初始化completion
- 等待completion(代码正在等待并等待完成某件事)
- 唤醒任务(将信号发送到睡眠部分)
- 检查状态
初始化completion
我们必须#include <linux/completion.h>
并创建一个struct completion
类型的变量,该变量只有两个字段:
struct completion {
unsigned int done;
wait_queue_head_t wait;
};
在这里,wait是等待任务放置的等待队列(如果有的话)。done
是用于指示其是否完成的完成标志。
我们可以通过两种方式创建类型变量。
- 静态方法
- 动态方法
您可以使用任何一种方法。
静态方法
DECLARE_COMPLETION(data_read_done);
其中data_read_done
是将要静态创建的结构的名称。
动态方法
init_completion (struct completion * x);
其中x
指向要初始化的completion结构
举例:
struct completion data_read_done;
init_completion(&data_read_done);
在此init_completion
调用中,我们初始化等待队列并将完成设置为0,即not completed
或not done
。
重新初始化完成
reinit_completion (struct completion * x);
这里x
指向要重新初始化的completion结构
举例:
reinit_completion(&data_read_done);
此函数应用于重新初始化completion结构,以便可以重用。在使用complete_all之后,这一点尤其重要。只需将->done
字段重置为0(“not done”),而无需触摸等待队列。该函数的调用者必须确保没有并行的wait_for_completion()
调用。
等待完成
为了让线程等待一些并发活动完成,它会根据用例调用该函数中的任何一个。
wait_for_completion
这用于使函数等待任务完成。
void wait_for_completion (struct completion * x);
这里x
指向保持此特定完成的状态
等待等待完成
特定任务的信号。它不是可中断的,没有超时。
例如:
wait_for_completion (&data_read_done);
请注意,wait_for_completion()
正在调用spin_lock_irq()/spin_unlock_irq()
,因此只有在知道启用了中断的情况下才能安全地调用它。从IRQs-off原子上下文中调用它会导致难以检测到的虚假中断启用。
wait_for_completion_timeout
这用于使函数等待超时的任务完成。超时最好使用msecs_to_jiffies()
或usecs_to_jiffies()
计算,以使代码在很大程度上保持HZ不变。
unsigned long wait_for_completion_timeout (struct completion * x, unsigned long timeout);
x
指向保持此特定完成的状态
timeout
单位是jiffies
这等待信号通知特定任务的完成或指定的超时时间到期。timeout的单位是jiffies。它不是可中断的。
如果超时,则返回0;
如果完成,则返回正值(剩下的jiffies时间:ret=timeout-runtime
)。
举例:
wait_for_completion_timeout (&data_read_done);
wait_for_completion_interruptible
这是等待要发出信号的特定任务的完成。它是可中断的。
int wait_for_completion_interruptible (struct completion * x);
x
指向 保持此特定完成的状态
timeout
单位是jiffies
如果被中断,则返回-ERESTARTSYS
;
如果超时,则返回0
;
如果已完成,则返回正数(至少1,或直到超时的剩余数量)。
wait_for_completion_killable
等待等待完成
特定任务的信号。它可以被终止信号打断。
int wait_for_completion_killable (struct completion * x);
x
指向保持此特定完成的状态
如果被中断,则返回-ERESTARTSYS
;
如果超时,则返回0
;
wait_for_completion_killable_timeout
这等待信号通知特定任务的完成
或等待指定的超时时间到期
。它可以被终止信号打断。超时最好使用msecs_to_jiffies()
或usecs_to_jiffies()
计算,以使代码在很大程度上保持HZ不变。
long wait_for_completion_killable_timeout (struct completion * x, unsigned long timeout);
x
指向 保持此特定完成的状态
timeout
单位是jiffies
如果被中断,则返回-ERESTARTSYS
;
如果超时,则返回0
;
如果已完成,则返回正数(至少1,或直到超时的剩余数量)。
try_wait_for_completion
此函数不会将线程放在等待队列中,而是在需要排队(阻塞)线程时返回false,否则它将消耗一个已发布的完成并返回true。
bool try_wait_for_completion (struct completion * x);
x
指向 保持此特定完成的状态
如果无法完成则返回0;
如果成功则返回1。
在IRQ或原子上下文中可以安全地调用此try_wait_for_completion()
。
唤醒任务
complete
这将唤醒一个线程,等待完成。线程将以排队的顺序被唤醒。
x
指向 保持此特定完成的状态
例子:
complete(&data_read_done);
complete_all
这将唤醒等待此特定完成事件的所有线程。
void complete_all (struct completion * x);
x
指向 保持此特定完成的状态
检查状态
这是一项测试,以查看完成是否有任何等待。
bool completion_done (struct completion * x);
x
指向 保持此特定完成的状态
如果有服务员,则返回0(wait_for_completion
正在进行);
如果没有服务员,则返回1。
可以在IRQ或原子上下文中安全地调用该complete_done()
。
Driver Source Code – Completion in Linux
首先,我将向您解释驱动程序代码的概念。
在此源代码中,我们将在两个地方发送完整的调用。一个来自读取功能,另一个来自驱动程序退出功能。
我创建了一个具有while(1)
的线程(wait_function
)。该线程将始终等待事件完成。它将一直处于睡眠状态,直到获得completion的调用。当它获得completion的调用时,它将检查条件。如果条件为1,则completion来自读取功能。它是2
,则completion来自退出功能。如果完成来自读取功能,它将打印读取计数并再次等待。如果它来自退出函数,它将从线程中退出。
在这里,我添加了两个版本的代码。
- 通过静态方法创建的补全
- 通过动态方法创建的补全
但是从操作角度来看,两者是相同的。
GitHub
通过静态方法创建的补全
/***************************************************************************//**
* \file driver.c
*
* \details Simple linux driver (Completion Static method)
*
* \author EmbeTronicX
*
* *******************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h> //kmalloc()
#include <linux/uaccess.h> //copy_to/from_user()
#include <linux/kthread.h>
#include <linux/completion.h> // Required for the completion
uint32_t read_count = 0;
static struct task_struct *wait_thread;
DECLARE_COMPLETION(data_read_done);
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
int completion_flag = 0;
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
/*************** Driver Functions **********************/
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off);
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.release = etx_release,
};
static int wait_function(void *unused)
{
while(1) {
printk(KERN_INFO "Waiting For Event...\n");
wait_for_completion (&data_read_done);
if(completion_flag == 2) {
printk(KERN_INFO "Event Came From Exit Function\n");
return 0;
}
printk(KERN_INFO "Event Came From Read Function - %d\n", ++read_count);
completion_flag = 0;
}
do_exit(0);
return 0;
}
static int etx_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File Opened...!!!\n");
return 0;
}
static int etx_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File Closed...!!!\n");
return 0;
}
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
printk(KERN_INFO "Read Function\n");
completion_flag = 1;
if(!completion_done (&data_read_done)) {
complete (&data_read_done);
}
return 0;
}
static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
printk(KERN_INFO "Write function\n");
return 0;
}
static int __init etx_driver_init(void)
{
/*Allocating Major number*/
if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
printk(KERN_INFO "Cannot allocate major number\n");
return -1;
}
printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
/*Creating cdev structure*/
cdev_init(&etx_cdev,&fops);
etx_cdev.owner = THIS_MODULE;
etx_cdev.ops = &fops;
/*Adding character device to the system*/
if((cdev_add(&etx_cdev,dev,1)) < 0){
printk(KERN_INFO "Cannot add the device to the system\n");
goto r_class;
}
/*Creating struct class*/
if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
printk(KERN_INFO "Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
printk(KERN_INFO "Cannot create the Device 1\n");
goto r_device;
}
//Create the kernel thread with name 'mythread'
wait_thread = kthread_create(wait_function, NULL, "WaitThread");
if (wait_thread) {
printk("Thread Created successfully\n");
wake_up_process(wait_thread);
} else
printk(KERN_INFO "Thread creation failed\n");
printk(KERN_INFO "Device Driver Insert...Done!!!\n");
return 0;
r_device:
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev,1);
return -1;
}
static void __exit etx_driver_exit(void)
{
completion_flag = 2;
if(!completion_done (&data_read_done)) {
complete (&data_read_done);
}
device_destroy(dev_class,dev);
class_destroy(dev_class);
cdev_del(&etx_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "Device Driver Remove...Done!!!\n");
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("A simple device driver - Completion (Static Method)");
MODULE_VERSION("1.23");
通过动态方法完成
/****************************************************************************//**
* \file driver.c
*
* \details Simple linux driver (Completion Dynamic method)
*
* \author EmbeTronicX
*
* *******************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h> //kmalloc()
#include <linux/uaccess.h> //copy_to/from_user()
#include <linux/kthread.h>
#include <linux/completion.h> // Required for the completion
uint32_t read_count = 0;
static struct task_struct *wait_thread;
struct completion data_read_done;
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
int completion_flag = 0;
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
/*************** Driver Functions **********************/
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off);
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.release = etx_release,
};
static int wait_function(void *unused)
{
while(1) {
printk(KERN_INFO "Waiting For Event...\n");
wait_for_completion (&data_read_done);
if(completion_flag == 2) {
printk(KERN_INFO "Event Came From Exit Function\n");
return 0;
}
printk(KERN_INFO "Event Came From Read Function - %d\n", ++read_count);
completion_flag = 0;
}
do_exit(0);
return 0;
}
static int etx_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File Opened...!!!\n");
return 0;
}
static int etx_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File Closed...!!!\n");
return 0;
}
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
printk(KERN_INFO "Read Function\n");
completion_flag = 1;
if(!completion_done (&data_read_done)) {
complete (&data_read_done);
}
return 0;
}
static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
printk(KERN_INFO "Write function\n");
return 0;
}
static int __init etx_driver_init(void)
{
/*Allocating Major number*/
if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
printk(KERN_INFO "Cannot allocate major number\n");
return -1;
}
printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
/*Creating cdev structure*/
cdev_init(&etx_cdev,&fops);
etx_cdev.owner = THIS_MODULE;
etx_cdev.ops = &fops;
/*Adding character device to the system*/
if((cdev_add(&etx_cdev,dev,1)) < 0){
printk(KERN_INFO "Cannot add the device to the system\n");
goto r_class;
}
/*Creating struct class*/
if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
printk(KERN_INFO "Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
printk(KERN_INFO "Cannot create the Device 1\n");
goto r_device;
}
//Create the kernel thread with name 'mythread'
wait_thread = kthread_create(wait_function, NULL, "WaitThread");
if (wait_thread) {
printk("Thread Created successfully\n");
wake_up_process(wait_thread);
} else
printk(KERN_INFO "Thread creation failed\n");
//Initializing Completion
init_completion(&data_read_done);
printk(KERN_INFO "Device Driver Insert...Done!!!\n");
return 0;
r_device:
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev,1);
return -1;
}
static void __exit etx_driver_exit(void)
{
completion_flag = 2;
if(!completion_done (&data_read_done)) {
complete (&data_read_done);
}
device_destroy(dev_class,dev);
class_destroy(dev_class);
cdev_del(&etx_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "Device Driver Remove...Done!!!\n");
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("A simple device driver - Completion (Dynamic Method)");
MODULE_VERSION("1.24");
MakeFile
obj-m += driver.o
KDIR = /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(shell pwd) modules
clean:
make -C $(KDIR) M=$(shell pwd) clean
构建和测试驱动程序
make #编译
insmod driver.ko # 加载模块
dmesg #查看打印
因此该线程正在等待事件。现在我们将通过使用
sudo cat /dev/etx_device
rmmod driver # 移除模块