Linux驱动学习笔记(十)

热插拔

1.热插拔:就是带电插拔,即允许用户在不关闭系统,不切断电源的情况下拆卸或安装硬盘,板卡等设备。热插拔是内核和用户空间之间,通过调用用户空间程序实现交互来实现的,当内核发生了某种热拔插事件时,内核就会调用用户空间的程序来实现交互。热插拔机制有devfs、udev和mdev,devfs如今已经不再使用。嵌入式设备上一般使用mdev,X86上一般用udev,当然嵌入式设备上也可以用udev,mdev是udev的简化版本。udev是基于netlink机制实现的,通过udevd守护进程监听内核发送的uevent事件来执行相应的热插拔操作。而mdev是基于uevent helper机制,内核产生的uevent会调用uevent_helper所指的用户程序medv来执行热拔插动作。

2.int kobject_uevent(struct kobject *kobj, enum kobject_action action);函数可用来在内核中向用户空间发送设备事件通知uevent,触发用户态的udev/mdev等设备管理工具响应设备状态变化,该函数执行成功返回0。其中,kobj是关联的内核对象指针,代表触发事件的设备或子系统;action是发生的事件类型,包括下图所示的几种事件:

udevadm是Linux系统中用于管理和调试udev设备管理器的核心命令行工具,它允许用户直接与udev交互,查询设备信息、触发事件、监控设备变动或调试规则。使用方法为:

例如可以使用udevadm monitor命令监听所有内核设备事件,示例如下:

kobject_uevent函数向用户态发送事件时会调用kobject_uevent_env函数,如下图:

kobject_uevent_env函数可用来发送带有环境变量数据的事件。kobject_uevent_env函数会根据事件的类型进行对应的操作,但是这一流程是借助kset来实现的(uevent是通过netlink socket发送给用户空间的应用程序的,而netlink socket是基于kset的),所以发送事件的kobject必须属于某个kset,否则会导致事件发送失败,如下图:

如上图所示,在获取到发送事件的kobject所属的kset以及该kset的事件操作uevent_ops后,kobject_uevent_env函数依次执行这些操作,如下图:

最终kobject_uevent_env函数会广播要发送的事件,以便用户空间的应用程序可以接收并处理这些事件(对应udev)。另外如果定义了CONFIG_UEVENT_HELPER则会调用用户空间的uevent_helper程序(可将其设置成mdev)来处理uevent事件,如下图:

3.kset->uevent_ops中定义了三个函数,如下图:

其中,filter函数用于过滤,即当一个kobject想要向用户空间发送uevent时,由filter函数决定这个uevent是否应该被发送;name函数用于为uevent生成一个特定的名称字符串,这个名称会被添加到uevent的环境变量中,帮助用户空间应用程序识别事件来源;uevent函数来填充或修改发送到用户空间的uevent消息中的环境变量。一个示例如下图:

4.Linux提供了多种方式实现内核和用户空间的数据交换,比如系统调用、sysfs等,但是这些通信机制均为单工通信机制。而netlink是基于socket通信机制,具有双工通信的特点,可以很好的满足内核和用户空间的数据交换。因为netlink是基于socket通信机制,所以需要在用户空间使用socket接口实现。首先介绍几个函数:

  • int socket(int domain, int type, int protocol):用于创建套接字。其中domain表示所用协议,使用netlink机制时将其设置为AF_NETLINK;type表示套接字的类型,指定通信的方式和特性,使用netlink机制时将其设置为SOCK_RAW;protocol表示套接字使用的协议,通常设置为0,让系统自动选择适当的协议,在接收uevent时可将其设置为NETLINK_KOBJECT_UEVENT;该函数调用成功返回创建的套接字对应的文件描述符,失败返回-1并设置errno
  • int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen):用于将创建的套接字与指定的地址结构绑定。其中sockfd为套接字对应的文件描述符;addr为传入参数,在接收uevent时通常使用sockaddr_nl结构体(强转为sockaddr类型),这个结构体成员包括协议族(这里应为AF_NETLINK),当前进程PID;addrlen为addr的长度;该函数绑定成功返回0,失败返回-1并设置errno
  • ssize_t recv(int sockfd, void *buf, size_t len, int flags):用于接收内核发出的uevent事件。注意与一般网络编程不同,在netlink中是不用调用listen函数的,可以直接使用recv函数进行接收。其中sockfd为套接字对应的文件描述符;buf指向接收数据的缓冲区;len指定要读取的数据的字节数;flags指定一些标志用于控制如何接收数据,通常设置为0;成功情况下该函数返回实际读取到的字节数

netlink需要在用户空间循环读取内核发来的uevent,下图是一个例子(可参考讯为Linux驱动视频第十期P5):

5.对于uevent helper机制,要想在kobject_uevent_env函数中调用用户空间的uevent_helper程序来处理uevent事件,则需要定义CONFIG_UEVENT_HELPER,并且需要定义uevent_helper的路径(即CONFIG_UEVENT_HELPER_PATH的值),如下图:

