Linux设备驱动中的阻塞与非阻塞I/O学习

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"); 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值