3. 内核模块管理:从概念到实战

内核模块,说白了就是Linux内核的“插件”。

我刚开始接触Linux时,总觉得内核是个庞然大物,动一下就得重新编译。后来才知道,大部分功能都可以通过模块按需加载。这就像你买了个手机,系统是固定的,但APP可以随时装、随时卸。内核模块就是内核的APP。

3.1 内核模块的概念

内核模块(Kernel Module)是一段可以在运行时动态加载到内核的代码。它不需要重新编译整个内核,也不需要重启系统。

为什么要用模块?我遇到过这样一个场景:一台服务器需要支持多种网卡驱动。如果全部编译进内核,内核会变得臃肿,启动也慢。用模块就灵活多了——哪个网卡插上去,对应的模块自动加载。这就是模块化的好处。

核心特点:

  • 动态加载:运行时加载,无需重启
  • 按需使用:用的时候加载,不用就卸载
  • 独立编译:模块可以单独编译,不影响内核
  • 节省内存:不用的模块不占用内存空间

模块文件通常以.ko(Kernel Object)结尾,存放在/lib/modules/$(uname -r)/目录下。你想想看,这个目录里可能有几百个模块,但系统启动时只加载了其中一小部分。

3.2 三大核心命令:lsmod、modprobe、modinfo

这三个命令,是我日常排查问题时用得最多的。咱们一个一个说。

3.2.1 lsmod —— 查看已加载的模块

lsmod命令列出当前内核中所有已加载的模块。它的输出格式很简单:

$ lsmod
Module                  Size  Used by
ext4                  589824  1
usb_storage            73728  0
vfat                   20480  1
fat                    81920  1 vfat

每一列的含义:

  • Module:模块名称
  • Size:模块占用的内存大小(字节)
  • Used by:被谁使用。数字表示引用计数,后面是依赖它的模块名

我习惯用lsmod | grep xxx来快速定位某个模块是否加载。比如检查USB存储驱动:

$ lsmod | grep usb_storage
usb_storage            73728  0

如果输出为空,说明这个模块没加载。嗯,这里要注意:Used by为0,表示当前没有其他模块依赖它,可以安全卸载。

3.2.2 modinfo —— 查看模块的详细信息

modinfo命令用来查看模块的元数据。它读取的是模块文件中的描述信息。

$ modinfo ext4
filename:       /lib/modules/5.15.0/kernel/fs/ext4/ext4.ko
license:        GPL
description:    Fourth Extended Filesystem
author:         Remy Card, Stephen Tweedie, ...
depends:        mbcache, jbd2
retpoline:      Y
intree:         Y
name:           ext4
vermagic:       5.15.0 SMP mod_unload modversions

我最关注的是depends字段。它告诉你这个模块依赖哪些其他模块。比如ext4依赖mbcache和jbd2。加载ext4之前,这两个模块必须先加载。

小技巧:用modinfo -F depends <模块名>可以只提取依赖信息,方便脚本处理。

3.2.3 modprobe —— 模块加载与卸载的瑞士军刀

modprobe是我最常用的模块管理工具。它比insmod和rmmod聪明得多——它会自动处理依赖关系。

举个例子,你想加载一个网卡驱动e1000:

$ sudo modprobe e1000

如果e1000依赖pci_hotplug,modprobe会自动先加载pci_hotplug。这就是它和insmod最大的区别。

我曾经在调试一个存储设备时,直接用insmod加载驱动,结果报了一堆“undefined symbol”错误。后来才发现是依赖模块没加载。用modprobe就省心多了。

3.3 模块的加载与卸载

加载和卸载模块,有几种方式。我按使用频率排个序:

3.3.1 加载模块

命令 特点 适用场景
modprobe <模块名> 自动处理依赖,推荐使用 日常加载
insmod /path/to/module.ko 手动指定路径,不处理依赖 调试自定义模块
modprobe -a <模块1> <模块2> 批量加载多个模块 批量操作

注意:insmod需要指定模块文件的完整路径。而modprobe只需要模块名,它会自动在/lib/modules/$(uname -r)/下查找。

3.3.2 卸载模块

# 卸载单个模块
$ sudo modprobe -r <模块名>

# 或者用 rmmod
$ sudo rmmod <模块名>

卸载时有个坑:如果模块正在被使用,卸载会失败。比如:

$ sudo rmmod ext4
rmmod: ERROR: Module ext4 is in use

为什么会这样?因为文件系统挂载着,内核还在用ext4模块。你得先卸载文件系统,才能卸载模块。

我曾经在生产环境上犯过这个错——想卸载一个网卡驱动,结果忘了还有SSH连接。一卸载,自己先断开了。嗯,从那以后我养成了习惯:卸载前先用lsmod看看引用计数。

3.4 模块依赖关系

模块依赖是内核模块管理中最容易出问题的地方。我画了一张图,帮你理清思路:

内核模块依赖关系示意图 模块A (网卡驱动) 模块B (PCI核心) 模块C (电源管理) 依赖 依赖 加载顺序:先加载模块C → 再加载模块B → 最后加载模块A 卸载顺序相反:先卸载A → 再卸载B → 最后卸载C 关键点: • modprobe 会自动按依赖顺序加载模块 • 卸载时如果模块被依赖,会提示 "in use" • 使用 lsmod 查看 Used by 列,了解依赖关系

依赖关系其实就是一个有向无环图。模块A依赖模块B,模块B依赖模块C。加载时必须从叶子节点开始,卸载时从根节点开始。

查看依赖关系,除了用modinfo,还可以直接看/lib/modules/$(uname -r)/modules.dep文件。这个文件记录了所有模块的依赖关系:

$ cat /lib/modules/$(uname -r)/modules.dep | grep ext4
kernel/fs/ext4/ext4.ko: kernel/fs/mbcache/mbcache.ko kernel/fs/jbd2/jbd2.ko

冒号前面是模块本身,冒号后面是它依赖的模块。多个依赖用空格分隔。

实战建议:如果你在编译自定义内核模块,记得运行depmod命令更新依赖文件。否则modprobe可能找不到你的模块。

3.5 避坑指南

这些年我踩过的坑,总结成几条:

  • 不要在生产环境随意卸载模块——我曾经在数据库服务器上卸载了SCSI驱动模块,结果磁盘阵列直接离线。还好有备用节点。
  • 模块加载失败时,先看dmesg——内核的错误信息都输出到内核日志里。用dmesg | tail查看最后几条,通常能找到原因。
  • 黑名单机制——有些模块你不想让它自动加载,可以加入黑名单。在/etc/modprobe.d/下创建.conf文件,写入blacklist 模块名。
  • 模块参数——很多模块支持参数。比如modprobe usb-storage delay_use=0可以设置USB存储设备的延迟时间。用modinfo -p 模块名查看支持的参数。

内核模块管理,说白了就是理解“加载-使用-卸载”这个生命周期。掌握了lsmod、modprobe、modinfo这三个命令,再配合依赖关系的理解,大部分问题都能搞定。

想要更多资料,联系可可