busybox 中 mdev 学习

busybox 的地位是牢牢不可撼动啊,前面简单学习了 busybox 的 init,今天自己研究研究它的 mdev机制。

 

1. 为什么需要 mdev?

简单来说,就是为了创建和管理 /dev 目录下的设备文件,包括初始化对象和动态更新。具体呢,在文件系统被加载时,通过读取内核放在 /sys/class 目录下的设备信息,在 /dev 目录下创建设备文件;在系统运行过程中,通过接收 uevent ,来判断是否需要移出设备文件。

简单来讲,/sys/class 多半负责列出内核支持项目的有无,mdev 则负责维护用户空间中那个可以读写的设备文件。

例如,我们写好了一个字符驱动,然后,我们可以在驱动 init 中使用 class_create() 和 class_device_create() 调用,以实现驱动被加载后,可以出现在 /sys/class 中,进而自动由 mdev 在 /dev 中创建设备节点。当然,我们也可以用 “mknode /dev/xxx c 主设备号 次设备号” 来手动创建,或者,将驱动注册为 MISC device,这样,驱动会出现在 /sys/misc/ 类下面,也会自动加载。

另外还需要说明的是,在针对大型机的 Linux 发行版中,对应的机制是 udev。功能比 mdev 丰富的多得多。

 

2. mdev 怎么用?

mdev 是 busybox 二进制文件的一个软链接;和其它由 busybox 提供的工具比如 ls、cp、mv 什么的一样,busybox 会先判断 *argc[0],即程序名,来决定具体执行哪个指令。如 ls -lh,*argc[0] 便指向 ls。

熟悉 C 的,对这样的用法应该不陌生。至于,mdev 在系统中的用法,基本上是固定的。

首先,是文件系统加载时,设备文件的初始化,在 init 脚本中使用 mdev -s 。一次扫描,为/sys/class (如果 /sys/class/block 不存在,还会去扫描 /dev/block)下所有的设备都创建设备文件。当然,前提是 /sys 已经被挂载,所以,busybox 的文档 mdev.txt 中,有下面一段经典的初始化代码:

Here's a typical code snippet from the init script: 
[0] mount -t proc proc /proc
[1] mount -t sysfs sysfs /sys
[2] echo /sbin/mdev > /proc/sys/kernel/hotplug
[3] mdev -s

Alternatively, without procfs the above becomes:
[1] mount -t sysfs sysfs /sys
[2] sysctl -w kernel.hotplug=/sbin/mdev
[3] mdev -s
Of course, a more "full" setup would entail executing this before the previous code snippet:

[4] mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
[5] mkdir /dev/pts
[6] mount -t devpts devpts /dev/pts

上面的 init 代码中,mdev -s 即负责所有设备初始化。另一句,“echo /sbin/mdev > /proc/sys/kernel/hotplug” 则是告诉内核,由 /sbin/mdev/ 来负责设备的热插拔;sysctl 一句是等效的。

然后,有了 /sys/class 作为依据还不够,mdev 还需要知道对设备文件权限的定义等信息。这是通过 mdev.conf 完成的,通常该文件放在 /etc/ 目录下,即 /etc/mdev.conf。这相当于 udev 的配置文件 /etc/udev/rules.d/(我手上的 mint13,实际是放在 /lib/udev/rules.d/ 下面)。

 

3. mdev.conf 的规则

mdev.conf 的规则主要有三个种。如果没有匹配到,那么赋予默认值 root:root 660。

1)基本规则:<device regex>       <uid>:<gid> <permissions>

第一项是正则表达式,多数情况直接用字符串就好。接下来是 uid 和 gid,控制哪些用户可以使用,简单系统里面这个也可以不管,比如我手上这个系统全部设 “0:0”。最后是文件的默认权限。mdev.txt 自带的例子:

hd[a-z][0-9]*    0:3    660
.*    1:1    777

前一个匹配所有的硬盘设备 hda-hdz 的所有分区,加载时 gid = 3,默认权限 660。第二条规则,uid 和 gid 默认值都是 1,文件权限全部 777.

