向下之旅(二十三):模块

  Linux内核是模块化组成的,它允许内核在运行时动态的向其中插入或从中删除代码。其中包括相关的子例程、数据、函数入口和函数出口被一并组合在一个单独的二进制镜像中,即可装载内核模块中,或被简称为模块。

  构建模块

  在2.6内核中,采用了新的"kbulid"构建系统,现在构建模块相比从前更加容易。构建过程中的第一步是决定在哪里管理模块代码。可以把模块源码加入到内核源代码树中——或者是作为一个补丁或者是最终把你的代码合并到正式的内核代码树中;另一种可行的方式是内核源代码树之外维护和创建你的模块源码。

  1.放在内核源代码树中

  模块放入Linux内核中,会被存放在内核源代码树中。假设你想建立自己代码的子目录,你的驱动程序是一个钓鱼杆和计算机的接口,名为Fish Master XL 2000 Titanium,那么你应该在/drivers/char/目录下创建一个名为fishing的子目录。

  现在你需要向drivers/char/下的Makefile文件中添加一行。编辑drivers/char/Makefile加入:

  obj-m += fishing/

  这行编译指令告诉模块构建系统在编译模块时需要进入fishing/子目录中。更可能发生的是,你的驱动程序的编译取决于一个特殊配置选项,比如,可能CONFIG_FISHING_POLE。如果这样,你需要用下面的指令代替刚才的那条指令:

  obj-$(CONFIG_FISHING_POLE) += fishing/

  最后,在drivers/char/fishing/下,需要添加一个新的Makefile文件,其中需要有下面这行:

  obj-m += fishing.o

  此刻创建系统运行就将会进入fishing/目录下,并且将fishing.c编译为fishing.ko模块。

  2.放在内核代码外

  在你自己的源代码树建立一个Makefile文件,它只需一行指令: obj-m : =fishing.o 就可以把fishing.c编译成fishing.ko。如果你有多个源文件,那么用两行代码足够了:

  obj-m := fishing.o

  fishing-objs := fishing-main.o fishing-line.o

  这样, fishing-main.c和fishing-line.c 就一起被编译和链接到fishing.ko模块内。

  上述两种最大的区别在于构建过程。当你的模块在内核源代码树外围时,你必须告诉make如何找到内核源代码文件和基础Makefile文件。

  安装模块

  编译后的模块被装入到目录/lib/modules/version/kernel/下。比如,如果使用2.6.10内核,而且你将你的模块源代码直接放在divers/char下,那么编译后的钓鱼杆驱动程序的存放路径将是:/lib/modules/2.6.10/kernel/drivers/char/fishing.ko

  下面的构建命令用来安装编译的模块到合适的目录下:

  make modules_install

  通常需要以root权限运行

  产生模块依赖性

  Linux模块之间存在依赖性,也就是说钓鱼模块依赖于鱼饵模块,那么当带入钓鱼模块时,鱼饵模块会被自动载入。这里需要的依赖信息必须事先生成。多数Linux发布版都能自动产生这些依赖关系信息,而且在每次启动时更新。

  载入模块

  载入模块最简单的方法是通过insmod命令,它的作用就是请求内核载入你指定的模块。insmod程序不执行依赖性分析或进一步的错误检查。

  另一种更先进的工具是modprobe,它提供了模块依赖性分析,错误智能检查,错误报告以及许多其他功能和选项。不但会加载指定的模块,而且会自动加载任何它所依赖的有关模块。

  管理配置选项

  从上面看到只要设置了CONFIG_FISHING_POLE配置选项,钓鱼杆模块就将自动编译。由于2.6版本以后,新引入的"kbulid"系统所赐,加入一个新配置选项现在很简单,就是向Kconfig文件中添加一项——用以对应内核源码树。对驱动程序而言,Kconfig通常和源代码处于同一目录。如果钓鱼杆驱动程序在目录drivers/char/下,那么你便会发现drivers/char/Kconfig同时存在。

  如果你建立了一个新子目录,而且也希望Kconfig文件存在于该目录中的话,那么必须在一个已存在的Konfig文件中将它引入,使用下面命令:

  source “drivers/char/fishing/Konfig”

  这里所谓存在的Konfig文件可能是drivers/char/Kconfig.

  可以很方便的在Kconfig文件中加入一个配置选型,钓鱼杆模块的选项如下:

  配置选项第一行定义了该选项所代表的配置目标。CONFIG_前缀并不需要写上。

  第二行声明选项类型为tristate,即可以被编译进内核(Y),也可作为模块编译(M),或者干脆不编译(N)。

  第三行指定了该选项的默认选择,这里默认操作不是编译它。

  Helo指令目的是为该选项提供帮助文档。

  除了上述选项外,还存在其他选项。比如depends指令规定了在该选项被设置前,首先要设置的选项。假如依赖性不满足,那么该选项就被禁止。

  select指令和depends类似,不同之处在于,只要是select指定了谁,它会就强行将被指定的选项打开。此外还有If指定等。

  模块参数

  Linux提供了一个简单的框架——它可允许驱动程序声明参数,从而用户可以在系统启动或者模块装载时在指定参数值。这些参数对于驱动程序属于全局变量。值得一提的是模块参数同时也将出现在sysfs系统文件中。

  定义一个模块参数可通过宏module_param()完成。

  module_param(name, type, perm)

  参数name既是用户可见的参数名,也是模块中存在模块参数的变量名。参数type存放了参数的类型,例如byte,short,unshort,int,uint,long,ulong,charp,bool或invbool。最后一个参数perm指定了模块在sysfs文件系统下对应的文件权限。

  上面的宏其实并没有定义变量,因此需要在宏前定义变量,如下所示:

  static int allow_live_bait = 1;

  module_param(allow_live_bait, bool ,0644)

  导出符号表

  模块被导入后,就会动态连接到内核。它与用户空间的动态连接库类似,只有当被显示导出后的外部函数,才可以被动态库调用。在内核中,到处内核函数需要使用特殊的指令:EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()。

  导出的内核函数可以被模块调用,而未导出的函数模块则无法被调用。而核心代码在内核中可以调用任意非静态接口,因为所有的核心源代码文件被链接成了同一个镜像。

  导出的内核符号表被看做是到处的内核接口,甚至称为内核API。

  导出符号相当简单,在声明函数后,紧跟上EXPORT_SYMBOL()指定就搞定了,比如:

  假定get_pirate_beard_color()同时也定义在一个可访问的头文件中,那么任何模块现在都可以访问它。

  如果你的代码被配置为模块,那么就必须确保它被编译为模块时所用的全部接口都已被导出,否则就会产生连接错误(而且模块不能成功编译)。

  

  参考自:《Linux Kernel Development》.

posted on 2016-04-01 11:47  画家丶  阅读(229)  评论(0编辑  收藏  举报