linux插入模块和删除模块

embedded/2025/3/4 7:24:45/

一、基础知识

        模块时一种向Linux内核添加设备驱动、文件系统及其他组件的有效方法,不需要重新编译内核或者重启系统。

内核模块具有以下优点:

        1.通过使用模块,内核发布者能够预先编译大量驱动程序,而不会使内核映像的尺寸放生膨胀。

        2.内核开发者可以将试验性的代码打包到模块中,模块可以卸载,修改代码或者重新打包后再重新装载。

1.添加和删除

        从用户角度看,模块可以通过两个不同的系统程序添加到运行的内核中。他们分别是modprobe和insmod。modprobe在识别出目标模块所依赖的模块之后,在内核也会使用insmod,在用户空间对模块的处理也是基于insmod。

        在处理该系统调用时,模块代码首先复制到内核内存中。接下来是重定位工作和解决模块中未定义的引用。因为模块使用了持久编译到内核中的函数,在模块本身编译时无法确定这些函数的地址,所以需要在这里处理未定义的引用。

        处理未解决的引用,为与内核的剩余部分协作,模块必须使用内核提供的函数。这些可能是通用的辅助函数,比如几乎内核每一部分都是用的printk或者kmalloc。

        很明显这些函数定义在内核的基础代码中,因而已经加载到内存,但是如本找到与相关函数名称匹配的地址,以便解决这些引用呢?为此,内核提供了一个所有到处函数的列表。该列表给出了所有到处函数的内存地址和对应的函数名,可以通过proc文件系统访问。即/proc/kallsyms:

场景描述

假设需要加载驱动模块A(driverA.ko),其依赖模块B(driverB.ko),操作流程如下:

步骤1:确认依赖关系
# 查看模块A的依赖信息
modinfo driverA.ko | grep depends  # 输出示例:depends: driverB
步骤2:递归加载依赖
# 加载基础依赖模块B(需使用绝对路径)
sudo insmod /lib/modules/$(uname -r)/kernel/drivers/base/driverB.ko# 验证模块B是否加载成功
lsmod | grep driverB  # 应显示模块B信息# 加载主模块A
sudo insmod /path/to/driverA.ko# 验证完整加载
dmesg | tail -10  # 查看内核日志中的初始化消息
步骤3:卸载逆向操作
# 先卸载主模块A
sudo rmmod driverA# 再卸载依赖模块B(需确保无其他模块依赖它)
sudo rmmod driverB

         在加载模块时所需要的操作,与通过ld和ld.so借助于动态库借链接应用程序的操作。从外部来看,模块只是普通的可重定位目标文件。file命令的输出表明模块文件是可重定位的,这是用户空间程序设计中一种专业术语。可重定位文件的函数都不会引用绝对地址,而只是向指向代码中的相对地址,因此可以在内存的任意偏移地址加载,当然在映像加载到内存时映像中的地址要由动态链接器ld.so进行适当的修改即可。重定位的工作有内核自身执行,而不是动态装载器。

         ramfs模块允许在内存中建立一个文件系统(成为RAM磁盘),因而需要调用register_filesystem函数将自身添加到内核中可用于文件系统的列表。此模块还使用内核代码中的generic_file_read/generic_write标准函数,基本大多数内核文件系统都会使用这些普通函数。

二、插入和删除模块

       用户空间工具和内核的模块实现之间的接口,包块两个系统调用。

  • init_module:用于将新模块插入内核,用户空间工具提供二进制数据,重定位和解决引用等工作由内核完成。
  • delete_module:功能是从内核移除模块,但要求该模块代码及其导出函数都不再被使用。
  • request_module:不是系统调用,可从内核端加载模块,还能实现热插拔功能 。

 1.模块的表示        

        module是一个非常重要的数据结构。内核中驻留的每个模块,都分配了该结构的一个实例。

        state表示该模块的当前状态,可以从枚举类型module_state取值:

 

         syms,num_syms和crc用于管理模块导出的符号。syms是一个数组,有num_syms个数组项,数组项类型为kernel_symbol,负责将标识符(name)分配到内存地址(value)

        

         在导出符号时,内核不仅考虑可以有所有模块使用符号,还要考虑只能由GPL兼容模块使用的符号。

        模块的二进制数据有两个部分 :初始化部分和核心部分

        内核提供license_is_gpl_compatible函数来判断给许可是否与GPL兼容

