T12 阻塞与非阻塞IO

1 阻塞与非阻塞

阻塞I/O与非阻塞I/O是设备访问的两种不一样模式,驱动程序能够灵活的支持用户空间对设备的这两种访问方式。node

  • 阻塞操做:指的是执行设备操做的时候若是不能得到资源,那么就挂起进程直到知足操做条件以后再进行操做。被挂起的进程进而进入休眠状态,被调度器移走,直到可以获取资源后才继续执行
  • 非阻塞操做:进程在不能进行设备操做的时候并不会挂起,它可能直接返回或者放弃,或者不断地查询,直到能够进行操做。非阻塞应用程序一般使用select系统调用查询是否能够对设备进行无阻塞的访问最终会引起设备驱动中poll函数执行
  • 注意:阻塞与非阻塞是设备访问的两种方式,在写阻塞与非阻塞驱动的时候,常常用到等待队列

2 等待队列

  • 在linux设备驱动中阻塞进程可使用等待队列来实现。
  • 在内核中,等待队列是有不少用处的,尤为是在中断处理进程同步定时等场合,可使用等待队列实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,可以用于实现内核中的异步事件通知机制,同步对系统资源的访问

2.1 等待队列API

  • 在linux中等待队列的结构以下:
struct __wait_queue_head {
    spinlock_t lock;			//自旋锁,用来对task_list链表起保护做用,实现了等待队列的互斥访问
    struct list_head task_list;	//用来存放等待的进程,
};
typedef struct __wait_queue_head wait_queue_head_t;
  • API
/* 定义等待队列 */
wait_queue_head_t wait;

/* 初始化等待队列 */
init_waitqueue_head(&wait);

/* 定义并初始化等待队列 */
#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

/* 添加等待队列,将等待队列元素wait添加到等待队列队头q所指向的等待队列链表中 */
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

/* 移除等待队列 */
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

/* 等待事件,在等待队列中睡眠直到condition为真 */
/* 注意:
	@queue:做为等待队列头的等待队列被唤醒
	@condition:必须为真,不然就阻塞
	@timeout:相较于condition,timeout有更高的优先级
*/
wait_event(wq, condition);
wait_evevt_timeout(wq, condition, timeout);
wait_event_interruptible(wq, condition);
wait_event_interruptible_timeout(wq, condition, timeout);

/* 睡眠,其中sleep_on做用是将目前进程的状态设置为TASK_UNINTERRUPTIBLE,直到资源可用,q引导的等待队列被唤醒;
		interruptible_sleep_on是将进程状态设置为TASK_INTERRUPTIBLE
*/
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);

/* 唤醒等待队列,可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程 */
#define wake_up(x)	__wake_up(x, TASK_NORMAL, 1, NULL)

/* 只能唤醒处于TASK_INTERRUPTIBLE状态的进程 */
#define wake_up_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

2.2 睡眠、阻塞、挂起解释

对线程的控制比如一个老板招募了一个雇工干活。linux

  • 挂起:比如老板对雇工说:“你睡觉去吧,等用得着你的时候我会去主动叫你,而后你接着干”
  • 睡眠:比如老板对雇工说:“你睡觉去吧,某时某刻以后来报道,而后接着干”
  • 阻塞:老板忽然发现雇工在睡觉,缘由是由于雇工的笤帚被偷了(无资源),而后你也没让雇工干其余的事情,他就只好睡觉。等雇工找到笤帚以后,他将继续干活

2.3 等待队列示例

  • 驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/wait.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    char *buffer;                           //my private memory
    int value;
    struct miscdevice *mdev;
    wait_queue_head_t queue_head;
    bool is_empty;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }

    if (demo->is_empty) {
        /* make this process sleep */
        wait_event_interruptible(demo->queue_head, !(demo->is_empty));
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }

    demo->is_empty = false;
    /* wake up this process */
    wake_up(&demo->queue_head);

    *pos += write_bytes;
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static struct miscdevice misc_struct = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &demo_operation,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);

    demo_dev->is_empty = true;
    init_waitqueue_head(&demo_dev->queue_head);
    

    demo_dev->mdev = &misc_struct; 
    ret = misc_register(demo_dev->mdev);
    if (ret < 0) {
        printk(KERN_ERR "failed to register misc\n");
        goto ERROR_MISC;
    }

    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_MISC:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    misc_deregister(demo_dev->mdev);

    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
  • 调试过程
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod wait_queue.ko 
[sudo] hq 的密码: 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev # 此处会阻塞直到有数据 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev 
Peng fei tan -


# 写入数据唤醒阻塞的进程
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "Peng fei tan" -> /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$

3 环形队列FIFO

3.1 老式设备驱动不足与改进

  • 不足:原来的驱动设备在驱动的类里面维护了一段缓存空间,大小为1024个字节,其中读取代码的数据以下,若是单次读写不超过BUF_SIZE大小个字节的数据,那么能够正常使用下述读写方法。假如单次读写数据量超过了BUF_SIZE大小个数据量的话将会致使数据没法正常地写入与读出,主要是由于偏移量的问题,即*pos += read_bytes;,当读取超过1024个字节数据的时候,偏移量就会大于1024,那么会致使数组越界,所以咱们可使用环形队列FIFO来储存数据,当偏移量pos大于1024的时候会从新从0开始偏移
#define BUF_SIZR           1024

struct demo_cdev {
    char *buffer;                           //my private memory
	...
};

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    /* determine whether user has finished reading data */
    /* 判断当前读取的数据量是否大于buffer所容纳的数据量 */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    /* 判断当前读取的数据量是否大于buffer中剩余的数据量 */
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}

static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
	
    /* 判断当前写入的数据量是否大于buffer所容纳的数据量 */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    /* 判断当前写入的数据量是否大于buffer所剩余的数据量 */
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    
    *pos += write_bytes;
    return write_bytes;
}
  • 改进:咱们能够添加2个指针和等待队列,一个读指针和一个写指针,刚开始读写指针均指向环形队列的队头,当读进程读取数据的时候若是发现队列中没有数据,那么读进程将阻塞;同理写进程向队列中写入数据的时候若是发现队列满了,那么写进程需阻塞
  • 假设咱们先写了4字节数据,那么写指针将会向前偏移4个单位,进而当读进程读取数据的时候能够用写指针减去读指针(此时写指针较读指针向前偏移了4个单位,即表示队列中有4字节数据可读),当读进程读取完4字节数据以后,读指针也须要偏移4个单位,那么此时读写指针将会重合,即表述队列中无数据可读

3.2 环形队列实现

  • 驱动代码
相关文章
相关标签/搜索