完成量, 功能与信号量差不多, 最大不同可以唤醒多个休眠的进程或线程
#include <linux/completion.h>
struct completion {
unsigned int done; //done表示资源,上锁时done--. 当done为0时再上锁则会安排调用进程或线程进入休眠.
//解锁时, done++
wait_queue_head_t wait; //等待完成的进程链表
};
void init_completion(struct completion *x); //初始化done=0;
void wait_for_completion(struct completion *); //done--,当done为0时,调用进程或线程被安排进入休眠
int wait_for_completion_interruptible(struct completion *x); //可被信号唤醒的休眠
extern void complete(struct completion *); //完成 done++
extern void complete_all(struct completion *); //把done的值设为非常大, 让所有在等待锁资源的休眠进程或线程醒过来. 注意complete_all一般是工作退出时发出通知使用,只用一次. 如驱动卸载时上锁等工作线程退出时再解锁.
如实现一个设备驱动,多个用户进程在读操作时等待数据写入, 当写操作时唤醒所有的休眠进程.
test.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <asm/current.h>
#include <linux/completion.h>
struct completion com;
ssize_t myread(struct file *fl, char __user *buf, size_t len, loff_t *off)
{
int ret;
ret = wait_for_completion_interruptible(&com);
if (ret < 0)
return -ERESTART;
printk(" pid = %d, %s waitting\n", current->pid, current->comm);
return ret;
}
ssize_t mywrite(struct file *fl, const char __user *buf, size_t len, loff_t *off)
{
complete_all(&com); //设done的值为UINT_MAX/2, 唤醒所有的休眠进程
return len;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.read = myread,
.write = mywrite,
};
struct miscdevice mymdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mymdev",
.fops = &fops,
};
static int __init test_init(void)
{
init_completion(&com);
return misc_register(&mymdev);
}
static void __exit test_exit(void)
{
misc_deregister(&mymdev);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
///
互斥锁,自旋锁,信号量,完成量等锁基本上都是按照先后顺序唤配休眠的进程.
但在某些场合,想唤醒满足特定条件的特定休眠进程时就无法做到. 在linux内核里可用等待队列来实现这种需求。
#include <linux/wait.h>
wait_queue_head_t //表示队列头类型. 相当于一个链表头.每个节点就是要等待某种条件的休眠进程.
void init_waitqueue_head(wait_queue_head_t *q); //初始化等待队列
// condition 是C语言表达式的条件语句
//wq 是等待队列头的变量. 下面三个函数把当前调用的进程根据condition创建出一个队列节点,加入指定的队列里
wait_event(wq, condition); //如果指定的condition条件不满足,就会创建节点加入队列.调用的进程就会进入休眠.
wait_event_timeout(wq, condition, timeout);
wait_event_interruptible(wq, condition);
//下面两个函数用于唤醒指定的队列,当唤醒时,内核会检查队列里每个节点的条件是否满足,如已满足,则让相应的进程醒过来. 通常情况下,唤醒队列前会改变条件的值.
wake_up(wait_queue_head_t *queue);
wake_up_interruptible(wait_queue_head_t *queue);
///
如实现一个设备驱动,当用户进程在读操作时等待变量mypid的值与它的进程号的个位值一致,当写操作时根据传进来的内容设置变量mypid的值.
test.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <asm/current.h>
#include <linux/wait.h>
wait_queue_head_t wq;
int mypid = -1;
ssize_t myread(struct file *fl, char __user *buf, size_t len, loff_t *off)
{
int ret;
ret = wait_event_interruptible(wq, mypid == (current->pid)%10);
if (ret < 0)
return -ERESTART;
printk(" pid = %d, %s after wait\n", current->pid, current->comm);
return ret;
}
ssize_t mywrite(struct file *fl, const char __user *buf, size_t len, loff_t *off)
{
mypid = buf[0] - '0';
wake_up(&wq); //让内核检查队列
return len;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.read = myread,
.write = mywrite,
};
struct miscdevice mymdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mymdev",
.fops = &fops,
};
static int __init test_init(void)
{
init_waitqueue_head(&wq);
return misc_register(&mymdev);
}
static void __exit test_exit(void)
{
misc_deregister(&mymdev);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
让设备驱动支持应用程序通过poll / select / epoll 函数来监控文件描述符的状态, 是由设备驱动里的struct file_opeartions结构体对象的poll成员实现的.
用法:
wait_queue_head_t queue; //1. 声明一个等待队列头对象, 在驱动的poll函数里供调用poll_wait时使用
init_waitqueue_head(&queue) //2. 初始化一个等待队列头对象
//3. 驱动里的poll函数主要就是调用poll_wait函数处理文件描述符和返回文件描述符的状态
unsigned int (*poll) (struct file *fl, struct poll_table_struct *tbl)
{
poll_wait(fl, &queue, tbl); //把文件描述符fl添加到当前调用进程所监控的文件描述符列表.
//注意调用poll_wait是不会发生休眠的,这里仅仅是把文件描述符加入监控列表而已.
if (condition) //根据条件返回不同的文件描述符状态值
return POLLIN|POLLRDNORM(可读), POLLOUT|POLLWRNORM(可写)
else
return 0; //文件描述符状态没有发生变化
//内核会根据此函数的返回值来确定当前调用进程的文件描述符fl是否发生状态变化,如当前文件描述符和进程的其它所有文件描述符都没有发生变化, 内核则会安排当前进程进入休眠.
}
//4. 当条件condition满足时需要设备驱动调用wake_up(&queue)来唤醒
//内核调用实现的poll函数在fs/select.c, 745行处
mask = file->f_op->poll(file, pwait);