阅读:2057回复:0
个人对ldd里的scull代码的理解,仅供菜鸟们参考和高手指正,多谢(未完,待续)
Linux Device Driver 中第三章scull代码的分析
最近上头给了本ldd,要我学做驱动,书只针对内核2。0的,但是我想这个scull的程序应该在ldd2里修改的也不是太多,所以就将自己的一点理解感受写出来供和我一样的菜鸟们品味,高手们看了有什么理解的不好的或者甚至是错误的,请给予更多的指正,而不是嘲笑,在下代表菜鸟群们多谢了,呵呵:) 一:本人对驱动的理解。 简单的说,驱动程序就是一个应用程序访问硬件的桥梁,从结构上看应用程序<->驱动程序--硬件,这个不说,大家也知道,如果从实际的操作来看呢,我举个简单的例子:比如你要访问一个外部设备(此刻该设备的驱动程序已经加载到内核里): 1. 你会调用open系统调用,它的作用是打开一个你指定的设备名的外部设备,并返回系统分配给该设备的文件描述符,比如:fd=open(“scull0”,O_WRONLY,) 2. 当你调用open以后,系统会根据你给的设备名找到相应的文件系统下的节点,(该节点是你在装载驱动的时候自己安装的如mknod……,安装节点的主要作用是根据为设备申请主设备号,指定次设备号,设备驱动类型,设备名来向操作系统注册,这样子,以后系统就是通过这个节点来找到相应设备的驱动的),好,在根据设备名找到主设备号次设备号以后,我们就可以根据主,次设备号来调用相关的设备驱动了,在这里有一个结构体要说明一下 3. struct file_operations *scull_fop_array[]={ 4. &scull_fops, /* type 0 */ 5. &scull_priv_fops, /* type 1 */ 6. &scull_pipe_fops, /* type 2 */ 7. &scull_sngl_fops, /* type 3 */ 8. &scull_user_fops, /* type 4 */ 9. &scull_wusr_fops /* type 5 */ 10. }; 11. 它的作用就是根据你调用的不同的设备驱动,而指定到一个相关的设备的驱动处理函数,它是一个集合,这个大家也看到了,呵呵,比如说你现在是对scull设备进行操作的话,那么你会把你的次设备号进行分解(因为LDD将scull做为了一个模块,这个模块里面可以操作不同的设备,等于说有很多对应的设备的驱动程序,但是主设备号只是linux操作系统找驱动这样来用的,但是驱动程序具体动作的话是根据次设备号来判断到底调用哪个驱动的)当然作为我们这些菜鸟们可能开始并不需要考虑那么的事情,只要一个主设备号,一个次设备号就够了,呵呵,至少我现在是这样想的。)分解得到一个高4位的和一个低4位的,高4位的指定是哪个设备类(如scull0-3还是scullpipe0-3,scullpipe0-3的代码我还没看到那里去:()那么低4位的就指定到底是调用scull0的驱动呢还是调用scull1..或者其他的该类里面的驱动,其实是一个驱动,只是他们是几个相同的设备罢了。所以上面的结构体我们在用的时候会指定用高四位的,来判断是用哪个设备类里面的相关驱动函数。 3. 好,在上面的一步中我们找到了相应的设备对应的open函数(就是int scull_open (struct inode *inode, struct file *filp) 那么以后我们每次调用fd=open(“scull0”,O_WRONLY,)的话就相当于调用int scull_open (struct inode *inode, struct file *filp)了,对于read,write操作也是这样的。 可能您看到这儿,一定会骂我的文字功底实在太差,但是不好意思,我的确只是看了scull的源码,如果您还不明白的话,就看下边的代码注释吧。 scull的功能: Scull是作者为了让大家能够了解驱动开发的整体过程,但是又不希望依靠具体硬件而设计的只在本机内存上操作的一系列驱动代码,并实现驱动开发过程中所要实现的如read,write,ioctl,,,,,,(此次分析不包括ioctl,呵呵,只看到第三章),这个例子完整的给出了,如何向系统注册你的驱动(包括建立节点,申请主设备号,申请资源),如何编写特定的驱动处理的函数(如read,write,ioctl等等),如何卸载驱动。下面,我对我所看过的进行注释,希望对大家有所帮助。 1. 向系统注册你的驱动程序-----scull_load脚本文件 该文件的主要主用是向系统注册你所编写好的驱动,并根据insmod申请相关的资源然后建立节点,关键地方如下: # invoke insmod with all arguments we got /sbin/insmod -f $module $* || exit 1 major=`cat /proc/devices | awk \"\\\\$2==\\\"$module\\\" {print \\\\$1}\"` //通过insmod调用到scull的init_module函数,然后根据该函数申请到的资源建立相关的节点 rm -f /dev/${device}[0-3] 删除以前建立的节点(应该不会有吧,呵呵,安全起见吧) mknod /dev/${device}0 c $major 0 建立scull0(设备名),c(字符设备),major(init_module里面申请的主设备号),0(scull0设备的次设备号) mknod /dev/${device}1 c $major 1 mknod /dev/${device}2 c $major 2 mknod /dev/${device}3 c $major 3 ln -sf ${device}0 /dev/${device} 建立相关连接 chgrp $group /dev/${device}[0-3] 改变访问它的权限,好象是改成本组的所有兄弟们都可以用那样子的,下条语句才是真的改变,这条应该是编个组吧,呵呵,刚接触linux对相关的命令还不是太熟悉,高手们别嘲笑俺 chmod $mode /dev/${device}[0-3] 这个文件里下面的那些都是和这个差不多的意思,只不过是针对不同的设备的建立节点等等的。 向系统注册你的驱动程序-----init_module函数 这个函数的主要作用是申请主设备号,和申请scull_devices内存资源,并初试化 关键地方如下: result = register_chrdev(scull_major, \"scull\", &scull_fops);向系统注册一个字符设备,并提交相关的驱动函数,如果scull_major是0 的话呢就是申请主设备号,如果不是零呢,就是注册指定的主设备号,不过我自己也觉得作者很对应该申请,否则和容易和别人用的冲突的,呵呵,注册成功呢返回0,如果是申请的话呢返回主设备号,失败的话好象是一个负数吧 if (result < 0) { printk(KERN_WARNING \"scull: can\'t get major %d\\n\",scull_major); return result; } if (scull_major == 0) scull_major = result; /* dynamic */这个语句就很经典啦,如果scull_major是0的话呢,就把申请到的主设备号给到scull_major,因为scull_load脚本下面的语句等着用major呢,呵呵 scull_devices = kmalloc(scull_nr_devs * sizeof (Scull_Dev), GFP_KERNEL); if (!scull_devices) { result = -ENOMEM; goto fail_malloc; }向内存申请scull_devices的资源,并初始化为0 memset(scull_devices, 0, scull_nr_devs * sizeof (Scull_Dev)); for (i=0; i < scull_nr_devs; i++) { scull_devices.quantum = scull_quantum; scull_devices.qset = scull_qset; } 对于quantum和qet初试化为4000和1000(这两个值是在scull.h里面定义的) 下面的是对pipe的和access的初始化(我还没看) 最值得借鉴的呢是再下面的几个出错处理的代码,虽然用到了goto语句,但是用的的确很经典,我将来写驱动一定引用 2 通过你提供的scull_read,scull_write读写内存设备------scull_read函数 在分析这两个函数之前呢,我想得先把这个驱动的主要目的告诉大家,我的书上的77页的那个图最重要了,图名叫做scull设备的结构布局。 大家看那个图,它是这样的一个意思,对于scull0,scull1,scull2,scull3这4个设备的话,每个设备都有独立的4M的空间供他们分别使用,等于说这个4M呢就是实际的设备了,那么我们就可以用驱动来读写这4M里的数据,那么4M的空间是怎么组织的呢。我的理解,它是这样来组织的,对于每个设备如scull0,scull1等,他们各自有一个typedef struct Scull_Dev结构体用于保存特定的狱,void **data; 对应内存的数据 struct Scull_Dev *next; /* next listitem */下一个dev int quantum; /* the current quantum size */为4000, int qset; /* the current array size */为1000 unsigned long size; 整个大小 unsigned int access_key; /* used by sculluid and scullpriv */别的用的 unsigned int usage; 当前是否正在使用的标志 简单的说,它的组织结构呢,就是一个多维数组那样的,qset是第一个下限,quantum是第二个下限,那么我们每次读写那段连续的内存区的时候,就要先找到你的pos所对应的这个树组里面的什么地方,这个会在下面解释到。比如说,我的OPS现在是0x10000,那么这个地址是实际的物理地址,如果我要读这个地址开始的0x200长度区域内的数据的话,我必须先将这个地址定位到是多少qset和多少quantum的地方,然后再看指定的count是否会超过我的这个quantum,如果超过了,那么本次读操作就不会返回实际读到的字节数,而应该是一个小于count的字节数,可能未读完的数据会在read调用里通过修改pos来反复调用scull_read来得到,但是这只是我的猜测,当初看linux的源码,看了一部分就被派去做别的事情了,所以这只是猜测。 在定位好在多维数组data[][]里的地址后,我们就可以开始去读写数据了,但是在这之前,你必须先锁定住该资源,以免产生竞争,比如你在读的时候,正好另外一个进程要写了等等之类的事情。 对于写呢,是这样的,在开始的open调用的时候,如果你是以只写的方式打开的,那么就将你这个当前的ops后面的数据都先清楚掉, 不好意思,先写到这里,现在有点别的事情要做,如果大家需要我再分析下去,请回个贴 最后再次申明,这只是本人自己对scull的理解,请老鸟们不要耻笑,如果里面有不对的地方,还请指正,多谢多谢 |
|