Linux 可加载内核模块剖析

news2024/11/28 15:41:51

Linux 就是通常所说的单内核(monolithic kernel),即操作系统的大部分功能都被称为内核,并在特权模式下运行。

它与微型内核不同,后者只把基本的功能(进程间通信 [IPC]、调度、基本的输入/输出 [I/O] 和内存管理)当作内核运行,而把其他功能(驱动程序、网络堆栈和文件系统)排除在特权空间之外。

因此,您可能认为 Linux 是一个完全静态的内核,但事实恰恰相反。

通过 Linux 内核模块(LKM)可以在运行时动态地更改 Linux。

可动态更改是指可以将新的功能加载到内核、从内核去除某个功能,甚至添加使用其他 LKM 的新 LKM。

LKM的优点是可以最小化内核的内存占用,只加载需要的元素(这是嵌入式系统的重要特性)。

Linux不是可以进行动态更改的惟一(也不是第一个)单内核。Berkeley Software Distribution(BSD)的变体、Sun Solaris、更老的内核(比如 OpenVMS),以及其他流行的操作系统(比如 Microsoft® Windows® 和 Apple Mac OS X)都支持可加载模块。

剖析内核模块

LKM与直接编译到内核或典型程序的元素有根本区别。典型的程序有一个main函数,其中LKM 包含entry和exit函数(在 2.6 版本,您可以任意命名这些函数)。

当向内核插入模块时,调用 entry函数,从内核删除模块时则调用exit函数。

因为entry和exit函数是用户定义的,所以存在module_init和module_exit 宏,用于定义这些函数属于哪种函数。

LKM还包含一组必要的宏和一组可选的宏,用于定义模块的许可证、模块的作者、模块的描述等等。图 1 提供了一个非常简单的 LKM 的视图。

2.6版本的Linux内核提供了一个新的更简单的方法,用于构建LKM。

构建LKM时,可以使用典型的用户工具管理模块(尽管内部已经改变):

  • 标准 insmod(安装 LKM),
  • rmmod (删除 LKM),
  • modprobe(insmod 和 rmmod 的包装器),
  • depmod(用于创建模块依赖项),
  • 以及 modinfo(用于为模块宏查找值)。

更多关于为2.6版本内核构建LKM的信息,请查看参考资料。

剖析内核模块对象

LKM只不过是一个特殊的可执行可链接格式(Executable and Linkable Format,ELF)对象文件。

通常,必须链接对象文件才能在可执行文件中解析它们的符号和结果。

由于必须将LKM加载到内核后LKM才能解析符号,所以LKM仍然是一个ELF对象。

您可以在LKM上使用标准对象工具(在 2.6 版本中,内核对象带有后缀.ko)。

例如,如果在LKM上使用objdump实用工具,您将发现一些熟悉的区段(section),比如 .text(说明)、.data(已初始化数据)和 .bss(块开始符号或未初始化数据)。

您还可以在模块中找到其他支持动态特性的区段。

.init.text区段包含module_init代码,.exit.text区段包含module_exit代码(参见图 2)。

.modinfo区段包含各种表示模块许可证、作者和描述等的宏文本。

了解LKM的基础知识之后,现在我们进一步探索模块是如何进入内核的,以及在内核内部是如何管理模块的。

LKM的生命周期

在用户空间中,insmod(插入模块)启动模块加载过程。

insmod 命令定义需要加载的模块,并调用init_module用户空间系统调用,开始加载过程。

2.6版本内核的insmod命令经过修改后变得非常简单(70 行代码),可以在内核中执行更多工作。

insmod并不进行所有必要的符号解析(处理 kerneld),它只是通过init_module 函数将模块二进制文件复制到内核,然后由内核完成剩余的任务。

init_module函数通过系统调用层,进入内核到达内核函数sys_init_module(参见图 3)。

这是加载模块的主要函数,它利用许多其他函数完成困难的工作。

类似地,rmmod命令会使delete_module执行system call调用,而delete_module 最终会进入内核,并调用sys_delete_module将模块从内核删除。

在模块的加载和卸载期间,模块子系统维护了一组简单的状态变量,用于表示模块的操作。

加载模块时,状态为MODULE_STATE_COMING。

如果模块已经加载并且可用,状态为MODULE_STATE_LIVE。

