linux驱动之阻塞与非阻塞I/O

本期主题:
通过例子讲解linux驱动中的阻塞与非阻塞I/O,先讲阻塞/非阻塞的含义
再展示代码,阻塞I/O例子使用的是wait_queue(等待队列),非阻塞I/O例子使用的是select、poll(I/O多路复用)


往期链接:

  • linux设备驱动中的并发
  • linux设备驱动中的编译乱序和执行乱序
  • linux设备驱动之内核模块
  • linux字符驱动
  • linux字符驱动之ioctl部分
  • linux字符驱动之read、write部分
  • linux驱动调试之Debugfs
  • Linux下如何操作寄存器(用户空间、内核空间方法讲解)
  • petalinux的module编译
  • 实例讲解,一文弄懂workqueue和waitqueue

目录

  • 1.阻塞、非阻塞 I/O含义
  • 2. 阻塞I/O例子
    • 2.1 Linux中的阻塞——等待队列(waitqueue)
    • 2.2 example
      • 1. 方案设计:
      • 2. 代码:
      • 3. 测试结果:
  • 3. 非阻塞例子
    • 1.轮询操作
    • 2.轮询example
      • 1. 方案设计:
      • 2. 代码:
      • 3. 测试结果
  • 4. 总结


1.阻塞、非阻塞 I/O含义

  1. 阻塞和非阻塞I/O是设备访问的两种不同模式,驱动程序可以灵活的支持这两种用户空间对设备的访问;
  2. 阻塞操作是在执行设备操作时,如果不能获得设备资源,则挂起进程直到满足可操作条件后再执行,被挂起的设备进入睡眠状态
  3. 非阻塞操作的进程,如果不能进行设备操作时,并不会挂起,它会要么放弃,要么不停查询,直到可以进行操作;

如下图所示,展示了阻塞、非阻塞I/O模型:
linux驱动之阻塞与非阻塞I/O_第1张图片

2. 阻塞I/O例子

2.1 Linux中的阻塞——等待队列(waitqueue)

linux驱动中经常使用等待队列来实现阻塞I/O,这里不详细解释等待队列,在原来的文章中已经讲过了,可以参考这篇文章, 实例讲解,一文弄懂workqueue和waitqueue

2.2 example

1. 方案设计:

编写一个读写使用等待队列的驱动,实现阻塞I/O场景:
当驱动的fifo为空时,此时去读会卡住等待写操作,即非空的时候才能去读
当驱动的fifo为满时,此时去写会卡住等待读操作,即非满的时候才能去写

linux驱动之阻塞与非阻塞I/O_第2张图片

2. 代码:

linux module driver的代码:

/* fifo_io_block.c */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_FIFO_SIZE   32
#define FIFO_MAJOR      230
struct fifo_dev *g_fifo_dev;
static dev_t devno = MKDEV(FIFO_MAJOR, 0);

struct fifo_dev {
    struct cdev cdev;
    unsigned int cur_len;
    unsigned char mem[MAX_FIFO_SIZE];
    struct mutex mutex;
    wait_queue_head_t r_wait;
    wait_queue_head_t w_wait;
};

static ssize_t fifo_read(struct file *filp, char __user *buf, size_t count,
			             loff_t *ppos)
{
    int ret;
    struct fifo_dev *dev = g_fifo_dev;

    mutex_lock(&dev->mutex);

    /* when fifo empty, need wakeup by write ops */
    while (dev->cur_len == 0) {
        /* if nonblock, return */
        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            goto out;
        }
        mutex_unlock(&dev->mutex);
        
        printk(KERN_NOTICE "fifo is empty, waiting for fifo write!\n");
        wait_event_interruptible(dev->r_wait, dev->cur_len != 0);
    }

    /*TODO: here need notice !!!! */
    printk(KERN_NOTICE "read bytes: %ld\n", count);

    if (count > dev->cur_len)
        count = dev->cur_len;

    if (copy_to_user(buf, dev->mem, count)) {
		ret = -EFAULT;
		printk(KERN_ALERT "copy to user failed!\n");
        goto out;
    } else {
        /* read out some buffer, so here delete cur_len */
        memcpy(dev->mem, dev->mem + count, dev->cur_len - count);
        dev->cur_len -= count;
        printk(KERN_NOTICE "read %ld bytes, cur_len: %d\n", count, dev->cur_len);
        wake_up_interruptible(&dev->w_wait);

        ret = count;
    }

