环境:
- 编译好的内核源码/home/cjl/driver/kernel
- x6818开发板,使用上边的内核
- 网络根文件系统 /home/cjl/driver/rootfs
- 将交叉编译工具目录下sysroot/lib和sysroot/usr/lib中的*.so*拷到下位机lib下
编写hello.c, 详情见注释
//以下两个头文件是内核模块编程时必须要加的,位于内核源码中
#include <linux/init.h>
#include <linux/module.h>
//声明为开源程序
//否则会报内核污染 而且内核中的大量功能(由EXPORT_SYMBOL_GPL导出的符号不可被非开源程序使用)限制使用
MODULE_LICENSE("GPL");
/*
如果该函数希望在安装内核模块时被调用到
该函数的类型必须为 int (*)(void)
并且该函数需要被__init修饰
__init,它是一个宏
被其修饰的函数链接时存入自定义段“.init.text”
放入该段的代码执行一次 其对应的内存空间就释放
static 加或不加都行,此处加是为了防止编译时和内核中的函数冲突,其实没必要加
内核中回调该函数时,虽然该函数加上了static,但是内核是通过函数指针直接调用到的,不影响内核调用它
*/
static int __init hello_init(void)
{
printk("<1>" "helloworld!\n");
return 0;
}
/*
希望在卸载模块时被调用
它也必须合规:void (*)(void)
__exit,也是一个宏
被其修饰的函数链接时放入“.exit.text”
该段的代码执行一次对应的内存空间也会被释放
static 加与不加都可
*/
static void __exit hello_exit(void)
{
printk("<1>" "goobbye!\n");
}
//被moudule_init宏修饰的函数在安装模块时会被调用
module_init(hello_init);
//被module_exit修饰的函数在卸载模块时会被调用
module_exit(hello_exit);
编写Makefile
KERNEL=/home/cjl/driver/kernel/
obj-m += hello.o
all:
make -C $(KERNEL) M=$(PWD) modules
cp *.ko /home/cjl/driver/rootfs/
clean:
make -C $(KERNEL) M=$(PWD) clean
Makefile说明
-C:指定进入哪个目录进行编译,该位置指定的是内核源码目录,该目录下的代码应该是编译通过的
M=$(PWD), pwd 当前路径
modules, 属于固定写法
执行效果
在上位机cp hello.ko /home/cjl/driver/rootfs
下位机insmod hello.ko
lsmod
rmmod hello
注:保持板子上的内核和/home/cjl/driver/kernel中编译出的内核是同步的,一致的,内核中加减功能一般都无所谓,但是如果修改了内核版本(配置文件中CONFIG_LOCALVERSION的值),是不能成功安装模块的。
补充:
- lsmod字段含义为 模块名 大小(B) 引用次数 引用它的模块名
- MODULE_DESCRIPTION(可选) MODULE_AUTHOR(可选)
- 一个.c文件中,至多只有一个函数可被module_init(), 至多只有一个函数可被module_exit()
- 一个内核模块中,至多只有一个函数可被module_init(), 至多只有一个函数可被module_exit()
- 第3点和第4点本质为:一个.ko里边只能有一个module_init和一个module_exit!
注:一个.c和一个模块的关系为,一个模块可以由一个或者多个.c实现,多个文件实现一个模块需要修改Makefile文件,使最终生成1个.ko文件,否则,每个.c都将生成一个.ko,内核安装模块时(insmod),需要根据依赖关系依次安装,详情见下篇博客 - 如果rmmod报错缺少目录,则主动创建即可, mkdir /lib/modules/`uname -r` -p,注意`不是单引号’