2)设备重命名:<device regex> <uid>:<gid> <permissions> [=path]

前面部分一致,但是多了 path 定义这部分,分为三种使用情况。分别举例说明。

hda 0:3 660 =disk/  #表示将名为 hda 的设备放入 /dev/disk/ 目录
hdb 0:3 660 =cdrom  #表示将名为 hdb 的设备重命名为 /dev/cdrom
hdc 0:3 660 >udisk  #表示将名为 hdc 的设备重命名为 /dev/udisk 的同时,在 /dev/ 目录下保有其 symbolink

3)附加执行指令:<device regex> <uid>:<gid> <permissions> [=path] [@|$|*<cmd>]

 这一部分,主要是添加了 cmd 部分:@ 表示创建设备之后执行;$ 表示移出设备之前执行;* 表示创建前和移出后都要执行。比如

vntwpa          1:1     777              * /bin/ltls.sh

这句的意思是,对设备 vntwpa 的设备文件,创建前/移出后,都要执行脚本 /bin/ltls.sh 一次。

4)额外,还有一个,在 <uid:gid> 规则后面加上 !(但是可以添加 cmd 部分,在 add/remove 等 uevent 信号来时,执行某个命令),可以控制不为该设备创建设备文件。比如:

tty[a-z]. 0:0 660 !

 

4. mdev 的内部实现

看它代码不多,索性看个究竟,到底都是怎么实现的。基本上所有的内容都在 mdev.c 这个文件里,从 main_dev() 开始。

chdir("/dev")之后,就是读取参数了,看参数受否带 -s,带 -s 说明是 init 调用,需要历遍 /sys/class/ (如果没有 /sys/class/block, 还要历遍 /sys/block/),为所有设备创建设备文件;如果不是,那么,就是内核 hotplug 的调用,只需要读取环境变量 $ACTION,并执行相应操作。

其中 ACTION 只支持 remove 和 add。然后,在设备 add 时,还支持 firmwaire 的加载(firmware  is usually a small piece of code that is uploaded directly to the device for it to function correctly. )。

加载 firmware ,是在 load_firmware() 函数中,把指定的文件通过 bb_copyfd_eof()拷贝到 /sys/$DEVPATH/data 。

而不管是历遍初始创建设备文件,还是 hotplug 动态管理,最后都是在 make_device中实现的。mdev.conf 中指定的 cmd,是使用 system() 调用来执行。具体最后执行相应动作的代码基本在这了:

            if (!delete && major >= 0) {
                if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST) // 创建设备文件
                    bb_perror_msg("can't create '%s'", node_name);
                if (major == G.root_major && minor == G.root_minor)
                    symlink(node_name, "root");
                if (ENABLE_FEATURE_MDEV_CONF) {
                    chmod(node_name, mode);//设置设备 mode
                    chown(node_name, ugid.uid, ugid.gid);//设置设备 uid 和 gid
                }
                if (ENABLE_FEATURE_MDEV_RENAME && alias) {
                    if (aliaslink == '>')
                        symlink(node_name, device_name); // 创建额外的软连接
                }
            }

            if (ENABLE_FEATURE_MDEV_EXEC && command) {
                /* setenv will leak memory, use putenv/unsetenv/free */
                char *s = xasprintf("%s=%s", "MDEV", node_name);
                char *s1 = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
                putenv(s);
                putenv(s1);
                if (system(command) == -1) // cmd 执行
                    bb_perror_msg("can't run '%s'", command);
                bb_unsetenv_and_free(s1);
                bb_unsetenv_and_free(s);
                free(command);
            }

            if (delete && major >= -1) {
                if (ENABLE_FEATURE_MDEV_RENAME && alias) {
                    if (aliaslink == '>')
                        unlink(device_name);
                }
                unlink(node_name); // 移出设备文件,留意上面一行的 device_name 与这里的区别
            }

            if (ENABLE_FEATURE_MDEV_RENAME)
                free(alias);
        }

以上基本就全部了。

posted @ 2016-08-16 12:20  Biiigfish  阅读(473)  评论(0编辑  收藏