Linux I/O多路转接之驱动程序

基本原理:I/O多路转接主要用于操作多个文件描述符的场合,先将要进行I/O操作的描述符构造为一个描述符列表,然后调用select或poll函数,当这些描述符中的一个可以进行I/O时,函数返回,进而我们可以根据结果找到该描述符并进行操作。

驱动程序设计:在设备驱动程序中,主要通过poll()函数实现该功能,我们在这里通过对一块内存的操作(这里将其定义为一个名为memfifo的设备),了解如何在驱动程序中实现I/O多路转接的功能。

先给出该设备的结构体:

struct memfifo_dev {
 struct cdev cdev;
 unsigned int current_len;
 unsigned char mem[FIFO_SIZE];
 struct mutex mutex;
 wait_queue_head_t r_wait;
 wait_queue_head_t w_wait;
};

其中mutex用于对该设备的互斥访问,r_wait和w_wait分别为读写操作的等待队列头。
驱动的入口函数:

static int __init memfifo_init(void)
{
 int ret;
 dev_t devno = MKDEV(memfifo_major, 0);
 
 if (MEMLFIFO_MAJOR) 
  ret = register_chrdev_region(devno, 1, "memfifo");
 else {
  ret = alloc_chrdev_region(&devno, 0, 1, "memfifo");
  memfifo_major = MAJOR(devno);
 }
 
 if (ret < 0)
  return ret;
 
 memfifo_devp = kzalloc(sizeof(struct memfifo_dev), GFP_KERNEL);
 if (!memfifo_devp) {
  ret = -ENOMEM;
  goto fail_malloc;
 }
 
 memfifo_setup_cdev(memfifo_devp, 0);
 
 mutex_init(&memfifo_devp->mutex);
 init_waitqueue_head(&memfifo_devp->r_wait);
 init_waitqueue_head(&memfifo_devp->w_wait);
 
 return 0;
 
fail_malloc:
 unregister_chrdev_region(devno, 1);
 return ret;
 
}
module_init(memfifo_init);

入口函数为一个memfifo_dev分配了内存并初始化其成员,并注册一个字符设备, memfifo_setup_cdev函数实现如下:

static void memfifo_setup_cdev(struct memfifo_dev *dev, int index)
{
 int err, devno = MKDEV(memfifo_major, index);
 
 cdev_init(&dev->cdev, &memfifo_fops);
 dev->cdev.owner = THIS_MODULE;
 err = cdev_add(&dev->cdev, devno, 1);
 if (err)
  printk(KERN_NOTICE "Error %d adding memfifo %d", err, index);
}

该函数初始化memfifo_dev的cdev成员,并注册该字符设备。
memfifo_fops结构定义如下:

struct file_operations memfifo_fops = {
 .owner = THIS_MODULE,
 .read = memfifo_read,
 .write = memfifo_write,
 .poll = memfifo_poll,
 .open = memfifo_open,
 .release = memfifo_release,
};

open和release函数:

static int memfifo_open(struct inode *inode, struct file *filp)
{
 filp->private_data = memfifo_devp;
 return 0;
}
static int memfifo_release(struct inode *inode, struct file *filp)
{
 return 0;
}

poll函数的实现:

static unsigned int memfifo_poll(struct file *filp, poll_table *wait)
{
 unsigned int mask = 0;
 struct memfifo_dev *dev = filp->private_data;
 
 mutex_lock(&dev->mutex);
 
 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 != FIFO_SIZE) {
  mask |= POLLOUT | POLLWRNORM;
 }
 
 mutex_unlock(&dev->mutex);
 
 return mask;
}

poll_wait()把当前进程添加到wait参数指定的等待列表,使得设备的r_wait和w_wait等待队列可以唤醒因select()而睡眠的进程。当current_len非0,设置POLLIN和POLLRDNORM,通知memfifo设备中有数据可读,当current_len小于FIFO_SIZE,设置POLLOUT和POLLWRNORM,通知还可以继续向memfifo设备中写数据。

read函数实现:

static ssize_t memfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
 int ret;
 struct memfifo_dev *dev = filp->private_data;
 DECLARE_WAITQUEUE(wait, current);
 
 mutex_lock(&dev->mutex);
 
 add_wait_queue(&dev->r_wait, &wait);
 
 while (dev->current_len == 0) {
  if (filp->f_flags & O_NONBLOCK) {
   ret = -EAGAIN;
   goto out;
  }
  __set_current_state(TASK_INTERRUPTIBLE);
  mutex_unlock(&dev->mutex);
  schedule();
  
  if (signal_pending(current)) {
   ret = -ERESTARTSYS;
   goto out2;
  }
  
  mutex_lock(&dev->mutex);
 }
 
 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("read %d byte(s), current_len:%d\n", count, dev->current_len);
  wake_up_interruptible(&dev->w_wait);
  ret = count;
 }
 
out:
 mutex_unlock(&dev->mutex);
out2:
 remove_wait_queue(&dev->r_wait, &wait);
 set_current_state(TASK_RUNNING);
 
 return ret;
}

如果设备无数据可读,在非阻塞访问的情况下,直接返回;在阻塞访问的情况下,切换进程状态并调度其他进程执行。如果有数据可读,则将数据复制到用户空间。设想有这样一种情况:memfifo已经被写满,那么后面的写进程将会被阻塞,当memfifo的数据被读走一部分时,我们就可以唤醒被阻塞的写进程,这就是我们读操作后添加“wake_up_interruptible(&dev->w_wait);”这个唤醒操作的原因。

static ssize_t memfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
 int ret;
 struct memfifo_dev *dev = filp->private_data;
 DECLARE_WAITQUEUE(wait, current);
 
 mutex_lock(&dev->mutex);
 
 add_wait_queue(&dev->w_wait, &wait);
 
 while (dev->current_len == FIFO_SIZE) {
  if (filp->f_flags & O_NONBLOCK) {
   ret = -EAGAIN;
   goto out;
  }
  __set_current_state(TASK_INTERRUPTIBLE);
  mutex_unlock(&dev->mutex);
  schedule();
  
  if (signal_pending(current)) {
   ret = -ERESTARTSYS;
   goto out2;
  }
  
  mutex_lock(&dev->mutex);
 }
 
 if (count > FIFO_SIZE - dev->current_len)
  count = FIFO_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("written %d byte(s), current_len:%d\n", count, dev->current_len);
  wake_up_interruptible(&dev->r_wait);
  
  ret = count;
 }
 out:
 mutex_unlock(&dev->mutex);
out2:
 remove_wait_queue(&dev->w_wait, &wait);
 set_current_state(TASK_RUNNING);
 return ret;
 }

write函数和read函数基本类似,memfifo如果为空,那么后续读进程将被阻塞,当写了一些数据到memfifo时,需要唤醒阻塞的读进程,通知其有数据可以被读出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值