在编译内核的时候,如果将某一功能编译成内核模块,那需要我们手动输入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插入后内核的响应:
在USB端口3-2检测到一个新的全速USB设备,并使用xHCI(eXtensible Host Controller Interface)USB主机控制器;
获取该USB设备的信息,包括供应商ID(4459)、产品ID(3233)、设备版本号(1.00);
Mfr=1
表示制造商信息在USB设备字符串索引1中,Product=2
表示产品信息在USB设备字符串索引2中,SerialNumber=3
表示序列号信息在USB设备字符串索引3中;
获取产品名称、制造商、序列号;
表示Linux媒体接口(Media Controller)已启用;
表示检测到USB设备是一个USB大容量存储设备;
为USB大容量存储设备创建了一个新的SCSI主机设备;
内核注册了新的USB驱动程序:usb-storage、uas、snd-usb-audio;
提供了SCSI设备的信息,显示它是一个Direct-Access可直接访问的存储设备,型号为”BR25 UDISK”,固件版本为1.00;33:0:0:0
是该SCSI设备的唯一标识符,PQ: 1
表示”Peripheral Qualifier”(外围设备限定符)为0,意味着设备可以正常使用,ANSI: 2
表示设备遵循的ANSI SCSI标准版本为2;
SCSI设备已作为一个通用SCSI设备附加;
SCSI设备已被附加为一个可移动磁盘,设备名为”sdb”。
经过对比可以看到,把驱动编译成模块,在usb插入后会自动加载相应的模块,这里有三条加载驱动信息是因为该内核配置了CONFIG_USB_STORAGE
、CONFIG_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_register
、module_usb_driver
、module_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 extern int usb_register_driver (struct usb_driver *, struct module *, const char *) ;#define usb_register(driver) \ usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) #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 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,并且可以看到当你插入不同设备时发生了什么。
该监视函数输出接收到的事件:
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/
中的文件为准。
规则文件包括匹配键和分配键,可用的匹配键包括 action
、name
和 subsystem
。这意味着如果探测到一个属于某个子系统的、带有特定名称的设备,就会给设备指定一个预设的配置,例如执行一段脚本。
ACTION
键:通过使用它,当在一个设备上发生了特定的事件,我们将指定我们要应用的规则的具体内容。有效的值有 add
、remove
以及 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