设备驱动(七)

IO模型
  • 阻塞
  • 非阻塞
  • 异步IO (信号驱动IO)
  • IO多路复用(select poll epoll)
阻塞与非阻塞
等待队列头 wait_queue_head_t
等待队列项 wait_queue_t

read操作示例
  1. 获取信号量
  2. 判断是否有数据
  3. 有数据,copy_to_user
  4. 没数据,使用以下方式
  5. 释放信号量
  6. 判断是否是非阻塞
  7. 若是非阻塞,返回EAGAIN
  8. 若是阻塞,在等待队列上睡眠
  9. 重新获取信号量,回到1,重复执行
  10. 释放信号量
  11. 返回
参考代码
struct hello_device{
     int ret;
     char data[N];     //数据
     int len;     //有效资源个数
     struct semaphore sem;
     wait_queue_head_t rq;     //读等待队列
     wait_queue_head_t wq;
}

size_t hello_read(struct file *filp, char *buf, ssize_t count, loff_t *pos)
{
     down(&hello_device.sem);
     while(hello_device.len == 0){
          up(&hello_device.sem);
          if(filp->f_flags & O_NONBLOCK){     //f_flags中有一位表示是否为阻塞,0表示阻塞,1表示非阻塞
               return -EAGAIN;
          }
          wait_event_interruptible(hello_device.rq, hello_device.len > 0){
               return -ERESTARTSYS;
          }
          down(&hello_device.sem);
     }
     if(copy_to_user(buf, hello_device.data, count)){
          hello_device.ret = -EFAULT;
     }else{
          hello_device.ret = count;
     }
     up(&hello_device.sem);
     return hello_device.ret;
}

等待队列的操作
等待事件
  • wait_event(queue, condition);
  • 当前进程在等待queue上睡眠,直到queue被唤醒,并且condition为真返回
  • wait_event_interruptible(queue, condition);
  • 返回0表示信号发生, 返回非0表示被信号中断
唤醒队列
  • void wake_up(wait_queue_head_t *queue);
  • void wake_up_interruptible(wait_queue_head_t *queue);
在队列上睡眠
  • sleep_on(wait_queue_head_t *queue);
IO多路复用
poll对应应用层的select
每个设备都有自己的等待队列
select调用过程
          select                                                       APP
               |
          sys_select                                                 VFS
               |
          do_select
               |
        ____|____
       |               |                                                driver
hello_poll     console_poll


异步IO
APP操作
  1. 捕捉SIGIO型号
  2. 把当前进程设为设备的宿主 fcntl(fd, F_SETOWN, getpid());   把进程号保存到file结构体中
  3. 设置异步模式-->调用驱动中fasync函数
驱动操作
  1. 定义异步结构体指针(异步通知队列,要接收信号的队列) struct fasync_struct * async_queue;
  2. 实现fasync操作,向异步通知队列添加/删除相关信息
  3. 在有资源的时候(write操作)给异步等待队列中所有进程发信号
  4. 在release操作中从异步通知队列中删除相关信息
参考代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
//#include <linux/errno.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/semaphore.h>

MODULE_LICENSE ("GPL");

int hello_major = 250;
int hello_minor = 0;
int number_of_devices = 1;

struct hello_device
{
     char data[128];
     int len;
     wait_queue_head_t rq, wq;
     struct semaphore sem;
     struct cdev cdev;
     struct fasync_struct *async_queue;
} hello_device;

static int hello_open (struct inode *inode, struct file *filp)
{
     filp->private_data = container_of(inode->i_cdev, struct hello_device, cdev);
     printk (KERN_INFO "Hey! device opened\n");

     return 0;
}

