在编译内核的时候,如果将某一功能编译成内核模块,那需要我们手动输入insmod或者modprobe命令进行加载吗?

经过实践发现,内核会在插入设备的时候自动加载相应的驱动模块,这是怎么实现的?本文接下来将进行一系列探索。

从dmesg开始

USB声卡插入插槽,如果把SND_USB_AUDIO编译进内核,即CONFIG_SND_USB_AUDIO=y时,dmesg出现如下输出:

1
2
3
4
5
6
7
8
9
10
[574786.075305] usb 3-1: new full-speed USB device number 3 using ohci-platform
[574786.301945] usb 3-1: New USB device found, idVendor=4459, idProduct=3233, bcdDevice= 1.00
[574786.302014] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[574786.302031] usb 3-1: Product: Yundea M1066
[574786.302045] usb 3-1: Manufacturer: Yundea Technology
[574786.302059] usb 3-1: SerialNumber: 433036323231382E
[574786.306246] usb-storage 3-1:1.0: USB Mass Storage device detected
[574786.310691] scsi host3: usb-storage 3-1:1.0
[574787.341957] scsi 3:0:0:0: Direct-Access BR25 UDISK 1.00 PQ: 0 ANSI: 2
[574787.369983] sd 3:0:0:0: [sdc] Attached SCSI removable disk

如果把SND_USB_AUDIO编译成模块,即CONFIG_SND_USB_AUDIO=m时,dmesg出现如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[ 1768.962881] usb 3-2: new full-speed USB device number 5 using xhci_hcd
[ 1769.311093] usb 3-2: New USB device found, idVendor=4459, idProduct=3233, bcdDevice= 1.00
[ 1769.311095] usb 3-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 1769.311096] usb 3-2: Product: Yundea M1066
[ 1769.311097] usb 3-2: Manufacturer: Yundea Technology
[ 1769.311098] usb 3-2: SerialNumber: 433036323231382E
[ 1769.327878] mc: Linux media interface: v0.10
[ 1769.328662] usb-storage 3-2:1.0: USB Mass Storage device detected
[ 1769.329237] scsi host33: usb-storage 3-2:1.0
[ 1769.329370] usbcore: registered new interface driver usb-storage
[ 1769.331443] usbcore: registered new interface driver uas
[ 1769.443024] usbcore: registered new interface driver snd-usb-audio
[ 1770.339466] scsi 33:0:0:0: Direct-Access BR25 UDISK 1.00 PQ: 0 ANSI: 2
[ 1770.339900] sd 33:0:0:0: Attached scsi generic sg2 type 0
[ 1770.409154] sd 33:0:0:0: [sdb] Attached SCSI removable disk

我们接下来分析一下驱动编译为内核模块时USB插入后内核的响应:

  1. 在USB端口3-2检测到一个新的全速USB设备,并使用xHCI(eXtensible Host Controller Interface)USB主机控制器;
  2. 获取该USB设备的信息,包括供应商ID(4459)、产品ID(3233)、设备版本号(1.00);
  3. Mfr=1表示制造商信息在USB设备字符串索引1中,Product=2表示产品信息在USB设备字符串索引2中,SerialNumber=3表示序列号信息在USB设备字符串索引3中;
  4. 获取产品名称、制造商、序列号;
  5. 表示Linux媒体接口(Media Controller)已启用;
  6. 表示检测到USB设备是一个USB大容量存储设备;
  7. 为USB大容量存储设备创建了一个新的SCSI主机设备;
  8. 内核注册了新的USB驱动程序:usb-storage、uas、snd-usb-audio;
  9. 提供了SCSI设备的信息,显示它是一个Direct-Access可直接访问的存储设备,型号为”BR25 UDISK”,固件版本为1.00;33:0:0:0是该SCSI设备的唯一标识符,PQ: 1表示”Peripheral Qualifier”(外围设备限定符)为0,意味着设备可以正常使用,ANSI: 2表示设备遵循的ANSI SCSI标准版本为2;
  10. SCSI设备已作为一个通用SCSI设备附加;
  11. SCSI设备已被附加为一个可移动磁盘,设备名为”sdb”。

经过对比可以看到,把驱动编译成模块,在usb插入后会自动加载相应的模块,这里有三条加载驱动信息是因为该内核配置了CONFIG_USB_STORAGECONFIG_USB_UAS=m,即usb存储驱动与USB Attached SCSI驱动。

为什么会有“usbcore: registered new interface driver snd-usb-audio”这一系列输出,是谁输出的?

usbcore是谁输出的?

观察dmesg中关于自动加载驱动的输出,通过查找内核源码可以将其定位为usb_register_driver函数。

usb_register_driver声明,位于/include/linux/usb.h中,在该头文件中,usb_registermodule_usb_drivermodule_usb_stor_driver函数对其进行了封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
* use these in module_init()/module_exit()
* and don't forget MODULE_DEVICE_TABLE(usb, ...)
*/
extern int usb_register_driver(struct usb_driver *, struct module *,
const char *);

/* use a define to avoid include chaining to get THIS_MODULE & friends */
#define usb_register(driver) \
usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

/**
* module_usb_driver() - Helper macro for registering a USB driver
* @__usb_driver: usb_driver struct
*
* Helper macro for USB drivers which do not do anything special in module
* init/exit. This eliminates a lot of boilerplate. Each module may only
* use this macro once, and calling it replaces module_init() and module_exit()
*/
#define module_usb_driver(__usb_driver) \
module_driver(__usb_driver, usb_register, \
usb_deregister)

#define module_usb_stor_driver(__driver, __sht, __name) \
static int __init __driver##_init(void) \
{ \
usb_stor_host_template_init(&(__sht), __name, THIS_MODULE); \
return usb_register(&(__driver)); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
usb_deregister(&(__driver)); \
} \
module_exit(__driver##_exit)

usb_register_driver定义,位于/drivers/usb/core/driver.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* usb_register_driver - register a USB interface driver
* @new_driver: USB operations for the interface driver
* @owner: module owner of this driver.
* @mod_name: module name string
*
* Registers a USB interface driver with the USB core. The list of
* unattached interfaces will be rescanned whenever a new driver is
* added, allowing the new driver to attach to any recognized interfaces.
*
* Return: A negative error code on failure and 0 on success.
*
* NOTE: if you want your driver to use the USB major number, you must call
* usb_register_dev() to enable that functionality. This function no longer
* takes care of that.
*/
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
const char *mod_name)
{
int retval = 0;

if (usb_disabled())
return -ENODEV;

new_driver->driver.name = new_driver->name;
new_driver->driver.bus = &usb_bus_type;
new_driver->driver.probe = usb_probe_interface;
new_driver->driver.remove = usb_unbind_interface;
new_driver->driver.owner = owner;
new_driver->driver.mod_name = mod_name;
new_driver->driver.dev_groups = new_driver->dev_groups;
spin_lock_init(&new_driver->dynids.lock);
INIT_LIST_HEAD(&new_driver->dynids.list);

retval = driver_register(&new_driver->driver);
if (retval)
goto out;

retval = usb_create_newid_files(new_driver);
if (retval)
goto out_newid;

pr_info("%s: registered new interface driver %s\n",
usbcore_name, new_driver->name); #dmesg中的usbcore输出

out:
return retval;

out_newid:
driver_unregister(&new_driver->driver);

pr_err("%s: error %d registering interface driver %s\n",
usbcore_name, retval, new_driver->name);
goto out;
}
EXPORT_SYMBOL_GPL(usb_register_driver);

通过函数的定义发现,是usb_register_driver内核函数在dmesg中打印了usbcore的信息。

uas驱动中,root/driver/usb/storage/uas.c文件调用了uas_init,uas_init调用了usb_register函数;

usb-storage驱动中,/root/driver/usb/storage/usb.c文件调用了module_usb_stor_driver函数;

snd-usb-audio驱动中,/root/sound/usb/card.c文件调用了module_usb_driver函数。

因此,在安装这些usb驱动的时候,会自动向dmesg输出内容,那么,是谁安装了这些驱动呢?

udev

udev 负责监听 Linux 内核发出的改变设备状态的事件,例如一个 USB 设备被插入或拔出。

使用 systemd 的机器上,udev 操作由 systemd-udevd 守护进程管理,可以通过常规的 systemd 方式使用 systemctl status 命令 检查 udev 守护进程的状态:

1
2
systemctl status systemd-udevd
systemctl status udev

udev 通过序列号、制造商、以及提供商 ID 和产品 ID 号来识别设备。

udevadm

udevadm(udev administrator), 是一个 udev 管理工具,可用于查询 udev 数据库中的设备信息,也可以从 sysfs 文件系统中查询到设备的属性以辅助创建 udev 规则。

使用 udevadm monitor 命令你可以实时利用 udev,并且可以看到当你插入不同设备时发生了什么。

1
sudo udevadm monitor

该监视函数输出接收到的事件:

  • KERNEL:内核发送 uevent 事件, udev 守护进程侦听来自内核的 uevent,以此添加或者删除 /dev下的设备文件
  • UDEV:在规则处理之后发出 udev 事件

以下内容是插入USB声卡后udevadm monitor的输出(节选)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
KERNEL[22751.610627] add      /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2 (usb)
KERNEL[22751.616782] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.0 (usb)
KERNEL[22751.618458] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.0/host33 (scsi)
KERNEL[22751.618484] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.0/host33/scsi_host/host33 (scsi_host)
KERNEL[22751.620373] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1 (usb)
KERNEL[22751.729567] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1 (sound)
KERNEL[22751.730062] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1/pcmC1D0p (sound)
KERNEL[22751.730387] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1/pcmC1D0c (sound)
KERNEL[22751.730624] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1/controlC1 (sound)
UDEV [22751.739014] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1 (sound)
UDEV [22751.739301] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.0/host33 (scsi)
UDEV [22751.740201] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.0/host33/scsi_host/host33 (scsi_host)
UDEV [22751.740729] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1/pcmC1D0p (sound)
KERNEL[22751.740740] change /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1 (sound)
UDEV [22751.740748] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1/pcmC1D0c (sound)
UDEV [22751.759173] add /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1/controlC1 (sound)
UDEV [22751.762417] change /devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.1/sound/card1 (sound)

udevadm info 命令去查询 udev 数据库中的指定设备信息。-q, --query=TYPE :从数据库中查询指定类型的设备,需要使用 --path--name 选项指定设备。合法的 TYPE 类型包括:设备名(name),链接(symlink),路径(path),属性(property)。

1
udevadm info -q path -n /dev/sdc1

udev规则

udev 的工作方式是试图将它收到的每个系统事件与 /lib/udev/rules.d//etc/udev/rules.d/ 目录下找到的规则集进行匹配。

udev 规则是定义在一个以 .rules 为扩展名的文件中。那些文件主要放在两个位置:/usr/lib/udev/rules.d,这个目录用于存放系统安装的规则;/etc/udev/rules.d/ 这个目录是保留给自定义规则的。如果两个目录下均有相同的文件,则以/etc/udev/rules.d/ 中的文件为准。

规则文件包括匹配键和分配键,可用的匹配键包括 actionnamesubsystem。这意味着如果探测到一个属于某个子系统的、带有特定名称的设备,就会给设备指定一个预设的配置,例如执行一段脚本。

ACTION 键:通过使用它,当在一个设备上发生了特定的事件,我们将指定我们要应用的规则的具体内容。有效的值有 addremove 以及 change。然后,我们使用 ATTRS 关键字去指定一个属性去匹配。

/lib/udev/rules.d/80-drivers.rules中有自动加载驱动的规则如下,有了这一行,udev就可以根据插入设备的modalias自动加载相应的驱动模块了。

1
2
3
ACTION!="add", GOTO="drivers_end"

ENV{MODALIAS}=="?*", RUN{builtin}+="kmod load $env{MODALIAS}"

我们也看可以自己更改udev规则,更改后需要重新加载udev规则

1
udevadm control --reload

hwdb

硬件数据库(HWDB)是一个由”modalias“风格的键(key)与”udev属性”风格的值(value)组成的 key-value 文本数据库。 主要用于 udev 为匹配到的硬件设备添加关联属性, 但也可以用于直接查询。

每个硬件数据库文件(hwdb)都包含一系列由”matche”与关联的”key-value”组成的记录。

/lib/udev/hwdb.d/20-usb-classes.hwdb文件中有如下一行:

1
2
usb:v*p*d*dc01*
ID_USB_CLASS_FROM_DATABASE=Audio

参考资料

https://zhuanlan.zhihu.com/p/51984452

https://zhuanlan.zhihu.com/p/33932734

https://www.jinbuguo.com/systemd/hwdb.html

https://www.jinbuguo.com/systemd/systemd-hwdb.html

https://documentation.suse.com/zh-cn/sles/12-SP5/html/SLES-all/cha-udev.html

https://stackoverflow.com/questions/62835556/usb-why-usb-devices-the-modules-automatically-loaded

https://stackoverflow.com/questions/73811317/how-to-find-the-udev-rule-which-causes-the-loading-of-the-kernel-module-88xxau-k