Linux是单内核体系,可通用核算渠道的外围设备是频频改变的,不行能将一切的(包括将来行将呈现的)设备的驱动程序都一次性编译进内核,为了处理这个问题,Linux提出了可加载内核模块(Loadable Kernel Module,LKM)的概念,答应一个设备驱动经过模块加载的办法,在内核运转起来之后”融入”内核,加载进内核的模块和本身就编译进内核的模块如出一辙。
一个程序在编译的地址的相对联系就现已确认了,运转的时分仅仅进行简略的偏移,为了使模块加载进内核后能够被放置在正确的地址,并正确的调用体系的运转的导出符号表,编译模块的时分有必要要运用体系的编译地址,并调用体系编译出得静态的导出符号表。即模块有必要运用体系的装备环境:Makefile+.config,一旦这两个文件恣意一个发生了改变,都很或许导致模块的编译地址与体系的编译地址不匹配,形成运转时的过错乃至宕机。
导出符号表
从供给体系运转功率的视点,一个模块不是也不该该是彻底独立的,即一个模块往往会调用其他模块供给的功用来完成自己的功用,这样做能更好完成体系的分工并进步功率。Linux为了完成模块间的彼此调用,规划了导出符号表,每个模块都能够将自己的一个私有的标号导出到体系层级,以使该标号对其他模块可见,体系在编译一个模块的时分会主动导出这个模块的导出符号表到modules.syms文件(假如没有导出任何符号,可认为空),并在加载一个模块的时分会主动将该模块的导出符号表与体系本身的导出符号表兼并。一个体系的源码的导出符号表一般在源码顶层目录的modules.syms文件中,检查正在运转的体系导出符号表运用cat /proc/kallsyms。留意,正如前面解说的,咱们的模块之所以能够正常运转,一个重要原因便是编译咱们模块运用的符号地址便是编译内核时运用的符号地址,所以运转起来尽管地址会有偏移,可是模块中相关的符号的地址也会和内核地址一同偏移,也就还能找得到。根据这种思维,咱们也能够直接检查体系当时运转的地址,将地址赋值给一个函数指针并运用,也是没有问题的,当然,这仅仅论述原理,并不主张这么写模块。
下面这个比如能够看出编译出的地址和运转时的地址是不一样的:
导出符号表能够大大的进步体系的运转功率,这也是只要开源体系才干供给的一个强壮的功用,可是,导出符号表的引入会导致一个小小的费事–模块的依靠,当咱们运用lsmod的时分,就能够检查体系当时的模块,其终究两列分别是该模块被引证的次数以及引证该模块的内核模块,当一个模块被其他模块引证时,咱们是不能进行卸载的,相同,假如模块A依靠于模块B,那么假如模块B不加载的时分模块A也加载不了。在编写多模块的时分特别要留意这个问题,能够写一个脚本办理多个依靠模块。Linux内核运用两个宏来导出一个模块的符号
EXPORT_SYMBOL(符号名)EXPORT_SYMBOL_GPL(符号名)
模块编译办法
凭借内核的Makefile,编译出的XXX.ko(Kernel Object)便是可加载到该内核的外部模块,为了运用内核的Makefile,咱们能够将编译外部模块的Makefile写成如下的格局:
ifneq ($(KERNELRELEASE),) export-objs = demo.o obj-m = extern.oelse KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd)endifall: $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean: .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c
这个简略的Makfile是运用ubuntu主机的源码Makefile来编译模块,学习模块编程的开端阶段在主机进行编译调试更便利一点,下面我解说一下这个Makefile,首要,咱们的思路仍是经过内核的Makefile来预备咱们的模块,而内核的Makefile一旦履行,就会给KERNELRELEASE这个变量赋值,所以第一次进入咱们这个Makfile的时分,这个变量仍是空,所以履行else的部分——给相关的变量赋值,make默许编译第一个方针all,make -C $(KERNELRELEASE)便是进入到KERNELRELEASE指定的目录并履行里边的Makefile,明显,这便是咱们内核源码的顶层Makefile,接下来的选项M=$(PWD) modules都是传入这个顶层Makefile的参数,表明我要编译一个模块,这个模块坐落M指定的目录,所以内核会进行相关的装备并终究进入到”这个模块地点的目录”,此刻,咱们的这个Makefile会再被进入一次,这一次是从内核Makefile中跳入这儿的,,KERNELRELEASE现已被界说过,内核Makefile想要的便是obj-m后边指定的要编译的方针文件,所以内核Makfile就会找到咱们写的模块源文件进行编译。如此咱们就得到了能在ubuntu下履行的xxx.ko文件,假如需求在开发板上运转,只需求将内核途径改成开发板运转体系的源码途径即可,一起记得要导出相关的环境变量( ARCH, CROSS_COMPILE )
注册/刊出模块
Linux为每个模块都预留了相应的地址,注册模块即让该模块对内核可见,这也是模块作业的先决条件。注册之后,咱们就能够经过检查内核输出信息dmesg指令来检查模块的运转状况。常常运用内核函数printk()来输出体系信息进行打印调试。运用insmod XXX.ko加载一个模块,运用rmmod XXX.ko卸载一个模块,运用lsmod检查当时体系中的模块及其引证状况
insmod运用的是init_module()体系调用,这个体系调用的完成是sys_init_module()
rmmod运用delete_module()体系调用,这个体系调用的完成是sys_delete_module()
模块的程序结构
#include #include #include /* 结构/析构函数 */static int __init mydemo_init(void){ //结构设备/驱动目标 //初始化设备/驱动目标 //注册设备/驱动目标 //必要的硬件初始化} staTIc void __exit mydemo_exit(void){ //收回资源 //刊出设备/驱动目标} /* 加载/卸载模块 */module_init(mydemo_init); module_exit(mydemo_exit); /* 授权 */MODULE_LICENSE(“GPL”); MODULE_AUTHOR(“XJ”);MODULE_DESCRIPTIPON(“mymydemo”);/* 导出符号 */EXPORT_SYMBOL(data);
留意这儿的授权是有必要的,假如一个模块没有授权,那么许多需求该授权的函数乃至都不能运用,同理,不合适的授权也会导致模块运转或加载的过错,所以初学者必定不要忽视这个授权,相关授权的选项在”linux/module.h”中,这儿我把相关的阐明贴出来供咱们参阅
/* * The following license idents are currently accepted as indicaTIng free * software modules * * “GPL” [GNU Public License v2 or later] * “GPL v2” [GNU Public License v2] * “GPL and addiTIonal rights” [GNU Public License v2 rights and more] * “Dual BSD/GPL” [GNU Public License v2 * or BSD license choice] * “Dual MIT/GPL” [GNU Public License v2 * or MIT license choice] * “Dual MPL/GPL” [GNU Public License v2 * or Mozilla license choice] * * The following other idents are available * * “Proprietary” [Non free products] */
另一个细节是Linux内核源码的默许头文件途径是顶层目录的include目录,所以包括头文件的时分include能够省掉,
第一个Linux模块
#include #include #include static int __init demo_init(void){ printk(KERN_INFO”demo_init:%s,%s,%d”__FILE__,__func__,__LINE__); return 0;}static void __exit demo_exit(void){ printk(KERN_INFO”demo_exit:%s,%s,%d”__FILE__,__func__,__LINE__);}module_init(demo_init);module_exit(demo_exit);MODULE_LICENSE(“GPL”);
履行insmod xjDemo.ko,检查履行成果
模块传参
咱们编写的模块还能够在insmod的时分传入参数,Linux供给了几个宏(函数)用于接纳外部的参数。模块内部运用这些函数,只需履行insmod xjDemo.ko num=2,insmod mydemo.ko i=10,insmod mydemo.ko extstr=”hello” 等指令就能够将参数传入模块
module_param(num,type,perm); //接纳一个传入的int数据module_param(num,type,perm); //接纳一个传入的charp数据module_param_array(num,type,nump,perm); //接纳一个数组module_param_string(name,string,len,perm); //接纳一个字符串MODULE_PARAM_DESC(“parameter description”);