在Linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。wait queue 很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问。 等待队列具有以下操作方式。 一:定义等待队列头 - wait_queue_head_t my_queue;
复制代码二:初始化等待队列头 - init_waitqueue_head(&my_queue);
复制代码三:定义等待队列 - DECLARE_WAITQUEUE(name, tsk)
复制代码该宏用于定义并初始化一个名为name的等待队列,它等效于前面两步的操作。 四:添加/移除等待队列 - void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
- void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
复制代码add_wait_queue()用于将等待队列wait添加到等待队列头q指向的等待队列链表中,而 remove_wait_queue()用于将等待队列wait从附属的等待队列头q指向的等待队列链表中移除。 五:等待事件 - wait_event(queue, condition)
- wait_event_interruptible(queue, condition)
- wait_event_timeout(queue, condition, timeout)
- wait_event_interruptible_timeout(queue, condition, timeout)
复制代码等待第一个参数queue作为等待队列头的等待队列被唤醒,而且第二个参数condition必须满足,否则阻塞。wait_event()和 wait_event_interruptible()的区别在于后者可以被信号打断,而前者不能。加上_timeout 后的宏意味着阻塞等待的超时时间,以jiffy为单位,在第三个参数的timeout到达时,不论condition是否满足,均返回。当condition满足时,以上四个函数均会立即返回,否则,阻塞等待 condition 满足。 六:唤醒队列 - void wake_up(wait_queue_head_t *queue);
- void wake_up_interruptible(wait_queue_head_t *queue);
复制代码上述操作会唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。 wake_up() 应与wait_event()或wait_event_timeout()成对使用,而wake_up_interruptible() 则应与wait_event_interruptible()或wait_event_interruptible_timeout()成对使用。wake_up()可 唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程,而wake_up_interruptible()只能唤醒处于TASK_INTERRUPTIBLE的进程。 七:在等待队列上睡眠 - sleep_on(wait_queue_head_t *q );
- interruptible_sleep_on(wait_queue_head_t *q );
复制代码sleep_on()函数的作用就是将目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒。 interruptible_sleep_on()与 sleep_on()函数类似,其作用是将目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒或者进程收到信号。 sleep_on()函数应该与wake_up()成对使用,interruptible_sleep_on()应该与wake_up_interruptible()成对使用。 不论是sleep_on()还是interruptible_sleep_on(),其流程都如下所示。 (1)定义并初始化一个等待队列,将进程状态改变为TASK_UNINTERRUPTIBLE(不能被信号打断)或TASK_INTERRUPTIBLE(可以被信号打断),并将等待队列添加到等待队列头。 (2)通过 schedule()放弃 CPU,调度其他进程执行。 (3)进程被其他地方唤醒,将等待队列移出等待队列头。 在内核中使用set_current_state()函数或__add_wait_queue()函数来实现目前进程状态的改变,直接采用current->state = TASK_UNINTERRUPTIBLE类似的赋值语句也是可行的。通常而言,set_current_state()函数在任何环境下都可以使用,不会存在并发问题,但是效率要低于__add_ wait_queue()。因此,在许多设备驱动中,并不调用sleep_on()或 interruptible_sleep_on(),而是亲自进行进程的状态改变和切换。 编写驱动,实现以下几个功能: 一:在驱动中定义一个4KB的全局内存,用于和进程间交互数据; 二:该全局内存类似于一个先进先出的仓库,即FIFO; 三:当有进程给FIFO写数据时,读进程将会从FIFO中读出数据,并实时清空FIFO; 四:只有当FIFO没有填满时,才允许给FIFO填写数据。 在kernel/drivers/char/x4412目录下新建x4412-globalfifo.c文件,编辑内容如下: - #include <linux/module.h>
- #include <linux/types.h>
- #include <linux/fs.h>
- #include <linux/errno.h>
- #include <linux/sched.h>
- #include <linux/init.h>
- #include <linux/cdev.h>
- #include <asm/uaccess.h>
- #include <linux/kernel.h>
- #include <linux/workqueue.h>
- #include <linux/slab.h>
- #include <linux/device.h>
- #define GLOBALFIFO_SIZE 0x1000
- #define FIFO_CLEAR 0x1
- static int globalfifo_major;
- static struct class *cdev_class;
- DEFINE_SEMAPHORE(sem);
- DECLARE_WAIT_QUEUE_HEAD(r_wait);
- DECLARE_WAIT_QUEUE_HEAD(w_wait);
- struct globalfifo_dev
- {
- struct cdev cdev;
- unsigned int current_len;
- unsigned char mem[GLOBALFIFO_SIZE];
- };
- struct globalfifo_dev *globalfifo_devp;
- int globalfifo_open(struct inode *inode, struct file *filp)
- {
- filp->private_data = globalfifo_devp;
- return 0;
- }
- int globalfifo_release(struct inode *inode, struct file *filp)
- {
- return 0;
- }
- static long globalfifo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
- {
- struct globalfifo_dev *dev = filp->private_data;
- switch (cmd)
- {
- case FIFO_CLEAR:
- down(&sem);
- dev->current_len = 0;
- memset(dev->mem,0,GLOBALFIFO_SIZE);
- up(&sem);
- printk(KERN_INFO "globalfifo is set to zero\n");
- break;
- default:
- return - EINVAL;
- }
- return 0;
- }
- static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
- {
- int ret;
- struct globalfifo_dev *dev = filp->private_data;
- DECLARE_WAITQUEUE(wait, current);
- down(&sem);
- add_wait_queue(&r_wait, &wait);
- if (dev->current_len == 0)
- {
- if (filp->f_flags &O_NONBLOCK)
- {
- ret = - EAGAIN;
- goto out;
- }
- __set_current_state(TASK_INTERRUPTIBLE);
- up(&sem);
- schedule();
- if (signal_pending(current))
- {
- ret = - ERESTARTSYS;
- goto out2;
- }
- down(&sem);
- }
- if (count > dev->current_len)
- count = dev->current_len;
- if (copy_to_user(buf, dev->mem, count))
- {
- ret = - EFAULT;
- goto out;
- }
- else
- {
- memcpy(dev->mem, dev->mem + count, dev->current_len - count);
- dev->current_len -= count;
- printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
- wake_up_interruptible(&w_wait);
- ret = count;
- }
- out:
- up(&sem);
- out2:
- remove_wait_queue(&w_wait, &wait);
- set_current_state(TASK_RUNNING);
- return ret;
- }
- static ssize_t globalfifo_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
- {
- int ret;
- struct globalfifo_dev *dev = filp->private_data;
- DECLARE_WAITQUEUE(wait, current);
- down(&sem);
- add_wait_queue(&w_wait, &wait);
- if (dev->current_len == GLOBALFIFO_SIZE)
- {
- if (filp->f_flags &O_NONBLOCK)
- {
- ret = - EAGAIN;
- goto out;
- }
- __set_current_state(TASK_INTERRUPTIBLE);
- up(&sem);
- schedule();
- if (signal_pending(current))
- {
- ret = - ERESTARTSYS;
- goto out2;
- }
- down(&sem);
- }
- if (count > GLOBALFIFO_SIZE - dev->current_len)
- count = GLOBALFIFO_SIZE - dev->current_len;
- if (copy_from_user(dev->mem + dev->current_len, buf, count))
- {
- ret = - EFAULT;
- goto out;
- }
- else
- {
- dev->current_len += count;
- printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count, dev->current_len);
- wake_up_interruptible(&r_wait);
- ret = count;
- }
- out:
- up(&sem);
- out2:
- remove_wait_queue(&w_wait, &wait);
- set_current_state(TASK_RUNNING);
- return ret;
- }
- static const struct file_operations globalfifo_fops =
- {
- .owner = THIS_MODULE,
- .read = globalfifo_read,
- .write = globalfifo_write,
- .unlocked_ioctl = globalfifo_ioctl,
- .open = globalfifo_open,
- .release = globalfifo_release,
- };
- static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
- {
- int err, devno = MKDEV(globalfifo_major, index);
- cdev_init(&dev->cdev, &globalfifo_fops);
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &globalfifo_fops;
- err = cdev_add(&dev->cdev, devno, 1);
- if (err)
- printk(KERN_NOTICE "Error %d adding LED%d", err, index);
- }
- int globalfifo_init(void)
- {
- int ret;
- dev_t devno;
- ret = alloc_chrdev_region(&devno, 0, 1, "x4412-globalfifo");
- globalfifo_major = MAJOR(devno);
- if (ret < 0)
- return ret;
- globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
- if (!globalfifo_devp)
- {
- ret = - ENOMEM;
- goto fail_malloc;
- }
- memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
- globalfifo_setup_cdev(globalfifo_devp, 0);
- cdev_class = class_create(THIS_MODULE,"x4412-globalfifo");
- if(IS_ERR(cdev_class))
- goto fail_class;
- device_create(cdev_class,NULL,devno,NULL,"x4412-globalfifo");
- return 0;
- fail_class:
- class_destroy(cdev_class);
- fail_malloc:
- unregister_chrdev_region(devno, 1);
- return ret;
- }
- void globalfifo_exit(void)
- {
- device_destroy(cdev_class,MKDEV(globalfifo_major,0));
- class_destroy(cdev_class);
- cdev_del(&globalfifo_devp->cdev);
- kfree(globalfifo_devp);
- unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
- }
- module_init(globalfifo_init);
- module_exit(globalfifo_exit);
- MODULE_AUTHOR("www.9tripod.com");
- MODULE_LICENSE("Dual BSD/GPL");
复制代码 在读写函数以及IOCTL中使用了信号量,在最前面通过DEFINE_SEMAPHORE(sem)声明信号量后,再将指定的临界区添加down(&sem)和up(&sem)函数即可。 在程序开始定义并初始化了r_wait和w_wait两个等待队列头,分别用于阻塞读和阻塞写。在读函数globalfifo_read中,DECLARE_WAITQUEUE宏初始化一个名为wait的等待队列,然后再通过add_wait_queue函数将等待队列wait添加到等待队列头r_wait指向的等待队列列表中。 在程序中有一个关键的变量dev->current_len,它在globalfifo_init函数中已经通过memset函数初始化为0,它时刻记录着当前指向的内存偏移位置。每读count个值,它将会向左偏移count位,每写count个值,它将向右偏移count位。当dev->current_len为0时,表示内存已经清空,当dev->current_len等于GLOBALFIFO_SIZE时,表示内存已经写满。 再回到读函数,if条件语句判断dev->current_len是否为0,如果为0,表示FIFO内容为空,这时通过__set_current_state函数设置工作队列的状态为睡眠,然后通过schedule函数调度其他进程运行。一旦等待队列检测到被唤醒,表明FIFO已经有写的动作,将通过copy_to_user函数将写入的数据上报给应用。然后通过memcpy函数将FIFO数据前移,dev->current_len也随之向左移动相应位,读完之后再唤醒写等待队列,移除当前队列,并将进程设置为TASK_RUNNING。随后,程序交将给写等待队列。 在写等待队列中,if语句判断dev->current_len是否等于GLOBALFIFO_SIZE。如果相等,则表明FIFO已满,__set_current_state函数将写等待队列设置为睡眠,再通过schedule函数调度其他进程运行。一旦写等待队列被唤醒,将通过copy_from_user函数将数据写入FIFO,同时dev->current_len右移,写完之后再唤醒读等待队列,程序再交给读等待队列,如此反复循环。 编辑kernel/drivers/char/x4412/Kconfig文件,添加如下语句: - config X4412_GLOBALFIFO_DRIVER
- tristate "x4412 globalfifo driver"
- default m
- help
- compile for x4412 globalfifo driver,y for kernel,m for module.
复制代码 编辑kernel/drivers/char/x4412/Makefile文件,添加如下语句: - obj-$(CONFIG_X4412_GLOBALFIFO_DRIVER) += x4412-globalfifo.o
复制代码 编译内核,在kernel/drivers/char/x4412目录下将会生成目标文件x4412-globalfifo.ko。加载该驱动模块,使用命令行测试,效果如下: - [root@x4412 mnt]# insmod x4412-globalfifo.ko
- [root@x4412 mnt]# mdev -s
- [root@x4412 mnt]# cat /dev/x4412-globalfifo &
- [1] 1205
- [root@x4412 mnt]# echo 'www.9tripod.com' > /dev/x4412-globalfifo
- [ 113.292857] written 16 bytes(s),current_len:16
- [ 113.296925] read 16 bytes(s),current_len:0
- www.9tripod.com
- [root@x4412 mnt]# echo 'www.bbs.9tripod.com' > /dev/x4412-globalfifo
- [ 145.165674] written 14 bytes(s),current_len:14
- [ 145.168950] read 14 bytes(s),current_len:0
- www.bbs.9tripod.com
- [root@x4412 mnt]#
复制代码 可见,每当echo进程向/dev/x4412-globalfifo写入一串数据,cat进程就立即将这串数据显示出来。 可直接在x4412开发板&ibox卡片电脑上运行的驱动模块:
x4412-globalfifo.ko
(5.56 KB, 下载次数: 4)
|