当前位置: 首页 > backend >正文

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文件写入以下内容:
    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文件,分别写入以下内容:
    #!/bin/sh/bin/mount -t vfat /dev/$1 /mnt
    sync
    #!/bin/shsync
    /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)。

http://www.xdnf.cn/news/9523.html

相关文章:

  • vue-04(深入了解 props:验证、类型和默认值)
  • Django实现文件上传
  • Docker-compose 编排lnmp(dockerfile) 完成Wordpress
  • [ERR] switch_core_session.c:2697 Invalid Application callcenter
  • 基于 HTTP 的邮件认证深入解读 ngx_mail_auth_http_module
  • bug: uniCloud 查询数组字段失败
  • # 使用 Selenium 爬取苏宁易购优质评价
  • 针对C语言的开发工具推荐及分析(涵盖编辑器、集成开发环境(IDE)、编译器、调试工具及辅助工具)
  • 【Python3教程】Python3基础篇之OS文件目录方法
  • Mac OS 使用说明
  • PySpark 中使用 SQL 语句和表进行计算
  • OpenCV CUDA模块直方图计算------生成一组均匀分布的灰度级函数evenLevels()
  • 【NLP】将 LangChain 与模型上下文协议 (MCP) 结合使用
  • Pipeline ADC高速高精度模数转换器UIA5301
  • 从零实现富文本编辑器#4-浏览器选区模型核心交互策略
  • LiveGBS作为下级平台GB28181国标级联2016|2022对接海康大华宇视华为政务公安内网等GB28181国标平台查看级联状态及会话
  • 域名解析怎么查询?有哪些域名解析查询方式?
  • Docker快速部署数据同步工具DataX-Web
  • 【AI】Spring AI MCP Server 三种实现方式的区别
  • JWT安全:弱签名测试.【实现越权绕过.】
  • C++ 异步编程与网络编程:工具、协议的层次与协同
  • 深度解析 Dockerfile 配置:构建高效轻量的FastAPI 应用镜像
  • 【Bluedriod】蓝牙协议栈 btm_init 源码解析
  • pycharm找不到高版本conda问题
  • 酒店用品源头厂家推荐
  • Building Android Kernels with Bazel
  • 五、web安全--XSS漏洞(1)--XSS漏洞利用全过程
  • Redis 缓存穿透、缓存雪崩、缓存击穿分别是什么?
  • Docker学习笔记:基础知识
  • 友达光电12.1寸液晶屏G121XN01 V001工控屏