1、阻塞与非阻塞I/O
阻塞和非阻塞I/O是访问设备的两种不同模式,驱动程序可以灵活地支持用户空间对设备的这两种访问方式。阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停的查询,直至可以进行操作为止。
2、阻塞I/O的实现方式
等待队列。在Linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。wait queue很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问,信号量机制在内核中
也依赖于等待队列实现。
1、定义等待队列头
wait_queue_head_t my_queue;
2、初始化等待队列头
init_waitqueue_head(&my_queue);
3、定义等待队列
DECLARE_WAITQUEUE(name,tsk);
4、添加/移除等待队列
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);
add_wait_queue()用于将等待队列wait添加到等待队列头q指向的等待队列链表中,而remove_wait_queue()用于将等待队列wait从附属的等待队列头q指向的等待队列链表中移除。
5、等待事件
wait_event(queue,conditon); wait_event_interruptible(queue,condition); wait_event_timeout(queue,condition,timeout); wait_event_interruptible_timeout(queue,condition,timeout);
等待第一个参数queue作为等待队列头的等待队列被唤醒,而且第二个参数condition必须满足,否则阻塞。wait_event()和wait_event_interruptible()的区别在于后者可以被信号打断,而前者不能。加上timeout后的宏意味着阻塞等待的超时时间,在第三个参数timeout到达时,不论condition是否满足,均返回。
6、唤醒队列
void wake_up(wait_queue_head_t *queue); void wake_up_interruptible(wait_queue_head_t *queue);
7、在等待队列上睡眠
sleep_on(wait_queue_head_t *q); interruptible_sleep_on(wait_queue_head_t *q);
globalfifo的实现。globalfifo根据结构体globalfifo_dev中的信号量,互斥进行读写操作。要注意信号量初始化需赋值为1。因为down操作获取信号量时根据信号量值是否大于0来判断信号量是否可以获取。在读进程中,如果fifo缓冲区为空,就会挂起,等待写进程唤醒。同理,在写进程中,如果fifo缓冲区满,就会挂起,等待读进程唤醒。
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pmbus.h>
#include <linux/util_macros.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#define GLOBALFIFO_SIZE 20
int globalfifo_major=0;
struct class *globalfifo_class;
struct globalfifo_dev{
//struct cdev cdev;//cdev结构体
unsigned int current_len;//fifo有效数据长度
unsigned char mem[GLOBALFIFO_SIZE];//全局内存123
struct semaphore sem;//并发控制用的信号量
wait_queue_head_t r_wait;//阻塞读用的等待队列头
wait_queue_head_t w_wait;//阻塞写用的等待队列头
};
struct globalfifo_dev *globalfifo_devp;
int globalfifo_open(struct inode *node, struct file *filp)
{
printk(KERN_INFO"globalfifo_open\n");
filp->private_data = globalfifo_devp;
printk(KERN_INFO"private_data\n");
return 0;
}
int globalfifo_release(struct inode *node, struct file *filp)
{
printk(KERN_INFO"globalfifo_release\n");
return 0;
}
ssize_t globalfifo_read(struct file *filp, char *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO"globalfifo_read\n");
int ret;
struct globalfifo_dev *dev = filp->private_data;
printk(KERN_INFO"read try to declare queue\n");
DECLARE_WAITQUEUE(wait,current);
printk(KERN_INFO"read try to get sem\n");
down(&dev->sem);
printk(KERN_INFO"read have got sem\n");
add_wait_queue(&dev->r_wait,&wait);
if(dev->current_len==0)
{
if(filp->f_flags & O_NONBLOCK)
{
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
up(&dev->sem);
printk(KERN_INFO"read release sem,begin shcedule\n");
schedule();
if(signal_pending(current))
{
ret = -ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
if(size > dev->current_len)
size = dev->current_len;
if(copy_to_user(buf,dev->mem,size))
{
ret = -EFAULT;
goto out;
}
else
{
//printk("%s",kern_buf);
memcpy(dev->mem,dev->mem+size,dev->current_len-size);
dev->current_len-=size;
printk(KERN_INFO"read %d bytes(s),current_len:%d\n",size,dev->current_len);
wake_up_interruptible(&dev->w_wait);
ret = size;
}
out:up(&dev->sem);
out2:remove_wait_queue(&dev->r_wait,&wait);
set_current_state(TASK_RUNNING);
return size;
}
ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
printk("global_fifo write");
struct globalfifo_dev *dev = filp->private_data;
int ret,i=0,j;
DECLARE_WAITQUEUE(wait,current);
down(&dev->sem);
add_wait_queue(&dev->w_wait,&wait);
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(size>GLOBALFIFO_SIZE-dev->current_len)
{
size = GLOBALFIFO_SIZE-dev->current_len;
}
//for(i=dev->current_len,j=0;i<dev->current_len+size;i++,j++)dev->mem[i]=buf[j];
if(copy_from_user(dev->mem + dev->current_len,buf,size))
{
ret = -EFAULT;
goto out;
}
else
{
dev->current_len+=size;
printk(KERN_INFO"written %d bytes(s),current_len:%d\n",size,dev->current_len);
wake_up_interruptible(&dev->r_wait);
ret = size;
}
out:up(&dev->sem);
out2:remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING);
return ret;
}
struct file_operations globalfifo_drv={
.owner = THIS_MODULE,
.open = globalfifo_open,
.release = globalfifo_release,
.read = globalfifo_read,
.write = globalfifo_write,
};
int globalfifo_init(void)
{
int ret;
//申请设备号
globalfifo_major = register_chrdev(0,"globalfifo",&globalfifo_drv);
globalfifo_class = class_create("globalfifo_class");
if (IS_ERR(globalfifo_class)) {
printk(KERN_ERR "globalfifo_class: failed to allocate class\n");
return PTR_ERR(globalfifo_class);
}
device_create(globalfifo_class,NULL,MKDEV(globalfifo_major,0),NULL,"globalfifo_device");
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);
sema_init(&globalfifo_devp->sem,1);
init_waitqueue_head(&globalfifo_devp->r_wait);
init_waitqueue_head(&globalfifo_devp->w_wait);
return 0;
fail_malloc:device_destroy(globalfifo_class,MKDEV(globalfifo_major,0));
class_destroy(globalfifo_class);
unregister_chrdev(globalfifo_major,"globalfifo_chrdev");
return ret;
}
void globalfifo_exit(void)
{
device_destroy(globalfifo_class,MKDEV(globalfifo_major,0));
class_destroy(globalfifo_class);
unregister_chrdev(globalfifo_major,"globalfifo_chrdev");
return;
}
module_init(globalfifo_init);
module_exit(globalfifo_exit);
MODULE_LICENSE("GPL");
3、非阻塞I/O的实现方式
轮询操作。使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问。
应用程序中的轮询编程:其中select()系统调用的原型如下,其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合,numfds的值是需要检查的号码最高的文件描述符加1。timeout参数是一个指向struct timeval类型的指针,可以使select()在等待timeout时间后若没有文件描述符准备好则返回。
int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *expectfds,struct timeval *timeout);
清除一个文件描述符集::
FD_ZERO(fd_set *set)
将一个文件描述符加入文件描述符集中:
FD_SET(int fd,fd_set *set)
将一个文件描述符从文件描述符集中清除:
FD_ISSET(int fd,fd_set *set)
判断文件描述符是否被置位:
FD_ISSET(int fd,fd_set *set)
设备驱动中的轮询编程:设备驱动中poll()函数的原型如下,第一个参数为file结构体指针,第二个参数为轮询表指针。这个函数应该进行以下两项工作:1、对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table。2、返回表示是否能对设备进行无阻塞读、写访问的掩码。
unsigned int (*poll) (struct file *filp,struct poll_table *wait);
关键的用于向poll_table注册等待队列的poll_wait()函数原型如下,poll_wait()函数所做的工作是把当前进程添加到wait参数指定的等待列表(poll_table)中,并不会引起阻塞。
void poll_wait(struct file *filp,wait_queue_head_t *queue,poll_table *wait);
驱动程序poll()函数应该返回设备资源的可获取状态,即POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL等宏的位”或“结果。每个宏的含义都表明设备的一种状态,如POLLIN表可无阻塞读,POLLOUT表可无阻塞写。 poll()函数典型模板如下:
static unsigned int xxx_poll(struct file *filp,poll_table *wait)
{
unsigned int mask=0;
struct xxx_dev *dev = filp->private_data;
poll_wait(filp,&dev->r_wait,wait);
poll_wait(filp,&dev->w_wait,wait);
if(...)//可读
{
mask |= POLLIN | POLLRDNORM;
}
if(...)//可写
{
mask |= POLLIN | POLLWRNORM;
}
return mask;
}
增加了轮询操作的globalfifo驱动。在驱动中利用poll机制,在应用程序想要读写文件的时候,通过poll函数率先判断读、写资源是否可用,如果不可用则返回资源不可用的信号。如果资源可用则返回可读、写信号。
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pmbus.h>
#include <linux/util_macros.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#define GLOBALFIFO_SIZE 20
int globalfifo_major=0;
struct class *globalfifo_class;
struct globalfifo_dev{
//struct cdev cdev;//cdev结构体
unsigned int current_len;//fifo有效数据长度
unsigned char mem[GLOBALFIFO_SIZE];//全局内存123
struct semaphore sem;//并发控制用的信号量
wait_queue_head_t r_wait;//阻塞读用的等待队列头
wait_queue_head_t w_wait;//阻塞写用的等待队列头
};
struct globalfifo_dev *globalfifo_devp;
int globalfifo_open(struct inode *node, struct file *filp)
{
printk(KERN_INFO"globalfifo_open\n");
filp->private_data = globalfifo_devp;
printk(KERN_INFO"private_data\n");
return 0;
}
int globalfifo_release(struct inode *node, struct file *filp)
{
printk(KERN_INFO"globalfifo_release\n");
return 0;
}
ssize_t globalfifo_read(struct file *filp, char *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO"globalfifo_read\n");
int ret;
struct globalfifo_dev *dev = filp->private_data;
printk(KERN_INFO"read try to declare queue\n");
DECLARE_WAITQUEUE(wait,current);
printk(KERN_INFO"read try to get sem\n");
down(&dev->sem);
printk(KERN_INFO"read have got sem\n");
add_wait_queue(&dev->r_wait,&wait);
if(dev->current_len==0)
{
if(filp->f_flags & O_NONBLOCK)
{
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
up(&dev->sem);
printk(KERN_INFO"read release sem,begin shcedule\n");
schedule();
if(signal_pending(current))
{
ret = -ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
if(size > dev->current_len)
size = dev->current_len;
if(copy_to_user(buf,dev->mem,size))
{
ret = -EFAULT;
goto out;
}
else
{
//printk("%s",kern_buf);
memcpy(dev->mem,dev->mem+size,dev->current_len-size);
dev->current_len-=size;
printk(KERN_INFO"read %d bytes(s),current_len:%d\n",size,dev->current_len);
wake_up_interruptible(&dev->w_wait);
ret = size;
}
out:up(&dev->sem);
out2:remove_wait_queue(&dev->r_wait,&wait);
set_current_state(TASK_RUNNING);
return size;
}
ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
printk("global_fifo write");
struct globalfifo_dev *dev = filp->private_data;
int ret,i=0,j;
DECLARE_WAITQUEUE(wait,current);
down(&dev->sem);
add_wait_queue(&dev->w_wait,&wait);
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(size>GLOBALFIFO_SIZE-dev->current_len)
{
size = GLOBALFIFO_SIZE-dev->current_len;
}
//for(i=dev->current_len,j=0;i<dev->current_len+size;i++,j++)dev->mem[i]=buf[j];
if(copy_from_user(dev->mem + dev->current_len,buf,size))
{
ret = -EFAULT;
goto out;
}
else
{
dev->current_len+=size;
printk(KERN_INFO"written %d bytes(s),current_len:%d\n",size,dev->current_len);
wake_up_interruptible(&dev->r_wait);
ret = size;
}
out:up(&dev->sem);
out2:remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING);
return ret;
}
unsigned int globalfifo_poll(struct file *filp, struct poll_table_struct *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);
if(dev->current_len != 0)
{
mask |= POLLIN | POLLRDNORM;
}
if(dev->current_len != GLOBALFIFO_SIZE)
{
mask |= POLLOUT | POLLWRNORM;
}
up(&dev->sem);
return mask;
}
struct file_operations globalfifo_drv={
.owner = THIS_MODULE,
.open = globalfifo_open,
.release = globalfifo_release,
.read = globalfifo_read,
.write = globalfifo_write,
.poll = globalfifo_poll,
};
int globalfifo_init(void)
{
int ret;
//申请设备号
globalfifo_major = register_chrdev(0,"globalfifo",&globalfifo_drv);
globalfifo_class = class_create("globalfifo_class");
if (IS_ERR(globalfifo_class)) {
printk(KERN_ERR "globalfifo_class: failed to allocate class\n");
return PTR_ERR(globalfifo_class);
}
device_create(globalfifo_class,NULL,MKDEV(globalfifo_major,0),NULL,"globalfifo_device");
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);
sema_init(&globalfifo_devp->sem,1);
init_waitqueue_head(&globalfifo_devp->r_wait);
init_waitqueue_head(&globalfifo_devp->w_wait);
return 0;
fail_malloc:device_destroy(globalfifo_class,MKDEV(globalfifo_major,0));
class_destroy(globalfifo_class);
unregister_chrdev(globalfifo_major,"globalfifo_chrdev");
return ret;
}
void globalfifo_exit(void)
{
device_destroy(globalfifo_class,MKDEV(globalfifo_major,0));
class_destroy(globalfifo_class);
unregister_chrdev(globalfifo_major,"globalfifo_chrdev");
return;
}
module_init(globalfifo_init);
module_exit(globalfifo_exit);
MODULE_LICENSE("GPL");