2.依赖关系和引用

 若模块 B 使用模块 A 提供的函数,二者就存在关联,有两种理解角度:
        一是模块 B 依赖模块 A,即模块 A 未驻留内核内存时,模块 B 无法装载;
        二是模块 B 引用模块 A,即模块 B 未移除时,模块 A 无法从内核移除,实际上需所有引用模块 A 的模块都移除才行,内核将这种关系称为模块 B 使用模块 A。为管理这些依赖关系,内核需引入新的数据结构。 

3.模块的二进制结构 

模块采用 ELF 二进制格式,包含一些普通程序或库中没有的额外段(少量编译器产生的重定位段与讨论无关)。
生成模块的步骤有三步:
        首先,将模块源代码中的所有 C 文件编译成普通的.o 目标文件;
        然后,内核分析目标文件,把找到的附加信息(如模块依赖关系)保存在独立文件中并编译为二进制文件;
        最后,把前两步产生的二进制文件链接起来,得到最终模块。

a.初始化及清理函数

        <init.h>中的module_init和module_exit宏用于定义init函数和exit函数。

b.导出符号

        内核为导出符号提供了两个宏:EXPORT_SYMBOL 和 EXPORT_SYMBOL_GPL。前者用于一般的导出符号,后者只用于 GPL 兼容代码的导出符号,目的是将相应符号放置到模块二进制映象的适当段中。

c.一般模块信息

        模块许可证、开发者和描述、备选名称、基本版本控制 

4.插入模块

        init_module系统调用是用户空间和内核之间用于装载新模块的接口,通过load_module函数将二进制数据传输到内核地址空间中:

        在实现load_module时会出现异常,内核源代码中该函数:完成所有碰到的异常问题,此函数可以完成的任务如下:

        1.从用户空间复制模块数据到内核地址空间中的一个临时内存位置

        2.查找各个段的位置

        3.确保内核和模块版本控制字符串和struct module的定义匹配问题

        4.将存在的各个段分配到其在内存中的最终位置

        5.重定位符号并解决引用,链接到模块符号任何版本控制信息都会注意到处理模块的参数。

5.删除模块 

        从内核移除模块比插入模块简单得多,系统调用delete_module函数来实现移除模块:

6.整体流程

 

在Linux内核中,模块的加载过程涉及用户空间工具与内核函数的协同工作。以下是insmod命令触发模块加载时的详细流程:


1. 用户空间操作:insmod触发系统调用

  • 当用户执行insmod example.ko时,insmod工具会:
    1. 读取模块的二进制文件(.ko)。
    2. 调用系统调用 init_module,将模块数据传递给内核。

2. 内核处理:init_module系统调用

  • init_module 是内核提供的系统调用(系统调用号由内核版本决定,如sys_init_module)。
  • 它的主要任务:
    1. 权限检查:验证用户是否有权限加载模块(需CAP_SYS_MODULE权限)。
    2. 调用load_module:将模块数据交给内核内部的load_module函数处理。

3. 内核核心函数:load_module

  • load_module 是模块加载的核心函数,负责:
    1. 解析ELF格式:检查模块的ELF头、段信息。
    2. 版本校验:确保模块与当前内核版本兼容(通过vermagic字符串)。
    3. 符号重定位:处理模块对其他模块或内核符号的引用(如调用resolve_symbol)。
    4. 分配内存:为模块的代码(.text)、数据(.data)等段分配内存。
    5. 注册模块元数据:创建struct module实例,记录模块状态、符号表等信息。
    6. 调用模块初始化函数:执行由module_init宏定义的函数(如example_init)。

