20251228 - Linux 驱动文件 (.ko) 深度解析笔记

学习笔记:Linux 驱动模块 (.ko) 全解

前置知识:在 Linux 的世界里,我们通常把 内核层(Kernel)驱动层(Driver) 视为一体,统称为内核态 ;而把 用户层应用层 统称为用户态。驱动其实是"寄生"在内核里的。内核是框架,而驱动是具体实现。

1. 身份揭秘:.ko 到底是什么?

1.1 名字的由来

  • 全称K ernel Object(内核对象)。
  • 后缀.ko

1.2 它是什么?(生活类比)

如果把 Linux 内核比作一辆正在高速公路上行驶的汽车

  • 内核 (zImage/uImage):是汽车的主体(引擎、底盘、车轮),出厂时就焊死在一起了。
  • 应用程序 (App):是车里的乘客。
  • 驱动模块 (.ko) :是**"USB 车载配件"**(比如一个外接的行车记录仪,或者车载空气净化器)。

特点:

  1. 即插即用 :你不需要把车停下来拆开发动机,在车跑的过程中插上(insmod)就能用。
  2. 附属品:它不能独立运行,必须==依附于汽车(内核)==才能工作。你不能把行车记录仪扔在地上让它自己跑。
  3. 权限极高:它一旦插上,就成了汽车电路的一部分,如果它短路了(代码写错了),整辆车都会熄火(内核崩溃/Kernel Panic)。

1.3 技术定义

从文件格式上讲,.ko 文件本质上是一种 ELF (Executable and Linkable Format) 文件。

它和你在 Ubuntu 上看到的 .o 文件(目标文件)非常像,但多了一些"自我介绍"的信息(比如它支持哪个版本的内核、它的依赖关系等),专门用于让内核加载器识别。


2. 前世:.ko 是怎么变出来的?(编译原理)

很多初学者最大的困惑是:"为什么编译驱动不能像编译 hello.c 那样简单?"

2.1 编译的核心差异

  • 编译 APP:只需要标准 C 库(stdio.h 等)。
  • 编译驱动坚决不能 用标准 C 库!驱动直接运行在内核里,它需要的是内核源码 提供的数据结构和函数定义(如 struct file, printk)。

所以,编译 .ko 必须手里有一份**"对应版本的内核源码"**。

2.2 编译过程详解

假设你有 driver.c,编译成 driver.ko 经历了以下步骤(Makefile 帮你做的):

  1. 预处理 & 编译 (.c -> .o)

    交叉编译器(gcc)读取内核源码的头文件,把 C 代码翻译成汇编,再转成机器码(.o 文件)。

    此时,代码里的 printk(内核提供的 )只是一个空标签,还不知道 printk 函数具体在内存的哪里。

  2. 生成版本信息 (.mod.c -> .mod.o)

    构建系统会自动生成一个 driver.mod.c 文件,里面记录了:"我是为 Linux 4.1.15 版本编译的,我依赖哪些符号"。然后把它也编译成 .mod.o。

  3. 链接 (.o + .mod.o -> .ko)

    链接器(ld)把你的驱动逻辑 (.o) 和版本信息 (.mod.o) 打包在一起,生成最终的 driver.ko。

总结.ko = 你的代码逻辑 + 专门给内核看的"入职简历"(版本校验信息)。


3. 今生:insmod 的原理(加载机制)

当你输入 insmod driver.ko 并回车时,就像给新员工办理入职手续。这个过程在内核里称为 "动态链接"

3.1 insmod 干了四件大事:

第一步:读取文件 (Read)

insmod 命令(其实是调用了 finit_module 系统调用)把 driver.ko 文件从磁盘(或 Flash)里原封不动地读到了内存中。

第二步:地址重定位 (Relocation) ------ 最核心步骤

这是最难理解的,请仔细看:

  • 问题 :驱动代码里有一些跳转指令(比如 if 判断跳转),在编译的时候,编译器不知道驱动会被放在内存的哪个位置(因为内存是动态分配的)。编译器只能填个相对偏移量(比如:往后跳 100 步)。
  • 解决内核给驱动分配了一块真实的物理内存地址(比如 0xC0001000)。然后,内核里的加载器会修改驱动代码中的地址,把那些"相对偏移"全部改成绝对真实的内存地址。
    • 类比:你住酒店。预订时你只知道是"标准间"(相对概念),只有办理入住(insmod)时,前台(内核)才会告诉你具体是"305 房间"(绝对地址)。
