天天看点

IO 的阻塞和非阻塞一:等待队列1. 等待队列: -- wait queue2. 例子

阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再 进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

阻塞,默认的形式简单直接效率低 非阻塞,相反,占用资源比较多

阻塞从字面上听起来似乎意味着低效率,实则不然,如果设备驱动不阻塞,则用户想获取设 备资源只能不停地查询,这反而会无谓地耗费 CPU 资源。而阻塞访问时,不能获取资源的进程将 进入 休眠,它将 CPU 资源“礼让”给其他进程。

IO 的阻塞用的是 等待队列 非阻塞用的是 轮询

介绍:以队列为基础数据结构,与进程调度机制紧密结合,能够用于 实现内核中的异步事件通知机制 ,也可以用来 同步对系统资源的访问 。 注意 :虽然说的是队列,但不是 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 再得到数据