热插拔理论与实例分析


前言

热插拔:带电的情况下装卸设备。
热插拔是内核与用户空间之间,通过调用用户空间程序来实现的。当内核发生了某种热插拔事件时,内核就会调用用户空间的程序实现交互。
产生机制主要有 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 没有半毛钱关系。

  1. 创建
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

  1. 绑定
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,然后执行相关的程序。

  1. 配置 buildroot 支持 udev 并打开 menuconfig ,将 dev management 设置为 Dynamic using devtmpfs + eudev,即使用 udev 去管理。
System configuration
/dev management ( Dynamic using devtmpfs + eudev )
Dynamic using devtmpfs + eudev
  1. 创建配置规则
    在 /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 目录下的规则文件优先级更高。

  1. 创建配置规则
    在 /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盘自动挂载

  1. 配置支持 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
  2. 设置 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 命令
$ 开头的句子表示一个系统命令或者可执行文件

  1. 创建脚本文件
    在 /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盘自动挂载

  1. 配置支持 mdev

    同上。

  2. 设置 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
  1. 创建脚本文件

    在 /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
  1. 测试
    在这里插入图片描述
    其实对于 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盘时,无需任何脚本,直接就可以挂载在系统中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值