Linux内核设备驱动概述
1、对于执行该应用程序的进程而言,建立起的连接就表现为一个已打开文件。从应用程序的角度看,设备文件逻辑上的空间是个线性空间。从这个逻辑空间到具体设备的物理空间的映射则由内核提供,并划分成文件操作与设备驱动两个层次。在物理介质上的第一层抽象使操作者不必关心读/写的物理位置究竟在哪一个磁道,哪一个扇区;而第二层抽象则使操作者不必关心读/写的内容在哪一个逻辑“记录块”中。很自然地,我们把第一层抽象归入设备驱动层,而第二层抽象则归入文件系统层。
2、对于普通文件,即“磁盘文件”,文件的逻辑空间在文件系统层内按具体文件系统的结构和规则映射到设备的线性逻辑空间,然后在设备驱动层进一步从设备的逻辑空间映射到其物理空间。这样,一共经历了两层映射。或者,也可以反过来说,磁盘设备的物理空间经过两层抽象而成为普通文件的线性逻辑空间。而对于“设备文件”,则文件的逻辑空间通常直接就等价于设备的逻辑空间,所以在文件系统层就不需要有映射。
3、内核中有个全局量jiffies,这个变量在每次时钟中断时都要递增,因此可以用作基本的计时手段。假定在一个可安装模块中需要访问这个变量,就可以在其源代码中把它说明为“外部”(extern)变量。在编译时,gcc知道这是一个需要在连接时解决的外部符号,但是并不知道这个变量在哪里,所以就将其地址暂时空着,留待连接时再来填写。到安装模块时,应用程序可以通过系统调用向内核查询变量jiffies所在的位置,只要这个变量是允许移出的,内核就会将其地址返回给应用程序。而应用程序则将其地址填入该模块中所有访问这个变量的指令内以及其他所有要用到其地址的地方。这样,变量jiffies就“连接”上了。
4、“设备文件”是文件系统中代表设备的特殊文件。与普通的文件相比,设备文件在磁盘(或宿主文件系统所在的其他设备)上只占一个索引节点,而没有任何用于存放数据的记录块与之相联系。当然,这是因为设备文件的目的并不在于存储和读取数据,而只在于为应用程序提供一条通向具体设备的访问途径,使应用程序可以跟具体设备建立起连接。可想而知,既然没有用于数据的记录块,则索引节点中的记录块映象表就没有什么用处了。mknod()主要用于设备文件的创建。
5、insmod所做的事情:
1)打开待安装模块并将其读入到用户空间。 所谓“模块”就是经过编译但未经连接的.o文件。
2)模块中必定有一些在模块内部无法落实的符号(函数名或变量名),对这些符号的引用必须连接到内核中的相应符号,也就是必须把这些符号在内核映象中的地址填入模块中需要访问这些符号的指令中以及数据结构中。为此目的,需要通过系统调用query_module()向内核询问这些符号在内核中的地址。如果内核允许“移出”这些符号的地址,就会返回有关的“符号表”。有些符号可能并不属于内核本身,而属于已经安装的其他模块。
3)取得了内核“移出”的符号表以后,就应该可以使模块中所有的符号引用都得到落实了。这部分操作与编译后的连接相似。不过,常规的“连接”常常是双向的,而现在只是在模块中引用内核里的符号或者某些已经安装的模块中的符号,而内核却并不要求(也不能)反过来引用这个待安装模块的符号。当然,内核最后一定会要访问模块中的某些变量或调用模块中的某些函数,否则模块中的函数就得不到执行,模块的存在也就失去了意义。从这个意义上说,模块与内核的连接只完成了一半,我们不妨称之为“完成了单向连接”的模块映象。
4)然后,通过系统调用create_module()在内核中创建一个module数据结构,并且“预订”所需的系统(内核)空间。
5)最后,通过系统调用init_module()把用户空间中完成了单向连接的模块映象装入内核空间,再调用模块中一个名为init_module()的函数。注意,不要把可安装模块中的函数init_module()与系统调用init_module()搞混淆了,这完全是两码事。系统调用init_module()在内核中的实现是sys_init_module(),这是由内核提供的,整个内核中只有这么一个。而模块中的函数init_module(),则是由可安装模块本身提供的,每个模块都有这样一个函数。通常,每个模块的init_module()负责向内核“登记”本模块中的一些包含着函数指针的数据结构(例如file_operations结构)。完成了这种登记以后,模块与内核之间的连接(另一个方向上的连接)才真正完成了。
6、指针deps指向一个module_ref结构数组,ndeps则为该数组的大小。这个数组中的每一个元素都通过其指针dep指向一个module结构,这些就是给定模块所依赖的模块。要安装一个模块时,这个模块所依赖的所有模块都必须已经安装好。
7、内核中的每一个符号都必须通过宏定义EXPORT_SYMBOL明确规定准予移出,才能由/sbin/insmod通过query_module()系统调用获得这些符号的地址,否则就都是不准移出的。