在Linux 驱动程序中,可以使用等待队列来实现阻塞进程的唤醒。等待队列的头部定义如下,是一个双向列表。

struct list_head {

struct list_head *next, *prev;

};

struct __wait_queue_head {

spinlock_t lock;

struct list_head task_list;

};

typedef struct __wait_queue_head wait_queue_head_t;

Linux提供了如下关于等待队列的操作:

1 __init_waitqueue_head(&my_queue):进行等待队列的初始化。

2 DECLARE_WAITQUEUE: 定义等待队列元素。代码如下

#define __WAITQUEUE_INITIALIZER(name, tsk) {                           \

.private    = tsk,                                                  \

.func                 = default_wake_function,                        \

.task_list  = { NULL, NULL } }

}

#define DECLARE_WAITQUEUE(name, tsk)                                      \

wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

其实就是两个宏定义,这个两个宏定义扩展开来其实就是

wait_queue_t name={

.private    = tsk,                #private代表当前进程指针

.func                 = default_wake_function,                        #唤醒的回调函数

.task_list  = { NULL, NULL } }   #等待队列

}

那么回到代码中的调用DECLARE_WAITQUEUE(wait, current)就是定义了一个等待队列元素wait。等待队列的private就等于当前进程指针。

3 add_wait_queue/remove_wait_queue:添加移除等待队列

4 等待事件

wait_event(queue,condition)

wait_event_interrupt(queue,condition)

wait_event_timeout(queue,condition,timeout)

wait_event_interruptible_timeout(queue,condition,timeout)

等待第1个参数queue作为等待队列头部的队列被唤醒,而且第2个参数必须满足,否则继续阻塞,wait_event和wait_event_interrupt的区别在于后者可以被信号中断打断。加上timeout后的宏意味着阻塞等待的超时时间,在第三个参数timeout到达时,不论condition是否满足,均返回。

来看下代码的实现,首先是wait_event。 先判断条件,如果条件,则立即退出,否则进入__wait_event

#define wait_event(wq, condition)                                         \

do {                                                                     \

if (condition)                                                   \

break;                                                        \

__wait_event(wq, condition);                                  \

} while (0)

#define __wait_event(wq, condition)                                     \

do {                                                                     \

DEFINE_WAIT(__wait);                                              \

\

for (;;) {                                                      \

prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);         \

if (condition)                                             \

break;                                                \

schedule();                                                 \

}                                                                  \

finish_wait(&wq, &__wait);                                     \

} while (0)

(1)     DEFINE_WAIT(__wait) 中申明一个当前进展的等待队列

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

#define DEFINE_WAIT_FUNC(name, function)                                \

wait_queue_t name = {                                            \

.private    = current,                          \

.func                 = function,                                \

.task_list  = LIST_HEAD_INIT((name).task_list),       \

}

(2)     进入一个无限循环。prepare_to_wait中将申明的等待任务加入到等待队列中去。并设置任务状态为TASK_UNINTERRUPTIBLE。如果condition满足,则退出循环。如果不满足则进行一次任务调度

(3)     满足条件退出循环后调用finish_wait从等待任务队列里面删除并设置进程状态为TASK_RUNNING

wait_event_timeout的代码也一样,不过是加了个schedule_timeout,如果超时则不管条件是否满足都直接退出

#define __wait_event_timeout(wq, condition, ret)                        \

do {                                                                     \

DEFINE_WAIT(__wait);                                              \

\

for (;;) {                                                      \

prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);         \

if (condition)                                             \

break;                                                \

ret = schedule_timeout(ret);                            \

if (!ret)                                               \

break;                                                \

}                                                                  \

finish_wait(&wq, &__wait);                                     \

} while (0)

5 wake_up(wait_queue_head_t *queue) wake_up会唤醒作为等待队列头部的队列中的所有进程。代码按如下,遍历队列,然后依次执行唤醒回调函数

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, int wake_flags, void *key)