此外,卸载模块时,状态为MODULE_STATE_GOING。

模块加载细节

现在,我们看看加载模块时的内部函数(参见图 4)。

当调用内核函数sys_init_module 时,会开始一个许可检查,查明调用者是否有权执行这个操作(通过capable 函数完成)。

然后,调用 load_module 函数,这个函数负责将模块加载到内核并执行必要的调试(后面还会讨论这点)。

load_module函数返回一个指向最新加载模块的模块引用。

这个模块加载到系统内具有双重链接的所有模块的列表上,并且通过notifier 列表通知正在等待模块状态改变的线程。

最后,调用模块的init()函数,更新模块状态,表明模块已经加载并且可用。

加载模块的内部细节是ELF模块解析和操作。

load_module函数(位于 ./linux/kernel/module.c)首先分配一块用于容纳整个ELF 模块的临时内存。

然后,通过copy_from_user函数将ELF模块从用户空间读入到临时内存。

作为一个ELF对象,这个文件的结构非常独特,易于解析和验证。

下一步是对加载的ELF映像执行一组健康检查(它是有效的ELF 文件吗?它适合当前的架构吗?等等)。

完成健康检查后,就会解析 ELF 映像,然后会为每个区段头创建一组方便变量,简化随后的访问。

因为 ELF 对象的偏移量是基于 0 的(除非重新分配),所以这些方便变量将相对偏移量包含到临时内存块中。

在创建方便变量的过程中还会验证ELF区段头,确保加载的是有效模块。

任何可选的模块参数都从用户空间加载到另一个已分配的内核内存块(第4 步),并且更新模块状态,表明模块已加载(MODULE_STATE_COMING)。

如果需要per-CPU数据(这在检查区段头时确定),那么就分配per-CPU块。

在前面的步骤,模块区段被加载到内核(临时)内存,并且知道哪个区段应该保持,哪个可以删除。

步骤7为内存中的模块分配最终的位置,并移动必要的区段(ELF 头中的 SHF_ALLOC,或在执行期间占用内存的区段)。

然后执行另一个分配,大小是模块必要区段所需的大小。

迭代临时ELF块中的每个区段,并将需要执行的区段复制到新的块中。

接下来要进行一些额外的维护。

同时还进行符号解析,可以解析位于内核中的符号(被编译成内核映象),或临时的符号(从其他模块导出)。

然后为每个剩余的区段迭代新的模块并执行重新定位。

