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中的如下代码:
首先,在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的源代码:
这个函数的实现和scull_init十分类似,注册设备号,然后初始化设备结构体里面的各个成员。下面看看scull_p_set函数和scull_pip结构体:
scull_p_setup_cedv的源码:
主要实现了调用cdev_init函数初始化dev->cdev,指定设备操作函数集是scull_pipe_fops。调用cdev_add函数将dev->cdev注册到系统中, devno是对应的设备编号。
write函数,看注释(与scull_p_read采用简单休眠不同,scull_p_write采用了手工休眠的方式):
scull_getwritespace函数,该函数定义如下:
使用spacefree函数判断是否有空间可写,该函数定义如下
接下来是测试,具体信息如图:
![](http://img.blog.csdn.net/20131027094645218?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3A1NjIwOTA3MzI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
左侧执行cat命令,会被阻塞,此时在右侧向scullpipe0设备输入信息,会在左侧被打印出来;
另外,参考博客:http://blog.csdn.net/liuhaoyutz/article/details/7395057,可以找出LDD3源码中的一个BUG,实验已做,就不放图了。
一、代码分析
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,实验已做,就不放图了。
相关文章推荐
- Cocos2d入门 <七> 游戏结束
- Android 源码系列之<七>从源码的角度深入理解IntentService及HandlerThread
- 实时开发框架Meteor API解读系列<七> Collection --01
- 一起学Maven(Maven中的生命周期与插件机制)<七>
- 黑马程序员 Java基础<七>---> 集合框架
- 软件工程基础<七>
- 黑马程序员 java基础<七>--网络编程(1)
- <七>阅读<<大话设计模式>>该模板模型
- LDD3源码学习日记<四>
- UML参考手册 第三部分 参 考 资 料 第13章 术 语 大 全 <七>
- 网络安全基础篇之<七>
- Android UI设计之<七>自定义Dialog,实现各种风格效果的对话框
- 黑马程序员 JAVA增强<七> class类
- 【Qt编程】基于Qt的词典开发系列<七>--调用网络API
- HTML学习记录<七> :表单
- Nop-Profiler的改进方向,通过Miniprofiler设置仅对某些条件下的访问开放profiler trace<七>
- D3.JS 基于javascript的图表展示库<七>
- Nop-Profiler的改进方向,通过Miniprofiler设置仅对某些条件下的访问开放profiler trace<七>
- 【MySql】使用记录<七>
- 程序员_Java基础之<七>-集合框架