{

wait_queue_t *curr, *next;

list_for_each_entry_safe(curr, next, &q->task_list, task_list) {

unsigned flags = curr->flags;

if (curr->func(curr, mode, wake_flags, key) &&

(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

break;

}

}

回调函数在生成队列wait的时候赋值为default_wake_function, 里面执行的就是try_to_wake_up。

6 sleep_on在等待队列上睡眠。将当前进程置成TASK_UNINTERRUPTIBLE,然后将当前进程加入到等待队列中去,待睡眠时间超时后,再从等待队列中删除。代码如下

sleep_on_common(wait_queue_head_t *q, int state, long timeout)

{

unsigned long flags;

wait_queue_t wait;

init_waitqueue_entry(&wait, current);

__set_current_state(state);

spin_lock_irqsave(&q->lock, flags);

__add_wait_queue(q, &wait);

spin_unlock(&q->lock);

timeout = schedule_timeout(timeout);

spin_lock_irq(&q->lock);

__remove_wait_queue(q, &wait);

spin_unlock_irqrestore(&q->lock, flags);

return timeout;

}

现在将前面的globalmem改成阻塞型的,代码如下:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/sched/signal.h> #define GLOBALMEM_SIZE 0X1000
#define MEM_CLEAR 0X1
#define GLOBALMEM_MAJOR 230 static int globalmem_major=GLOBALMEM_MAJOR;
module_param(globalmem_major,int,S_IRUGO); struct globalmem_dev{
struct cdev cdev;
unsigned char mem[GLOBALMEM_SIZE];
struct mutex mutex;
unsigned int current_len;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
}; struct globalmem_dev *globalmem_devp; static int globalmem_open(struct inode *inode,struct file *filp)
{
filp->private_data=globalmem_devp;
return ;
} static int globalmem_release(struct inode *inode,struct file *filp)
{
return ;
} static long globalmem_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
struct globalmem_dev *dev=filp->private_data;
switch(cmd)
{
case MEM_CLEAR:
mutex_lock(&dev->mutex);
memset(dev->mem,,GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
mutex_unlock(&dev->mutex);
default:
return -EINVAL;
}
return ;
} static ssize_t globalmem_read_queue(struct file *filp,char __user *buf,size_t size,loff_t *ppos)
{
int ret;
struct globalmem_dev *dev=filp->private_data;
DECLARE_WAITQUEUE(wait,current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->r_wait,&wait);
while(dev->current_len == ){
if(filp->f_flags & O_NONBLOCK){
ret=-EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if(signal_pending(current)){
ret=-ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if(size > dev->current_len)
size=dev->current_len;
if(copy_to_user(buf,dev->mem,size)){
printk("copy_to_user_fail\n");
ret=-EFAULT;
goto out;
}else{
memcpy(dev->mem,dev->mem+size,dev->current_len-size);
dev->current_len-=size;
wake_up_interruptible(&dev->w_wait);
ret=size;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING); return ret;
} static ssize_t globalmem_write_queue(struct file *filp,const char __user *buf,size_t size, loff_t *ppos)
{
int ret;
struct globalmem_dev *dev=filp->private_data;
DECLARE_WAITQUEUE(wait,current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait,&wait);
while(dev->current_len == GLOBALMEM_SIZE){
if(filp->f_flags & O_NONBLOCK){
ret=-EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if(signal_pending(current)){
ret=-ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if(size > GLOBALMEM_SIZE- dev->current_len)
size=GLOBALMEM_SIZE - dev->current_len;
if(copy_from_user(dev->mem+dev->current_len,buf,size)){
ret=-EFAULT;
goto out;
}else{
dev->current_len+=size;
wake_up_interruptible(&dev->r_wait);
ret=size;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING);
return ret;
} static loff_t globalmem_llseek(struct file *filp,loff_t offset,int orig)
{
loff_t ret=;
switch(orig){
case :
if (offset < )
ret=-EFAULT;
break;
if ((unsigned int)offset > GLOBALMEM_SIZE){
ret=-EFAULT;
break;
}
filp->f_pos=(unsigned int)offset;
ret=filp->f_pos;
break;
case :
if((filp->f_pos+offset) > GLOBALMEM_SIZE){
ret=-EFAULT;
break;
}
if((filp->f_pos+offset) < ){
ret=-EFAULT;
break;
}
filp->f_pos+=offset;
ret=filp->f_pos;
break;
}
return ret;
} static const struct file_operations globalmem_fops={
.owner=THIS_MODULE,
.llseek=globalmem_llseek,
.read=globalmem_read_queue,
.write=globalmem_write_queue,
.unlocked_ioctl=globalmem_ioctl,
.open=globalmem_open,
.release=globalmem_release,
}; static void globalmem_setup_dev(struct globalmem_dev *dev,int index)
{
int err,devno=MKDEV(globalmem_major,index);
cdev_init(&dev->cdev,&globalmem_fops);
dev->cdev.owner=THIS_MODULE;
err=cdev_add(&dev->cdev,devno,);
if(err)
printk(KERN_NOTICE "Error %d adding globalmem%d",err,index);
} static int __init globalmem_init(void)
{
int ret;
dev_t devno=MKDEV(globalmem_major,);
printk("devno=%d\n",devno);
if(globalmem_major)
ret=register_chrdev_region(devno,,"globalmem_tmp");
else{
ret=alloc_chrdev_region(&devno,,,"globalmem_tmp");
globalmem_major=MAJOR(devno);
}
if(ret < )
return ret;
globalmem_devp=kzalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
if(!globalmem_devp){
ret=-EFAULT;
goto fail_malloc;
} mutex_init(&globalmem_devp->mutex);
globalmem_setup_dev(globalmem_devp,);
printk("globalmem init success\n");
init_waitqueue_head(&globalmem_devp->r_wait);
init_waitqueue_head(&globalmem_devp->w_wait);
return ;
fail_malloc:
unregister_chrdev_region(devno,);
return ret;
} module_init(globalmem_init); static void __exit globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev);
kfree(globalmem_devp);
unregister_chrdev_region(MKDEV(globalmem_major,),);
printk("global_mem exited\n");
} module_exit(globalmem_exit); MODULE_AUTHOR("zhf");
MODULE_LICENSE("GPL");

代码主要做了如下几点改动:

1 在globalmem_dev中增加读和写的队列r_wait以及w_wait

2 在globalmem_init中调用init_waitqueue_head初始化写和读队列

3 在globalmem_read_queue中将当前进程加入读的等待队列,只有当读队列完成copy_to_user的操作后,才唤醒写队列的进程

4 在globalmem_write_queue中将当前进程加入写的等待队列,只有当写队列完成copy_from_user的操作后,才唤醒读队列的进程

linux驱动开发学习二:创建一个阻塞型的字符设备的更多相关文章

  1. Linux驱动开发学习的一些必要步骤

      1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ...

  2. linux驱动开发学习一:创建一个字符设备

    首先是内核初始化函数.代码如下.主要是三个步骤.1 生成设备号. 2 注册设备号.3 创建设备. #include <linux/module.h> #include <linux/ ...

  3. Linux驱动开发学习笔记(1):LINUX驱动版本的hello world

    1.关于目录    /lib/modules/2.6.9-42.ELsmp/build/   这个是内核源码所在的目录    一般使用这样的命令进入这个目录:cd /lib/modules/$(una ...

  4. Android Wear 开发入门——怎样创建一个手机与可穿戴设备关联的通知(Notification)

    创建通知 为了创建在手机与可穿戴设备中都能展现的通知,能够使用 NotificationCompat.Builder.通过该类创建的通知,系统会处理该通知是否展如今手机或者穿戴设备中. 导入必要的类库 ...

  5. pipelinewise 学习二 创建一个简单的pipeline

    pipelinewise 提供了方便的创建简单pipeline的命令,可以简化pipeline 的创建,同时也可以帮我们学习 生成demo pipeline pipelinewise init --n ...

  6. micronaut 学习 二 创建一个简单的服务

    micronaut 提供的cli 很方便,我们可以快速创建具有所需特性的应用,以下是一个简单的web server app 创建命令 mn create-app hello-world 效果 mn c ...

  7. Hasen的linux设备驱动开发学习之旅--时钟

    /** * Author:hasen * 參考 :<linux设备驱动开发具体解释> * 简单介绍:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:时钟 * Date ...

  8. linux驱动开发之块设备学习笔记

    我的博客主要用来存放我的学习笔记,如有侵权,请与我练习,我会立刻删除.学习参考:http://www.cnblogs.com/yuanfang/archive/2010/12/24/1916231.h ...

  9. Linux内核(17) - 高效学习Linux驱动开发

    这本<Linux内核修炼之道>已经开卖(网上的链接为: 卓越.当当.china-pub ),虽然是严肃文学,但为了保证流畅性,大部分文字我还都是斟词灼句,反复的念几遍才写上去的,尽量考虑到 ...

随机推荐

  1. 实验3-1 求一元二次方程的根 (20 分) 《C语言程序设计实验与习题指导(第3版)》

    本题目要求一元二次方程的根,结果保留2位小数. 输入格式: 输入在一行中给出3个浮点系数a.b.c,中间用空格分开. 输出格式: 根据系数情况,输出不同结果: 1)如果方程有两个不相等的实数根,则每行 ...

  2. IE 常见bug

    1. 双空白边浮动bug 最常见且最容易发现的bug 之一是IE6和更低版本中的双空白边浮动bug.顾名思义,这个Windows bug使任何浮动元素上的空白边加倍 这个bug 很容易修复,将元素的d ...

  3. str = @"abc ""def"" ghi """"jkl"""" mn";

    namespace ConsoleQuotes { class Program { static void Main(string[] args) { string str = @"abc ...

  4. 函数式编程:面向可复用的map和pipeline机制的编程语言

    函数式编程:面向可复用的map和pipeline机制的编程语言

  5. loj2058 「TJOI / HEOI2016」求和 NTT

    loj2058 「TJOI / HEOI2016」求和 NTT 链接 loj 思路 \[S(i,j)=\frac{1}{j!}\sum\limits_{k=0}^{j}(-1)^{k}C_{j}^{k ...

  6. 高考数学答卷策略[K12论坛转载]

    一.试卷上给你的启发 1.试卷上有参考公式,80%是有用的,它为你的解题指引了方向: 2.解答题的各小问之间有一种阶梯关系,通常后面的问要使用前问的结论.如果前问是证明,即使不会证明结论,该结论在后问 ...

  7. ARC096 E Everything on It [容斥,斯特林数]

    Atcoder 一个900分的题耗了我这么久--而且官方题解还那么短--必须纪念一下-- 思路 发现每种元素必须出现两次以上的限制极为恶心,所以容斥,枚举出现0/1次的元素个数分别有几个.设出现1次的 ...

  8. gethostname、gethostbyname

    gethostname():返回本地主机的标准主机名 原型: #include<unistd.h> int gethostname(char *name, size_t len); 参数说 ...

  9. GoCN每日新闻(2019-11-10)

    GoCN每日新闻(2019-11-10) 1. Go Netpoll I/O多路复用构建原生网络模型之源码深度解析 https://taohuawu.club/go-netpoll-io-multip ...

  10. SSH登录慢解方案 - 关闭UseDNS加速

    每次登录SSH时总是要停顿等待一会儿才能连接上,,这是因为OpenSSH服务器有一个DNS查找选项UseDNS默认情况下是打开的. UseDNS 选项打开状态下,当通过终端登录SSH服务器时,服务器端 ...