文章目录
前言
热插拔:带电的情况下装卸设备。
热插拔是内核与用户空间之间,通过调用用户空间程序来实现的。当内核发生了某种热插拔事件时,内核就会调用用户空间的程序实现交互。
产生机制主要有 udev、mdev,mdev 是 udev 的简化版本。
udev 通过 netlink 监听内核发送的 uevent 执行相应的回调。
mdev 基于 uevent_helper 机制,内核产生的 uevent_helper 所指的用户程序 mdev 来执行热插拔操作。
一、内核 uevent 如何发送?
参考链接:https://www.freesion.com/article/12021598476/
参考链接:https://cloud.tencent.com/developer/article/1603473
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
作用:发送一个 uevent
kobj:发生动作的kobject
action:发生的动作
案例:
struct kobject *mykobject;
struct kset *mykset;
struct kobj_type *mytype;
static int mykobj_init(void)
{
mykest = kset_create_and_add("mykset", NULL, NULL);
mykobject = kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject->kset=mykset;
int ret = kobject_init_and_add(mykobject, &mytype, NULL, "%s", "mykobject");
ret = kobject_uevent(mykobject, KOBJ_CHANGE); //KOBJ_ADD 表示添加了一个设备
return ret;
}
static void mykobj_exit(void)
{
kobject_put(mykobject);
kset_unregister(mykset);
}
action 参数:
KOBJ_ADD 表示添加了一个对象到内核对象系统中
KOBJ_REMOVE 表示从内核对象系统中删除一个对象
KOBJ_CHANGE 表示对内核对象进行更改,例如属性修改等
KOBJ_MOVE 表示将一个内核对象从一个位置移动到另一个位置
KOBJ_ONLINE 表示将一个对象上线,使其可以被访问
KOBJ_OFFLINE 表示将一个对象下线,使其不能被访问
KOBJ_BIND 表示将一个设备连接到内核对象上
KOBJ_UNBIND 表示从内核对象上将一个设备解绑
KOBJ_MAX 表示枚举类型的最大值,通常用于表示没有任何操作行为
那么如何知道内核有无发送 uevent 通知?
udevadm
info 查询sysfs或者udev的数据库
trigger 从内核请求events
settle 查看udev事件队列,如果所有的events已处理则退出
control 修改udev后台的内部状态信息
monitor 监控内核的uevents
…
主要使用 monitor 来监听内核的 uevent 事件。
先使用 udevadm monitor & 开启后台监听,再加载注册有 uevent 的驱动。
加载上例的驱动,实际收到两条 uevent :CHANGE 和 ADD
有一个问题:假如没有设置 kset,那么还会有 uevent 出现吗???
答案是我们增加的 uevent(KOBJ_CHAGE) 并没有出现,只出现了 KOBJ_ADD。
那么回到代码:
kobject_uevent
kobject_uevent_env
kobject_uevent_env 解析
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp[]);
/*
* kobj:正在发生动作的对象
* action:正在发生的动作
* envp:环境变量的指针
*/
再看具体实现:
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])
{
struct kobj_uevent_env *env; //用于存储发送的事件和环境变量
const char *action_string = kobject_actions[action]; //事件的类型
const char *devpath = NULL; //存放kobject的路径
const char *subsystem; //存放所属子系统的名称
struct kobject *top_kobj; //指向顶层top_kobj的kobject指针
struct kset *kset; //指向所属的kset
const struct kset_uevent_ops *uevent_ops; //表示kset对象的uevent的操作函数
int i = 0; //计数器i,用来编译环境变量
int retval = 0; //执行结果
#ifdef CONFIG_NET
struct uevent_sock *ue_sk;
#endif
/* while循环找到kobj所属的顶层kset */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
if (!top_kobj->kset) { //发送一个envet必须存在kset
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " "without kset!\n", kobject_name(kobj), kobj, __func__);
return -EINVAL;
}
kset = top_kobj->kset; //得到最顶层kset的uevent_ops
uevent_ops = kset->uevent_ops;
/* skip the event, if uevent_suppress is set*/
if (kobj->uevent_suppress) { //如果uevnet_suppress=1,则不发送uevent
pr_debug("kobject: '%s' (%p): %s: uevent_suppress ""caused the event to drop!\n", kobject_name(kobj), kobj, __func__);
return 0;
}
/* skip the event, if the filter returns zero. */
if (uevent_ops && uevent_ops->filter) //通过filter函数过滤,如果返回0,则说明顶层的kset过滤了此event
if (!uevent_ops->filter(kset, kobj)) {
pr_debug("kobject: '%s' (%p): %s: filter function " "caused the event to drop!\n", kobject_name(kobj), kobj, __func__);
return 0;
}
/* originating subsystem */
if (uevent_ops && uevent_ops->name) //通过name函数设置subsystem
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj, __func__);
return 0;
}
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); //分配环境变量buff
if (!env)
return -ENOMEM;
/* complete object path */
devpath = kobject_get_path(kobj, GFP_KERNEL); //得到此obj的路径
if (!devpath) {
retval = -ENOENT;
goto exit;
}
/* default keys */
retval = add_uevent_var(env, "ACTION=%s", action_string); //添加环境变量,ACTION, DEVPATH, SUBSYSTEM到环境变量buff中
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
/* keys passed in from the caller */
if (envp_ext) { //添加调用者提供的参数
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
}
/* let the kset specific function add its stuff */ //让Kset完成一些自己的私人处理
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kset, kobj, env);
if (retval) {
pr_debug("kobject: '%s' (%p): %s: uevent() returned " "%d\n", kobject_name(kobj), kobj, __func__, retval);
goto exit;
}
}
/* Mark "add" and "remove" events in the object to ensure proper
* events to userspace during automatic cleanup. If the object did
* send an "add" event, "remove" will automatically generated by
* the core, if not already done by the caller.
*/
if (action == KOBJ_ADD)
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
mutex_lock(&uevent_sock_mutex);
/* we will send an event, so request a new sequence number */ //更新uevent seq number
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
if (retval) {
mutex_unlock(&uevent_sock_mutex);
goto exit;
}
#if defined(CONFIG_NET) //如果开启了CONFIG_NET就使用netlink发送Uevent
/* send netlink message */
list_for_each_entry(ue_sk, &uevent_sock_list, list) {
struct sock *uevent_sock = ue_sk->sk;
struct sk_buff *skb;
size_t len;
if (!netlink_has_listeners(uevent_sock, 1))
continue;
/* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
if (skb) {
char *scratch;
/* add header */
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);
/* copy keys to our continuous event payload buffer */
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}
NETLINK_CB(skb).dst_group = 1;
retval = netlink_broadcast_filtered(uevent_sock, skb,
0, 1, GFP_KERNEL,
kobj_bcast_filter,
kobj);
/* ENOBUFS should be handled in userspace */
if (retval == -ENOBUFS || retval == -ESRCH)
retval = 0;
} else
retval = -ENOMEM;
}
#endif //实际到了这里,udev 的热插拔机制就结束了,下面是 mdev 的
mutex_unlock(&uevent_sock_mutex);
#ifdef CONFIG_UEVENT_HELPER //如果开启了就是用uevent_helper发送uevent。
/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
struct subprocess_info *info;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env, "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
retval = init_uevent_argv(env, subsystem);
if (retval)
goto exit;
retval = -ENOMEM;
info = call_usermodehelper_setup(env->argv[0], env->argv, env->envp, GFP_KERNEL, NULL, cleanup_uevent_env, env);
if (info) {
retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
env = NULL; /* freed by cleanup_uevent_env */
}
}
#endif
exit:
kfree(devpath);
kfree(env);
return retval;
}
udev 机制最重要的一个API:
kobject_uevent_net_broadcast(kobj, env, action_string, devpath);
作用:将一个 uevent 事件发送到系统中所有的网络命名空间中。
action_string:是一个字符串,表示 uevent 事件的类型。
这个函数主要用途是在内核中广播一个 uevent 事件,以便用户空间的应用程序可以接收并处理这些事件。
mdev 机制:
#ifdef CONFIG_UEVENT_HELPER //如果开启了就是用uevent_helper发送uevent
/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
struct subprocess_info *info;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env, "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
retval = init_uevent_argv(env, subsystem);
if (retval)
goto exit;
retval = -ENOMEM;
info = call_usermodehelper_setup(env->argv[0], env->argv, env->envp, GFP_KERNEL, NULL, cleanup_uevent_env, env);
if (info) {
retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
env = NULL; /* freed by cleanup_uevent_env */
}
}
#endif
uevent_helper 是一个用户空间程序,它可以在内核空间生成 uevent 事件时被调用。
上面的代码中,如果 uevent_helper 变量不为空且 kobj_usermode_filter 返回 false,那么就会调用 call_usermodehelper_setup() 启动一个用户空间进程,并将 env 中的参数传递给该进程。
在这个过程中,env 中的参数将会被转换成环境变量,并被传递给用户空间进程。
kset_uevent_ops 结构
之前在注册 kset 的时候,kset_create_and_add(… , const struct kset_uevent_ops *uevent_ops, …)中:
struct kset_uevent_ops {
int (* const filter)(struct kset *kset, struct kobject *kobj);
const char *(* const name)(struct kset *kset, struct kobject *kobj);
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
案例
struct kobject *mykobject_1;
struct kobject *mykobject_2;
struct kset *mykset;
struct kobj_type *mytype;
int myuevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env){
add_uevent_var(env, "MYDEVICE=%s", "wei");
return 0;
}
/* 过滤名为 mykobject01 的内核对象的 uevent */
int myfilter(struct kset *kset, struct kobject *kobj){
if(strcmp(kobj->name, "mykobject01") == 0)
return 0;
else
return 1;
}
const char *myname(struct kset *kset, struct kobject *kobj){
return "my_kset";
}
struct kset_uevent_ops my_uevent_ops = {
.filter = myfilter,
.uevent = myuevent,
.name = myname,
}
static int mykobj_init(void)
{
mykest = kset_create_and_add("mykset", NULL, NULL);
mykobject_1 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject_1->kset=mykset;
int ret = kobject_init_and_add(mykobject_1, &mytype, NULL, "%s", "mykobject01");
mykobject_2 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject_2->kset=mykset;
int ret = kobject_init_and_add(mykobject_2, &mytype, NULL, "%s", "mykobject02");
ret = kobject_uevent(mykobject_1, KOBJ_CHANGE); //KOBJ_ADD 表示添加了一个设备
ret = kobject_uevent(mykobject_2, KOBJ_ADD);
return ret;
}
static void mykobj_exit(void)
{
kobject_put(mykobject);
kset_unregister(mykset);
}
先使用 udevadm monitor & 开启后台监听,再加载注册有 uevent 的驱动。
加载上例的驱动,实际收到两条 uevent :
/module/uevent_ops 的 ADD
/mykset/mykobject02 的 ADD
可以看到 mykobject01 的 uevent 已经被过滤掉了。
通过 netlink 监听广播信息
netlink 是基于 socket 的进程间通信,跟网络方面的 socket 没有半毛钱关系。
- 创建
int socket(int domain, int type, int protocol);
domain:选择用于通信的协议簇,netlink 机制设置为 AF_NETLINK
type:指定套接字的类型。netlink 固定为 SOCK_RAW
protocol:为给定的通信域和套接字类型选择默认协议。目前支持的协议类型在 linux/netlink.h 中定义。
#define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_UNUSED 1 /* Unused number */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */
#define NETLINK_SOCK_DIAG 4 /* socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
#define NETLINK_CTRL 17
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
#define NETLINK_ECRYPTFS 19
#define NETLINK_RDMA 20
#define NETLINK_CRYPTO 21 /* Crypto layer */
#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
#define MAX_LINKS 32</span>
要接收 uevent, 就需要设置 protocol 为 NETLINK_KOBJECT_UEVENT
- 绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket描述符
struct sockaddr_nl{
sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
__u32 nl_groups;
}
nl_family:固定为 AF_NETLINK
nl_pad:填充字段,设置为0
nl_pid:设置为当前进程的PID,也可以设置为0,表示不加入任何多播组
nl_groups:表示消息发送到的组播的掩码。设置为1时,表示用户空间进程只会接收内核事件的基本组的内核事件。
addrlen:指定了 addr 指向的结构体对应的字节长度。
3. 接收
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:指定套接字描述符
buf:接收缓冲
len:读取数据的字节大小
flags:指定一些标志用于控制如何接收数据,通常设置为0
返回值:接收成功则返回实际接收的字节数
案例:接收内核所有 uevent 消息
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
int main(int argc, char *argv[]){
int ret;
struct sockaddr_nl *nl;
bzero(nl, (sizeof(struct sockaddr_nl)));
nl->nl_family = AF_NETLINK;
nl->nl_pid = 0;
nl->nl_groups = 1;
int socket_fd = socket(AF_NETLINK, SOCK_RAM, NETLINK_KOBJECT_UEVENT);
ret = bind(socket_fd, (struct sockaddr *)nl, sizeof(struct sockaddr_nl));
while(1)
{
bzero(buf, 4096);
len = recv(socket_fd, &buf, 4096, 0);
for(i=0; i<len; i++){
if(*(buf+i)=='\0'){
buf[i]='\n';
}
}
printf("%s\n", buf);
}
return 0;
}
设置 uevent_helper
#ifdef CONFIG_UEVENT_HELPER
char uevent_helper(UEVENT_HELPER_PATH_LEN) = CONFIG_UEVENT_HELPER_PATH;
#endif
我使用的开发板处理器是 RK3568,所以我需要配置一下内核的架构确保是arm64。
export ARCH=arm64
make menuconfig
- 方法1:
在编译内核的时候直接配置 CONFIG_UEVENT_HELPER_PATH,并且在系统启动后不去修改 uevent_helper 和 hotplug 属性值。
配置1:
Device Driver
Generic Driver Options
[*]Support for uevent helper
()path to uevent helper //设置 mdev 路径
配置2:
File systems
Pseudo filesystems
[*]/proc file system support
配置3:
file systems
Pseudo filesystems
proc file system support
[*]Sysctl support(/proc/sys)
配置4:
[*]Networking support
- 方法2:
不管 CONFIG_UEVENT_HELPER_PATH 有没有被配置,系统启动后可以通过命令。
echo /sbin/mdev > /sys/kernel/uevent_helper
- 方法3:
不管 CONFIG_UEVENT_HELPER_PATH 有没有被配置,系统启动后可以通过命令。
echo /sbin/mdev > /proc/sys/kernel/hotplug
其实都是把路径传给环境变量。
在内核源码 kernel/sysctl.c 下可以看到对 hotplug 操作就是对 uevent_helper 操作。
#ifdef CONFIG_UEVENT_HELPER
{
.procname = "hotplug", //文件名
.data = &uevent_helper, //指向 uevent_helper 结构体的指针
.maxlen = UEVENT_HELPER_PATH_LEN, //文件最大长度
.mode = 0644, //文件访问权限
.proc_handler= proc_dostring, //触发回调函数
}
案例:通过 uevent_helper 处理 uevent 事件
实际的 udev 和 mdev 是非常发杂的,这里只是捕捉了其中一个非常非常简单的事件。
int main(int argc, char *argv[])
{
int fd = open("dev/tty", O_WRONLY);
dup2(fd, STDOUT_FILENO);
printf("SUBSYSTEM is %s\n", getenv("SUBSYSTEM"));
/* 在之前的驱动代码中,kobject_uevent() 会调用 kobject_uevent_env(),该函数会设置一些列固定的环境变量。
其中,retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem) 就是其中的一句
*/
close(fd);
return 0;
}
在配置了 CONFIG_UEVENT_HELPER 并且 uevent_helper 不为空的情况下,就可以使用 call_usermodehelper_exec 去调用用户空间的程序,而 uevent_helper 为 CONFIG_UEVENT_HELPER_PATH。
call_usermodehelper_exec() 是一个内核空间调用用户空间程序的函数,该函数执行用户空间程序时,将其作为子进程运行,并将其标准输入输出错误重定向到相应的文件描述符。
所以用户空间的 printf 无法在终端展示。
但是在调用 call_usermodehelper_exec() 时,可以用 dup() 文件描述符复制函数,将标准输出重定向到终端。
int fd = open("dev/tty", O_WRONLY);
dup2(fd, STDOUT_FILENO);
close(fd);
dev/tty 设备代表当前终端。
char *getenv(const char *name)
/*作用:搜索 name 所指向的环境字符串,并返回相关的值给字符串
返回值:返回一个以 null 结尾的字符串,该字符串为被请求环境变量的值。如果该环境变量不存在,则返回 NULL。
*/
udev 实现U盘自动挂载
udev 其实是一个死循环,一直在监听uevent,然后执行相关的程序。
- 配置 buildroot 支持 udev 并打开 menuconfig ,将 dev management 设置为 Dynamic using devtmpfs + eudev,即使用 udev 去管理。
System configuration
/dev management ( Dynamic using devtmpfs + eudev )
Dynamic using devtmpfs + eudev
- 创建配置规则
在 /etc/udev/rules.d/ 目录下创建一个 001.rules 文件。如果没有 /etc/udev/rules.d 则使用 mkdir 命令创建对应的目录。
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
/* 当 U盘插入时,会在 /dev/ 目录下生成 sdxxx节点,命名方式就是 sd[a-z][0-9] */
注意 !ACTION== 后面加的uevent 事件。不是对应的事件是不会触发规则的。
当新增一个 usb 设备,执行 /etc/udev/usb-add.sh 脚本文件,并传入参数 sd[a-z][0-9]
当移除一个 usb 设备,执行 /etc/udev/usb-remove.sh 脚本文件
在 /etc/udev/rules.d 目录下创建一个 usb 目录,在 usb 目录下创建一个 usb-add.sh 和 usb-remove.sh 脚本:
usb-add.sh
#!/bin/sh
/bin/mount -t vfat /dev/$1/mnt /* 将 U盘 挂载到 /mnt/ 目录
sync /* 把缓存写进U盘 */
usb-remove.sh
#!/bin/sh
sync
/bin/umount -l /mnt
然后使用 chmod 命令将 usb-add.sh 和 usb-remove.sh 的权限设置为 777。
先用 df 查看插入前的硬盘分区:
再将U盘插进开发板,可以看到终端这些调试信息:
其中,sda: sda1 就是我们的 U盘设备。
我们注意到调试信息中出现了U盘生产商的信息(Kingston),那么就可以根据生产商信息来决定是否让开发板使用该 U盘。
使用命令:df
可以看到系统硬盘各个分区的情况,其中就有U盘 sd 分区。
udev 实现TF盘自动挂载
Linux系统本身就支持自动挂载 TF卡,从 /lib/udev/rules.d 目录中可以看到很多规则文件,这些都是相应的热插拔调用的规则。
其中 sd-cards-auto-mount.rules 就是TF卡自动挂载的规则。
注意:/etc/udev/rules.d/ 和 /lib/udev/rules.d 下的规则文件都可以起到功能,但是 /lib/udev/rules.d 目录下的规则文件优先级更高。
- 创建配置规则
在 /etc/udev/rules.d/ 目录下创建一个 001.rules 文件。如果没有 /etc/udev/rules.d 则使用 mkdir 命令创建对应的目录。
KERNEL=="mmcblk[0-9]p[0-9]",SUBSYSTEM=="block",ACTION=="add",RUN+="/etc/udev/rules.d/tf/tf-add.sh %k"
SUBSYSTEM=="block",ACTION=="remove",RUN+="/etc/udev/rules.d/tf/tf-remove.sh
在 /etc/udev/rules.d 目录下创建一个 tf 目录,在 tf 目录下创建一个 tf-add.sh 和 tf-remove.sh 脚本:
tf-add.sh
#!/bin/sh
/bin/mount -t vfat /dev/$1/mnt /* 将 U盘 挂载到 /mnt/ 目录
sync /* 把缓存写进U盘 */
tf-remove.sh
#!/bin/sh
sync
/bin/umount -l /mnt
然后使用 chmod 命令将 tf-add.sh 和 tf-remove.sh 的权限设置为 777。
mdev 实现U盘自动挂载
- 配置支持 mdev
System configuration
/dev management ( Dynamic using devtmpfs + mdev )
设置成 mdev
在 buildroot 目录下使用命令 make busybox-memuconfig 打开 busybox 配置界面。
Linux System Utilities
mdev
Support /etc/mdev.config
Support subdirs/syslinks
Support command execution at device addition/removal - 设置 mdev 规则
在 /etc/mdev.conf 文件中添加对 U盘热插拔的事件响应。
添加以下规则:
sd[a-z][0-9] 0:0 666 @/etc/mdev/usb_insert.sh
sd[a-z] 0:0 666 $/etc/mdev/usb_remove.sh
@ 开头的句子表示一个 shell 命令
$ 开头的句子表示一个系统命令或者可执行文件
- 创建脚本文件
在 /etc/mdev/ 目录下创建执行脚本 usb_insert.sh
#!/bin/sh
if [-d /sys/block/*/SMDEV];then
mount /dev/$MDEV /mnt
sync
fi
和 usb_remove.sh
#!/bin/sh
sync
umount -l /mnt
测试
使用命令 echo “/sbin/mdev” > "/proc/sys/kernel/hotplug 设置 mdev。
插入U盘,用 df 命令查看。
如果没有发现自己的 U盘,那么使用命令 ps -aux | grep “udev” 查看有没有 udev 进程。
mdev 实现TF盘自动挂载
-
配置支持 mdev
同上。
-
设置 mdev 规则
在 /etc/mdev.conf 文件中添加对 U盘热插拔的事件响应。
添加以下规则:
mmcblk[a-z][0-9] 0:0 666 @/etc/mdev/tf_insert.sh
mmcblk[a-z] 0:0 666 $/etc/mdev/tf_remove.sh
-
创建脚本文件
在 /etc/mdev/ 目录下创建一个脚本文件 tf_insert.sh
#!/bin/sh
if [-d /sys/block/*/SMDEV];then
mount /dev/$MDEV /mnt
sync
fi
和 tf_remove.sh
#!/bin/sh
sync
umount -l /mnt
- 测试
其实对于 mdev,操作比 udev 相对方便一些,而且 全程下来,同样类型的设备其实只是设备名不同而已,而这些设备名又是固定的。
udev + usbmount 自动挂载
usbmount 是一个用于自动挂载USB存储设备的工具,可以在Linux系统中自动挂载插入的USB存储设备,并在设备拔出时自动卸载。
USBmount 的原理是通过 udev 监视 USB 设备的插拔事件,并在检测到设备插入时自动挂载设备,检测到设备拔出时自动卸载设备。
USBmount 不需要手动挂载或卸载USB存储设备。
添加方式:
在 buildroot 目录 make menuconfig
System configuration
/dev management( Dynamic using devtmpfs + eudev )
Dynamic using devtmpfs + eudev
回到 menu
Target package
Hardware handling
usbmount
保存后,直接编译文件系统,烧写到系统即可。
再次插入U盘时,无需任何脚本,直接就可以挂载在系统中。