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

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


往期链接:



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

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

如下图所示,展示了阻塞、非阻塞I/O模型:
在这里插入图片描述

2. 阻塞I/O例子

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

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

2.2 example

1. 方案设计:

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

在这里插入图片描述

2. 代码:

linux module driver的代码:

/* fifo_io_block.c */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/cdev.h>
#include <linux/slab.h>

#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写设备,就能看到对应效果;

在这里插入图片描述

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函数的原理:
在这里插入图片描述
文件描述符集合的操作方式:

  • 清除一个文件描述符集合:
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
在这里插入图片描述

2. 代码:

驱动代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/poll.h>

#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 <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

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()等系统调用会阻塞等待最少一个文件描述符集合可访问或超时。

5. 同步/异步、阻塞/非阻塞 对比

同步(Synchronous)和异步(Asynchronous),以及阻塞(Blocking)和非阻塞(Non-blocking),是描述程序或系统中不同操作和通信方式的概念。

同步(Synchronous):指的是在执行一个操作时,调用者需要等待该操作完成才能继续执行后续的操作。也就是说,调用者需要在操作完成之后才能获取到结果或者继续执行下一步操作。
异步(Asynchronous):与同步相反,异步指的是在执行一个操作时,调用者不需要等待操作完成,而是可以继续执行后续的操作。被调用的操作通常会在后台或另一个线程中执行,执行完毕后会通知调用者或触发回调函数来处理结果。

阻塞(Blocking):阻塞是指调用一个操作时,如果该操作无法立即完成,调用者会被挂起(即阻塞),直到操作完成或者达到某个条件。在阻塞状态下,调用者不能执行其他操作。
非阻塞(Non-blocking):与阻塞相反,非阻塞指的是调用一个操作时,即使该操作无法立即完成,调用者也不会被挂起,而是可以立即返回,继续执行后续操作。调用者可以通过轮询或者回调等方式来检查操作是否完成,从而避免被阻塞。

总的来说,同步与异步描述的是调用者与被调用者之间的交互模式,而阻塞与非阻塞描述的是调用者在等待操作完成时的状态。在实际编程中,可以根据需求选择合适的模式来进行操作和通信,以提高系统的效率和性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值