Modalias
本文档旨在介绍 Linux 内核和模块如何查看并理解硬件,以及这如何转化为 sysfs 的 'modalias'
什么是 modalias
Modalias 是 sysfs 的一个小技巧,它将硬件信息导出到名为 'modalias' 的文件中。该文件简单地包含了普通硬件所公开信息的格式化形式。
$ cat /sys/devices/pci0000:00/0000:00:1f.1/modalias
pci:v00008086d000024DBsv0000103Csd0000006Abc01sc01i8A
什么是 modalias 文件?
如上所述,modalias 文件仅公开了给定硬件已经告知内核的信息。该文件只是指定了一种公开这些信息的结构。让我们回到上面的例子
$ cat /sys/devices/pci0000:00/0000:00:1f.1/modalias
pci:v00008086d000024DBsv0000103Csd0000006Abc01sc01i8A
让我们将其逐部分拆解。首先是文件名:/sys/devices/pci0000:00/0000:00:1f.1/modalias
- pci0000:00 是第一个 PCI 总线的 ID。对于大多数机器,这将是你拥有的唯一 PCI 总线,但也有可能扩展到 pci0000:01 或 pci0000:02 —— 具体细节并不重要,因为大概率你只有一个 PCI 总线(提示: 尝试
ls /sys/devices/pci*来检查) - 0000:00:1f.1 是给定设备在 PCI 总线上的索引。具体来说,它位于总线 0000:00 上,索引为 1f.1
- 除非你想知道所有这些数字的来源,否则这一切都并不重要。为了完整起见,如果你检查
lspci的输出,你会看到相同的信息
$ lspci
00:1f.1 IDE interface: Intel Corp.: Unknown device 24db (rev 02)
现在,让我们看看设备 00:1f.1 的这个 modalias 文件的内容
pci:v00008086d000024DBsv0000103Csd0000006Abc01sc01i8A
瞧,我看到了 pci!我认得那个,但结尾这些乱码是什么?这些乱码实际上是结构化数据。你会注意到一个重复的字母/数字模式。让我们将其分开以方便阅读
v 00008086 d 000024DB sv 0000103C sd 0000006A bc 01 sc 01 i 8A
每一个标识符及其对应的十六进制数字代表了给定设备公开的部分信息。首先,v 是 供应商 ID (vendor id),d 是 设备 ID (device id) —— 这些是非常标准的数字。事实上,像 hwdetect 这样的工具就是利用这些以及其他 sysfs 文件来提供设备信息的。你甚至可以找到基于供应商和设备 ID 查询特定硬件标识的网站,例如 https://devicehunt.com/
我们也可以在这里看到这些数字
$ lspci -n
00:1f.1 Class 0101: 8086:24db (rev 02)
看到 8086:24db 如何与上面列出的 v 和 d 令牌匹配了吗?
顺便说一下,sv 和 sd 是供应商和设备的“子系统 (subsystem)”版本。大多数情况下它们会被忽略。它们主要由硬件开发人员用于区分内部运作的细微差异,而这些差异不会改变设备整体。
bc (基本类 base class) 和 sc (子类 sub class) 用于创建 lspci 列出的“Class”,顺序为 "bcsc"。这是设备类,相当通用。在这种情况下,“class”在正常的 lspci 输出中被查询。我们可以看到 “Class 0101” 映射到 “IDE Interface”(lspci 同样会查询供应商和设备 ID —— 8086 映射到 “Intel Corp.”,24DB 映射到 “Unknown Device”,呵呵)
i 是 “编程接口 (Programming interface)”,这仅对少数设备类有意义。
这些信息是如何被使用的?
好了,现在我们都知道这些信息是什么了。一堆每个设备公开的晦涩数字。没什么大不了的。但在讨论模块时,这有什么意义?
人们往往容易忽略的一点是 depmod 所做的所有工作。当你运行 depmod 时,它会在 /lib/modules/`uname -r` 中构建一系列“映射”文件,告诉 modprobe 如何处理它需要执行的某些事项。在这种情况下,我们可以忽略大多数文件。重要的是 modules.alias。该文件包含别名,即模块的次要名称。为了演示,让我们看看例如 snd_intel8x0m 的别名
$ grep snd_intel8x0m /lib/modules/$(uname -r)/modules.alias
alias pci:v00008086d00002416sv*sd*bc*sc*i* snd_intel8x0m alias pci:v00008086d00002426sv*sd*bc*sc*i* snd_intel8x0m alias pci:v00008086d00002446sv*sd*bc*sc*i* snd_intel8x0m alias pci:v00008086d00002486sv*sd*bc*sc*i* snd_intel8x0m alias pci:v00008086d000024C6sv*sd*bc*sc*i* snd_intel8x0m alias pci:v00008086d000024D6sv*sd*bc*sc*i* snd_intel8x0m alias pci:v00008086d0000266Dsv*sd*bc*sc*i* snd_intel8x0m alias pci:v00008086d000027DDsv*sd*bc*sc*i* snd_intel8x0m alias pci:v00008086d00007196sv*sd*bc*sc*i* snd_intel8x0m alias pci:v00001022d00007446sv*sd*bc*sc*i* snd_intel8x0m alias pci:v00001039d00007013sv*sd*bc*sc*i* snd_intel8x0m alias pci:v000010DEd000001C1sv*sd*bc*sc*i* snd_intel8x0m alias pci:v000010DEd00000069sv*sd*bc*sc*i* snd_intel8x0m alias pci:v000010DEd00000089sv*sd*bc*sc*i* snd_intel8x0m alias pci:v000010DEd000000D9sv*sd*bc*sc*i* snd_intel8x0m
嘿,等一下!我认出那个了!那是之前的供应商/设备 ID 信息!
是的,没错。它是一种相当简单的格式:“alias <某物> <实际模块>”。事实上,你几乎可以为任何你想定义的东西创建别名。我可以添加 “alias boogabooga snd_intel8x0m”,然后安全地执行 “modprobe boogabooga”。
"*" 表示它将匹配任何内容,非常像文件系统的通配符(ls somedir/*)。如前所述,大多数别名通过 "*" 匹配而忽略 sv, sd, bc, sc 和 i。
modules.alias 文件是从哪里来的?
现在你可能会想:“好吧,硬件探测 (hardware probe) 以前是根据设备表来查询的,这有什么不同?”
不同之处在于,这个查询表不是静态的。它不是手动维护的。事实上,每当你运行 depmod 时,它都会动态构建。“这些信息从哪里来?”你可能会问。当然是从 内核模块 本身。仔细想想,每个特定的模块应该知道它支持哪些硬件,因为它是专门为该硬件编写的。我的意思是,nvidia 模块的开发人员知道他们的模块仅适用于 Nvidia (供应商) 显卡 (类)。事实上,模块本身会导出这些信息。它会说:“嘿,我可以支持这个:”。
$ modinfo nvidia filename: /lib/modules/2.6.14-ARCH/kernel/drivers/video/nvidia.ko license: NVIDIA alias: char-major-195-* vermagic: 2.6.14-ARCH SMP preempt 686 gcc-4.1 depends: agpgart alias: pci:v000010DEd*sv*sd*bc03sc00i00*
正如你从列出的别名中看到的,它专门寻找供应商 “10DE” (Nvidia) 和 bc/sc 0300 (极有可能是 'graphics cards')。事实上,如果你查看 snd_intel8x0m 的 modinfo
$ modinfo snd_intel8x0m filename: /lib/modules/2.6.14-ARCH/kernel/sound/pci/snd-intel8x0m.ko author: Jaroslav Kysela <perex@suse.cz> description: Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; SiS 7013; NVidia MCP/2/2S/3 modems license: GPL vermagic: 2.6.14-ARCH SMP preempt 686 gcc-4.1 depends: snd-ac97-codec,snd-pcm,snd-page-alloc,snd alias: pci:v00008086d00002416sv*sd*bc*sc*i* alias: pci:v00008086d00002426sv*sd*bc*sc*i* alias: pci:v00008086d00002446sv*sd*bc*sc*i* alias: pci:v00008086d00002486sv*sd*bc*sc*i* alias: pci:v00008086d000024C6sv*sd*bc*sc*i* alias: pci:v00008086d000024D6sv*sd*bc*sc*i* alias: pci:v00008086d0000266Dsv*sd*bc*sc*i* alias: pci:v00008086d000027DDsv*sd*bc*sc*i* alias: pci:v00008086d00007196sv*sd*bc*sc*i* alias: pci:v00001022d00007446sv*sd*bc*sc*i* alias: pci:v00001039d00007013sv*sd*bc*sc*i* alias: pci:v000010DEd000001C1sv*sd*bc*sc*i* alias: pci:v000010DEd00000069sv*sd*bc*sc*i* alias: pci:v000010DEd00000089sv*sd*bc*sc*i* alias: pci:v000010DEd000000D9sv*sd*bc*sc*i*
它与通过 'grep' 别名文件找到的别名相匹配。这些由每个模块导出的别名被 depmod 收集并动态合并到 modules.alias 文件中。不存在手动修改查询表的情况,因为它是即时构建的。每个模块都确切知道它支持什么,因此 depmod 可以利用这些信息来帮助加载模块。
udev 是如何工作的?
udev 与 sysfs(最初公开 modalias 的文件系统)紧密相关。事实上,在添加新设备时(或在启动时 udev 首次启动时)根据 modalias 加载模块,其过程极其简单
DRIVER!="?*", ENV{MODALIAS}=="?*", RUN{builtin}="kmod load $env{MODALIAS}"
没错,就是这样。只有一行代码。这行简单的代码是默认 udev 规则的一部分,取代了 hotplug。很神奇,不是吗?
参见
本文档展示了其他 modalias 模板,例如针对 usb, dmi 和 acpi 子类型的模板
- Modalias strings - a practical way to map "stuff" to hardware 作者:Petter Reinholdtsen