【linux kernel】mdev详解_iriczhao的博客-CSDN博客
udev 是Linux kernel 2.6系列的设备管理器。它主要的功能是管理/dev目录底下的设备节点。它同时也用来接替devfs及热插拔的功能,这意味着它要在添加/删除硬件时处理/dev目录以及所有用户空间的行为
mdev是busybox提供的一个工具,在嵌入式系统中,相当于简化版的udev,作用是:在系统启动、热插拔和动态加载驱动程序时,自动创建设备节点。文件系统中的/dev目录下的设备节点都是由mdev创建的。在加载驱动过程中,根据驱动程序,在/dev下自动创建设备节点。
以下内容摘自busybox-1.23.1的mdev.txt文件,该文件中描述了mdev工具的功能和用法:
Mdev has two primary uses: initial population and dynamic updates. Both
require sysfs support in the kernel and have it mounted at /sys. For dynamic
updates, you also need to have hotplugging enabled in your kernel(该功能依赖于内核的hotpluggin功能).
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
The simple explanation here is that [1] you need to have /sys mounted before
executing mdev. Then you [2] instruct the kernel to execute /sbin/mdev whenever
a device is added or removed so that the device node can be created or
destroyed. Then you [3] seed /dev with all the device nodes that were created
while the system was booting.
For the "full" setup, you want to [4] make sure /dev is a tmpfs filesystem
(assuming you're running out of flash). Then you want to [5] create the
/dev/pts mount point and finally [6] mount the devpts filesystem on it.
-------------
MDEV Config (/etc/mdev.conf)
-------------
Mdev has an optional config file for controlling ownership/permissions of
device nodes if your system needs something more than the default root/root
660 permissions.
The file has the format:
[-][envmatch]
or
[envmatch]@
or
$envvar=
For example:
hd[a-z][0-9]* 0:3 660
The config file parsing stops at the first matching line unless this line
starts with "-". If no line is matched, then the default of 0:0 660 is used.
To set your own default, simply create your own total match like so:
.* 1:1 777
You can rename/move device nodes by using the next optional field.
So if you want to place the device node into a subdirectory, make sure the path
has a trailing /. If you want to rename the device node, just place the name.
hda 0:3 660 =drives/
This will move "hda" into the drives/ subdirectory.
hdb 0:3 660 =cdrom
This will rename "hdb" to "cdrom".
Similarly, ">path" renames/moves the device but it also creates
a direct symlink /dev/DEVNAME to the renamed/moved device.
You can also prevent creation of device nodes with the 4th field as "!":
tty[a-z]. 0:0 660 !
pty[a-z]. 0:0 660 !
If you also enable support for executing your own commands, then the file has
the format:
or
or
For example:
---8<---
# block devices
([hs]d[a-z]) root:disk 660 >disk/%1/0
([hs]d[a-z])([0-9]+) root:disk 660 >disk/%1/%2
mmcblk([0-9]+) root:disk 660 >disk/mmc/%1/0
mmcblk([0-9]+)p([0-9]+) root:disk 660 >disk/mmc/%1/%2
# network devices
(tun|tap) root:network 660 >net/%1
---8<---
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.
The command is executed via the system() function (which means you're giving a
command to the shell), so make sure you have a shell installed at /bin/sh. You
should also keep in mind that the kernel executes hotplug helpers with stdin,
stdout, and stderr connected to /dev/null.
For your convenience, the shell env var $MDEV is set to the device name. So if
the device "hdc" was matched, MDEV would be set to "hdc".
----------
FIRMWARE
----------
Some kernel device drivers need to request firmware at runtime in order to
properly initialize a device. Place all such firmware files into the
/lib/firmware/ directory. At runtime, the kernel will invoke mdev with the
filename of the firmware which mdev will load out of /lib/firmware/ and into
the kernel via the sysfs interface. The exact filename is hardcoded in the
kernel, so look there if you need to know how to name the file in userspace.
------------
SEQUENCING
------------
Kernel does not serialize hotplug events. It increments SEQNUM environmental
variable for each successive hotplug invocation. Normally, mdev doesn't care.
This may reorder hotplug and hot-unplug events, with typical symptoms of
device nodes sometimes not created as expected.(这段话说明hotplug事件是乱序的)
However, if /dev/mdev.seq file is found, mdev will compare its
contents with SEQNUM. It will retry up to two seconds, waiting for them
to match. If they match exactly (not even trailing '\n' is allowed),
or if two seconds pass, mdev runs as usual, then it rewrites /dev/mdev.seq
with SEQNUM+1.
IOW: this will serialize concurrent mdev invocations.
If you want to activate this feature, execute "echo >/dev/mdev.seq" prior to
setting mdev to be the hotplug handler. This writes single '\n' to the file.
NB: mdev recognizes /dev/mdev.seq consisting of single '\n' character
as a special case. IOW: this will not make your first hotplug event
to stall for two seconds.
1、在/etc/init.d/rcS脚本里有“mdev -s”
解释:在系统启动时,通过执行“mdev -s”扫描/sys/class和/sys/block,在目录中查找dev文件。
例如:/sys/class/tty/tty0/dev,
它的内容为”4:0”,即主设备号是4,次设备号是0,dev的上一级目录为设备名,这里是tty0。/sys/class/下的每个文件夹都代表着一个子系统。
2、在/etc/init.d/rcS脚本里有“echo /sbin/mdev > /proc/sys/kernel/hotplug”,即是把/sbin/mdev写到/proc/sys/kernel/hotplug文件里
解释:当有热插拔事件产生时,内核会调用/proc/sys/kernel/hotplug文件里指定的应用程序来处理热插拔事件
根据mdev.txt的说明可知在使用mdev之前要满足下面的条件:
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
mkdir /dev/pts
mount -t devpts devpts /dev/pts
说明所有设备都可以在/sys/class下找到,这个文件夹下的每一个文件夹代表了一类设备,表示类设备的文件夹下也有文件夹,这些文件夹代表设备。如:/sys/class/test/test_dev,test代表类,如,net, tty,sound,test_dev代表某个设备,他的名字和/dev下的设备节点名字是一样的。
平时当我们添加驱动时,如果想自动创建设备节点,可以调用class_create()和device_create()函数。class_create用于创建类设备,就是在/sys/class/创建一个文件夹,这个文件夹就代表一类设备,这个文件夹里会包含device_create创建的设备,也是一个文件夹。下面从device_create入手,分析其是怎么实现自动创建设备节点的。源码基于linux-2.6.30.4内核,device_create()函数定义如下:
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
retval = device_register(dev);
return device_add(dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
return kobject_uevent_env(kobj, action, NULL); // action = KOBJ_ADD
const char *action_string = kobject_actions[action]; // action_string = "add"
……
//把相关信息存到环境变量里,ACTION代表操作类型,DEVPATH为设备在class下存在的路径,SUBSYSTEM为class_create创建的设备类
//ACTION=add , DEVPATH=/class/test/test_dev , SUBSYSTEM=test
retval = add_uevent_var(env, "ACTION=%s", action_string);
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;
……
if (uevent_helper[0]){
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
//内核空间调用用户空间程序,调用的程序由argv [0] = uevent_helper指定
retval = call_usermodehelper(argv[0], argv,
env->envp, UMH_WAIT_EXEC);
下面看看uevent_helper
定义如下:
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
在.config中文件中查看,可知:
CONFIG_UEVENT_HELPER_PATH=”/sbin/hotplug”
但是去/sbin目录下查看,并没有hotplug这个文件,所以肯定不是这个文件起作用,于是在上面的if (uevent_helper[0])里加了一句调试信息,打印uevent_helper,内核启动相关打印信息如下:
uevent_helper is /sbin/hotplug
uevent_helper is /sbin/hotplug
s3c2410-rtc s3c2410-rtc: setting system clock to 2015-04-30 08:12:15 UTC (1430381535)
yaffs: dev is 32505858 name is "mtdblock2"
yaffs: passed flags ""
yaffs: Attempting MTD mount on 31.2, "mtdblock2"
yaffs: auto selecting yaffs2
block 646 is bad
yaffs_read_super: isCheckpointed 0
VFS: Mounted root (yaffs filesystem) on device 31:2.
Freeing init memory: 240K
Start Qtopia-2.2.0
uevent_helper is /sbin/mdev
uevent_helper is /sbin/mdev
从以上输出分析,刚开始确实是/sbin/hotplug,但后来就变成了/sbin/mdev。很据上面信息,我们知道是在文件系统启动的过程中发生了改变。文件系统启动过程中,改变mdev的只有“echo /sbin/mdev > /proc/sys/kernel/hotplug”,也确实是这个导致了uevent_helper的改变。涉及到的数据在/kernel/sysctl.c下至于为什么“echo /sbin/mdev > /proc/sys/kernel/hotplug”能改变uevent_helper就是proc虚拟文件系统的内容了,这里不讨论。
#if defined(CONFIG_HOTPLUG) && defined(CONFIG_NET)
{
.ctl_name = KERN_HOTPLUG,
.procname = "hotplug",
.data = &uevent_helper,
.maxlen = UEVENT_HELPER_PATH_LEN,
.mode = 0644,
.proc_handler = &proc_dostring,
.strategy = &sysctl_string,
},
#endif
设置mdev有三种方法,总结如下:
1、编译内核的时候直接配置CONFIG_UEVENT_HELPER_PATH,并且在之后的启动中不去修改uevent_helper,那么uevent_helper代表的程序就是CONFIG_UEVENT_HELPER_PATH指定的程序。
2、不管CONFIG_UEVENT_HELPER_PATH配置与否或如何设置,通过echo /sbin/mdev > /sys/kernel/uevent_helper修改uevent_helper的内容,这个指令将会调用内核函数uevent_helper_store
。过程涉及sysfs虚拟文件系统的内容,这里不讨论。改变之后,/proc/sys/kernel/hotplug里的内容也会立即发生改变。
3、不管CONFIG_UEVENT_HELPER_PATH配置与否或如何设置,通过echo /sbin/mdev > /proc/sys/kernel/hotplug修改uevent_helper的内容.它的修改也会导致/sys/kernel/uevent_helper里的内容立即改变。
对于上述2、3两种方法,都是通过用户层的接口直接uevent_helper,所以谁在后面谁起作用。
内核源码最后是调用uevent_helper指定的用户程序,这个用户程序通常是mdev,那么mdev如何会执行什么操作呢,来看一下busybox的源码。源码基于busybox-1.23.1:
int mdev_main(int argc UNUSED_PARAM, char **argv)
xchdir("/dev"); // 先把目录改变到/dev下
if (argv[1] && strcmp(argv[1], "-s") == 0) { // 在文件系统启动的时候会调用 mdev -s,创建所有驱动设备节点
putenv((char*)"ACTION=add"); // mdev -s 的动作是创建设备节点,所以为add
if (access("/sys/class/block", F_OK) != 0) { // 当/sys/class/block目录不存在时,才扫描/sys/block
/* Scan obsolete /sys/block only if /sys/class/block
* doesn't exist. Otherwise we'll have dupes.
* Also, do not complain if it doesn't exist.
* Some people configure kernel to have no blockdevs.
*/
recursive_action("/sys/block",
ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
fileAction, dirAction, temp, 0);
}
/*
* 这个函数是递归函数,它会扫描/sys/class目录下的所有文件,如果发现dev文件,将按照
* /etc/mdev.conf文件进行相应的配置。如果没有配置文件,那么直接创建设备节点
* 最终调用的创建函数是 make_device
*/
recursive_action("/sys/class",
ACTION_RECURSE | ACTION_FOLLOWLINKS,
fileAction, dirAction, temp, 0);
}
else{
// 获得环境变量,环境变量是内核在调用mdev之前设置的
env_devname = getenv("DEVNAME"); /* can be NULL */
G.subsystem = getenv("SUBSYSTEM");
action = getenv("ACTION");
env_devpath = getenv("DEVPATH");
snprintf(temp, PATH_MAX, "/sys%s", env_devpath);
make_device(env_devname, temp, op);
}
由以上代码分析可知,无论对于何种操作,最后都是调用make_device来创建节点,看一下这个函数:
static void make_device(char *device_name, char *path, int operation)
int major, minor, type, len;
char *path_end = path + strlen(path); //path_end指定path结尾处
major = -1;
if (operation == OP_add) {
strcpy(path_end, "/dev"); // 往path结尾处拷贝“/dev”,这时path=/sys/class/test/test_dev/dev
len = open_read_close(path, path_end + 1, SCRATCH_SIZE - 1); // 打开并读取/sys/class/test/test_dev/dev
*path_end = '\0';
if (len < 1) {
if (!ENABLE_FEATURE_MDEV_EXEC)
return;
} else if (sscanf(path_end + 1, "%u:%u", &major, &minor) == 2) { //从/sys/class/test/test_dev/dev获得主次设备号
dbg1("dev %u,%u", major, minor);
} else {
major = -1;
}
}
if (operation == OP_add && major >= 0) // 如果是add,即创建节点
mknod(node_name, rule->mode | type, makedev(major, minor)) // 最终用mknod函数在/dev下创建设备节点
if (operation == OP_remove && major >= -1) // 如果是remove,即删除节点
unlink(node_name);
创建节点最后还是调用mknod
,当然在class_create和device_create自动创建设备节点时,也会在/sys/class下自动创建和删除相关设备类和设备,这是sysfs的驱动内容,这里不在展开啦!!