提示:本系列文章重点学习Linux内核
Linux内核--内核模块源码/相关资源
简介
提示:在这里先对Linux内核模块源码做一个概述,以便大家能更好的理解:
Linux内核模块是一种动态加载到内核中并可以在运行时加载和卸载的代码单元。它们提供了一种灵活的方式,使开发人员能够扩展和定制Linux内核的功能,而无需重新编译整个内核。本文将介绍Linux内核模块源码的概述,包括模块的定义、结构、编写方式以及常见用途。
提示:以下是本篇文章将重点阐述Linux内核的内核模块源码以及内核源码的一写资源分享
一、模块的定义
Linux内核模块是一种可插拔的代码单元,它们可以动态加载到内核中,并在不需要时卸载。模块可以包含设备驱动程序、文件系统支持、网络协议栈等功能,以及特定于某个子系统的代码。
二、模块的结构
Linux内核模块通常具有以下结构:
- 模块初始化函数:当模块被加载到内核时,内核会调用模块的初始化函数,执行一些必要的初始化操作,例如注册设备、申请资源等。
- 模块退出函数:当模块被卸载时,内核会调用模块的退出函数,执行一些清理操作,例如释放资源、取消注册等。
- 模块信息:包括模块的作者、描述、版本号等信息,用于标识模块和提供相关信息。
三、 编写模块的方式
编写Linux内核模块的方式包括:
- 使用C语言编写:Linux内核模块通常使用C语言编写,利用内核提供的API来与内核进行交互。开发人员可以使用任何文本编辑器编写模块源码。
- 使用内核API:模块可以使用Linux内核提供的API来访问内核的功能和数据结构,例如设备驱动程序、文件系统、网络协议栈等。
- 编译为可加载模块:模块的源码需要经过编译,生成可加载模块文件(.ko文件),然后可以使用
insmod
命令加载到内核中。
四、常见用途
Linux内核模块的常见用途包括:
- 设备驱动程序:实现新硬件设备的驱动程序,以支持新设备的操作和通信。
- 文件系统支持:添加新的文件系统类型或文件系统功能,以满足特定的存储需求。
- 网络协议栈:实现新的网络协议或网络功能,以扩展Linux内核的网络能力。
- 功能扩展:为内核添加新的功能和特性,以满足特定的应用场景或需求。
五、Linux内核的插件机制——内核模块
类似于浏览器、IDEA这些软件的插件开发,Linux提供了一种可以向正在运行的内核中插入新的代码段、在代码段不需要继续运行时也可以从内核中移除的机制,这个可以被插入、移除的代码段被称为内核模块。
(1)作用:
单内核扩展性差的缺点
减小内核镜像文件体积,一定程度上节省内存资源
提高开发效率
不能彻底解决稳定性低的缺点:内核模块代码出错可能会导致整个系统崩溃
六、模块基础代码解析
(1)内核模块的本质
一段隶属于内核的“动态”代码,与其它内核代码是同一个运行实体,共用同一套运行资源,只是存在形式上是独立的。
(2)模块三要素
入口函数、出口函数、MODULE__LICENSE
#include <linux/module.h> //包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h> //包含模块编程相关的宏定义,如:MODULE_LICENSE
/*该函数在模块被插入进内核时调用,主要作用为新功能做好预备工作
被称为模块的入口函数
__init的作用 :
1. 一个宏,展开后为:__attribute__ ((__section__ (".init.text"))) 实际是gcc的一个特殊链接标记
2. 指示链接器将该函数放置在 .init.text区段
3. 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
*/
int __init myhello_init(void)
{
/*内核是裸机程序,不可以调用C库中printf函数来打印程序信息,
Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel)
printk不支持浮点数打印*/
printk("myhello is running\n");
printk("#####################################################\n");
return 0;
}
/*该函数在模块从内核中被移除时调用,主要作用做些init函数的反操作
被称为模块的出口函数
__exit的作用:
1.一个宏,展开后为:__attribute__ ((__section__ (".exit.text"))) 实际也是gcc的一个特殊链接标记
2.指示链接器将该函数放置在 .exit.text区段
3.在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置
*/
void __exit myhello_exit(void)
{
printk("myhello will exit\n");
}
/*
MODULE_LICENSE(字符串常量);
字符串常量内容为源码的许可证协议 可以是"GPL" "GPL v2" "GPL and additional rights" "Dual BSD/GPL" "Dual MIT/GPL" "Dual MPL/GPL"等, "GPL"最常用
其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证
在模块插入到内核时,内核会检查新模块的许可证是不是也遵循GPL协议,
如果发现不遵循GPL,则在插入模块时打印信息:
myhello:module license 'unspecified' taints kernel
Disabling lock debugging due to kernel taint
也会导致新模块没法使用一些内核其它模块提供的高级功能
*/
MODULE_LICENSE("GPL");
/*
module_init 宏
1. 用法:module_init(模块入口函数名)
2. 动态加载模块,对应函数被调用
3. 静态加载模块,内核启动过程中对应函数被调用
4. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
5. 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名
*/
module_init(myhello_init);
/*
module_exit宏
1.用法:module_exit(模块出口函数名)
2.动态加载的模块在卸载时,对应函数被调用
3.静态加载的模块可以认为在系统退出时,对应函数被调用,实际上对应函数被忽略
4.对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
5.对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名
*/
module_exit(myhello_exit);
七、内核模块的多源文件编程
(1)Makefile中:
obj-m 用来指定模块名,注意模块名加.o而不是.ko
可以用 模块名-objs 变量来指定编译到ko中的所有.o文件名(每个同名的.c文件对应的.o目标文件)
一个目录下的Makefile可以编译多个模块:
添加:obj-m += 下一个模块名.o
(2)内核模块信息宏
MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明
MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明
MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名
这些宏用来描述一些当前模块的信息,可选宏
这些宏的本质是定义static字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo字段,可以用modinfo命令来查看这些模块信息,用法:
modinfo 模块文件名
八、 注意事项
在编写Linux内核模块时,需要注意以下事项:
- 遵循内核编程规范:编写模块时应遵循Linux内核编程规范,包括代码风格、命名约定等。
- 处理错误和异常情况:模块应该具备良好的错误处理和异常情况处理能力,以确保系统的稳定性和可靠性。
- 测试和验证:编写模块后,需要进行充分的测试和验证,确保模块的功能和性能符合预期,并且不会引入不必要的问题。
相关资源
Linux内核源码下载:
下载教程:Linux内核介绍and下载-CSDN博客
下载地址:The Linux Kernel Archives
Linux内核结构详细描述图:
Interactive map of Linux kernel (makelinux.github.io)
Linux指令查询
Linux指令大全【详细介绍Linux命令】-CSDN博客