有以下几种配置方法(可参考讯为Linux驱动视频第十期P6):

  • 在编译内核时直接配置CONFIG_UEVENT_HELPER_PATH:make menuconfig打开图形化配置界面后,选中Device Drivers->Generic Driver Options->Support for uevent helper后配置path to uevent helper,即配置uevent_helper的路径(例如可将其设置为/sbin/mdev)
  • make menuconfig打开图形化配置界面后,依次选中:Device Drivers->Generic Driver Options->Support for uevent helper(这一步是打开宏定义CONFIG_UEVENT_HELPER)、File systems->Pseudo fllesystems->/proc file system support、File systems->Pseudo fllesystems-> Sysctl support(/proc/sys)、Networking support。选中上述几个配置之后,就可以通过命令echo /sbin/mdev > /sys/kernel/uevent_helper对uevent_helper进行设置,或通过命令echo /sbin/mdev > /proc/sys/kernel/hotplug对uevent_helper进行设置(这两种设置方法实际就是通过对属性文件进行读写实现的)

一个简单的mdev程序如下图所示(可参考讯为Linux驱动视频第十期P7):

需要注意的是,kobject_uevent_env函数中调用的call_usermodehelper_exec函数是一个在内核空间中调用用户空间程序的函数,该函数执行用户空间程序时,将其作为子进程运行,并将其标准输入、标准输出和标准错误输出重定向到相应的文件描迷符。因此如果在用户空间程序中使用printf打印信息,这些信息将被输出到标准输出文件描述符(文件描述符1),而不是终端。因此需要在调用call_usermodehelper_exec时将标准输出重定向到终端,这样才可以在终端上看到printf输出的信息。

6.实现U盘热插拔的几个步骤,采用udev(可参考讯为Linux驱动视频第十期P8):

  • 首先需要在编译源码时配置所使用的Linux系统支持udev,例如对于buildroot文件系统,执行make menuconfig之后将System configuration->/dev management设置为Dynamic using devtmpfs + eudev表示使用udev

  • 启动系统后在/etc/udev/rules.d/目录下创建一个001.rules文件(若没有rules.d/目录则创建),其中001表示第一个规则文件,.rules是固定后缀。向在001.rules文件写入以下内容:

    bash 复制代码
    KERNEL=="sd[a-z][0-9]",SUBSYSTEM=="block",ACTION=="add",RUN+="/etc/udev/rules.d/usb/usb-add.sh %k"
    SUBSYSTEM=="block",ACTION=="remove",RUN+="/etc/udev/rules.d/usb/usb-remove.sh"

    第一行表示当新增一个usb设备,执行/etc/udev/rules.d/usb/usb-add.sh脚本文件,并传入参数sd[a-z][0-9],第二行表示当移除一个usb设备,执行/etc/udev/rules.d/usb/usb-remove.sh脚本文件

  • 分别创建/etc/udev/rules.d/usb/usb-add.sh和/etc/udev/rules.d/usb/usb-remove.sh文件,分别写入以下内容:

    bash 复制代码
    #!/bin/sh
    
    /bin/mount -t vfat /dev/$1 /mnt
    sync
    bash 复制代码
    #!/bin/sh
    
    sync
    /bin/unmount -l /mnt

还可以在/lib/udev/rules.d/目录下创建规则文件,但是/etc/udev/rules.d/比/lib/udev/rules.d的优先级高。TF卡的udev热插拔实现方式和U盘类似,只是U盘的节点名格式为sd[a-z][0-9],而TF卡的节点名格式为mmcblk[0-9]p[0-9]。采用mdev实现U盘和TF卡的热插拔步骤与udev类似,可参考讯为Linux驱动视频第十期P10、P11。

7.USBmount是一个用于自动挂载USB存储设备的工具,它可以在Linux系统中自动挂载插入的USB存储设备并在设备拔出时自动卸载。USBmount的工作原理是通过udev监视USB设备的插拔事件,并在检测到设备插入时自动挂载设备,检测到设备拔出时自动卸载设备。USBmount不需要手动挂载或卸载USB存储设备,因此可以方便地在嵌入式系统中使用(USBmount使用方式可参考讯为Linux驱动视频第十期P12)。

相关推荐
UpUpUp……1 小时前
C++复习
开发语言·c++·笔记
努力学习的小廉1 小时前
深入了解linux系统—— 库的制作和使用
linux·运维·单片机
艾莉丝努力练剑1 小时前
深入详解编译与链接:翻译环境和运行环境,翻译环境:预编译+编译+汇编+链接,运行环境
c语言·开发语言·汇编·学习
泉飒2 小时前
lua的注意事项2
笔记·lua
444A4E2 小时前
深入解析 Linux 进程状态:从 task_struct 双链表到 R/S/D/Z 状态的内核奥秘
linux·操作系统
黑风风3 小时前
Ubuntu 22.04 上安装 PostgreSQL(使用官方 APT 源)
linux·ubuntu·postgresql
眼镜哥(with glasses)3 小时前
0527漏洞原理:XSS笔记
运维·笔记·自动化
杏仁海棠花饼3 小时前
杏仁海棠花饼的学习日记第十四天CSS
前端·css·学习
行星0083 小时前
Ubuntu 中安装 PostgreSQL 及常规操作指南
linux·ubuntu·postgresql
奋斗者1号3 小时前
提升WSL中Ubuntu编译速度的完整指南
linux·运维·ubuntu