本期主题:
通过例子讲解linux驱动中的阻塞与非阻塞I/O,先讲阻塞/非阻塞的含义
再展示代码,阻塞I/O例子使用的是wait_queue(等待队列),非阻塞I/O例子使用的是select、poll(I/O多路复用)
往期链接:
如果不能获得设备资源,则挂起进程直到满足可操作条件后再执行
,被挂起的设备进入睡眠状态;linux驱动中经常使用等待队列来实现阻塞I/O,这里不详细解释等待队列,在原来的文章中已经讲过了,可以参考这篇文章, 实例讲解,一文弄懂workqueue和waitqueue
编写一个读写使用等待队列的驱动,实现阻塞I/O场景:
当驱动的fifo为空时,此时去读会卡住等待写操作,即非空的时候才能去读
当驱动的fifo为满时,此时去写会卡住等待读操作,即非满的时候才能去写
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
操作流程:
- 先demsg -C清空掉内核Log,然后dmesg -w 监测内核log;
- insmod 对应ko,并使用mknod创建设备节点(创建设备节点的讲解在:linux字符驱动);
- 启动两个进程,一个启动cat 在后台跑,另一个使用echo写设备,就能看到对应效果;
在用户程序中,常常会使用 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是超时时间的设置
- 清除一个文件描述符集合:
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)
在驱动代码中设计一个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
驱动代码:
#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");
}
}
操作流程:
$ 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
...