• 33.Linux-实现U盘自动挂载(详解)


    1.当我们每次插入u盘后,都会自动创键U盘的设备节点/dev/sda%d

    这是因为里面调用了device_create()实现的, busybox的mdev机制就会根据主次设备号等信息,在/dev下创建设备节点,如下图所示:

     

    而想使用上面的sda1设备节点,读写数据时,还需要使用mount /dev/sda1  /mnt,来挂载u盘才行,会显得非常麻烦,如下图所示:

     

    2.其实,可以在/etc/mdev.conf文件里加入一行语句就能实现自动装载u盘,也可以在里面干其它与设备节点相关的事

    2.1而/etc/mdev.conf又是什么?

    它是属于mdev的一个配置文件,而mdev之前就讲过了,它主要的功能是管理/dev目录底下的设备节点

    当系统中有自动注册设备节点的时候,mdev就会调用/etc/mdev.conf一次, 该文件可以实现与设备节点相关的事,比如自动装载usb,打印创建的设备节点信息等

    3.我们首先来分析device_create(),是如何来调用到/etc/mdev.conf的,后面再讲如何使用mdev.conf(也可以直接跳过,直接看下面第4小节,如何使用)

    (PS: 之前创建字符设备节点用的class_device_create(),其实是和device_create功能差不多)

    3.1 device_create()最终调用了:device_create()->device_register()->device_add():

    device_create()->device_register()->device_add()函数如下所示:
    
    int class_device_add(struct class_device *class_dev)
    {
           ... ...
           kobject_uevent(&class_dev->kobj, KOBJ_ADD);         // KOBJ_ADD是一个枚举值
                  //调用了kobject_uevent_env(kobj, action, NULL);              // action=KOBJ_ADD
    }

    3.2 device_create()->device_register()->device_add()->kobject_uevent_env()函数如下所示:

    int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,char *envp_ext[])
    {
           char **envp;
           char *buffer;
           char *scratch;
           int i = 0;
           ... ...
    
           /* 通过KOBJ_ADD获取字符串"add",所以action_string="add"  */
           action_string = action_to_string(action);              // action=KOBJ_ADD
    
                                                           
           /* environment index */
           envp = kzalloc(NUM_ENVP * sizeof (char *), GFP_KERNEL);      //分配一个环境变量索引值
    
           /* environment values */
        buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);     //分配一个环境变量缓冲值      
    
    /* event environemnt for helper process only */
    /*设置环境变量*/
           envp[i++] = "HOME=/";
           envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
           scratch = buffer;
           envp [i++] = scratch;
           scratch += sprintf(scratch, "ACTION=%s", action_string) + 1;  //"ACTION= add"
           envp [i++] = scratch;
           scratch += sprintf (scratch, "DEVPATH=%s", devpath) + 1;
           envp [i++] = scratch;
           scratch += sprintf(scratch, "SUBSYSTEM=%s", subsystem) + 1;
           ... ...
           /*调用应用程序,比如mdev*/
           if (uevent_helper[0]) {
                char *argv [3];
                  argv [0] = uevent_helper;       // uevent_helper[]= "/sbin/hotplug";
                  argv [1] = (char *)subsystem;
                  argv [2] = NULL;
                  call_usermodehelper (argv[0], argv, envp, 0);        //调用应用程序,根据传入的环境变量参数来创建设备节点
           }
    }

    从上面的代码和注释来看,最终通过*argv[], *envp[]两个字符串数组里面存的环境变量参数来创建设备节点的

    3.2接下来便在kobject_uevent_env()函数里添加打印信息, 然后重新烧内核:

     

     

    3.3然后我们以注册一个按键驱动为例 

    输入 insmod key.ko,打印了以下语句:

    class_device: argv[0]=/sbin/mdev                 //调用mdev
    
    class_device: argv[1]=sixth_dev                      //类名
    
    class_device: envp[0]=HOME=/
    
    class_device: envp[1]=PATH=/sbin:/bin:/usr/sbin:/usr/bin
    
    class_device: envp[2]=ACTION=add             //add:表示添加设备节点,  若=remove:表示卸载设备节点
    
    class_device: envp[3]=DEVPATH=/class/sixth_dev/buttons   //设备的路径
    
    class_device: envp[4]=SUBSYSTEM=sixth_dev                //类名
    
    class_device: envp[5]=SEQNUM=745
    
    class_device: envp[6]=MAJOR=252                          //主设备号
    
    class_device: envp[7]=MINOR=0

    3.4最终这些参数根据/sbin/mdev就进入了busybox的mdev.c的mdev_main()函数里:

    int mdev_main(int argc, char **argv)
    {
    ... ...
    action = getenv("ACTION");           //获取传进来的执行参数,它等于“add”,则表示创建设备节点
    env_path = getenv("DEVPATH");      //获取设备的路径“/class/sixth_dev/buttons”
    sprintf(temp, "/sys%s", env_path);   //指定temp (真正设备路径)为“/sys/class/sixth_dev/buttons”
    
    if (!strcmp(action, "remove"))           //卸载设备节点
                        make_device(temp, 1);
    
    else if (!strcmp(action, "add")) {       //创建设备节点
                         make_device(temp, 0);
     ... ... 
    }

    3.5最终调用mdev_main ()->make_device()函数来创建/卸载设备节点,该函数如下所示:

    static void make_device(char *path, int delete) //delete=0:创建, delete=1:卸载
    {
           /*判断创建的设备节点是否是有效的设备*/
           if (!delete) {
                  strcat(path, "/dev");
                  len = open_read_close(path, temp + 1, 64);
                  *temp++ = 0;
                  if (len < 1) return;
           }
    
    device_name = bb_basename(path);    //通过设备路径,来获取要创建/卸载的设备节点名称
                          //例: path =“/sys /class/sixth_dev/buttons”,那么device_name=“buttons”
    
     
    
    type = path[5]=='c' ? S_IFCHR : S_IFBLK;     //判断如果是在/sys/class/目录下,那么就是字符设备
                                                  //因为块设备,是存在/sys/block/目录下的
    
    
    /* 如果配置了支持mdev.conf选项,那么就解析里边内容并执行   */
     if (ENABLE_FEATURE_MDEV_CONF) { 
           /* mmap the config file */
    fd = open("/etc/mdev.conf", O_RDONLY);     //调用/etc/mdev.conf配置文件
         
         ... ...
    //开始操作 mdev.conf配置文件 } if (!delete) { //如果是创建设备节点 if (sscanf(temp, "%d:%d", &major, &minor) != 2) return; //获取主次设备号
            /*调用mknod ()创建字符设备节点*/ if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) bb_perror_msg_and_die("mknod %s", device_name); if (major == root_major && minor == root_minor) symlink(device_name, "root"); /*若配置了支持mdev.conf选项,则调用chown命令来改变属主,默认uid和gid=0 */ if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid); } if (delete) unlink(device_name); //如果是卸载设备节点 }

    从上面的代码和注释分析到,要使用mdev.conf配置文件,还需要配置busybox的menuconfig, 使mdev支持mdev.conf选项才行

    如下图,进入busybox目录,然后输入make menuconfig,发现我们已经配置过了该选项了

     

    4.接下来,便来看看如何使用mdev.conf,  参考busybox-1.7.0/docs/mdev.txt文档

    使用方法如下所示:

    the format:

           <device regex> <uid>:<gid> <octal permissions> [<@|$|*> <command>]

    The special characters have the meaning:

    @ Run after creating the device.

    $ Run before removing the device.

    * Run both after creating and before removing the device.

    大概就是:

    配置文件格式:

    <device regex> <uid>:<gid> <octal permissions> [<@|$|*> <command>]  

    各个参数代表的含义如下:  

    device regex:

    正则表达式,来表达哪一个设备 ,正则表达式讲解链接:https://deerchao.net/tutorials/regex/regex.htm

    uid: 

    owner       (uid,gid:注册设备节点时,就会被chown命令调用,来改变设备的属主,默认都填0即可)

    gid: 

    组ID  

    octal permissions:

    以八进制表示的权限值,会被chmod命令调用,来更改设备的访问权限,默认填660即可

    @ : 创建设备节点之后执行命令  

    $  :  删除设备节点之前执行命令  

    *   : 创建设备节点之后 和 删除设备节点之前 执行命令  

    command : 要执行的命令  

     

    5.接下来便来使用mdev.conf,实现u盘自动装载

    vi /etc/mdev.conf

    添加以下一句:

    sda[1-9]+ 0:0 660 * if [ $ACTION = "add" ]; then mount /dev/$MDEV /mnt; else umount /mnt; fi


    [1-9] : 匹配1~9的数字,

    +   :  重复匹配一次或更多次

    $ACTION=="add"   :表示注册设备节点,否则就是注销设备节点

    /dev/$MDEV      :表示要创建/注销的那个设备节点


    所以当我们插上u盘,自动创建了/dev/sda1时,mdev便会进入/etc/mdev.conf配置文件,然后执行mount /dev/ 命令,即可自动装载U盘,如下图所示:

     

    输入ls /dev/sda1  -l,可以看到都是通过mdev.conf里配置信息来创建的设备节点,如下图所示:

    而取出u盘时,同样自动umount  /mnt来卸载

     

    接下来下章来学习如何制作多分区U盘自动挂载:https://www.cnblogs.com/lifexy/p/10107310.html

  • 相关阅读:
    优化SQL查询:如何写出高性能SQL语句
    动态库与静态库
    多线程程序中fork导致的一些问题
    合理的使用size_t可以提高程序的可移植性和代码的可读性,让你的程序更高效。
    linux下C++ STL hash_map的使用以及使用char *型变量作为Key值的一大“坑”
    阅读腾讯编程规范的笔记
    2、vector的实现
    linux下C++对线程的封装
    1、空间配置器
    SQL Server三种表连接原理
  • 原文地址:https://www.cnblogs.com/lifexy/p/7891883.html
Copyright © 2020-2023  润新知