out:
    mutex_unlock(&dev->mutex);

	return ret;
}

static ssize_t fifo_write(struct file *filp, const char __user *buf, size_t count,
			              loff_t *ppos)
{
    int ret;
    struct fifo_dev *dev = g_fifo_dev;

    mutex_lock(&dev->mutex);

    while (dev->cur_len == MAX_FIFO_SIZE) {
        /* if nonblock, return */
        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            goto out;
        }
        mutex_unlock(&dev->mutex);
        
        printk(KERN_NOTICE "fifo is full, waiting for fifo read!\n");
        wait_event_interruptible(dev->w_wait, dev->cur_len != MAX_FIFO_SIZE);
    }

    if (count > (MAX_FIFO_SIZE - dev->cur_len)) {
        count = MAX_FIFO_SIZE - dev->cur_len;
    }

	if (copy_from_user(dev->mem + dev->cur_len, buf, count)) {
		ret = -EFAULT;
		printk(KERN_ALERT "fifo_write failed!\n");
        goto out;
	} else {
		dev->cur_len += count;
		ret = count;

		printk(KERN_NOTICE "write %ld bytes to KERNEL!\n", count);
        wake_up_interruptible(&dev->r_wait);
	}

out:
    mutex_unlock(&dev->mutex);

    return ret;
}

static const struct file_operations fifo_fops = {
    .read = fifo_read,
    .write = fifo_write,
};

static int fifo_dev_setup(struct fifo_dev *dev)
{
    int ret;
    
    cdev_init(&dev->cdev, &fifo_fops);

    ret = cdev_add(&dev->cdev, devno, 1);
    if (ret) {
        printk(KERN_ALERT "cdev_add failed!\n");
        return ret;
    }

    /* mutex and waitqueue init */
    mutex_init(&dev->mutex);
    init_waitqueue_head(&dev->r_wait);
    init_waitqueue_head(&dev->w_wait);

    return 0;
}

static int fifo_dev_init(void)
{
    int ret;
    struct fifo_dev *dev = kzalloc(sizeof(struct fifo_dev), GFP_KERNEL);

    if (!dev) {
        printk(KERN_ALERT "No mem, alloc dev failed!\n");
        return -ENOMEM;
    }

    ret = register_chrdev_region(devno, 1, "block_fifo");
    if (ret) {
        printk(KERN_ALERT "register_chrdev_region failed!\n");
        return ret;
    }

    ret = fifo_dev_setup(dev);
    if (ret) {
        printk(KERN_ALERT "fifo_dev_setup failed!\n");
        return ret;
    }

    g_fifo_dev = dev;

    printk(KERN_NOTICE "fifo dev init success!\n");

    return 0;
}

static void fifo_dev_exit(void)
{
    cdev_del(&g_fifo_dev->cdev);
    kfree(g_fifo_dev);
    unregister_chrdev_region(devno, 1);

    printk(KERN_NOTICE "fifo dev exit success!\n");
}

module_init(fifo_dev_init);
module_exit(fifo_dev_exit);
MODULE_LICENSE("GPL");

编译的makefile

# Makefile
ifneq ($(KERNELRELEASE),)

obj-m:=fifo_io_block.o

else

KDIR :=/lib/modules/$(shell uname -r)/build

PWD  :=$(shell pwd)

all:

	make -C $(KDIR) M=$(PWD) modules