static int hello_release (struct inode *inode, struct file *filp)
{
     struct hello_device *dev = filp->private_data;

     if (dev->async_queue) fasync_helper(-1, filp, 0, &dev->async_queue);
     printk (KERN_INFO "Hmmm... device closed\n");

     return 0;
}

ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
{
     ssize_t result = 0;
     struct hello_device *dev = filp->private_data;

     down(&dev->sem);
     while (hello_device.len == 0)
     {
          up(&dev->sem);
          if (filp->f_flags & O_NONBLOCK)
               return -EAGAIN;
          if (wait_event_interruptible(dev->rq, (dev->len != 0))) return -ERESTARTSYS;
          down(&dev->sem);
     }

     if (count > dev->len) count = dev->len;
     if (copy_to_user (buff, dev->data, count))
     {
          result = -EFAULT;
     }
     else
     {
          printk (KERN_INFO "wrote %d bytes\n", count);
          dev->len -= count;
          result = count;
          memcpy(dev->data, dev->data+count, dev->len);
     }
     up(&dev->sem);
     wake_up(&dev->wq);

     return result;
}

ssize_t hello_write (struct file *filp, const char  *buf, size_t count, loff_t *f_pos)
{
     ssize_t ret = 0;
     struct hello_device *dev = filp->private_data;

     if (count > 128) return -ENOMEM;
    
     down(&dev->sem);
     while (dev->len == 128)
     {
          up(&dev->sem);
          if (filp->f_flags & O_NONBLOCK)
               return -EAGAIN;
          if (wait_event_interruptible(dev->wq, (dev->len != 128))) return -ERESTARTSYS;
          down(&dev->sem);
     }

     if (count > (128 - dev->len)) count = 128 - dev->len;
     if (copy_from_user (dev->data+dev->len, buf, count)) {
          ret = -EFAULT;
     }
     else {
          dev->len += count;
          ret = count;
     }
     up(&dev->sem);
     wake_up(&dev->rq);

     /* 产生异步读信号 */
    if (dev->async_queue)
       kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

     return ret;
}

/*
 * poll_table 为轮询指针
 */
static unsigned int hello_poll(struct file *filp, poll_table *wait)
{
     unsigned int mask = 0;
     struct hello_device *dev = filp->private_data;
     poll_wait(filp, &dev->rq, wait);          //
读等待队列加入到轮询表
     poll_wait(filp, &dev->wq, wait);         //
写等待队列加入到轮询表
     down(&dev->sem);
     if (dev->len > 0)
     {
          mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
     }
     if (dev->len != 128)
     {
          mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/
     }
     up(&dev->sem);
     return mask;
}

static int hello_fasync(int fd, struct file *filp, int mode)
{
     struct hello_device *dev = filp->private_data;
     return fasync_helper(fd, filp, mode, &dev->async_queue);
}

struct file_operations hello_fops = {
     .owner = THIS_MODULE,
     .open  = hello_open,
     .release = hello_release,
     .read  = hello_read,
     .write = hello_write,
     .poll = hello_poll,
     .fasync = hello_fasync
};

static void char_reg_setup_cdev (void)
{
     int error;
     dev_t devno;

     devno = MKDEV (hello_major, hello_minor);
     cdev_init (&hello_device.cdev, &hello_fops);
     hello_device.cdev.owner = THIS_MODULE;
     error = cdev_add (&hello_device.cdev, devno , 1);
     if (error)
          printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
}

static int __init hello_2_init (void)
{
     int result;
     dev_t devno;

     devno = MKDEV (hello_major, hello_minor);
     result = register_chrdev_region (devno, number_of_devices, "hello");

     if (result < 0) {
          printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
          return result;
     }

     char_reg_setup_cdev ();
     init_waitqueue_head(&hello_device.rq);
     init_waitqueue_head(&hello_device.wq);
     sema_init(&hello_device.sem, 1);
     memset(hello_device.data, 0, 128);
     hello_device.len = 0;

     printk (KERN_INFO "char device registered\n");

     return 0;
}

static void __exit hello_2_exit (void)
{
     dev_t devno = MKDEV (hello_major, hello_minor);

     cdev_del (&hello_device.cdev);

     unregister_chrdev_region (devno, number_of_devices);
}

module_init (hello_2_init);
module_exit (hello_2_exit);

你可能感兴趣的:(linux,设备驱动)