4. 模块初始化:module_init函数

  • 开发者通过module_init(example_init)定义的函数example_init
    • load_module完成后被调用。
    • 负责模块特有的初始化工作(如注册设备驱动、注册文件系统等)。
    • 如果初始化失败,内核会回滚加载操作。


http://www.ppmy.cn/embedded/169843.html

相关文章

Python网络爬虫技术详解文档

Python网络爬虫技术详解文档 目录 网络爬虫概述爬虫核心技术解析常用Python爬虫库实战案例演示反爬虫机制与应对策略爬虫法律与道德规范高级爬虫技术资源推荐与学习路径1. 网络爬虫概述 1.1 什么是网络爬虫 网络爬虫(Web Crawler)是一种按特定规则自动抓取互联网信息的程序…

【计算机网络入门】初学计算机网络(七)

目录 1. 滑动窗口机制 2. 停止等待协议&#xff08;S-W&#xff09; 2.1 滑动窗口机制 2.2 确认机制 2.3 重传机制 2.4 为什么要给帧编号 3. 后退N帧协议&#xff08;GBN&#xff09; 3.1 滑动窗口机制 3.2 确认机制 3.3 重传机制 4. 选择重传协议&#xff08;SR&a…

W3C标准和ES规范之一文通

W3C标准和ES规范之一文通 以下是关于W3C标准和ES规范的透彻解析&#xff0c;通过结构化对比和生活化类比帮助理解和记忆&#xff1a; 一、核心概念对比&#xff08;总览&#xff09; 维度W3C标准ES规范&#xff08;ECMAScript&#xff09;定位Web技术的建筑蓝图JavaScript的语…

计算机网络-面试总结

计算机网络 从输入一个URL到页面加载完成的过程 整体流程 DNS查询过程SSL四次握手HTTP 的长连接与短连接 HTTP 的 GET 和 POST 区别浏览器访问资源没有响应&#xff0c;怎么排查? OSI七层参考模型 TCP/IP四层参考模型比较 TCP/IP 参考模型与 OSI 参考模型 TCP三次握手&四…

使用haproxy实现MySQL服务器负载均衡

一、环境准备 主机名IP地址备注openEuler-1192.168.121.11mysql-server-1openEuler-2192.168.121.12mysql-server-2openEuler-3192.168.121.13clientRocky-1192.168.121.51haproxy 二、mysql-server配置 [rootopenEuler-1 ~]# yum install -y mariadb-server [rootopenEuler…

【错误记录】Windows 中 DevEco Studio 真机调试无法连接设备 ( 低版本的 HarmonyOS 4.2.0 华为手机无法在 DevEco Studio 5.0.2 上真机调试 )

文章目录 一、错误记录二、问题排查三、解决方案 参考文档 : hdc&#xff08;HarmonyOS Device Connector&#xff09;文档设备连接后&#xff0c;无法识别设备的处理指导真机调试流程 一、错误记录 手机 使用的是 HarmonyOS 4.2.0 系统 ; 使用 HarmonyOS 的 hdc 工具 , 执行 …

postgresql链接详解

PostgreSQL连接概述 连接基础 在探讨PostgreSQL连接的基础之前&#xff0c;我们需要理解什么是数据库连接。 数据库连接 是客户端应用程序与数据库服务器之间建立的一种通信通道&#xff0c;使用户能够访问和操作数据库中的数据。 PostgreSQL连接涉及以下几个关键要素&#…

微软具身智能感知交互多面手!Magma:基于基础模型的多模态AI智能体

作者&#xff1a; Jianwei Yang, Reuben Tan, Qianhui Wu, Ruijie Zheng, Baolin Peng, Yongyuan Liang, Yu Gu, MuCai, SeonghyeonYe, JoelJang, Yuquan Deng, Lars Liden, Jianfeng Gao 单位&#xff1a;微软研究院&#xff0c;马里兰大学&#xff0c;威斯康星大学麦迪逊分校…