clean:

	rm -f *.ko *.o *.mod.o *.symvers *.cmd  *.mod.c *.order

endif

3. 测试结果:

操作流程:

  1. 先demsg -C清空掉内核Log,然后dmesg -w 监测内核log;
  2. insmod 对应ko,并使用mknod创建设备节点(创建设备节点的讲解在:linux字符驱动);
  3. 启动两个进程,一个启动cat 在后台跑,另一个使用echo写设备,就能看到对应效果;

linux驱动之阻塞与非阻塞I/O_第3张图片

3. 非阻塞例子

1.轮询操作

在用户程序中,常常会使用 select()和poll()来对设备进行非阻塞访问,这两个系统调用可以对设备进行无阻塞的访问。
应用程序中,最广泛用到的是select()系统调用,其原型是:

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

函数功能:
select函数可以监视一些文件描述符,直到有文件描述符fd符合文件I/O(可读、可写)的条件,参考下图

参数详解:

  • nfds是需要检查的最高的fd加1
  • readfds、writefds、exceptfds 分别是被select()监视的读、写和异常处理的文件描述符集合
  • timeout是超时时间的设置

select函数的原理:
linux驱动之阻塞与非阻塞I/O_第4张图片
文件描述符集合的操作方式:

  • 清除一个文件描述符集合:
FD_ZERO(fd_set *set)
  • 将一个文件描述符加入到文件描述符集合中:
FD_SET(int fd, fd_set *set)
  • 将一个文件描述符从文件描述符集合中删除:
FD_CLR(int fd, fd_set *set)
  • 判断文件描述符是否被置位
FD_ISSET(int fd, fd_set *set)

2.轮询example

1. 方案设计:

在驱动代码中设计一个poll()函数,对应着用户层的select()函数,Poll()函数返回对应的mask,通过mask用户层知道当前fifo的状态。
其中驱动中的poll()函数使用到了poll_wait接口,poll_wait是将当前进程添加到wait参数指定的等待队列中(poll_table)中,实际作用是让唤醒参数queue对应的等待队列可以唤醒因select()而睡眠的进程
注意:poll_wait()函数本身并不阻塞,但是select()系统调用有可能会阻塞。

/* 设备驱动中 poll() 函数原型 */
unsigned int (*poll)(struct file *flip, struct poll_table *wait)

/* poll_wait()函数原型 */
void poll_wait(struct file *flip, wait_queue_t *queue, poll_table *wait)

下面是测试FD是否可读的一个方案示意图:
当用户程序调用select时,调用到驱动的poll函数,poll_wait就是将进程添加到等待队列中,如果没有满足条件(可读)的FD,select会阻塞卡住,直到写了/dev/fifo_test,才能够通过r_wait唤醒selecet的休眠进程,返回FD
linux驱动之阻塞与非阻塞I/O_第5张图片

2. 代码:

驱动代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_FIFO_SIZE   32
#define FIFO_MAJOR      230
struct fifo_dev *g_fifo_dev;
static dev_t devno = MKDEV(FIFO_MAJOR, 0);

struct fifo_dev {
    struct cdev cdev;
    unsigned int cur_len;
    unsigned char mem[MAX_FIFO_SIZE];
    struct mutex mutex;
    wait_queue_head_t r_wait;
    wait_queue_head_t w_wait;
};

static int fifo_open(struct inode *inode, struct file *filp)
{
    printk(KERN_NOTICE "fifo dev open ok\n");
    return 0;
}

