Chinaunix首页 | 论坛 | 博客
  • 博客访问: 25492
  • 博文数量: 7
  • 博客积分: 106
  • 博客等级: 民兵
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-27 20:27
文章分类
文章存档

2011年(7)

分类: LINUX

2011-05-08 13:25:32

    进入ldd3学习,关于这一章的重要知识点Tekkamanninja的博客中已经归纳的很清楚了,再次佩服tek的细心,所以果断的转载了,有很多地方都是我没有注意到的,或者是没有那么系统的概括。以后如果要编类似的程序,可以把那篇笔记当做手册用。下面是我对于简单设备驱动特别是scull例程的学习笔记。
    在读代码过程中,由于本人C语言实力有限,参考了 前辈的一些心得和程序注解,特此感谢!

一、Makefile
    linux2.6之后编译内核模块就要用到Makefile文件了,本来这是内核模块学习时应该掌握的,为了以后更好的学习驱动程序,索性弄一个相对完整一点的Makefile,同时巩固一下Makefile的编写。对照ldd3和Tek的Makefile,稍微改动了一下,当然是没有包含调试的部分。

  1. target = hello
  2. ifneq ($(KERNELRELEASE),)
  3. obj-m := $(target).o
  4. else
  5. KERNELDIR := /lib/modules/2.6.29.4-FriendlyARM/build/
  6. INSTALLDIR := /home/yayaso/packet/rootfs/tmp/
  7. modules:
  8. $(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-
  9. modules_install:
  10. cp $(target).ko $(INSTALLDIR)
  11. clean:
  12. rm -f *.ko *.o *.mod.o *.mod.c *.symvers modul*
  13. endif
红色部分改成要编译的文件名即可。

    对于这个Makefile,ldd3中说明了会被执行两次,首先判断KERNELRELEASE是否为空,很明显执行的是else,然后make将调用内核源码树中的Makefile,在Makefile的353行有KERNELRELEASE的定义。
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null),查看include/config/kernel.release可以看到非空,于是KERNELEASE被赋值,此时$(MAKE) -C $(KERNELDIR) M=$(PWD) modules 又会调用当前目录下的Makefile,于是就运行obj-m := $(target).o。


二、struct scull_dev{}

scull_dev的代码如下:
  1. struct scull_dev {
  2.     struct scull_qset *data; /* 指向第一个量子集的指针*/
  3.     int quantum;             /* 当前量子大小 */
  4.     int qset;                /* 当前数组大小 */
  5.     unsigned long size;      /* 保存的数据总量 */
  6.     unsigned int access_key; /* used by sculluid and scullpriv */
  7.     struct semaphore sem; /* 互斥信号量     */
  8.     struct cdev cdev;     /* 嵌入的字符设备结构        */
  9. };
    关于结构方面的图示tek博文中已经讲的很清楚了,所以就不在啰嗦了。当中可能对于量子和量子集要看一下这个结构图和相关代码。结合scull_dev和scull_qset两个结构和ldd3书上的内存分配图,那个图画的还是有点模糊的,一个量子其实包含着quantum个字节,图中的标注容易让人误解为一个量子就是一个字节,这对于后面说read和write都是针对一个量子的这个说法造成困扰。如果你quantum和qset都确定了,你的一排量子大小也就确定了,如果还是写入或者要读取的内容还是太大,很好办,follow函数会帮你分配一样的量子数组的。
    还有是要理解这个自己定义的设备结构体内嵌cdev,这个是关键的,之后与内核的通信就要通过cdev来实现,因为一个文件被定义为设备文件,内核是通过inode结构下的i_cedv结构来描述的,而现在自己定义的结构包含它,再之后的open等操作中就需要转换一下。
    还有一个要注意的是在scull_init_module中,分配完设备编号后,马上就为scull_dev调用了kmalloc,这里只是把这个结构体(也就是后来要当做设备的那段内存)指定到一段内存(因为其他参数什么的都已经给定了,可以说这个结构是一定的),为了后来操作提供一个地址。刚开始可能以后kmalloc函数叫内存分配,有点觉得莫名其妙,凭啥就它要分配。

三、scull_open()
  1. int scull_open(struct inode *inode, struct file *filp)
  2. {
  3.     struct scull_dev *dev; /* device information */

  4.     dev = container_of(inode->i_cdev, struct scull_dev, cdev);//通过inode的i_cdev结构也就是cdev结构我们可以得到自己定义的scull_dev结构指针
  5.     filp->private_data = dev; /*将找到的指针保存到file结构中的private_data字段中,用以备用 */

  6.     /* now trim to 0 the length of the device if open was write-only */
  7.     if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
  8.         if (down_interruptible(&dev->sem))
  9.             return -ERESTARTSYS;
  10.         scull_trim(dev); /* ignore errors */
  11.         up(&dev->sem);
  12.     }
  13.     return 0; /* success */
  14. }
    scull_open()需要注意的是一个container_of(),这里需要得到的dev是一个指向struct scull_dev的指针,但是inode->i_cdev包含的信息是当此文件被用作设备文件时,指向其struct cdev,所以container_of()函数就是通过已知文件的cdev来找到包含它的scull_cdev的地址,其中的inode->i_cdev就是指向已知设备的cdev结构的指针。
    其次,open函数中  filp->private_data = dev; 又将地址转存,用来后面的read,这么做的目的是open函数和read函数共有一个参数*flip,而read中没有inode,也就无法获得cdev,明显自己定义的scull_cedv地址也得不到。

四、scull_read()和scull_write()

    要看懂这两个函数,首先要看懂scull_follow()函数,而这个函数链表指针很多,而且特别要看懂scull_dev结构和scull_qset这两个结构,刚开始我看这个follow函数一直没弄明白就是因为对两个结构体似懂非懂,特别是对于scull_dev结构,理解了这两个结构,函数也就不是那么难了。follow函数主要的功能就是分配量子集,这个对于scull整个函数来说也是相当关键的,因为scull的初衷里面就包含着大小可变,就体现在这里了,这些在后面的read和write函数里面将会实现。
    read和write函数中我觉得要看的主要还是scull宣称的每次只处理一个量子的实现方式,两个函数都用了
  1. if (count > quantum - q_pos)
  2.     count = quantum - q_pos;
    这两句话的意思就是不管你要读的字节有多少,只能读取quantum - q_pos那么大,多的不给读,如果你想再读的话可以循环读取,一样下次还是只能读取一个量子大小的字节。写也是同样的。这就是scull所谓的读写每次只处理一个量子,而且每次
处理的量子的大小 <= 设定的quantum值 

五、测试

    测试程序是用的tek给的程序,再次感谢!实验结果和他在博客里面贴的是一样的,就不贴图了。首先这个测试程序编的很好,要是我编的话肯定不能想到那么全面的,可以说要写出这样的测试程序,对于这段驱动程序的功能要掌握的非常清楚,特别是对于那两个结构体的理解要透彻,还要清楚如何分配量子集等。
    实验结果给出了量子大小对于读写时的影响,以及对于整体上读写的影响。就像驱动程序解释里面说的那样,每次只能处理一个量子,剩下的字节再来过,但是是不会影响整体的读和写的。

六、总结

   对于简单字符驱动的学习基本上告一段落,可以说学习的还是比较深入的,对于scull里面的代码基本上也看熟了,本来这一块上个礼拜已经匆匆看过了,但是觉得没有学到特别多的东西,后来看了tek的博客,在佩服的同时也觉得自己应该静下来仔细研读一下,也就花了2天的时间写了这篇学习笔记,当中一直在改,一直在学习中,过程很快乐...哈哈...
    
阅读(1548) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~