这个步骤与架构有关,因此依赖于为架构(./linux/arch//kernel/module.c)定义的 helper 函数。

最后,刷新指令缓存(因为使用了临时 .text 区段),执行一些额外的维护(释放临时模块内存,设置系统文件),并将模块最终返回到 load_module。

模块卸载细节

卸载模块的过程和加载模块基本一样,除了必须进行几个健康检查外(确保安全删除模块)。

卸载模块过程首先在用户空间调用 rmmod(删除模块)命令。

在rmmod命令内部,对delete_module执行系统调用,它最终会导致在内核内部调用 sys_delete_module(查看 图 3)。

图5演示了删除模块的基本操作过程。

当调用内核函数sys_delete_module(将要删除的模块的名称作为参数传入)之后,第一步便是确保调用方具有权限。

接下来会检查一个列表,查看是否存在依赖于这个模块的其他模块。

这里有一个名为 modules_which_use_me 的列表,它包含每个依赖模块的一个元素。

如果这个列表为空,就不存在任何模块依赖项,因此这个模块就是要删除的模块(否则会返回一个错误)。

接下来还要测试模块是否加载。

用户可以在当前安装的模块上调用 rmmod,因此这个检查确保模块已经加载。

在几个维护检查之后,倒数第二个步骤是调用模块的exit函数(模块内部自带)。

最后,调用 free_module 函数。

调用free_module函数之后,您将发现模块将被安全删除。

该模块不存在依赖项,因此可以开始模块的内核清理过程。

首先,从安装期间添加的各种列表中(系统文件、模块列表等)删除模块。

其次,调用一个与架构相关的清理例程(可以在 ./linux/arch//kernel/module.c 中找到)。

然后迭代具有依赖性的模块,并将这个模块从这些列表中删除。

最后,从内核的角度而言,清理已经完成,为模块分配的各种内存已被释放,包括参数内存、per-CPU内存和模块的ELF内存(core 和 init)。

为模块管理优化内核

在许多应用程序中,动态加载模块非常重要,但加载之后,就没有必要卸载模块。

这允许内核在启动时是动态的(根据找到的设备加载模块),但并不是在整个操作过程中都是动态的。

如果不需要在加载之后卸载模块,那么可以进行一些优化,减少模块管理所需的代码。

您可以“取消”内核配置选项CONFIG_MODULE_UNLOAD,删除大量与卸载模块相关的内核功能。

结束语

这一直是内核里面模块管理过程的高级视图。

要获得模块管理的细节,源代码本身就是最佳的文档。

关于在模块管理中调用的主要函数,请查看 ./linux/kernel/module.c(以及 ./linux/include/linux/module.h 中的头文件)。

您还可以在./linux/arch//kernel/module.c 中找到几个与架构相关的函数。

最后,可以在 ./linux/kernel/kmod.c 中找到内核自动加载函数(可以根据需要从内核自动加载模块)。

这个功能可以通过CONFIG_KMOD配置选项启用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/153055.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hudi系列1:Hudi介绍

文章目录一. 什么是Hudi二. 发展历史三. Hudi 功能和特性四. Hudi 基础架构五. 使用公司六. 小结参考:一. 什么是Hudi Apache Hudi(发音“hoodie”)是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接带到数据湖中。Hudi提供了表&#xff0c…

6.6 工具-ELK安装

目录 6.6.1 Elasticsearch安装 6.6.1.1 安装 6.6.1.1.1 window 6.6.1.1.2 Linux 6.6.1.2 问题 6.6.1.2.1 问题一 6.6.1.2.2 问题二 6.6.2 Logstash安装 6.6.2.1 安装 6.6.2.1.1 window 6.6.2.1.2 Linux 6.6.2.2 问题 6.6.2.2.1 问题一 6.6.3 Kibana 6.6.3.1 安装…

论文投稿指南——中文核心期刊推荐(中国医学)

【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…

拿捏几道经典的字符串模拟问题

希望本篇对你有所帮助 我发现这种字符串的问题其实写起来很麻烦,可能思路不难多少都能想到一些,主要就是代码的处理,细节问题。太考验代码编写的能力了。这两天写了好多道字符串,模拟之类的问题,今天就分享分享吧 刚…

算法设计与分析-DP习题

7-1 最小路径和给定一个m行n列的矩阵&#xff0c;从左上角开始每次只能向右或者向下移动&#xff0c;最后到达右下角的位置&#xff0c;路径上的所有数字累加起来作为这条路径的和。求矩阵的最小路径和。输入格式:输入第一行&#xff1a;两个正整数m和n(1<m, n<1000)&…

【C++】非类型模板参数、模板特化、模板的分离编译、模板总结

文章目录一、非类型模板参数二、模板特化1.函数模板特化2.类模板特化三、模板的分离编译四、模板总结一、非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 #define N 10…

Spring_FrameWork_05(AOP)

Spring整合Junit RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(classes SpringConfig.class)加载test运行类和spring配置文件 使用Junit提供的Runwith注解&#xff0c;将Junit原有的运行器替换成spring提供的SpringJUnit4ClassRunner。 这个注解的值就是运…

【计算机视觉】Softmax代码实现、过拟合和欠拟合的表现与解决方法

Softmax原理 Softmax函数用于将分类结果归一化&#xff0c;形成一个概率分布。作用类似于二分类中的Sigmoid函数。 对于一个k维向量z&#xff0c;我们想把这个结果转换为一个k个类别的概率分布p(z)。softmax可以用于实现上述结果&#xff0c;具体计算公式为&#xff1a; 对于k…

程序员不了解这些投简历的巨坑,面试注定一开始就失败!

目录 前言第一阶段&#xff1a;练手第二阶段&#xff1a;冲刺第三阶段&#xff1a;收尾 前言 之前写了两篇文章&#xff0c;给大家介绍了一下如何利用短期的时间&#xff0c;尽可能充分的为面试做准备&#xff1a; 1.《我只是把握好了这3点&#xff0c;1个月后成功拿下大厂…

2023春节祝福系列第一弹(下)(放飞祈福孔明灯,祝福大家身体健康)(附完整源代码及资源免费下载)

2023春节祝福系列第一弹&#xff08;下&#xff09; &#xff08;放飞祈福孔明灯&#xff0c;祝福大家身体健康&#xff09; &#xff08;附完整源代码及资源免费下载&#xff09; 目录 四、画一朵真实的祥云 &#xff08;1&#xff09;、画一个渐变的白色径向渐变背景 &a…

外业调查工具助手,照片采集、精准定位、导航、地图查看

你是不是在外业调查时要背着一堆图纸 是不是一不小心图纸污损或丢失&#xff0c;工作又得重做 是不是经常会出现图纸标注的空间不足 是不是外业采集中要携带一大堆繁琐的仪器 是不是每次收集的数据、照片等在整理的过程中发现工作量巨大 是不是经常会出现采集回来的内容跟…

《MySQL 入门教程》第 36 篇 Python 访问 MySQL

本篇我们介绍如何利用 Python DB API 连接和操作 MySQL 数据库&#xff0c;包括数据的增删改查操作、存储过程调用以及事务处理等。 Python 是一种高级、通用的解释型编程语言&#xff0c;以其优雅、准确、 简单的语言特性&#xff0c;在云计算、Web 开发、自动化运维、数据科…

Spark / Java - atomic.LongAccumulator 与 Spark.util.LongAccumulator 计数使用

目录 一.引言 二.atomic.LongAccumulator 1.构造方法 2.使用方法 3.创建并使用 三.Spark.util.LongAccumulator 1.构造方法 2.使用方法 一.引言 使用 Spark 进行大数据分析或相关操作时&#xff0c;经常需要统计某个步骤或多个步骤的相对耗时或数量&#xff0c;java.u…

Java设计模式-适配器模式Adapter

介绍 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示&#xff0c;主的目的是兼容性&#xff0c;让原本 因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)适配器模式属于结构型模式主要分为三类&#xff1a;类适配器模式、…

树莓派自带的python3.9->python3.7

卸载python3.9&#xff1a;sudo apt-get remove python3卸载之后一些包可以使用sudo apt autoremove这个命令删除卸载成功如果出现问题后续再来更新&#xff08;出现问题后后续安装python也会失败&#xff09;&#xff08;先不要安装先看&#xff09;安装python3.7&#xff1a;…

C语言第30课笔记

1.strerror(errno要包含头文件errno.h) 2.perror头文件为stdio.h 3.一些字符函数 4.字母大小写转换函数 5.memmove理论上是memcpy的升级版(可以自己拷贝自己)。 6.匿名结构体类型在类型创建好了之后直接创建变量&#xff0c;只能用一次。两个完全相同的匿名结构体类型&#xf…

【八】Netty HTTP协议--文件服务系统开发

Netty HTTP协议--文件服务系统开发介绍HTTP-文件系统场景描述流程图代码展示netty依赖服务端启动类 HttpFileServer服务端业务逻辑处理类 HttpFileServerHandler结果展示错误路径文件夹路径文件路径遗留bugbug版本总结介绍 由于Netty天生是异步事件驱动的架构&#xff0c;因此…

java EE初阶 — Synchronized 的原理

文章目录1. Synchronized 的优化操作1.1 偏向锁1.2 轻量级锁&#xff08;自旋锁&#xff09;1.3 重量级锁2. 其他的优化操作2.1 锁消除2.2 锁粗化3. 相关面试题1. Synchronized 的优化操作 两个线程针对同一个对象加锁&#xff0c;就会产生阻塞等待。 Synchronized 内部其实还有…

ubuntu docker elasticsearch kibana安装部署

ubuntu docker elasticsearch 安装部署 所有操作尽量在root下操作. 安装docker 1. 由于是基于宝塔面板安装的所以简答的点击操作即可完成安装. 我这里已经是正常的安装好了. 2.dcoker 镜像加速 https://cr.console.aliyun.com/cn-hangzhou/instances访问这个网址进去进行了…

快速上手Golang

自动推导赋值:自动推导赋值Go中 不同的数据类型不能进行计算对于浮点型默认都是float64 精确到小数点后15位单引号的 为字节类型 一位0~255的字符转换双引号的 为字符串类型多重赋值多重赋值a,b:1,2格式输出格式输出printf“%3d”三位整数&#xff0c;不满足三位时头部补空格“…