基本原理: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时,需要唤醒阻塞的读进程,通知其有数据可以被读出。