static ssize_t fifo_read(struct file *filp, char __user *buf, size_t count,
			             loff_t *ppos)
{
    int ret;
    struct fifo_dev *dev = g_fifo_dev;

    mutex_lock(&dev->mutex);

    /* when fifo empty, need wakeup by write ops */
    while (dev->cur_len == 0) {
        /* if nonblock, return */
        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            goto out;
        }
        mutex_unlock(&dev->mutex);
        
        printk(KERN_NOTICE "fifo is empty, waiting for fifo write!\n");
        wait_event_interruptible(dev->r_wait, dev->cur_len != 0);
    }

    /*TDOO: here need notice !!!! */
    printk(KERN_NOTICE "read bytes: %ld\n", count);

    if (count > dev->cur_len)
        count = dev->cur_len;

    if (copy_to_user(buf, dev->mem, count)) {
		ret = -EFAULT;
		printk(KERN_ALERT "copy to user failed!\n");
        goto out;
    } else {
        /* read out some buffer, so here delete cur_len */
        memcpy(dev->mem, dev->mem + count, dev->cur_len - count);
        dev->cur_len -= count;
        printk(KERN_NOTICE "read %ld bytes, cur_len: %d\n", count, dev->cur_len);
        wake_up_interruptible(&dev->w_wait);

        ret = count;
    }

out:
    mutex_unlock(&dev->mutex);

	return ret;
}

static ssize_t fifo_write(struct file *filp, const char __user *buf, size_t count,
			              loff_t *ppos)
{
    int ret;
    struct fifo_dev *dev = g_fifo_dev;

    mutex_lock(&dev->mutex);

    while (dev->cur_len == MAX_FIFO_SIZE) {
        /* if nonblock, return */
        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            goto out;
        }
        mutex_unlock(&dev->mutex);
        
        printk(KERN_NOTICE "fifo is full, waiting for fifo read!\n");
        wait_event_interruptible(dev->w_wait, dev->cur_len != MAX_FIFO_SIZE);
    }

    if (count > (MAX_FIFO_SIZE - dev->cur_len)) {
        count = MAX_FIFO_SIZE - dev->cur_len;
    }

	if (copy_from_user(dev->mem + dev->cur_len, buf, count)) {
		ret = -EFAULT;
		printk(KERN_ALERT "fifo_write failed!\n");
        goto out;
	} else {
		dev->cur_len += count;
		ret = count;

		printk(KERN_NOTICE "write %ld bytes to KERNEL!\n", count);
        wake_up_interruptible(&dev->r_wait);
	}

out:
    mutex_unlock(&dev->mutex);

    return ret;
}

static unsigned int fifo_poll(struct file *filp, poll_table *wait)
{
    unsigned int mask = 0;
    struct fifo_dev *dev = g_fifo_dev;

    mutex_lock(&dev->mutex);

    poll_wait(filp, &dev->r_wait, wait);
    poll_wait(filp, &dev->w_wait, wait);

    if (dev->cur_len != 0) {
        printk(KERN_NOTICE "mask pollin, cur_len %d\n", dev->cur_len);
        mask |= POLLIN | POLLRDNORM;
    }

    if (dev->cur_len != MAX_FIFO_SIZE) {
        printk(KERN_NOTICE "mask pollout, cur_len %d\n", dev->cur_len);
        mask |= POLLOUT | POLLWRNORM;
    }

    mutex_unlock(&dev->mutex);
    printk(KERN_NOTICE "mask 0x%x\n", mask);
    
    return mask;
}

static const struct file_operations fifo_fops = {
    .open  = fifo_open,
    .read = fifo_read,
    .write = fifo_write,
    .poll = fifo_poll,
};

static int fifo_dev_setup(struct fifo_dev *dev)
{
    int ret;
    
    cdev_init(&dev->cdev, &fifo_fops);

    ret = cdev_add(&dev->cdev, devno, 1);
    if (ret) {
        printk(KERN_ALERT "cdev_add failed!\n");
        return ret;
    }

    /* mutex and waitqueue init */
    mutex_init(&dev->mutex);
    init_waitqueue_head(&dev->r_wait);
    init_waitqueue_head(&dev->w_wait);

    return 0;
}

