Linux 是单内核操作系统,所有的内核功能被整体编译在一起,形成一个单独的内核镜像文件。
优点是执行效率非常高,缺点是要增加、删除、修改内核的某个功能,需要重新编译和重启整个系统。
后来 Linux 引入内核模块来弥补这一缺点。
内核模块是单独编译的一段代码,在 Linux 运行时可动态的加载和卸载,不需要重启整个系统。。
内核模块有利于减小内核镜像文件的体积,因为一个产品并不会使用所有的功能,把需要的部分编译进镜像中就行了。
内核模块并不特指驱动程序。
模块编程与应用编程的区别:
内核模块基本格式
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int __init mydrv_init(void) {
printk("Hello linux module init!\n");
return 0;
}
static void __exit mydrv_exit(void) {
printk("Hello linux module exit!\n");
}
module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("HLTJ");
MODULE_DESCRIPTION("Driver Description");
MODULE_VERSION("1.0");
MODULE_ALIAS("my-drv");
module_init() 宏:模块被加载时,返回0表示成功,返回负值表示失败。
module_exit() 宏:模块被卸载时
老版本的是固定的函数名 init_module、cleanup_module,已经不用了。
初始化和销毁函数都只会被调用一次,所以该函数所占用的内存应该被释放掉,加上 __init 和 __exit 就是这个作用。
函数名可以自定义带来的一个问题是,可能和内核中的某个函数重名了,导致编译时出现函数重复定义问题。
C 语言没有 C++ 中的命名空间,所以使用 static 将函数的链接属性设置为内部,从而解决该问题。
内核模块信息宏:
MODULE_LICENSE("GPL"); 版权声明,这个是必须的,防止你写的代码污染了开源的内核的版,没有这行代码,内核中的某些功能函数无法使用,并且会报错:
常用:
GPL
GPL v2
MODULE_AUTHOR:作者
MODULE_DESCRIPTION:模块的描述
MODULE_VERSION:
MODULE_ALIAS:可以有多个别名
编译内核模块
(1)编译进Linux内核镜像
当模块调试没问题后直接编译到内核中。
1、学习阶段的驱动,没啥实际意义,放在 /drivers/misc/ 杂项驱动目录中。
mydrv.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int __init mydrv_init(void) {
printk("Hello linux module init!\n");
printk("####################\n####################\n####################\n");
return 0;
}
static void __exit mydrv_exit(void) {
printk("Hello linux module exit!\n");
printk("####################\n####################\n####################\n");
}
module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("HLTJ");
MODULE_DESCRIPTION("Driver Description");
MODULE_VERSION("1.0");
MODULE_ALIAS("my-drv");2、在 /drivers/misc/Kconfig 中添加一行
menu "Misc devices"
config MY_DRV
tristate "test driver"
default y
help
The driver only for study.default y 这里默认是选中的。
3、在 /drivers/misc/Makefile 中添加一行
obj-$(CONFIG_MY_DRV) += mydrv.oconfig 名字 和 CONFIG_名字,这俩的名字要保持一致。
mydrv.o 是 mydrv.c 这俩的名字要保持一致。
4、图形界面中勾选

5、编译
make zImage -j$(nproc)6、看启动日志,驱动被正确加载

编译到内核中的模块不支持卸载。
(2)编译出单独的模块
用于模块调试阶段,编译模块之前需要先完整的编译一次内核镜像,因为 ko 模块的编译依赖内核生成的文件。
需要 Linux 内核支持模块动态加载,一般默认都是支持的:

模块随便放在哪里。
Makefile 文件模板:
KERNELDIR := /opt/Linux_Workspace/i.MX6ULL/zdyz/linux
CURRENT_PATH := $(shell pwd)
obj-m := mydrv.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules -j$(nproc)
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean编译:
make多个源文件编译为一个内核模块:
内核模块操作
1)加载模块
insmod 命令
insmod 模块文件名.komodprobe 命令,推荐使用。
modprobe 模块文件名
modprobe 模块别名模块加载后使用 dmesg 命令,查看模块的输出信息:
2)卸载模块
rrmod 模块文件名3)查看模块信息
modinfo 模块文件名