您的位置:首页 > 其它

LDD3源码学习日记<七>

2013-10-27 09:49 281 查看
这篇是学习关于阻塞IO的源码内容,源代码在examples/scull/pipe.c examples/scull/main.c中,下面开始分析源代码:

一、代码分析

scullpipe使用一个进程来产生数据并唤醒读取进程,类似的,利用读取进程来唤醒等待缓冲区可用的写入进程,scullpipe的主体实现在examples/scull/pipe.c中,但是也利用了examples/scull/main.c中的一些代码。

分析scullpipe的起点在main.c中,首先看main.c中的如下代码:

/* 初始化scull_nr_devs个设备,也就是设置scull_dev结构体的成员 */
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
init_MUTEX(&scull_devices[i].sem);
scull_setup_cdev(&scull_devices[i], i);
}

/* At this point call the init function for any friend device */
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
dev += scull_p_init(dev);
dev += scull_access_init(dev);

首先,在module_init函数里,调用了MKDEV宏来为scullpip生成设备号,scull_ninor等于0,scull_nr_devs等于四,在这里,就是为了生成一个主设备号随机产生,次设备号为4的scullpip设备号,再利用这个设备号作为参数,传递给scull_p_init函数,在这个函数里面,调用了register_chrdev_region函数,申请从dev开始的scull_p_nr_devs个设备编号,使得scullpip0~3的次设备号分别为4、5、6、7、8.下面是scull_p_init的源代码:

int scull_p_init(dev_t firstdev)
{
int i, result;

result = register_chrdev_region(firstdev, scull_p_nr_devs, "scullp");
if (result < 0) {
printk(KERN_NOTICE "Unable to get scullp region, error %d\n", result);
return 0;
}
scull_p_devno = firstdev;
scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL);
if (scull_p_devices == NULL) {
unregister_chrdev_region(firstdev, scull_p_nr_devs);
return 0;
}
memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe));
for (i = 0; i < scull_p_nr_devs; i++) {
init_waitqueue_head(&(scull_p_devices[i].inq));  //休眠在scullpip设备上等待写入的进程
init_waitqueue_head(&(scull_p_devices[i].outq)); //休眠在scullpip设备上等待被读的进程

init_MUTEX(&scull_p_devices[i].sem);
scull_p_setup_cdev(scull_p_devices + i, i);
}
#ifdef SCULL_DEBUG
create_proc_read_entry("scullpipe", 0, NULL, scull_read_p_mem, NULL);
#endif
return scull_p_nr_devs;
}

这个函数的实现和scull_init十分类似,注册设备号,然后初始化设备结构体里面的各个成员。下面看看scull_p_set函数和scull_pip结构体:

struct scull_pipe {
wait_queue_head_t inq, outq;       /* read and write queues */
char *buffer, *end;                /* begin of buf, end of buf */
int buffersize;                    /* used in pointer arithmetic */
char *rp, *wp;                     /* where to read, where to write */
int nreaders, nwriters;            /* number of openings for r/w */
struct fasync_struct *async_queue; /* asynchronous readers */
struct semaphore sem;              /* mutual exclusion semaphore */
struct cdev cdev;                  /* Char device structure */
};


scull_p_setup_cedv的源码:

static void scull_p_setup_cdev(struct scull_pipe *dev, int index)
{
int err, devno = scull_p_devno + index;

cdev_init(&dev->cdev, &scull_pipe_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scullpipe%d", err, index);
}

主要实现了调用cdev_init函数初始化dev->cdev,指定设备操作函数集是scull_pipe_fops。调用cdev_add函数将dev->cdev注册到系统中, devno是对应的设备编号。

static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;

if (down_interruptible(&dev->sem))
return -ERESTARTSYS;

while (dev->rp == dev->wp) { /* nothing to read,当读指针dev->rp与写指针 dev->wp相等时,*/
/*说明缓冲区中没有数据可读。这种情况下,要根据用户指定的标志位决定是进入休眠等待还是直接返回。 */
up(&dev->sem); /* release the lock */
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
/*调用wait_event_interruptible函数休眠在dev->inq等待队列上,
注意被唤醒的条件是(dev->rp != dev->wp),即缓冲区中有数据可读。
由这一句也可以看出,用户空间的read进程对应的等待队列是dev->inq。
另外,wait_event_interruptible的返回值有两种,返回0表示缓冲区中有数据可读,被唤醒。
返回非0值表示休眠被某个信号中断,这时,132行直接返回-ERESTARTSYS。*/
if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
/* otherwise loop, but first reacquire the lock */
/*休眠被唤醒后,首先重新获得互斥锁,         */
/*然后返回到while循环开始处判断是否有数据可读*/
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
/* ok, data is there, return something */
/*确认有数据可读后,退出while循环,    */
/*根据读写指针的位置位及count值,决定要读多少数据*/
if (dev->wp > dev->rp)
count = min(count, (size_t)(dev->wp - dev->rp));
else /* the write pointer has wrapped, return data up to dev->end */
count = min(count, (size_t)(dev->end - dev->rp));

/*读数据到用户空间*/
if (copy_to_user(buf, dev->rp, count)) {
up (&dev->sem);
return -EFAULT;
}
/*更新读指针的位置*/
dev->rp += count;
if (dev->rp == dev->end)
dev->rp = dev->buffer; /* wrapped */
/*释放互斥锁*/
up (&dev->sem);

/* finally, awake any writers and return */
/*读取结束后,就有缓冲区空间可以进行写操作了。*/
/*所以唤醒所有休眠在dev->outq上的写进程,唤醒函数是wake_up_interruptible。*/
wake_up_interruptible(&dev->outq);
PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
/*返回读取的字节数*/
return count;
}

write函数,看注释(与scull_p_read采用简单休眠不同,scull_p_write采用了手工休眠的方式):

static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
int result;
/*获得互斥锁*/
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;

/* Make sure there's space to write */
/*调用scull_getwritespace函数*/
result = scull_getwritespace(dev, filp);
if (result)
return result; /* scull_getwritespace called up(&dev->sem) */

/* ok, space is there, accept something */
/*计算可写入数据的大小。*/
count = min(count, (size_t)spacefree(dev));
if (dev->wp >= dev->rp)
count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */
else /* the write pointer has wrapped, fill up to rp-1 */
count = min(count, (size_t)(dev->rp - dev->wp - 1));
PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buf);
/*将数据从用户空间拷贝到内核空间*/
if (copy_from_user(dev->wp, buf, count)) {
up (&dev->sem);
return -EFAULT;
}
/*调整写入指针的位置*/
dev->wp += count;
if (dev->wp == dev->end)
dev->wp = dev->buffer; /* wrapped */
/*释放互斥锁*/
up(&dev->sem);

/* finally, awake any reader */
wake_up_interruptible(&dev->inq);  /* blocked in read() and select() */

/* and signal asynchronous readers, explained late in chapter 5 */
if (dev->async_queue)
/*异步通知相关的操作*/
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count);
return count;
}

scull_getwritespace函数,该函数定义如下:

static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
while (spacefree(dev) == 0) { /* full */
DEFINE_WAIT(wait);

up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("\"%s\" writing: going to sleep\n",current->comm);
prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);
if (spacefree(dev) == 0)
schedule();
finish_wait(&dev->outq, &wait);
if (signal_pending(current))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
return 0;
}

使用spacefree函数判断是否有空间可写,该函数定义如下

/* How much space is free? */
/*如果spacefree函数返回值为0,说明现在scullpipe设备缓冲区已满,没有空间可以写入。*/
/*写进程需要根据调用者设置的标志位决定是否休眠等待。*/
static int spacefree(struct scull_pipe *dev)
{
if (dev->rp == dev->wp)
return dev->buffersize - 1;
return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1;
}

接下来是测试,具体信息如图:



左侧执行cat命令,会被阻塞,此时在右侧向scullpipe0设备输入信息,会在左侧被打印出来;

另外,参考博客:http://blog.csdn.net/liuhaoyutz/article/details/7395057,可以找出LDD3源码中的一个BUG,实验已做,就不放图了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: