linux2.6之后编译内核模块就要用到Makefile文件了,本来这是内核模块学习时应该掌握的,为了以后更好的学习驱动程序,索性弄一个相对完整一点的Makefile,同时巩固一下Makefile的编写。对照ldd3和Tek的Makefile,稍微改动了一下,当然是没有包含调试的部分。
- target = hello
-
-
ifneq ($(KERNELRELEASE),)
-
-
obj-m := $(target).o
-
-
else
-
-
KERNELDIR := /lib/modules/2.6.29.4-FriendlyARM/build/
-
-
INSTALLDIR := /home/yayaso/packet/rootfs/tmp/
-
-
modules:
-
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-
-
-
modules_install:
-
cp $(target).ko $(INSTALLDIR)
-
-
clean:
-
rm -f *.ko *.o *.mod.o *.mod.c *.symvers modul*
-
-
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的代码如下:
- struct scull_dev {
-
struct scull_qset *data; /* 指向第一个量子集的指针*/
-
int quantum; /* 当前量子大小 */
-
int qset; /* 当前数组大小 */
-
unsigned long size; /* 保存的数据总量 */
-
unsigned int access_key; /* used by sculluid and scullpriv */
-
struct semaphore sem; /* 互斥信号量 */
-
struct cdev cdev; /* 嵌入的字符设备结构 */
-
};
关于结构方面的图示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()
- int scull_open(struct inode *inode, struct file *filp)
-
{
-
struct scull_dev *dev; /* device information */
-
-
dev = container_of(inode->i_cdev, struct scull_dev, cdev);//通过inode的i_cdev结构也就是cdev结构我们可以得到自己定义的scull_dev结构指针
-
filp->private_data = dev; /*将找到的指针保存到file结构中的private_data字段中,用以备用 */
-
-
/* now trim to 0 the length of the device if open was write-only */
-
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
-
if (down_interruptible(&dev->sem))
-
return -ERESTARTSYS;
-
scull_trim(dev); /* ignore errors */
-
up(&dev->sem);
-
}
-
return 0; /* success */
-
}
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宣称的每次只处理一个量子的实现方式,两个函数都用了
- if (count > quantum - q_pos)
-
count = quantum - q_pos;
这两句话的意思就是不管你要读的字节有多少,只能读取quantum - q_pos那么大,多的不给读,如果你想再读的话可以循环读取,一样下次还是只能读取一个量子大小的字节。写也是同样的。这就是scull所谓的读写每次只处理一个量子,而且每次
处理的量子的大小 <= 设定的quantum值 。
五、测试
测试程序是用的tek给的程序,再次感谢!实验结果和他在博客里面贴的是一样的,就不贴图了。首先这个测试程序编的很好,要是我编的话肯定不能想到那么全面的,可以说要写出这样的测试程序,对于这段驱动程序的功能要掌握的非常清楚,特别是对于那两个结构体的理解要透彻,还要清楚如何分配量子集等。
实验结果给出了量子大小对于读写时的影响,以及对于整体上读写的影响。就像驱动程序解释里面说的那样,每次只能处理一个量子,剩下的字节再来过,但是是不会影响整体的读和写的。
六、总结
对于简单字符驱动的学习基本上告一段落,可以说学习的还是比较深入的,对于scull里面的代码基本上也看熟了,本来这一块上个礼拜已经匆匆看过了,但是觉得没有学到特别多的东西,后来看了tek的博客,在佩服的同时也觉得自己应该静下来仔细研读一下,也就花了2天的时间写了这篇学习笔记,当中一直在改,一直在学习中,过程很快乐...哈哈...