管理 Linux 内核模块
了解 Linux 内核
Linux内核是Linux操作系统的核心。它包含对硬件进行寻址的主要组件,并允许用户和硬件之间的通信和交互。 Linux 内核不是一个单一的系统,而是非常灵活的,并且内核可以通过所谓的内核模块进行扩展。什么是内核模块?
一般来说,内核模块是“可以根据需要加载和卸载到内核中的一段代码”。它们扩展了内核的功能,而无需重新启动系统”[1]。这导致操作过程中具有很大的灵活性。
此外,“内核模块可以配置为内置或可加载。要动态加载或删除模块,必须在内核配置中将其配置为可加载模块”[1]。这是在内核源文件 /usr/src/linux/.config [2] 中完成的。内置模块用“y”标记,可加载模块用“m”标记。作为示例,清单 1 演示了 SCSI 模块的这一点:
清单 1:SCSI 模块使用声明
CONFIG_SCSI=m # loadable module
# CONFIG_SCSI # variable is not set
我们不建议直接编辑配置文件,而是使用命令“make config”、“make menuconfig”或“make xconfig”来定义Linux内核中相应模块的用法。
模块命令
Linux 系统附带了许多不同的命令来处理内核模块。这包括列出当前加载到 Linux 内核中的模块、显示模块信息以及加载和卸载内核模块。下面我们将更详细地解释这些命令。
对于当前的 Linux 内核,kmod 包 [3] 提供了以下命令。所有命令都是 kmod 的符号链接。
使用 lsmod 列出当前加载的模块
我们从 lsmod 命令开始。 lsmod 是“list module”的缩写,它通过很好地格式化文件 /proc/modules 的内容来显示当前加载到 Linux 内核中的所有模块。清单 2 显示了由三列组成的输出:模块名称、内存中使用的大小以及使用该特定模块的其他内核模块。
清单 2:使用 lsmod
Module Size Used by
ctr 12927 2
ccm 17534 2
snd_hrtimer 12604 1
snd_seq 57112 1
snd_seq_device 13132 1 snd_seq
...
$
查找当前内核可用的模块
可能存在您还不知道的可用内核模块。它们存储在目录 /lib/modules 中。借助 find 并结合 uname 命令,您可以打印这些模块的列表。 “uname -r”仅打印当前运行的 Linux 内核的版本。清单 3 演示了旧版 3.16.0-7 Linuxkernel 的这一点,并显示了 IPv6 和 IRDA 的模块。
清单 3:显示可用模块(选择)
/lib/modules/3.16.0-7-amd64/kernel/net/ipv6/ip6_vti.ko
/lib/modules/3.16.0-7-amd64/kernel/net/ipv6/xfrm6_tunnel.ko
/lib/modules/3.16.0-7-amd64/kernel/net/ipv6/ip6_tunnel.ko
/lib/modules/3.16.0-7-amd64/kernel/net/ipv6/ip6_gre.ko
/lib/modules/3.16.0-7-amd64/kernel/net/irda/irnet/irnet.ko
/lib/modules/3.16.0-7-amd64/kernel/net/irda/irlan/irlan.ko
/lib/modules/3.16.0-7-amd64/kernel/net/irda/irda.ko
/lib/modules/3.16.0-7-amd64/kernel/net/irda/ircomm/ircomm.ko
/lib/modules/3.16.0-7-amd64/kernel/net/irda/ircomm/ircomm-tty.ko
...
$
使用modinfo显示模块信息
命令 modinfo 告诉您有关所请求的内核模块的更多信息(“模块信息”)。作为参数,modinfo 需要完整的模块路径或简单的模块名称。清单 4 演示了处理红外直接访问协议栈的 IrDA 内核模块的情况。
清单 4:显示模块信息
filename: /lib/modules/3.16.0-7-amd64/kernel/net/irda/irda.ko
alias: net-pf-23
license: GPL
description: The Linux IrDA Protocol Stack
author: Dag Brattli <dagb@cs.uit.no> & Jean Tourrilhes <jt@hpl.hp.com>
depends: crc-ccitt
vermagic: 3.16.0-7-amd64 SMP mod_unload modversions
$
输出包含不同的信息字段,例如内核模块的完整路径、别名、软件许可证、模块描述、作者以及内核内部结构。 “depends”字段显示了它所依赖的其他内核模块。
信息字段因模块而异。为了将输出限制为特定信息字段,modinfo 接受参数“-F”(“–field”的缩写),后跟字段名称。在清单 5 中,输出仅限于使用许可证字段提供的许可证信息。
清单 5:仅显示特定字段。
GPL
$
在较新的 Linux 内核中,提供了有用的安全功能。这涵盖了加密签名的内核模块。正如 Linux 内核项目网站 [4] 上所解释的,“这可以通过禁止加载未签名的模块或使用无效密钥签名的模块来提高内核安全性。模块签名使将恶意模块加载到内核中变得更加困难,从而提高了安全性。模块签名检查由内核完成,因此不需要具有“受信任的用户空间位”。下图显示了 parport_pc 模块的情况。
使用 modprobe 显示模块配置
每个内核模块都带有特定的配置。命令 modprobe 后跟选项“-c”(“–showconfig”的缩写)列出了模块配置。与 grep 结合使用,此输出仅限于特定符号。清单 6 演示了 IPv6 选项的这一点。
清单 6:显示模块配置
alias net_pf_10_proto_0_type_6 dccp_ipv6
alias net_pf_10_proto_33_type_6 dccp_ipv6
alias nf_conntrack_10 nf_conntrack_ipv6
alias nf_nat_10 nf_nat_ipv6
alias nft_afinfo_10 nf_tables_ipv6
alias nft_chain_10_nat nft_chain_nat_ipv6
alias nft_chain_10_route nft_chain_route_ipv6
alias nft_expr_10_reject nft_reject_ipv6
alias symbol:nf_defrag_ipv6_enable nf_defrag_ipv6
alias symbol:nf_nat_icmpv6_reply_translation nf_nat_ipv6
alias symbol:nft_af_ipv6 nf_tables_ipv6
alias symbol:nft_reject_ipv6_eval nft_reject_ipv6
$
显示模块依赖关系
Linux 内核被设计为模块化,功能分布在许多模块上。这导致了几个可以再次使用 modprobe 显示的模块依赖关系。清单 7 使用选项“–show-depends”来列出 i915 模块的依赖项。
清单 7:显示模块依赖关系
insmod /lib/modules/3.16.0-7-amd64/kernel/drivers/i2c/i2c-core.ko
insmod /lib/modules/3.16.0-7-amd64/kernel/drivers/i2c/algos/i2c-algo-bit.ko
insmod /lib/modules/3.16.0-7-amd64/kernel/drivers/thermal/thermal_sys.ko
insmod /lib/modules/3.16.0-7-amd64/kernel/drivers/gpu/drm/drm.ko
insmod /lib/modules/3.16.0-7-amd64/kernel/drivers/gpu/drm/drm_kms_helper.ko
insmod /lib/modules/3.16.0-7-amd64/kernel/drivers/acpi/video.ko
insmod /lib/modules/3.16.0-7-amd64/kernel/drivers/acpi/button.ko
insmod /lib/modules/3.16.0-7-amd64/kernel/drivers/gpu/drm/i915/i915.ko
$
为了将依赖项显示为类似于“tree”或“lsblk”命令的树,modtree 项目 [5] 可以提供帮助(请参阅下图的 i915 模块树)。尽管它可以在 GitHub 上免费获得,但它需要进行一些修改才能遵守自由软件的规则并作为软件包成为 Linux 发行版的一部分。
加载模块
将模块加载到正在运行的内核可以通过两个命令来完成——insmod(“插入模块”)和modprobe。请注意,这两者之间有一个细微但重要的区别:insmod 不解决模块依赖关系,但 modprobe 更聪明,可以解决这个问题。
清单 8 显示了如何插入 IrDA 内核模块。请注意,insmode 使用完整的模块路径,而 modprobe 对模块的名称感到满意,并在当前 Linux 内核的模块树中查找它本身。
清单 8:插入内核模块
...
# modprobe irda
卸载模块
最后一步涉及从正在运行的内核卸载模块。同样,有两个命令可用于此任务 - modprobe 和 rmmod(“删除模块”)。这两个命令都需要模块名称作为参数。清单 9 显示了从正在运行的 Linux 内核中删除 IrDA 模块的情况。
清单 9:删除内核模块
...
# modprobe -r irda
...
结论
处理 Linux 内核模块并不是什么大魔法。只需学会几个命令,你就是厨房的主人。
谢谢
作者要感谢 Axel Beckert(苏黎世联邦理工学院)和 Saif du Plessis(开普敦 Hothead Studio)在准备本文时提供的帮助。
链接和参考
[1] 内核模块,Arch Linux wiki,https://wiki.archlinux.org/index.php/Kernel_module
[2] 内核配置,https://tldp.org/HOWTO/SCSI-2.4-HOWTO/kconfig.html
[3] kmod,https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
[4] 内核模块签名工具,https://www.kernel.org/doc/html/v4.15/admin-guide/module-signing.html
[5] modtree,https://github.com/falconindy/modtree