阻塞和非阻塞I/O是设备访问的两种不同模式,驱动程序可以灵活的支持用户空间对设备的这两种访问方式
本例子讲述了这两者的区别 并实现I/O的等待队列机制, 并进行了用户空间的验证
基本概念:
1> 阻塞操作 是指 在执行设备操作时,若不能获得资源,则挂起进程直到满足操作条件后再进行操作。被挂起的进 程进入休眠, 被从调度器移走,直到条件满足。
2> 非阻塞操作 在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作。非阻塞应用 程序通常使 用select系统调用查询是否可以对设备进行无阻塞的访问最终会引发设备驱动中poll函数执行。
globalfifo.c代码如下:
/*
* a globalfifo driver as example of char device drivers
* This example is to introduce poll, blocking and non-blocking access
*
*The initial developer of the original code is Baohua Song
*<auther@linuxdriver.cn>. All Rights Reserved
*
* 1>只当FIFO 中有数据时(有进程把数据写到这个 FIFO而且没有被 读进程 读空)
* 读进程才能把数据读出,读出后数据 从 FIFO 中拿掉
* 2>只有当FIFO 非满时(即还有空间未被读写或满后被读进程读出了数据)
* 写进程才能往里面写数据,
* 这样 读唤醒写 写唤醒读
*/
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
//#include<asm/system.h>
#include <linux/slab.h>
#include<asm/uaccess.h>
#include<linux/poll.h>
#define GLOBALFIFO_SIZE 10
/*全局fifo最大10字节 不要太大 方便写满测试*/
#define FIFO_CLEAR 0X1
/*清0全局内存的长度*/
#define GLOBALFIFO_MAJOR 249
/*预设的globalfifo 的主设备号*/
static int globalfifo_major = GLOBALFIFO_MAJOR;
/*globalfifo设备结构体*/
/*
struct globalfifo_dev
{
struct cdev cdev; //cdev结构体
unsigned int current_len; //fifo有效数据长度
unsigned char mem[GLOBALFIFO_SIZE];
struct semaphore sem; //并发控制用的信号量
wait_queue_head_t r_wait; //阻塞读用的等待队列 内核双向循环链表 都可以为头
wait_queue_head_t w_wait; //阻塞写用的等待队列头
}; */
struct globalfifo_dev
{
struct cdev cdev;
unsigned int current_len;
unsigned char mem[GLOBALFIFO_SIZE];
struct semaphore sem;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
};
struct globalfifo_dev *globalfifo_devp; /*设备结构体指针*/
/*globalfifo读函数*/
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int ret;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
down(&dev->sem); /*获得信号量*/
add_wait_queue(&dev->r_wait, &wait); /*加入读等待队列头 到内核*/
/*等待FIFO非空*/
if(dev->current_len == 0)
{
if(filp->f_flags & O_NONBLOCK)
{ /*如果进程为 非阻塞打开 设备文件*/
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); /*改变进程状态为睡眠*/
up(&dev->sem); /*释放信号量*/
schedule(); /*调度其他进程执行*/
if(signal_pending(current))
{
/*如果是因为信号唤醒*/
ret = -ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
/*拷贝到用户空间*/
if(count > dev->current_len)
count = dev->current_len;
if(copy_to_user(buf, dev->mem, count))
{
ret = -EFAULT;
goto out;
}
else
{
memcpy(dev->mem, dev->mem + count, dev->current_len - count);/*数据前移*/
dev->current_len -= count; /*有效数据长度减少*/
printk(KERN_INFO"read %d bytes(s),current_len:%d\n",count, dev->current_len);
wake_up_interruptible(&dev->w_wait); /*唤醒写等待队列*/
ret = count;
}
out:
up(&dev->sem); /*释放信号量*/
out2:
remove_wait_queue(&dev->w_wait, &wait); /*从属的等待队列头移除*/
set_current_state(TASK_RUNNING);
return ret;
}
/*globalfifo 写操作*/
static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
struct globalfifo_dev *dev = filp->private_data;
int ret;
DECLARE_WAITQUEUE(wait, current); /*定义等待队列*/
down(&dev->sem); /*获得信号量*/
add_wait_queue(&dev->w_wait, &wait); /*进入写等待队列头*/
/*等待FIFO非满*/
// if(dev->current_len == GLOBALFIFO_SIZE)
if( dev->current_len == GLOBALFIFO_SIZE )
{
if(filp->f_flags & O_NONBLOCK)
{ /*如果进程非阻塞打开的文件*/
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); /*改变进程状态为睡眠*/
up(&dev->sem); /*释放信号量*/
schedule(); /*调度其他进程执行*/
if(signal_pending(current))
{
/*如果是因为信号唤醒*/
ret = -ERESTARTSYS;
goto out2;
}
down(&dev->sem); /*获得信号量*/
}
/*从用户空间拷贝数据到内核空间*/
if(count > GLOBALFIFO_SIZE - dev->current_len)
{
/*如果要拷贝的数据大于 剩余有效内存长度
*则 只拷贝最大 能装下的长度
*/
count = GLOBALFIFO_SIZE - dev->current_len;
}
if(copy_from_user(dev->mem + dev->current_len, buf, count))
{
ret = -EFAULT;
goto out;
}
else
{
dev->current_len += count;
printk(KERN_INFO"written %d bytes(s), current_len: %d\n",count, dev->current_len);
wake_up_interruptible(&dev->r_wait); /*唤醒读等待队列*/
ret = count;
}
out:
up(&dev->sem); /*释放信号量*/
out2:
remove_wait_queue(&dev->w_wait, &wait); /*从附属的等待队列头移除*/
set_current_state(TASK_RUNNING);
return ret;
}
/*ioctl 设备控制函数*/
static long globalfifo_ioctl(/*struct inode *inodep,*/struct file *filp, unsigned int cmd, unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch(cmd)
{
case FIFO_CLEAR:
down(&dev->sem); /*获得信号量*/
dev->current_len = 0;
memset(dev->mem,0,GLOBALFIFO_SIZE);
up(&dev->sem); /*释放信号量*/
printk(KERN_INFO"globalfifo is set to zero");
break;
default:
return -EINVAL;
}
return 0;
}
/*在驱动中的增加轮询操作*/
static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data;/*获得设备结构体指针*/
down(&dev->sem);
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);
/*fifo非空*/
if(dev->current_len != 0)
{
mask |= POLLIN | POLLRDNORM; /*标示数据可以获得*/
}
/*fifo 非满*/
if(dev->current_len != GLOBALFIFO_SIZE)
{
mask |= POLLOUT | POLLWRNORM ; /*标示数据可以写入*/
}
up(&dev->sem);
return mask; /*返回驱动是否可读 或可写的 状态*/
}
/*文件打开函数*/
int globalfifo_open(struct inode *inode, struct file *filp)
{
/*让设备结构体作为设备的私有信息*/
filp->private_data = globalfifo_devp;
return 0;
}
/*文件释放函数*/
int globalfifo_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*文件操作结构体*/
static const struct file_operations globalfifo_fops = {
.owner = THIS_MODULE,
.read = globalfifo_read,
.write = globalfifo_write,
.unlocked_ioctl = globalfifo_ioctl,
.poll = globalfifo_poll,
.open = globalfifo_open,
.release = globalfifo_release,
};
/*初始化并注册cdev*/
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
int err, devno = MKDEV(globalfifo_major, index);
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if(err)
printk(KERN_NOTICE "Error %d adding LED %d", err, index);
}
/*设备驱动模块加载函数*/
int globalfifo_init(void)
{
int ret;
dev_t devno = MKDEV(globalfifo_major, 0);
/*申请设备号*/
if(globalfifo_major)
ret = register_chrdev_region(devno, 1, "globalfifo");
else
{ /*动态申请设备号*/
ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
globalfifo_major = MAJOR(devno);
}
if(ret < 0)
return ret;
/*动态申请设备结构体的内存*/
globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
if(!globalfifo_devp)
{
ret = - ENOMEM;
goto fail_malloc;
}
memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
globalfifo_setup_cdev(globalfifo_devp, 0);
//init_MUTEX(&globalfifo_devp->sem); /*初始化信号量,此方法也被抛弃
sema_init( &globalfifo_devp->sem, 1 );
init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化读等待队列头*/
init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化写等待队列头*/
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return ret;
}
void globalfifo_exit(void)
{
cdev_del(&globalfifo_devp->cdev); /*注销cdev*/
kfree(globalfifo_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*释放设备号*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
//module_param()
module_init(globalfifo_init);
module_exit(globalfifo_exit);
Makefile如下:
obj-m := globalfifo.o
#KERNELDIR := /lib/modules/3.13.0-32-generic/build
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm *.o *.ko *.mod.c *.order *.symvers
阻塞测试:
make; sudo insmod globalfifo; lsmod | grep globalfifo; mknod /dev/globalfifo c 249 0;
cat /dev/globalfifo &
echo 'i want to be' > /dev/globalfifo输出i want to be; dmesg查看printk打印的消息
驱动的轮询操作(非阻塞) 测试程序:
pollmonitor.c代码如下:
#include<stdio.h>
#include<sys/select.h>
#include<fcntl.h>
#define FIFO_CLEAR 0X1
#define BUFFER_LEN 20
int main(void)
{
int fd, num;
char rd_ch[BUFFER_LEN];
fd_set rfds, wfds; /*读写文件描述符集*/
/*以非阻塞的方式打开 设备文件*/
fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK);
if(fd != -1)
{
/*FIFO 清零*/
//if(ioctl(fd, FIFO_CLEAR, 0) < 0)
// printf("ioctl command failed\n");
while(1)
{
FD_ZERO(&rfds); /*清除一个文件描述符集*/
FD_ZERO(&wfds);
FD_SET(fd, &rfds); /*将一个文件描述符 加入 文件描述符集中*/
FD_SET(fd, &wfds);
/*最高文件描述符+1 要监控的 读 写 异常 等待超时返回*/
select(fd + 1, &rfds, &wfds, NULL, NULL); /*该函数最终调用poll*/
/*数据可获得*/
if(FD_ISSET(fd, &rfds))
{
printf("Poll monitor:can be read\n");
}
/*数据可写入*/
if(FD_ISSET(fd, &wfds))
{
printf("Poll monitor:can be written\n");
}
}
}
else
{
printf("Device open failure\n");
}
return 0;
}
gcc pollmonitor.c -o pollout
把后台cat /dev/globalfifo进程杀死
下面demsg查看printk打印的消息;
1>FIFO 为空 只可写
2>FIFO 为满 只可读
3>FIFO 不满 不空 可读可写
直接拷贝代码可能会出现:
error: stray ‘\240’ in program,由于出现了中文空格,查看#define宏定义,一定要保证宏后面没东西
#define SIZE 100 /*sdf*/ 错误
#define SIZE 100 也为错误,100后面有空格