本文共 4934 字,大约阅读时间需要 16 分钟。
驱动程序通常是以内核模块的形式存在。
Linux内核的整体结构非常庞大,其包含的组件也非常多,如果把所有的组件都编译进内核文件,即:zImage或bzImage,这样会导致内核文件过大, 占用内存过多。解决方法是让内核文件本身并不包含某组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中,而这些组件就是内核模块。由此可得知内核模块的特点有:
①模块本身并不被编译进内核文件中 ②根据需求,在内核运行期间动态地安装或卸载一个内核模块开发例程:
#include#include static int hello_init(void){ printk(KERN_WARNING"Hello, world !\n"); return 0;}static void hello_exit(void){ printk(KERN_INFO "Goodbye, world\n");} module_init(hello_init);module_exit(hello_exit);
由上面的例程,我们可以发现:
①一般的应用程序都有main()函数,该函数是程序的入口。但内核模块并没有main()函数。实际上,内核模块不仅有入口函数,还有出口函数,入口函数又叫做加载函数,出口函数又叫做卸载函数。当将编译后的内核模块安装到内核时,调用的函数就是加载函数;当将内核模块卸载时调用的函数就是卸载函数。通过module_init()来指明加载函数,module_exit()指明卸载函数。 ②头文件:<linux/init.h><linux/module.h>
。内核模块不能使用用户空间的库函数,即不能使用/usr/include下的头文件,只能使用/usr/src下包含的头文件。 ③static是为了防止函数命名污染 对比应用程序,内核模块具有以下不同:
①应用程序是从头(main)到尾执行任务,执行结束后从内存中消失。 ②内核模块的初始化函数结束时,模块仍然存在于内核中,直到卸载函数被调用,模块才从内核中消失(系统关闭也会使内核模块消失)编写好程序后,接下来就要编写Makefile文件来编译内核模块(ps:名称为Makefile,不能为makefile)。如需要编译的文件为hello.c,则Makefile内容可以为:
obj-m :=hello.oKDIR :=/lib/modules/$(shell uname -r)/buildall : make -C $(KDIR) M=$(PWD) modules
如果需要编译的文件不止一个源文件,如有hello.c,printf.c,则Makefile可编写为:
obj-m :=test.otest-objs :=hello.o printf.oKDIR :=/lib/modules/$(shell uname -r)/buildall : make -C $(KDIR) M=$(PWD) modules
生成的内核模块文件为test.ko
一般采用的Makefile模版为:
ifneq ($(KERNELRELEASE),)obj-m := hello.oelseKDIR := /lib/modules/$(shell uname -r)/buildall: make -C $(KDIR) M=$(PWD) modulesclean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.unsigned *.orderendif
①KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量。ifneq($(KERNELRELEASE),)
判断该变量是否为空。在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容。
KDIR := /lib/modules/ $(shell uname -r) /build
是给KDIR这个变量赋值,值为当前linux运行的内核源码。 ③当make的目标为all时,-C $(KDIR)
指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD)
表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已经被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。 ④可以把上述的Makefile文件作为一个模版,只需要改动obj-m := hello.o这条语句就可以了(该Makefile编译生成的内核模块运行在pc机Linux系统上,可通过修改KDIR改换运行环境) 安装内核模块使用的命令是insmod,如:insmod hello.ko
卸载内核模块使用的命令是rmmod,如:rmmod hello 查看安装的内核模块使用的命令是lsmod内核模块有一些可选模块,包括
①模块声明 ②模块参数 ③符号输出在Linux的文件module.h中包含着一些宏,这些宏的作用是用来对模块的声明和描述。下面我们把这些宏中常用的罗列在下面。
MODULE_AUTHOR (author); //声明模块的作者MODULE_DESCRIPTION (description); //声明模块的描述MODULE_VERSION (version_string); //声明模块的版本MODULE_DEVICE_TABLE (table_info); //声明模块的设备表MODULE_ALIAS (alternate_name); //声明模块的别名MODULE_LICENSE("GPL"); //声明模块的许可证
通过宏module_param指定保存模块参数的变量。模块参数用于在加载模块时传递参数给模块。module_param(name,type,perm)
name:变量的名称 type:变量类型,bool:布尔型 int:整型 charp:字符串型 perm是访问权限。 S_IRUGO:读权限 S_IWUSR:写权限 例如:int a;module_param(a,int, S_IRUGO);char *buf;module_param(buf,charp, S_IRUGO);
一个例子:
#include#include MODULE_AUTHOR("jx");MODULE_LICENSE("GPL");int a;module_param(a,int, S_IRUGO);char *buf;module_param(buf,charp, S_IRUGO);static int hello_init(void){ printk("<0>""a is :%d\n",a); printk("<0>""buf is :%s\n",buf); printk("<0>""Hello, world !\n"); return 0;}static void hello_exit(void){ printk(KERN_INFO "Goodbye, world\n");} module_init(hello_init);module_exit(hello_exit);
在安装该内核模块时,需要输入模块参数,,如:insmod hello.ko a=10 buf=hello
假如有两个内核模块printf.ko,hello.ko,其中hello.ko中需要使用printf.ko里面定义的函数,可以在printf.c中将该函数导出,当编译并安装好printf.ko后,其它内核模块就可以使用该函数了。
内核符号的导出使用宏 EXPORT_SYMBOL(符号名) EXPORT_SYMBOL_GPL(符号名) 说明:EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块printf.c程序代码:
#include#include int printf_hello(){ printk("<0>""hello world\n"); return 0; }EXPORT_SYMBOL(printf_hello);static int printf_init(void){ return 0;}static void printf_exit(void){ printk(KERN_INFO "Goodbye, world\n");} module_init(printf_init);module_exit(printf_exit);
hello.c程序代码:
#include#include MODULE_AUTHOR("jx");MODULE_LICENSE("GPL");extern int printf_hello(void);static int hello_init(void){ printf_hello(); return 0;}static void hello_exit(void){ printk(KERN_INFO "Goodbye, world\n");} module_init(hello_init);module_exit(hello_exit);
必须先安装好printf.ko,然后才能成功安装hello.ko
printk是内核中出现最频繁的函数之一,它与printf不同之处有:
①printk用于内核中的打印信息;printf用于应用程序 ②printk根据严重程度,可以对打印的信息添加优先级 在#define KERN_EMERG "<0>" /* 致命级:紧急事件消息,系统崩溃之前提示,表示系统不可用*/#define KERN_ALERT "<1>" /* 警戒级:报告消息,表示必须采取措施*/#define KERN_CRIT "<2>" /* 临界级:临界条件,通常涉及严重的硬件或软件操作失败*/#define KERN_ERR "<3>" /* 错误级:错误条件,驱动程序常用KERN_ERR来报告硬件错误*/#define KERN_WARNING "<4>" /* 告警级:警告条件,对可能出现问题的情况进行警告*/#define KERN_NOTICE "<5>" /* 注意级:正常但又重要的条件,用于提醒*/#define KERN_INFO "<6>" /* 通知级:提示信息,如驱动程序启动时,打印硬件信息*/#define KERN_DEBUG "<7>" /* 调试级:调试级别的信息*/
没有指定优先级的printk默认使用DEFAULT_MESSAGE_LOGLEVEL优先级,它是一个在kernel/printk.c中定义的整数。
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
通过优先级可以控制信息打印的地方,当优先级过低时,信息不会打印到终端。