内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
现在我们开始编写最简单的模块程序,HelloWorld模块。
1.编写hello.c
/* hello world driver for kernel 2.6 */
#include<linux/init.h> //包含init.h的目的是指定初始化和清除函数。
#include <linux/module.h> //包含有可装载模块需要的大量符号和函数的定义。
/*
* 这是模块的初始化函数,它必需包含诸如要编译的代码、初始化数据结构等内容。
*/
static int __init my_init(void)
{
printk("Hello, old kernel 2.6.xxx\n");
return 0;
}
/*
* 这是模块的退出和清理函数。此处可以做所有终止该驱动程序时相关的清理工作。
*/
static void __exit my_exit(void)
{
printk("Bye kernel\n");
}
/*
* 这是驱动程序初始化的入口点。对于内置模块,内核在引导时调用该入口点;
* 对于可加载模块则在该模块插入内核时才调用。
*/
module_init(my_init);
/*
* 对于可加载模块,内核在此处调用hello_exit函数,而对于内置的模块,
* 它什么都不做。
*/
module_exit(my_exit);
MODULE_AUTHOR("JX");//描述模块作者
MODULE_LICENSE("GPL");//指定代码所需要的许可证
MODULE_VERSION("1.0");//描述模块版本
MODULE_DESCRIPTION("Simple test for module");//用来说明模块用途的简短描述
驱动程序说白了就是提供函数接口给用户空间的程序调用。在c语言中都有main()入口,那设备驱动程序的入口在哪儿呢?就是module_init(),它的参数是一个函数指针,告诉说咋们的入口在hello_init()。明白这层意思,module_exit就好理解了。
2.编写Makefile
obj-m :=hello.o
KERNEL :=/lib/modules/`uname -r`/build //将目录改为内核所在目录
all:
make -C $(KERNEL) M=`pwd` modules
install:
make -C $(KERNEL) M=`pwd` modules_install
clean:
make -C $(KERNEL) M=`pwd` clean
rm -f .*.swp Module.* modules.*
make -C $(KERNEL) M=$(PWD) modules这一句可能不太容易理解,首先改变目录到-C选项指定的目录(即内核源代码目录),M=选项让该makefile在构造modules目标返回之前到模块源代码目录,然后,modules目标指向obj-m变量设定的模块。
3.调试程序
//装载和卸载
insmod:将驱动模块加载到内核
rmmod:将驱动模块移出内核
lsmod:查看模块信息。信息来源于文件/proc/modules
modprobe 命令是比较高级的加载和删除模块命令,其可以解决模块之间的依赖性问题。
modinfo命令用于查询模块的相关信息。
dmesg 显示内核信息
dmesg -c 显示信息後,清除ring buffer中的内容。
加载模块
在加载模块之前要先用dmesg -c 命令清除中的ring buffer的内容
显示模块
在终端输入lsmod 则可看到我们加载的hello模块
显示模块信息
在终端输入modinfo hello.ko
卸载模块