Linux热插拔
时间轴
2025-12-02
init
热插拔
热插拔是指在设备运行的情况下,能够安全地插入或拔出硬件设备,而无需关闭或重启系统。这意味着你可以在计算机或其他电子设备上插入或拔出硬件组件(比如 USB 设备,扩展卡,硬件驱动器等),而无需关机或中断正在进行的操作。
热插拔的主要目的是提供方便性和灵活性。通过热插拔,你可以快速更换或添加硬件设备,而无需停止正在进行的任务。这在许多场景下非常有用,比如:
- USB 设备:你可以随时插入或拔出 USB 设备,比如鼠标,键盘,打印机,存储设备等,而无需重新启动系统。
- 硬盘驱动器:在某些服务器或存储系统中,你可以在运行时添加或替换硬盘驱动器,以扩展存储容量或替换故障驱动器。
- 扩展卡:你可以在计算机上插入或拔出显卡,网卡或声卡等扩展卡,以满足不同的需求或升级硬件性能。
为了支持热插拔功能,硬件设备和系统必须具备相应的支持。硬件方面,设备接口必须设计成可以插入和拔出而不会损坏设备或系统。系统需要提供相应的驱动程序和管理功能,以便在插入和拔出设备时进行正确的配置和识别。
热插拔的机制
热插拔是内核和用户空间之间,通过调用用户空间程序(如 hotplug、udev 和 mdev)的交互。当需要通知用户内核发生了某种热插拔事件时,内核才调用这个用户空间程序来实现交互。
在 Linux 内核中,热插拔机制支持 USB 设备、PCI 设备甚至 CPU 等部件的动态插入和拔出。这个机制实现了底层硬件、内核空间和用户空间程序之间的连接,并且一直在不断演变和改进。设备文件系统是用来管理设备文件的一种机制,在 Linux 中有三种常见的设备文件系统:devfs、mdev 和 udev。
devfs:devfs 是基于内核的动态设备文件系统,最早出现在 Linux 2.3.46 内核中。它通过动态创建和管理设备节点的方式来处理设备文件。然而,devfs 存在一些限制和性能问题,从 Linux 2.6.13 版本开始被移除。mdev:mdev 是一个轻量级的热插拔设备用户空间程序,通常用于嵌入式 Linux 系统。它是 udev的简化版本,使用uevent_helper机制来处理设备的插入和拔出事件。mdev 在设备插入时调用相应的用户程序来创建设备节点。udev:udev 是目前在 PC 机上广泛使用的热插拔设备用户空间程序。它基于netlink机制,监听内核发送的 uevent 来处理设备的插入和拔出。udev 能够动态创建和管理设备节点,并在设备插入时加载适当的驱动程序。它提供了丰富的配置选项,使用户能够灵活地管理设备文件。
内核发送事件到用户空间
kobject_uevent()
kobject_uevent 是 Linux 内核中的一个函数,用于生成和发送 uevent 事件。它是 udev 和其他设备管理工具与内核通信的一种方式。
1 | // lib/kboject_uevent.c |
参数说明:
kobj: 要发送 uevent 事件的内核对象(kobject)action: 表示触发 uevent 的动作,可以是设备的插入,拔出,属性变化等。以下是一些常见的 action 参数值。这些动作类型用于描述设备发生的不同事件,通过将相应的动作类型作为 action 参数传递给kobject_uevent函数,可以触发相应的 uevent 事件,通知用户空间的 udev 进行相应的操作。KOBJ_ADD:表示设备的添加或插入操作,表示添加一个对象到内核对象系统中。KOBJ_REMOVE:表示设备的移除或拔出操作,表示从内核对象系统中删除一个对象。KOBJ_CHANGE:表示设备属性的修改操作,表示对内核对象进行更改,例如属性修改等。KOBJ_MOVE:表示设备的移动操作,即设备从一个位置移动到另一个位置。KOBJ_ONLINE:表示设备的上线操作,即设备从离线状态变为在线状态,使其可以被访问。KOBJ_OFFLINE:表示设备的离线操作,即设备从在线状态变为离线状态,使其不可以被访问。KOBJ_BIND:表示将一个设备连接到内核对象上KOBJ_UNBIND: 表示从内核对象上将一个设备解绑KOBJ_MAX:表示枚举类型的最大值,通常用于表示没有任何操作行为。
kobject_uevent 函数的主要作用是在内核中生成 uevent 事件,并通过 netlink 机制将该事件发送给用户空间的 udev。在调用该函数时,内核会将相关的设备信息和事件类型封装为 uevent消息,并通过 netlink 套接字将消息发送给用户空间。
用户空间的 udev 会接收到这些 uevent 消息,并根据消息中的设备信息和事件类型来执行相应的操作,例如创建或删除设备节点,加载或卸载驱动程序等
示例
1 |
|
udevadm 命令
udevadm 是一个用于与 udev 设备管理器进行交互的命令行工具。它提供了一系列的子命令,用于查询和管理设备、触发 uevent 事件以及执行其他与 udev 相关的操作。一些常见的 udevadm 子命令及其功能如下:
udevadm info:用于获取设备的详细信息,包括设备路径、属性、驱动程序等。udevadm monitor:用于监视和显示当前系统中的 uevent 事件。它会实时显示设备的插入、拔出以及其他相关事件。udevadm trigger:用于手动触发设备的 uevent 事件。可以使用该命令模拟设备的插入、拔出等操作,以便触发相应的事件处理。udevadm settle:用于等待 udev 处理所有已排队的 uevent 事件。它会阻塞直到 udev 完成当前所有的设备处理操作。udevadm control:用于与 udev 守护进程进行交互,控制其行为。例如,可以使用该命令重新加载 udev 规则、设置日志级别等。udevadm test:用于测试 udev 规则的匹配和执行过程。可以通过该命令测试特定设备是否能够正确触发相应的规则。
测试:
1 | root@topeet:/root# udevadm monitor & |
uevent 是基于 kset 实现的
上面的驱动代码中如果去掉了创建 kset 的步骤,用户空间无法收到 uevent 事件,分析如下:
kobject_uevent_env()
1 | /** |
因为 uevent 是通过 netlink socket 发送给用户空间的应用程序的,而 netlink socket 是基于 kset 的。
kobject_uevent_net_broadcast 是一个内核函数,用于将一个 uevent 事件发送到系统中所有的网络命名空间中。它的参数包括:
kobj是与uevent 事件相关的内核对象env是一个包含 uevent 环境变量的列表action_string是一个字符串,表示 uevent 事件的类型devpath是一个字符串,表示与 uevent 事件相关的设备路径。
该函数会遍历系统中所有的网络命名空间,并将 uevent 事件发送到每个网络命名空间中。这个函数的主要作用是在内核中广播一个 uevent 事件,以便用户空间的应用程序可以接收并处理这些事件。
在内核中调用用户空间的 uevent_helper 程序来处理 uevent 事件。uevent_helper 是一个用户空间程序,它可以 在内核空间生成 uevent 事件时被调用。如果CONFIG_UEVENT_HELPER 宏被定义,那么内核会在生成 uevent 事件时调用uevent_helper 程序,以便在用户空间中处理这些事件。在上述代码中,如果 uevent_helper 变量不为空且 kobj_usermode_filter 函数返回 false,那么就会调用 call_usermodehelper_setup 函数来启动一个用户空间进程,并将 env 中的参数传递给该进程。在这个过程中,env 中的参数将会被转换成环境变量,并被传递给用户空间进程。
而在创建 kset 并添加到系统中的函数中的第二个参数,就是传入的 uevent 相关 ops
1
2
3 extern struct kset * __must_check kset_create_and_add(const char *name,
const struct kset_uevent_ops *u,
struct kobject *parent_kobj);
struct kset_uevent_ops
而struct kset_uevent_ops定义如下
1 | struct kset_uevent_ops { |
1. filter 事件过滤器
作用:决定是否阻止向用户空间发送该
kobject的 uevent。返回值:
0:过滤掉(不发送 uevent)- 非
0:允许发送
典型用途:
- 某些内部设备不需要用户空间干预;
- 避免为虚拟设备(如 debugfs 条目)触发 uevent。
示例:
1 | static int my_filter(struct kset *kset, struct kobject *kobj) |
2. name 子系统名称提供者
- 作用:返回该
kobject所属的 “subsystem” 名称,作为 uevent 中的SUBSYSTEM=字段。 - 返回值:字符串(如
"usb","block","mybus")。 - 重要性:用户空间(如 udev rules)常根据
SUBSYSTEM匹配规则。 - 示例:
1 | static const char *my_name(struct kset *kset, struct kobject *kobj) |
对应的 uevent 消息会包含
1 | ACTION=add |
注意:如果不提供 name 回调,内核默认使用 kset->uevent_ops 所在 kset 的名字(通常是父目录名)。
3. uevent 自定义环境变量注入器
作用:向 uevent 的环境变量缓冲区(
env)中添加自定义键值对。参数:
env:类型为struct kobj_uevent_env,内部是一个字符串数组(最大 32 项,每项 2048 字节)。
调用方式:使用
add_uevent_var(env, "KEY=%s", value)。典型用途:
- 传递设备属性(如厂商 ID、序列号);
- 触发特定用户空间逻辑。
示例
1 | static int my_uevent(struct kset *kset, struct kobject *kobj, |
对应的 uevent 将包含
1 | MYBUS_VERSION=1.0 |
示例:完善kset_uevent_ops结构体
1 |
|
测试:
1 | root@topeet:/root# udevadm monitor & |
netlink
netlink 监听广播信息
Netlink 是 Linux 内核中用于内核和用户空间之间进行双工通信的机制。它基于 socket 通信机制,并提供了一种可靠的、异步的、多播的、有序的通信方式。
Netlink 机制的主要特点包括:
- 双工通信:Netlink 允许内核和用户空间之间进行双向通信,使得内核可以向用户空间发送消息,同时也可以接收来自用户空间的消息。
- 可靠性:Netlink 提供了可靠的消息传递机制,保证消息的完整性和可靠性。它使用了确认和重传机制,以确保消息的可靠传输。
- 异步通信:Netlink 支持异步通信,即内核和用户空间可以独立地发送和接收消息,无需同步等待对方的响应。
- 多播支持:Netlink 允许向多个进程或套接字广播消息,以实现一对多的通信。
- 有序传输:Netlink 保证消息的有序传输,即发送的消息按照发送的顺序在接收端按序接收。
Netlink 的应用广泛,常见的应用包括:
- 系统管理工具:如 ifconfig、ip 等工具使用 Netlink 与内核通信来获取和配置网络接口的信息。
- 进程间通信:进程可以使用 Netlink 进行跨进程通信,实现进程间的数据交换和协调。
- 内核模块和用户空间应用程序的通信:内核模块可以通过 Netlink 向用户空间应用程序发送通知或接收用户空间应用程序的指令。
netlink 的使用
创建socket
在 Linux socket 编程中,创建套接字是构建网络应用程序的第一步。套接字可以理解为应用程序和网络之间的桥梁,用于在网络上进行数据的收发和处理。该系统调用的原型和所需头文件如下所示:
| 头文件 | 函数原型 |
|---|---|
#include<sys/types.h>#include<sys/socket.h> |
int socket(int domain, int type, int protocol); |
domain 参数指定了套接字的协议族
协议族指定了套接字所使用的协议类型,常用的协议族包括 AF_INET、AF_INET6、AF_UNIX等。其中:
AF_INET表示 IPv4 协议族AF_INET6表示 IPv6 协议族AF_UNIX表示 Unix 域协议族AF_NETLINK表示 Netlink 域协议族。
type 参数指定了套接字的类型
套接字类型指定了套接字的数据传输方式,常用的套接字类型包括 SOCK_STREAM、SOCK_DGRAM、SOCK_RAW 等。其中:
SOCK_STREAM表示面向连接的流套接字,主要用于可靠传输数据,例如 TCP 协议。SOCK_DGRAM表示无连接的数据报套接字,主要用于不可靠传输数据,例如 UDP 协议。SOCK_RAW表示原始套接字,可以直接访问底层网络协议。
protocol 参数指定了套接字所使用的具体协议。下面分别介绍这三个参数的含义:
协议类型指定了套接字所使用的具体协议类型,常用的协议类型包括 IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP 等。其中:
IPPROTO_TCP表示 TCP 协议IPPROTO_UDP表示 UDP协议IPPROTO_ICMP表示 ICMP 协议NETLINK_KOBJECT_UEVENT表示 NETLINK_KOBJECT 协议
将使用以下代码创建一个新的套接字
1 | int socket_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); |
AF_NETLINK:指定了使用 Netlink 协议族。Netlink 协议族是一种 Linux 特定的协议族,用于内核和用户空间之间的通信SOCK_RAW:指定了创建原始套接字,这种套接字类型可以直接访问底层协议,而不需要进行协议栈处理。在这种情况下,我们可以直接使用 Netlink 协议进行通信。NETLINK_KOBJECT_UEVENT:指定了 Netlink 协议的一种类型,即 kobject uevent 类型。kobject uevent 用于内核对象相关的事件通知,当内核中的 kobject 对象发生变化时,会通过此类型的Netlink 消息通知用户空间。
绑定套接字
| 所需头文件 | 函数原型 |
|---|---|
#include<sys/types.h>#include<sys/socket.h> |
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); |
sockfd 参数:指定了需要绑定的套接字描述符,
addr 参数:指定了需要绑定的地址信息,这里使用
struct sockaddr_nl结构体,struct sockaddr_nl结构体的定义如下:1
2
3
4
5
6struct sockaddr_nl {
sa_family_t nl_family; // AF_NETLINK
unsigned short nl_pad; // zero
uint32_t nl_pid; // port ID
uint32_t nl_groups;// multicast groups mask
};nl_family:表示地址族,此处固定为AF_NETLINK,指示使用 Netlink 协议族。nl_pad:填充字段,设置为 0。在结构体中进行字节对齐时使用。nl_pid:Port ID, 设置为0表示由内核分配。nl_groups:多播组掩码,用于指定感兴趣的多播组。当设置为 1 时,表示用户空间进程只会接收内核事件的基本组的内核事件。这意味着,用户空间进程将只接收到属于基本组的内核事件,而不会接收其他多播组的事件。
addrlen 参数:addrlen 参数是一个整数,指定了 addr 所指向的结构体对应的字节长度。它用于确保正确解析传递给 addr 参数的结构体的大小
示例
1 | { |
接收数据
Netlink 套接字在接收数据时不需要调用 listen 函数,而是可以直接使用 recv 函数进行接收。
| 所需头文件 | 函数原型 |
|---|---|
#include<sys/types.h>#include<sys/socket.h> |
ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
函数参数:
sockfd:指定套接字描述符,即要接收数据的 Netlink 套接字。buf:指向数据接收缓冲区的指针,用于存储接收到的数据。len:指定要读取的数据的字节大小。flags:指定一些标志,用于控制数据的接收方式。通常情况下,可以将其设置为 0。
返回值:
- 成功情况下,返回实际读取到的字节数。
- 如果返回值为 0,表示对方已经关闭了连接。
- 如果返回值为-1,表示发生了错误,可以通过查看 errno 变量来获取具体的错误代码。
使用 recv 函数可以从指定的 Netlink 套接字中接收数据,并将其存储在提供的缓冲区中。函数的返回值表示实际读取到的字节数,可以根据返回值来判断是否成功接收到数据。
示例代码:
1 | while (1) { |
示例代码
1 |
|
测试:
1 | root@topeet:/root# ./netlink_test.o & |
uevent_helper
在kobject_uevent_env()函数中有:
1 |
|
第 3 行为一个 if 表达式,它检查 uevent_helper 数组的第一个元素是否为真。并调用 kobj_usermode_filter 函数进行用户模式过滤,uevent_helper定义如下:
1 | char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH; |
CONFIG_UEVENT_HELPER_PATH是 一 个 宏 定 义 在 内 核 源 码 的include/generated/autoconf.h文件中,如下所示:
1 |
该宏为空 ,所以为了使能 uevent_helper 功能需要在图形配置界面使能CONFIG_UEVENT_HELPER宏来使能uevent_helper 和 CONFIG_UEVENT_HELPER_PATH 宏来设置默认 uevent_helper 路径(如 /sbin/mdev);
uevent_helper 配置方法 1
1 | Device Drivers |
保存配置:
1 | cp .config arch/arm64/configs/rockchip_linux_defconfig |
在上面的配置 1 中设置了 uevent helper 和相对应的路径,这就是配置方法 1,但是这种方式需要重新编译内核,使用起来较为麻烦,除了方法1之外还有更快捷的方法 2 和方法 3:
uevent_helper 配置方法 2
无论是否配置了 CONFIG_UEVENT_HELPER_PATH,在系统启动后,可以使用以下命令来设置 uevent_helper:
1 | echo /sbin/mdev > /sys/kernel/uevent_helper |
这将把 uevent_helper 设置为/sbin/mdev。
uevent_helper 配置方法 3
无论是否配置了 CONFIG_UEVENT_HELPER_PATH,在系统启动后,可以使用以下命令来设置 uevent_helper:
1 | echo /sbin/mdev > /proc/sys/kernel/hotplug |
这将把 uevent_helper 设置为/sbin/mdev。
需要注意的是配置方法 2 和配置方法 3 依赖于上面的配置的Support for uevent helper,File systems和Networking选项,并且可以通过配置方法 2 和配置方法 3 修改配置方法 1 中已经写好的值。对/proc/sys/kernel/hotplug 和/sys/kernel/uevent_helper 进行读写都是为了对 uevent_helper属性进行读写操作。
/sys/kernel/uevent_helper 是 sysfs 文件系统中的一个文件,它是 uevent_helper 属性的接口。通过对该文件进行读写操作,可以读取或修改 uevent_helper 属性的值。在内核源码的kernel/ksysfs.c 目录下可以找到对 uevent_helper 属性的定义和相关操作的实现,具体内容如下所示:
1 | // kernel/ksysfs.c |
uevent_helper_show函数用于将uevent_helper的值写入 buf 中,并返回写入的字符数。uevent_helper_store函数用于将 buf 中的值复制到uevent_helper中,并根据需要进行处理,然后返回写入的字符数。
/proc/sys/kernel/hotplug 是一个虚拟文件,用于配置内核中的热插拔事件处理程序。通过对该文件进行写操作,可以设置 uevent_helper 属性的值。在内核源码的 kernel/sysctl.c 文件中,可以看到对 hotplug 操作其实是对 uevent_helper 进行操作。
1 | // kernel/sysctl.c` |
这段代码定义了一个名为 hotplug 的文件,用于处理 uevent 事件。它与 uevent_helper 属性相关联。
.procname表示文件名,即/proc/hotplug。.data是一个指向uevent_helper结构体的指针,用于保存与该文件相关的数据。该指针指向uevent_helper结构体,用于处理 uevent 事件。.maxlen表示文件的最大长度,即文件内容的最大长度。该值为UEVENT_HELPER_PATH_LEN,表示文件内容的最大长度。.mode表示文件的访问权限。该值为 0644,表示该文件的权限为-rw-r--r--,即所有用户都可以读取该文件,但只有 root 用户可以写入该文件。
示例
在内核中调用用户空间的 uevent_helper 程序来处理 uevent 事件。
uevent_helper 是一个用户空间程序,它可以在内核空间生成 uevent 事件时被调用。如果CONFIG_UEVENT_HELPER宏被定义,那么内核会在生 uevent 事件时调用 uevent_helper 程序,以便在用户空间中处理这些事件。
在kobject_uevent_env()函数中,如果 uevent_helper 变量不为空且 kobj_usermode_filter 函数返回 false,那么就会调用 call_usermodehelper_setup 函数来启动一个用户空间进程,并将 env 中的参数传递给该进程。在这个过程中,env 中的参数将会被转换成环境变量,并被传递给用户空间进程。
1 |
|
方法2:
1 | root@topeet:/root# echo /root/uevent_helper_test.o > /sys/kernel/uevent_helper |
方法3:
1 | root@topeet:/root# echo /root/uevent_helper_test.o > /proc/sys/kernel/hotplug |
可以看到上面两种配置都可以打印出内核加载时传递的 SUBSYSTEM 环境变量。
使用 udev 挂载 U 盘和 TF 卡
配置 buildroot 支持 udev
1 | System configuration |
挂载 U 盘
想使用 udev 来实现 U 盘的自动挂载,还需在/etc/udev/rules.d 目录下创建相应的规则文件
1 | KERNEL=="sd[a-z][0-9]", SUBSYSTEM=="block", ACTION=="add", RUN+="/etc/udev/rules.d/usb/usb-add.sh %k" |
KERNEL=="sd[a-z][0-9]KERNEL:表示匹配设备的内核名。sd[a-z][0-9]:是一个正则表达式模式,sd:表示设备名以 “sd” 开头[a-z]:表示设备名的第三个字符是小写字母[0-9]:表示设备名的第四个字符是数字。
这个模式用于匹配 USB 存储设备的块设备节点,如 /dev/sda1、/dev/sdb2 等。
SUBSYSTEM=="block"SUBSYSTEM:表示匹配设备的子系统名称。block:表示设备的子系统是块设备子系统,即与磁盘、分区等相关的设备。这部分规则是为了确保只匹配块设备子系统下的设备。
ACTION=="add"和ACTION=="remove"ACTION:表示匹配设备的动作。add:表示设备被添加。remove:表示设备被移除。
这部分规则是为了处理设备被添加和被删除的事件。
RUN+="/etc/udev/rules.d/usb/usb-add.sh %kRUN+="...":表示在匹配的设备上执行指定的命令。"/etc/udev/rules.d/usb/usb-add.sh":是要执行的命令的路径,即在设备添加时执行/etc/udev/rules.d/usb/usb-add.sh脚本文件。%k:是 udev 提供的一个变量,表示匹配的设备的内核名
当块设备被添加的时候会执行/etc/udev/rules.d/usb/usb-add.sh 脚本,块设备被删除的时候会执行/etc/udev/rules.d/usb/usb-remove.sh 脚本
/etc/udev/rules.d/usb/usb-add.sh内容如下1
2
/bin/mount -t vfat /dev/$1 /mnt/bin/mount:调用 mount 命令(显式指定完整路径)。-t vfat:指定要挂载的文件系统类型为 VFAT(即 FAT32 或 FAT16,常用于 U 盘、SD 卡等)。/dev/ $ 1:表示设备文件,其中$ 1是脚本或命令行传入的第一个参数。
/etc/udev/rules.d/usb/usb-remove.sh内容如下1
2
3
sync
/bin/umount -l /mnt-l:表示 “lazy unmount”(延迟卸载)。它会立即从文件系统命名空间中解除挂载点,但实际的清理(比如释放设备)会等到设备不再繁忙(没有进程在使用)时才进行。常用于设备“忙”无法正常卸载的情况(比如有 shell 当前在/mnt目录里)。
然后要赋予这两个脚本的执行权限
1 | chmod a+x /etc/udev/rules.d/usb/usb-add.sh /etc/udev/rules.d/usb/usb-remove.sh |
挂载 TF 卡
/etc/udev/rules.d/目录下,创建一个名为 002.rules 的文件
1 | KERNEL=="mmcblk[0-9]p[0-9]", SUBSYSTEM=="block", ACTION=="add", RUN+="/etc/udev/rules.d/tf/tf-add.sh %k" |
KERNEL=="mmcblk[0-9]p[0-9]"KERNEL:表示匹配设备的内核名。"mmcblk[a-z][0-9]":是一个正则表达式模式mmcblk:表示设备名以"mmcblk"开头[0-9]:表示设备名的第 7 个字符和第 9 个字符是数字。
这个模式用于匹配 TF 卡存储设备的块设备节点,如 /dev/mmcblk1p1 等。
SUBSYSTEM=="block"SUBSYSTEM:表示匹配设备的子系统名称。"block":表示设备的子系统是块设备子系统,即与磁盘、分区等相关的设备。
这部分规则是为了确保只匹配块设备子系统下的设备。
ACTION=="add"和ACTION=="remove“ACTION:表示匹配设备的动作。"add":表示设备被添加。"remove":表示设备被 yichu。
这部分规则是为了处理设备被添加和被删除的事件。
RUN+="/etc/udev/rules.d/tf/tf-add.sh %k"RUN+="...":表示在匹配的设备上执行指定的命令。"/etc/udev/rules.d/tf/tf-add.sh":是要执行的命令的路径,即在设备添加时执行/etc/udev/rules.d/tf/tf-add.sh脚本文件。%k:是 udev 提供的一个变量,表示匹配的设备的内核名
当 TF 卡块设备被添加的时候会执行/etc/udev/rules.d/usb/tf-add.sh 脚本,TF 卡块设备被删除的时候会执行/etc/udev/rules.d/tf/tf-remove.sh 脚本
在 /etc/udev/rules.d/usb/tf-add.sh 文件中写入以下内容:
1 |
|
在 /etc/udev/rules.d/usb/tf-remove.sh 文件中写入以下内容
1 |
|
然后要赋予这两个脚本的执行权限
1 | chmod a+x /etc/udev/rules.d/usb/tf-add.sh /etc/udev/rules.d/usb/tf-remove.sh` |
使用 mdev 挂载 U 盘和 TF 卡
配置 buildroot 支持 mdev
1 | make rockchip_rk3568_defconfig |
配置如下:
1 | System configuration |
除了 buildroot 需要配置之外,还需要配置 busybox 的相关选项,因为buildroot使用了busybox
1 | make busybox-menuconfig |
配置如下:
1 | Linux System Utilities |
挂载 U 盘
跟 udev 相同,mdev 也需要添加相应的规则,不同的是 mdev 使用/etc/mdev.conf 文件来配置 mdev 工具的规则和行为,要想使用 mdev 自动挂载 U 盘需要向/etc/mdev.conf 文件中添加以下两条规则
1 | sd[a-z][0-9] 0:0 666 @/etc/mdev/usb_insert.sh |
这两个规则用于处理 U 盘的热插拔事件,并执行相应的操作。在 /etc/mdev.conf 文件中,每一行都是一个规则,具有以下格式:
1 | <设备节点正则表达式> <设备的所有者:设备的所属组> <设备的权限> <设备插入或移除时需要执行的命令> |
下面是对上述两个规则的详细介绍:
sd[a-z][0-9]是一个正则表达式模式,用于匹配以”sd”开头,后跟一个小写字母和一个数字的设备节点,例如/dev/sda1等。0:0 666表示设置设备节点的所有者和权限。0:0 表示所有者和所属组的用户ID和组 ID 均为 0,即 root 用户。666 表示权限为可读可写。@/etc/mdev/usb_insert.sh表示当符合规则的设备插入时,mdev 会执行/etc/mdev/usb_insert.sh脚本。@符号表示执行的是一个 shell 命令。$/etc/mdev/usb_remove.sh表示当符合规则的设备移除时,mdev 会执行/etc/mdev/usb_remove.sh脚本。$符号表示执行的是一个内部命令。
/etc/mdev/usb_insert.sh 和 /etc/mdev/usb_remove.sh 两个文件如下图所示:
/etc/mdev/usb_insert.sh
1 |
|
/etc/mdev/usb_remove.sh
1 |
|
然后要赋予这两个脚本的执行权限
1 | chmod a+x /etc/mdev/usb_insert.sh /etc/mdev/usb_remove.sh |
挂载 TF 卡
向/etc/mdev.conf 文件中添加以下两条规则
1 | mmcblk[0-9]p[0-9] 0:0 666 @/etc/mdev/tf_insert.sh |
mmcblk[0-9]p[0-9]是一个正则表达式模式,用于匹配以"mmcblk"开头的 TF 卡块设备,例如/dev/mmcblk1p1等。0:0 666表示设置设备节点的所有者和权限。0:0表示所有者和所属组的用户 ID 和组 ID 均为 0,即 root 用户。666表示权限为可读可写。@/etc/mdev/tf_insert.sh表示当符合规则的设备插入时,mdev 会执行/etc/mdev/tf_insert.sh脚本。@符号表示执行的是一个 shell 命令。$/etc/mdev/tf_remove.sh表示当符合规则的设备移除时,mdev 会执行/etc/mdev/tf_remove.sh脚本。$符号表示执行的是一个内部命令。
/etc/mdev/tf_insert.sh和/etc/mdev/tf_remove.sh如下所示
/etc/mdev/tf_insert.sh
1 |
|
/etc/mdev/tf_remove.sh
1 |
|
然后要赋予这两个脚本的执行权限
1 | chmod a+x /etc/mdev/tf_insert.sh /etc/mdev/tf_remove.sh |