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,模块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这三个命令,再配合依赖关系的理解,大部分问题都能搞定。
想要更多资料,联系可可