static int fifo_dev_init(void)
{
    int ret;
    struct fifo_dev *dev = kzalloc(sizeof(struct fifo_dev), GFP_KERNEL);

    if (!dev) {
        printk(KERN_ALERT "No mem, alloc dev failed!\n");
        return -ENOMEM;
    }

    ret = register_chrdev_region(devno, 1, "fifo_test");
    if (ret) {
        printk(KERN_ALERT "register_chrdev_region failed!\n");
        return ret;
    }

    ret = fifo_dev_setup(dev);
    if (ret) {
        printk(KERN_ALERT "fifo_dev_setup failed!\n");
        return ret;
    }

    g_fifo_dev = dev;

    printk(KERN_NOTICE "fifo dev init success!\n");

    return 0;
}

static void fifo_dev_exit(void)
{
    cdev_del(&g_fifo_dev->cdev);
    kfree(g_fifo_dev);
    unregister_chrdev_region(devno, 1);

    printk(KERN_NOTICE "fifo dev exit success!\n");
}

module_init(fifo_dev_init);
module_exit(fifo_dev_exit);
MODULE_LICENSE("GPL");

应用代码:

#include 
#include 
#include 
#include 
#include 
#include 

void main(void)
{
    int fd, num;
    fd_set rfds, wfds;

    fd = open("/dev/fifo_test", O_RDWR | O_NONBLOCK);
    if (fd != -1) {
        while (1) {
            FD_ZERO(&rfds);
            FD_ZERO(&wfds);
            FD_SET(fd, &rfds);
            FD_SET(fd, &wfds);

            select(fd + 1, &rfds, &wfds, NULL, NULL);
            if (FD_ISSET(fd, &rfds)) {
                printf("poll monitor, fifo can be read!\n");
            }
            if (FD_ISSET(fd, &wfds)) {
                printf("poll monitor, fifo can be write!\n");
            }

            /* avoid too much log */
            sleep(1);
        }
    } else {
        printf("Device open failed!\n");
    }
}

3. 测试结果

操作流程:

$ sudo insmod fifo_io_block.ko 
$ gcc usr_noblock_fifo.c 
$ sudo mknod /dev/fifo_test c 230 0 
$ sudo ./a.out 
# 一开始一直都是can be write,直到使用echo 往/dev/fifo_test写东西之后,才会打印can be read
poll monitor, fifo can be write!
...
poll monitor, fifo can be write!

poll monitor, fifo can be read!
poll monitor, fifo can be write!
poll monitor, fifo can be read!
poll monitor, fifo can be write!
poll monitor, fifo can be read!
...

另一个terminal使用echo写数据

# 需要先切到root再使用echo
$ sudo su
$ echo "test" > /dev/fifo_test

Log:

$ dmesg -w
[  874.839154] fifo_io_block: loading out-of-tree module taints kernel.
[  874.839208] fifo_io_block: module verification failed: signature and/or required key missing - tainting kernel
[  874.839528] fifo dev init success!
[  933.146113] fifo dev open ok
[  933.146117] mask pollout, cur_len 0
[  933.146118] mask 0x104
[  934.146569] mask pollout, cur_len 0
[  934.146571] mask 0x104
...
# 使用echo之后
[  972.642956] fifo dev open ok
[  972.642972] write 5 bytes to KERNEL!
[  973.161913] mask pollin, cur_len 5
[  973.161928] mask pollout, cur_len 5
[  973.161930] mask 0x145
...

4. 总结

  1. 在设备驱动中,阻塞I/O一般使用等待队列或者基于等待队列的其他API来实现,等待队列用于同步驱动中事件发生的先后顺序;
  2. 使用非阻塞I/O的应用程序可以借助轮询函数来查询设备是否能够被立即访问,用户空间调用select()、poll()、epoll()接口,设备提供poll()函数。设备驱动中的poll()函数本身不会阻塞,但是select()、poll()、epoll()等系统调用会阻塞等待最少一个文件描述符集合可访问或超时。

你可能感兴趣的:(linux设备驱动开发,linux,java,数据库)