转载自:
(1)https://blog.csdn.net/sh21_/article/details/60878812?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight
(2)https://www.cnblogs.com/wangyuezhuiyi/archive/2011/11/15/2250102.html
(3)https://blog.csdn.net/u010632165/article/details/86541941?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight
内核模块基本原理:
内核模块是可以根据实际需要可以动态加载和卸载到内核中的代码。它们扩展了内核的功能,而无需重启系统,就可以进行模块加载,并工作。
Linux 内核模块(LKM)是可以根据实际需要可以动态加载和卸载到内核中的代码。它们扩展了操作系统内核功能却不需要重新编译内核、启动系统。如果没有内核模块,就不得不反复编译生成操作系统的内核镜像来加入新功能,当附加的功能很多时,还会使内核变得臃肿。一个Linux 内核模块主要由以下几个部分组成:
(1) 模块加载函数(必须):当通过insmod 或modprobe 命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块相关初始化工作。
(2) 模块卸载函数(必须):当通过rmmod 命令卸载模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。
(3) 模块许可证声明(必须):模块许可证(LICENCE)声明描述内核模块的许可权限,如果不声明LICENCE,模块被加载时将收到内核被污染的警告。大多数情况下,内核模块应遵循GPL 兼容许可权。Linux2.6 内核模块最常见的是以MODULE_LICENSE(“Dual BSD/GPL”)语句声明模块采用BSD/GPL 双LICENSE。
(4) 模块参数(可选):模块参数是模块被加载的时候可以被传递给他的值,它本身对应模块内部的全局变量。
(5) 模块导出符号(可选):内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数。
(6) 模块作者等信息声明(可选)。
一个内核模块至少包含两个函数,模块被加载时执行的初始化函数init_module()和模块被卸载时执行的结束函数cleanup_module()。在最新内核稳定版本2.6 中,两个函数可以起任意的名字,通过宏module_init()和module_exit()注册调用要编译内核模块,把代码嵌进内核空间,首先要获取内核源代码,且版本必需与当前正在运行的版本一致。
helloworld.c
(1) moudle.h 包含了大量加载模块需要的函数和符号的定义.
(2) init.h 来指定你的初始化和清理函数
(3) MODULE_LICENSE("GPL");指定代码使用哪个许可
内核认识的特定许可有,
"GPL"( 适用 GNU 通用公共许可的任何版本 ),
"GPL v2"( 只适用 GPL 版本 2 ),
"GPL and additional rights",
"Dual BSD/GPL",
"Dual MPL/GPL",
"Proprietary".
(4) 除此之外还可以包含模块的其他描述性定义
MODULE_AUTHOR ( 声明谁编写了模块 ),
MODULE_DESCRIPION( 一个人可读的关于模块做什么的声明 ), MODULE_VERSION ( 一个代码修订版本号),
MODULE_ALIAS ( 模块为人所知的另一个名子 ),
MODULE_DEVICE_TABLE ( 来告知用户空间, 模块支持那些设备).
(5) static int hello_init(void)
初始化函数应当声明成静态的,
static void hello_exit(void)
清理函数, 它注销接口, 在模块被去除之前返回所有资源给系统
(6) module_init(hello_init);
这个宏定义增加了特别的段到模块目标代码中, 表明在哪里找到模块的初始化函数. 没有这个定义, 你的初始化函数不会被调用.
module_exit(hello_exit);
(7)printk
在 Linux 内核中定义并且对模块可用; 它与标准 C 库函数 printf 的行为相似. 内核需要它自己的打印函数, 因为它靠自己运行
(8)字串 KERN_ALERT 是消息的优先级,因为使用缺省优先级的消息可能不会在任何有用的地方显示
Makefile
(1)obj-m := hello.o
表明有一个模块要从目标文件 hello.o 建立. 在从目标文件建立后结果模块命名为 hello.ko。
(2)KernelDIr := /lib/modules/$(shell uname -r)/build/
用来定位内核源码目录
(3)PWD := $(shell pwd)
获得当前目录路径
(4)M=$(PWD) M= 选项
使 makefile 在试图建立模块目标前, 回到你的模块源码目录
(5)$(MAKE) -C $(KERNELDR) M=$(PWD) modules
这个命令开始是改变它的目录到用 -C 选项提供的目录下( 就是说, 你的内核源码目录 ). 它在那里会发现内核的顶层 makefile. 这个 M= 选项使 makefile 在试图建立模块目标前, 回到你的模块源码目录. 这个目标, 依次地, 是指在 obj-m 变量中发现的模块列表, 在我们的例子里设成了 module.o.
(6)$(MAKE) -C $(KERNELDR) M=$(PWD) modules_install
用于模块的安装
(7)rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
用于make clean清除上次编译生成的文件
模块加载、查看、卸载
insmod helloworld.ko #将编译生成的ko文件加载到内核中
lsmod | grep helloworld #查看内核模块是否有helloword
rmmod helloworld.ko #删除内核中的ko模块
模块调试:
dmesg | grep "init success" # 在终端查看init函数中,KERN——INFO的打印信息
dmesg | grep "exit success" # 在终端查看exit函数中,KERN——INFO的打印信息
dmesg #查看内存缓冲区的内容
dmesg | tail -12 #查看内核输出信息,tail -12 显示最后12条;
dmesg | grep word_count |tail -n 2
cat /var/log/syslog | grep word_count |tail -n 2
modinfo 模块名.ko #查看模块路径、协议、作者等信息
echo 'la zi ji' > /dev/wordcount #向设备文件写数据
dmesg
初次编写时出现的问题:
1. 卸载函数exit的返回值必须为void;
2. $(MAKE) -C $(KERNELDIR) M=$(PWD) 后面只能是modules 或空