阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再
进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
阻塞,默认的形式简单直接效率低
非阻塞,相反,占用资源比较多
阻塞从字面上听起来似乎意味着低效率,实则不然,如果设备驱动不阻塞,则用户想获取设
备资源只能不停地查询,这反而会无谓地耗费 CPU 资源。而阻塞访问时,不能获取资源的进程将
进入
休眠,它将 CPU 资源“礼让”给其他进程。
介绍:以队列为基础数据结构,与进程调度机制紧密结合,能够用于
实现内核中的异步事件通知机制
,也可以用来
同步对系统资源的访问
。
注意
:虽然说的是队列,但不是 fifo,
没有 fifo 的特性
1. 等待队列: -- wait queue
在 Linux 驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问,信号量在内核中依赖等待队列来实现。
定义“等待队列头”
wait_queue_head_t my_queue;
初始化“等待队列头”
init_waitqueue_head(&my_queue);
而下面的 DECLARE_WAIT_QUEUE_HEAD()宏可以作为
定义并初始化等待队列头的“快捷方式”。
DECLARE_WAIT_QUEUE_HEAD (name)
定义等待队列
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(wait_queue_head_t queue, condition) --> 深睡,不可以被信号打断wait_event_interruptible(wait_queue_head_t queue, condition) --> 浅睡,可以被信号打断wait_event_timeout(wait_queue_head_t queue, condition, timeout)wait_event_interruptible_timeout(wait_queue_head_t queue, condition, timeout)
queue,作为等待队列头的等待队列被唤醒condition,条件,满足 唤醒,否则 阻塞timeout,阻塞等待的超时时间,单位是 jiffy,等待时间 timeout 后,无论条件满足不满足,都返回
唤醒队列
void wake_up(wait_queue_head_t *queue);void wake_up_interruptible(wait_queue_head_t *queue);
唤醒时会判断 condition
①wake_up() -- wait_event() / wait_event_timeout()
②wake_up_interruptible() -- wait_event_interruptible() / wait_event_interruptible_timeout()
①可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 的进程
②可以唤醒处于 TASK_INTERRUPTIBLE 的进程
在等待队列上睡眠:
sleep_on(wait_queue_head_t *q);interruptible_sleep_on(wait_queue_head_t *q);
sleep_on 将目前进程的状态置成 TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头 q,直到资源可获得,q 引导的等待队列被唤醒。
wake_up_interruptible将目前进程的状态置成 TASK_ INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒或者进程收到信号。
sleep_on()函数应该与 wake_up()成对使用,interruptible_sleep_on()应该wake_up_interruptible()
成对使用。
注意:
在许多设备驱动中,并不调用 sleep_on()或 interruptible_sleep_on(),而是亲自进行进程的状态改变和切换
2. 例子
为了让 驱动支持 阻塞和非阻塞,需要在驱动中使用等待队列:
waitqueue.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/semaphore.h>
#include <linux/device.h>
MODULE_LICENSE ("GPL");
int hello_major = 250;
int hello_minor = 0;
int number_of_devices = 1;
struct class *my_class;
struct hello_device
{
char data[128];
int len;
wait_queue_head_t rq, wq;
struct semaphore sem;
struct cdev cdev;
} hello_device;
static int hello_open (struct inode *inode, struct file *filp)
{
filp->private_data = container_of(inode->i_cdev, struct hello_device, cdev);
printk (KERN_INFO "Hey! device opened\n");
printk (KERN_INFO "len = %d\n",(*((int *)filp->private_data)));
return 0;
}
static int hello_release (struct inode *inode, struct file *filp)
{
printk (KERN_INFO "Hmmm... device closed\n");
return 0;
}
ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
{
ssize_t result = 0;
struct hello_device *dev = filp->private_data;
down(&dev->sem);
while (hello_device.len == 0)
{
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if (wait_event_interruptible(dev->rq, (dev->len != 0))) return -ERESTARTSYS;
down(&dev->sem);
}
if (count > dev->len) count = dev->len;
if (copy_to_user (buff, dev->data, count))
{
result = -EFAULT;
}
else
{
printk (KERN_INFO "read %d bytes\n", (int)count);
dev->len -= count;
result = count;
memcpy(dev->data, dev->data+count, dev->len);
}
up(&dev->sem);
wake_up(&dev->wq);
return result;
}
ssize_t hello_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
ssize_t ret = 0;
struct hello_device *dev = filp->private_data;
if (count > 128) return -ENOMEM;
down(&dev->sem);
while (dev->len == 128)
{
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if (wait_event_interruptible(dev->wq, (dev->len != 128))) return -ERESTARTSYS;
down(&dev->sem);
}
if (count > (128 - dev->len)) count = 128 - dev->len;
if (copy_from_user (dev->data+dev->len, buf, count)) {
ret = -EFAULT;
}
else {
printk (KERN_INFO "write %d bytes\n", (int)count);
dev->len += count;
ret = count;
}
up(&dev->sem);
wake_up(&dev->rq);
return ret;
}
struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write
};
static void char_reg_setup_cdev (void)
{
int error;
dev_t devno;
devno = MKDEV (hello_major, hello_minor);
cdev_init (&hello_device.cdev, &hello_fops);
hello_device.cdev.owner = THIS_MODULE;
error = cdev_add (&hello_device.cdev, devno , 1);
if (error)
printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
}
static int char_dev_create (void)
{
my_class = class_create(THIS_MODULE,"waitqueue_class");
if(IS_ERR(my_class))
{
printk("Err: failed in creating class.\n");
return -1;
}
device_create(my_class, NULL, MKDEV (hello_major, hello_minor), NULL, "waitqueue");
return 0;
}
static int __init hello_2_init (void)
{
int result;
dev_t devno;
devno = MKDEV (hello_major, hello_minor);
result = register_chrdev_region (devno, number_of_devices, "waitqueue");
if (result < 0) {
printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
goto err1;
}
char_dev_create();
char_reg_setup_cdev ();
init_waitqueue_head(&hello_device.rq);
init_waitqueue_head(&hello_device.wq);
sema_init(&hello_device.sem, 1);
memset(hello_device.data, 0, 128);
hello_device.len = 0;
printk (KERN_INFO "char device registered\n");
return 0;
err1:
device_destroy(my_class, devno);
class_destroy(my_class);
unregister_chrdev_region(devno, 1);
return result;
}
static void __exit hello_2_exit (void)
{
dev_t devno = MKDEV (hello_major, hello_minor);
cdev_del (&hello_device.cdev);
device_destroy(my_class, devno); //delete device node under /dev//必须先删除设备,再删除class类
class_destroy(my_class); //delete class created by us
unregister_chrdev_region (devno, number_of_devices);
printk("waitqueue module exit \n");
return;
}
module_init (hello_2_init);
module_exit (hello_2_exit);
test_write.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define N 90
int main()
{
int i, fd;
char buf[N];
for (i=0; i<90; i++)
{
buf[i] = i + 33;
}
if ((fd = open("/dev/waitqueue", O_WRONLY)) < 0)
{
perror("fail to open");
}
printf("wrote %d bytes\n", (int)write(fd, buf, N));
close(fd);
return 0;
}
test_read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define N 90
int main()
{
int i, fd;
char buf[N] = {0};
int num = 0;
if ((fd = open("/dev/waitqueue", O_RDWR)) < 0)
{
perror("fail to open");
return -1;
}
puts("open is ok");
if((num = read(fd, buf, 10)) < 0)
{
printf("num = %d \n", num);
perror("read:");
}
printf("read num = %d \n", num);
printf("Is:");
puts(buf);
close(fd);
return 0;
}
Makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
#KERNELDIR ?= ~/wor_lip/linux-3.4.112
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules* Module*
.PHONY: modules modules_install clean
else
obj-m := waitqueue.o
endif
程序思路:
①在驱动中的读和写的方法中分别判断 filp->f_flags 是不是 O_NONBLOCK ,如果标志是非阻塞,就马上返回,②在读和写的方法中还要加上信号量实现 PV 操作,防止多个函数读的时候出现混乱的情况③在条件不满足(读的时候,缓冲区中内容长度 = 0.写的时候缓冲区长度 = 128)调用相应的读写等待队列
程序的功能:
写函数一次写 90 个字符,读函数每次只读 10 个字符,
如果读了很多次,读完了 buf 中的内容,就会阻塞的等在哪里,打开另外的客户端,进行写操作,读客户端在写完的刹那,能够读出数据
在有 A 客户端读完了,并且处于阻塞状态,再开一个 B 客户端依然读阻塞,在 C 客户端进行写的时候,写完后,A 客户端会先得到数据, B 再得到数据