第三步:符号解析 (Symbol Resolution)
  • 问题 :你的驱动里调用了 printk。但 printk 的代码是在内核里的,你的驱动怎么找到它?

  • 解决:内核有一张"公开导出符号表"(Exported Symbol Table),里面记录了 printk 在内存 0x80045000 的位置。

    加载时,内核会把你的驱动里所有调用 printk 的地方,直接填上 0x80045000 这个地址。

    • 类比:新员工(驱动)入职,问老板:"打印机(printk)在哪里?" 老板指了一下:"在走廊尽头(地址)。"
第四步:执行初始化 (Init)

一切准备就绪,内核找到你代码中标记为 module_init 的函数(比如 my_driver_init),执行它。

  • 这就相当于新员工入职后的"自我介绍"。通常在这里注册设备号、初始化 GPIO 等。

4. 为什么要用 .ko?为什么不直接编进内核?

你可能会问:"为什么不把所有驱动都直接编译到 zImage 里?这样开机就有了,不用 insmod,多省事?"

比较维度 静态编译 (zImage) 动态模块 (.ko)
形象比喻 你的手臂(长在身上) 手里的锤子(拿起来用)
优点 启动速度快,不需要文件系统支持 灵活!修改驱动只需重编 .ko (1秒),不用重编内核 (10分钟)
缺点 内核体积巨大;每次修改都要重启刷机 启动时需要脚本加载
适用场景 必须有的驱动(如文件系统、LCD驱动) 调试阶段的驱动、非必要的设备(如USB设备)

对于初学者:

永远使用 .ko 方式开发! 想象一下,如果你每改一行代码,都要重新编译整个内核并烧写到板子上,你需要 20 分钟才能看一次运行结果;而用 .ko,只需要 make -> cp -> insmod,10 秒钟搞定。


5. 极简总结(复习小抄)

  1. .ko 是什么 :Linux 的动态链接库 ,相当于 Windows 的 .dll
  2. 编译前提 :必须指定内核源码路径,不能用普通 GCC 编译。
  3. insmod 原理
    • 搬运:把文件搬到内存。
    • 改地址:告诉驱动它住在内存哪里。
    • 找帮手:帮驱动找到内核函数(如 printk)的地址。
    • 跑函数 :运行 module_init 里的代码。
  4. 卸载rmmod 命令会执行 module_exit 函数,并释放内存,就像把 USB 拔掉一样。
相关推荐
￴ㅤ￴￴ㅤ9527超级帅2 小时前
4、stm32异常与中断
stm32·单片机·嵌入式硬件
BreezeJuvenile4 小时前
通用定时器_测量PWM方波的周期和占空比案例
stm32·单片机·学习·通用定时器·pwm输入·测量占空比
进阶的猪4 小时前
stm32f407 RCC时钟配置
stm32·单片机·嵌入式硬件
小刘爱玩单片机6 小时前
【stm32简单外设篇】- 三色LED
c语言·stm32·单片机·嵌入式硬件
d111111111d6 小时前
STM32平衡车测试,定时中断读取速度
笔记·stm32·单片机·嵌入式硬件·学习·模块测试
Dillon Dong7 小时前
从C到Simulink:什么是MATLAB_MEX_FILE 宏,如何阻挡STM32 HAL 头文件
c语言·stm32·matlab
Darken037 小时前
基于STM32---编码器测速(利用GPIO模拟脉冲信号)
人工智能·stm32·串口助手·gpio模拟编码器
youcans_9 小时前
【STM32-MBD】(9)Simulink 模型开发之上位机显示波形
stm32·单片机·嵌入式硬件·上位机·simulink
兆龙电子单片机设计9 小时前
【STM32项目开源】STM32单片机智能家居语音控制系统
stm32·单片机·物联网·开源·毕业设计·智能家居