一、概述
(1)udev是构建在linux的sysfs之上的是一个一个用户程序,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等,设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev的的工作过程大致是这样的:
1. 当内核检测到在系统中出现了新设备后,内核会在sysfs文件系统中为该新设备生成一项新的记录,新记录是以文件或目录的方式来表示,每个文件都包含有特定的信息。
2. udev在系统中是以守护进程的方式运行,它通过某种途径检测到新设备的出现,通过查找设备对应的sysfs中的记录得到设备的信息。
3. udev会根据/etc/udev/udev.conf文件中的udev_rules指定的目录,逐个检查该目录下的文件,这个目录下的文件都是针对某类或某个设备应该施行什么措施的规则文件。udev读取文件是按照文件名的ASCII字母顺序来读取的,如果udev一旦找到了与新加入的设备匹配的规则,udev就会根据规则定义的措施对新设备进行配置。同时不再读后续的规则文件。
(2)udev的工作可以简单的概括为:监控系统中设备状态的变化,当有设备的状态发生变化时,根据用户的配置对设备文件执行相应的操作。
(3)udev的配置规则
udev的全局的配置文件是/etc/udev/udev.conf,该文件一般缺省有这样几项:
udev_root="/dev" #udev产生的设备文件的根目录是/dev
udev_rules="/etc/udev/rules.d" #用于指导udev工作的规则所在目录。
udev_log="err" #当出现错误时,用syslog记录错误信息。
其中最关键的就是规则文件,即/etc/udev/rules.d/目录下的文件,udev是按照文件名的ASCII字母顺序来读取的,一旦找到与设备匹配的规则,就运用该规则,并不再读后续的规则文件了。下面简单看下udev定义了那些规则,以及它是如何进行配置的,怎样去编写这种规则文件。
udev的规则文件以行为单位,以"#"开头的行代表注释行。其余的每一行代表一个规则。每个规则分成一个或多个“匹配”和“赋值”部分。“匹配”部分用“匹配“专用的关键字来表示,相应的“赋值”部分用“赋值”专用的关键字来表示。“匹配”关键字包括:ACTION,KERNEL,BUS, SYSFS等等,“赋值”关键字包括:NAME,SYMLINK,OWNER等等。具体详细的描述可以阅读udev的man文档。下面通过一个具体的例子来说明,看如下的规则:
# PCI device 0x8086:0x1096 (e1000e)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:19:21:ff:cc:7c", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
这个规则中的“匹配”部分有四项,分别是SUBSYSTEM,ACTION,DRIVERS,KERNEL。而"赋值"部分有一项,是NAME。这个规则就是说,当系统中出现的新硬件属于net子系统范畴,系统对该硬件采取的动作是加入这个硬件,且这个硬件在DRIVERS信息中的address="00:19:21:ff:cc:7c",dev_id="0x0",内核的命名为eth*时,udev会在建立新的网络设备,并命名为eth0。udev规则文件的编写可以参考man手册。
二、udev自动生成设备节点
生成设备文件节点的方法有三个:1.手动mknod 2.利用devfs 3.利用udev
udev是硬件平台无关的,属于user space的进程,它脱离驱动层的关联而建立在操作系统之上,基于这种设计实现,我们可以随时修改及删除/dev下的设备文件名称和指向,随心所欲地按照我们的愿望安排和管理设备文件系统,而完成如此灵活的功能只需要简单地修改udev的配置文件即可,无需重新启动操作系统。udev已经使得我们对设备的管理如探囊取物般轻松自如。
内核中定义了struct class 结构体,顾名思义,一个struct class 结构体类型变量对应一个类,内核同时提供了class_create() 函数,可以用它来创建一个类,这个类存放于sysfs 下面,一旦创建好了这个类,再调用device_create() 函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create() 函数,去/sysfs 下寻找对应的类从而创建设备节点。
struct class {
const char * name;
struct module * owner;
nbsp; struct kset subsys;
struct list_head devices;
struct list_head interfaces;
struct kset class_dirs;
struct semaphore sem; /* locks children, devices, interfaces */
struct class_attribute * class_attrs;
struct device_attribute * dev_attrs;
int ( * dev_uevent) ( struct device * dev, struct kobj_uevent_env * env) ;
void ( * class_release) ( struct class * class ) ;
void ( * dev_release) ( struct device * dev) ;
int ( * suspend) ( struct device * dev, pm_message_t state) ;
int ( * resume) ( struct device * dev) ;
} ;
//第一个参数指定类的所有者是哪个模块,第二个参数指定类名。
struct class * class_create( struct module * owner, const char * name)
{
struct class * cls;
int retval;
cls = kzalloc( sizeof ( * cls) , GFP_KERNEL) ;//分配一个类结构
if ( ! cls) {
retval = - ENOMEM;
goto error ;
}
cls- > name = name;
cls- > owner = owner;
cls- > class_release = class_create_release;
retval = class_register( cls) ;//注册这个类,即在/sys/class目录下添加目录
if ( retval)
goto error ;
return cls;
error :
kfree( cls) ;
return ERR_PTR( retval) ;
}
//第一个参数指定所要创建的设备所从属的类,第二个参数是这个设备的父设备,如果没有就指定为NULL,第三个参数是设备号,第四个参数是设备名称,第五个参数是从设备号。
struct device * device_create( struct class * class , struct device * parent,dev_t devt, const char * fmt, . . . )
{
va_list vargs;
struct device * dev;
va_start ( vargs, fmt);
//device_create_vargs函数流程:
//device_create-->device_create_vargs-->device_register-->device_add-->kobject_uevent(&dev->kobj, KOBJ_ADD);-->
//kobject_uevent_env(kobj, action, NULL);-->call_usermodehelper(argv[0], argv,env->envp, UMH_WAIT_EXEC);}
dev = device_create_vargs( class , parent, devt, NULL , fmt, vargs) ;
va_end ( vargs) ;
return dev;
}
三、事件通知
在device_add()例程,其用于将一个device注册到device model,其中调用了kobject_uevent(&dev->kobj, KOBJ_ADD)例程向用户空间发出KOBJ_ADD 事件并输出环境变量,以表明一个device被添加了。在《Linux设备模型浅析之设备篇》中介绍过rtc_device_register()例程 ,其最终调用device_add()例程添加了一个rtc0的device,我们就以它为例子来完成uevent的分析。让我们看看kobject_uevent()这个例程的代码,如下:
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
它又调用了kobject_uevent_env()例程,部分代码如下:
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]; // 本例是“add”命令
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
struct kset_uevent_ops *uevent_ops;
u64 seq;
int i = 0;
int retval = 0;
pr_debug("kobject: '%s' (%p): %s\n",kobject_name(kobj), kobj, __func__);
top_kobj = kobj;
/* 找到其所属的 kset容器,如果没找到就从其父kobj找,一直持续下去,直到父kobj不存在 */
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
if (!top_kobj->kset) {
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent ""without kset!\n", kobject_name(kobj), kobj,__func__);
return -EINVAL;
}
/* 在本例中是devices_kset容器 */
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops; // 本例中uevent_ops = &device_uevent_ops
/* 回调 uevent_ops->filter ()例程,本例中是dev_uevent_filter()例程,主要是检查是否uevent suppress*/
/* skip the event, if the filter returns zero. */
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) { // 如果不成功,即uevent suppress,则直接返回
pr_debug("kobject: '%s' (%p): %s: filter function ""caused the event to drop!\n",kobject_name(kobj), kobj, __func__);
return 0;
}
/* 回调 uevent_ops-> name (),本例中是dev_uevent_name()例程,获取bus或class的名字,本例中rtc0不存在bus,所以是class的名字“rtc”,后面分析 */
if (uevent_ops && uevent_ops->name)
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;
}
// 获得用于存放环境变量的buffer
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;
/* 获取该kobj在sysfs的路径,通过遍历其父kobj来获得,本例是/sys/devices/platform/s3c2410-rtc/rtc/rtc0 */
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}
// 添加 ACTION环境变量,本例是“add”命令
retval = add_uevent_var(env, "ACTION=%s", action_string);
if (retval)
goto exit;
// 添加 DEVPATH环境变量,本例是/sys/devices/platform/s3c2410-rtc/rtc/rtc0
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
// 添加 SUBSYSTEM 环境变量,本例中是“rtc”
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
/* keys passed in from the caller */
if (envp_ext) { // 为NULL,不执行
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
}
// 回调 uevent_ops->uevent(),本例中是dev_uevent()例程,输出一些环境变量,后面分析
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;
}
}
if (action == KOBJ_ADD)
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
/* 增加event序列号的值,并输出到环境变量的buffer。该系列号可以从/sys/kernel/uevent_seqnum属性文件读取,至于uevent_seqnum属性文件及/sys/kernel/目录是怎样产生的,后面会分析 */
spin_lock(&sequence_lock);
seq = ++uevent_seqnum;
spin_unlock(&sequence_lock);
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
if (retval)
goto exit;
/* 如果配置了网络,那么就会通过netlink socket 向用户空间发送环境标量,而用户空间则通过netlink socket 接收,然后采取一些列的动作。这种机制目前用在udev中,也就是pc机系统中,后面会分析*/
#if defined(CONFIG_NET)
/* 如果配置了net,则会在kobject_uevent_init()例程中将全局比昂俩uevent_sock 初试化为NETLINK_KOBJECT_UEVENT 类型的socket。*/
if (uevent_sock) {
struct sk_buff *skb;
size_t len;
/* 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(uevent_sock, skb, 0, 1,GFP_KERNEL); // 广播
} else
retval = -ENOMEM;
}
#endif
/* 对于嵌入式系统来说,busybox采用的是mdev,在系统启动脚本rcS 中会使用echo /sbin/mdev > /proc/sys/kernel/hotplug命令,而这个 hotplug文件通过一定的方法映射到了uevent_helper[]数组,所以uevent_helper[] = “/sbin/mdev” 。所以对于采用busybox的嵌入式系统来说会执行里面的代码,而pc机不会。也就是说内核会call用户空间的/sbin/mdev这个应用程序来做动作,后面分析 */
if (uevent_helper[0]) {
char *argv [3];
// 加入到环境变量buffer
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
//添加HOME目录环境变量
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
//添加PATH环境变量
retval = add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
// 呼叫应用程序来处理, UMH_WAIT_EXEC表明等待应用程序处理完,dev会根据先前设置的环境变量进行处理
retval = call_usermodehelper(argv[0], argv,env->envp, UMH_WAIT_EXEC);
}
exit:
kfree(devpath);
kfree(env);
return retval;
}
kobject_uevent_env函数最后调用了mdev,mdev的入口函数在busybox的mdev_main
mdev_main
{
if (argv[1] && !strcmp(argv[1], "-s"))//先判断参数1是否为-s,如果为-s则表明mdev为开机执行的情况(mdev -s位于/etc/init.d/rcS中)
else
getenv //提取各个环境变量
make_device //根据action创建设备
/*对于设备,当我们创建其节点时,我们可以通过配置文件进行配置,该配置文件位于/etc/mdev.conf
*设备名称正则表达式用户id 组id 节点属性 创建的设备节点路径 shell命令
*配置方式为<device regex> <uid>:<gid> <octal permissions> [=path] [@|$|*<command>]
*/
parser = config_open2("/etc/mdev.conf", fopen_for_read); //打开/etc/mdev.conf文件
while (config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) //分析mdev.conf文件内容,并执行相关操作command
{...}
mknod(device_name, mode | type, makedev(major, minor)) //调用mknod进行节点创建
}
四、示例
Udev获取和设置设备节点的信息是通过建立一个本地套接口来实现:socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0)。其中SOCK_SEQPACKET提供连续可信赖的数据包连接。然后通过sendmsg和recvmsg去发送和获取相应的信息。当获取到设备的相应信息后再根据用户设置的规则调用mknod函数去创建相应的设备节点。
Udev创建的所有设备节点的设备号都可以在/sys/dev/{block,char}目录中找到。如果驱动程序要让udev自动创建设备节点,那么你就必须保存在这个目录下有这个设备号。下面例举一个简单的例子:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/device.h>
static int mem_major = 240;
static struct class *test_class;
struct cdev cdev;
/*文件操作结构体*/
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
};
/*设备驱动模块加载函数*/
static int memdev_init(void)
{
dev_t devno = MKDEV(mem_major, 0);
int result = register_chrdev_region(devno, 2, "memdev");
if (result < 0)
return result;
cdev_add(&cdev, MKDEV(mem_major, 0), 1);
test_class = class_create(THIS_MODULE, "juvi_test_class");//创建一个类,用来表示该设备文件是哪一种类型,创建成功会在/sys/class中出现。
device_create(test_class, NULL, devno, NULL, "test_udev");//创建设备文件,它会在/sys/dev/char中出现相应的设备接点号。然后dev目录下面会自动创建设备文件。
return result;
}
/*模块卸载函数*/
static void memdev_exit(void)
{
cdev_del(&cdev); /*注销设备*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
class_destroy(test_class);//删除这个类型的设备。
}
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
五、分析mdev
a、执行mdev -s命令时,mdev扫描/sys/block(块设备保存在/sys/block目录下,内核2.6.25版本以后,块设备也保存在/sys/class/block目录下。mdev扫描/sys/block是为了实现向后兼容)和/sys/class两个目录下的dev属性文件,从该dev属性文件中获取到设备编号(dev属性文件以"major:minor\n"形式保存设备编号),并以包含该dev属性文件的目录名称作为设备名device_name(即包含dev属性文件的目录称为device_name,而/sys/class和device_name之间的那部分目录称为subsystem。也就是每个dev属性文件所在的路径都可表示为/sys/class/subsystem/device_name/dev),在/dev目录下创建相应的设备文件。例如,cat /sys/class/tty/tty0/dev会得到4:0,subsystem为tty,device_name为tty0。
b、当mdev因uevnet事件(以前叫hotplug事件)被调用时,mdev通过由uevent事件传递给它的环境变量获取到:引起该uevent事件的设备action及该设备所在的路径device path。然后判断引起该uevent事件的action是什么。若该action是add,即有新设备加入到系统中,不管该设备是虚拟设备还是实际物理设备,mdev都会通过device path路径下的dev属性文件获取到设备编号,然后以device path路径最后一个目录(即包含该dev属性文件的目录)作为设备名,在/dev目录下创建相应的设备文件。若该action是remote,即设备已从系统中移除,则删除/dev目录下以device path路径最后一个目录名称作为文件名的设备文件。如果该action既不是add也不是remove,mdev则什么都不做。
由上面可知,如果我们想在设备加入到系统中或从系统中移除时,由mdev自动地创建和删除设备文件,那么就必须做到以下三点:1、在/sys/class的某一subsystem目录下,2、创建一个以设备名device_name作为名称的目录,3、并且在该device_name目录下还必须包含一个dev属性文件,该dev属性文件以"major:minor\n"形式输出设备编号。
int mdev_main(int argc UNUSED_PARAM, char **argv)
{
//#define RESERVE_CONFIG_BUFFER(buffer,len) char buffer[len]
//声明一个数组
RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);
/*
struct globals {
int root_major, root_minor;
char *subsystem;
#if ENABLE_FEATURE_MDEV_CONF
const char *filename;
parser_t *parser;
struct rule **rule_vec;
unsigned rule_idx;
#endif
struct rule cur_rule;
} FIX_ALIASING;
#define G (*(struct globals*)&bb_common_bufsiz1)
enum { COMMON_BUFSIZE = (BUFSIZ >= 256*sizeof(void*) ? BUFSIZ+1 : 256*sizeof(void*)) };
extern char bb_common_bufsiz1[COMMON_BUFSIZE];
#define INIT_G() do { \
IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.maj = -1;) \
IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.mode = 0660;) \
} while (0)
*/
//初始化结构体
INIT_G();
#if ENABLE_FEATURE_MDEV_CONF
G.filename = "/etc/mdev.conf";
#endif
/* We can be called as hotplug helper */
//阅读这个函数的源代码后,发现这里其实就是判断了/dev/null是否存在。如果打不开,那么就进入die状态。
bb_sanitize_stdio();
/* Force the configuration file settings exactly */
umask(0);//配置屏蔽位
xchdir("/dev");//切换到/dev目录
if (argv[1] && strcmp(argv[1], "-s") == 0) {//如果执行的是mdev -s,这是在shell里调用的。在系统启动时调用。创建所有设备驱动的节点。
struct stat st;
#if ENABLE_FEATURE_MDEV_CONF
/* Same as xrealloc_vector(NULL, 4, 0): */
G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec));//给rule结构体分配空间
#endif
xstat("/", &st);//返回根目录文件状态信息
G.root_major = major(st.st_dev);//保存文件的设备号
G.root_minor = minor(st.st_dev);
if (access("/sys/class/block", F_OK) != 0) {//判断/sys/class/block这个文件或者目录是否存在。存在返回0,否则返回-1
//这个函数是递归函数,它会把/sys/block目录下的所有文件文件夹都去查看一遍,如果发现dev文件,那么将按照/etc/mdev.conf文件进行相应的配置。如果没有配置文件,那么直接创建设备节点。
recursive_action("/sys/block",ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,fileAction, dirAction, temp, 0);
}
//这个函数是递归函数,它会把/sys/class目录下的所有文件文件夹都去查看一遍,如果发现dev文件,那么将按照/etc/mdev.conf文件进行相应的配置。如果没有配置文件,那么直接创建设备节点。
recursive_action("/sys/class",ACTION_RECURSE | ACTION_FOLLOWLINKS,fileAction, dirAction, temp, 0);
} else {//通过hotplug通知mdev创建设备节点
char *fw;
char *seq;
char *action;
char *env_path;
static const char keywords[] ALIGN1 = "remove\0add\0";
enum { OP_remove = 0, OP_add };
smalluint op;
/* Hotplug:
* env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
* ACTION can be "add" or "remove"
* DEVPATH is like "/block/sda" or "/class/input/mice"
经过驱动层分析,所得的环境变量为,这里是以spidev,0.0设备为例:
ACTION=add: kobject_actions[KOBJ_ADD]
DEVPATH=/class/spidev/spidev0.0/: kobject_get_path(kobj, GFP_KERNEL) /sys不存在,这里只统计到/sys目录下
SUBSYSTEM=spidev: dev->bus->name,dev->class->name,如果dev->bus不存在的情况下,那么才使用dev->class->name
MAJOR=MAJOR(dev->devt)
MINOR=MINOR(dev->devt)
PHYSDEVPATH=/devices/platform/atmel_spi.0/spi0.0/: kobject_get_path(&dev->parent->kobj, GFP_KERNEL) /sys不存在,这里只统计到/sys目录下
PHYSDEVBUS=/bus/spi/: dev->parent->bus->name /sys不存在,这里只统计到/sys目录下
PHYSDEVDRIVER=spidev: dev->parent->driver->name
SEQNUM=++uevent_seqnum
HOME=/
PATH=/sbin:/bin:/usr/sbin:/usr/bin
*/
//获得环境变量
action = getenv("ACTION");
env_path = getenv("DEVPATH");
G.subsystem = getenv("SUBSYSTEM");
if (!action || !env_path)
bb_show_usage();
fw = getenv("FIRMWARE");
op = index_in_strings(keywords, action);//比较keywords数组中和action,若是remove则返回0,若是add则返回1
seq = getenv("SEQNUM");
/*
内核不序列化热插拔事件,而是为每一个成功的热插拔调用增加了 SEQNUM 这个环境变量。通常情况下,mdev不在乎这个。这样也许可以重新对热插拔事件进行重调用,典型的症状就是有时某些设备节点不能像期待的那样被创建出来。不管怎么说,如果 /dev/mdev.seq 文件存在,mdev将比较它和SEQNUM的内容,它将重试直到有两个第二,等待他们匹配。如果他们精确的匹配(甚至连"\n"都不被允许),或者两个第二出现,mdev依旧运行,然后它用SEQNUM+1重写/dev/mdev.seq。
*/
if (seq) {
int timeout = 2000 / 32; /* 2000 msec */
do {
int seqlen;
char seqbuf[sizeof(int)*3 + 2];
//从mdev.seq文件中读出seq
seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf) - 1);
if (seqlen < 0) {
seq = NULL;
break;
}
seqbuf[seqlen] = '\0';
if (seqbuf[0] == '\n' /* seed file? */|| strcmp(seq, seqbuf) == 0) { /* correct idx? */
break;
}
usleep(32*1000);
} while (--timeout);
}
//创建的设备路径名赋值给temp,例如/sys/class/spidev/spidev0.0/
snprintf(temp, PATH_MAX, "/sys%s", env_path);
if (op == OP_remove) {//删除节点
if (!fw)
make_device(temp, /*delete:*/ 1);
}
else if (op == OP_add) {//添加节点
make_device(temp, /*delete:*/ 0);
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
if (fw)
load_firmware(fw, temp);
}
}
if (seq) {//重写mdev.seq文件
xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1));
}
}
if (ENABLE_FEATURE_CLEAN_UP)
RELEASE_CONFIG_BUFFER(temp);
return EXIT_SUCCESS;
}
//recursive_action("/sys/block",ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,fileAction, dirAction, temp, 0);
int FAST_FUNC recursive_action(const char *fileName,unsigned flags,int FAST_FUNC (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
int FAST_FUNC (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),void* userData,unsigned depth)
{
struct stat statbuf;
unsigned follow;
int status;
DIR *dir;
struct dirent *next;
if (!fileAction)
fileAction = true_action;
if (!dirAction)
dirAction = true_action;
follow = ACTION_FOLLOWLINKS;
if (depth == 0)//第一次调用时传入depth参数为0
follow = ACTION_FOLLOWLINKS | ACTION_FOLLOWLINKS_L0;
follow &= flags;//也设置了ACTION_FOLLOWLINKS标志
//stat和lstat的区别:当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;而stat返回的是该链接指向的文件的信息。
status = (follow ? stat : lstat)(fileName, &statbuf);//这里用stat读取目录或文件属性
if (status < 0) {
if ((flags & ACTION_DANGLING_OK)&& errno == ENOENT&& lstat(fileName, &statbuf) == 0) {/* Dangling link */
return fileAction(fileName, &statbuf, userData, depth);
}
goto done_nak_warn;
}
if (!S_ISDIR(statbuf.st_mode)) {//如果不是目录,则调用下边函数进行创建设备节点
return fileAction(fileName, &statbuf, userData, depth);
}
//ACTION_RECURSE标志设置过,不进行以下操作
if (!(flags & ACTION_RECURSE)) {
return dirAction(fileName, &statbuf, userData, depth);
}
//没有设置过ACTION_DEPTHFIRST
if (!(flags & ACTION_DEPTHFIRST)) {
status = dirAction(fileName, &statbuf, userData, depth);//返回TRUE=1
if (!status)
goto done_nak_warn;
if (status == SKIP)
return TRUE;
}
//执行到这里表明传入的参数是目录
dir = opendir(fileName);//打开目录
if (!dir) {
goto done_nak_warn;
}
status = TRUE;
while ((next = readdir(dir)) != NULL) {//读目录
char *nextFile;
nextFile = concat_subpath_file(fileName, next->d_name);//读目录下的文件
if (nextFile == NULL)
continue;
/* process every file (NB: ACTION_RECURSE is set in flags) */
if (!recursive_action(nextFile, flags, fileAction, dirAction,userData, depth + 1))//递归调用
status = FALSE;
free(nextFile);
}
closedir(dir);
//没有设置过ACTION_DEPTHFIRST,不会进去执行
if (flags & ACTION_DEPTHFIRST) {
if (!dirAction(fileName, &statbuf, userData, depth))
goto done_nak_warn;
}
return status;
done_nak_warn:
if (!(flags & ACTION_QUIET))
bb_simple_perror_msg(fileName);
return FALSE;
}
static int FAST_FUNC fileAction(const char *fileName,struct stat *statbuf UNUSED_PARAM,void *userData,int depth UNUSED_PARAM)
{
size_t len = strlen(fileName) - 4; /* can't underflow */
char *scratch = userData;
//检查传入进来的路径名的最后4个字符是否为“/dev”,即只有根据dev文件来创建设备节点
if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)
return FALSE;
strcpy(scratch, fileName);//复制文件名
scratch[len] = '\0';//将最后的"/dev"去掉
make_device(scratch, /*delete:*/ 0);//进行创建设备节点
return TRUE;
}
static void make_device(char *path, int delete)
{
char *device_name, *subsystem_slash_devname;
int major, minor, type, len;
dbg("%s('%s', delete:%d)", __func__, path, delete);
major = -1;
if (!delete) {
char *dev_maj_min = path + strlen(path);//将dev_maj_min指针指向路径名的最后
//传入的路径名是最后dev文件的父目录,例如/sys/block/mtdblock0
strcpy(dev_maj_min, "/dev");//即将路径名添加“/dev”,即/sys/block/mtdblock0/dev确保读的文件时dev文件
len = open_read_close(path, dev_maj_min + 1, 64);//从dev文件中读出主设备号和次设备号存到dev_maj_min + 1地址处
*dev_maj_min = '\0';
if (len < 1) {
if (!ENABLE_FEATURE_MDEV_EXEC)
return;
} else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2) {//主设备号和次设备号复制给major和minor
major = -1;
}
}
//调用strrchr() 函数查找字符在指定字符串中从后面开始的第一次出现的位置,即找到设备名
//例如:/sys/block/mtdblock0,返回的设备名就是mtdblock0
device_name = (char*) bb_basename(path);
type = S_IFCHR;
//判断是否为block设备
if (strstr(path, "/block/") || (G.subsystem && strncmp(G.subsystem, "block", 5) == 0))
type = S_IFBLK;
/* Make path point to "subsystem/device_name" */
subsystem_slash_devname = NULL;
//path路径名前移
if (strncmp(path, "/sys/block/", 11) == 0) /* legacy case */
path += sizeof("/sys/") - 1;
else if (strncmp(path, "/sys/class/", 11) == 0)
path += sizeof("/sys/class/") - 1;
else {
/* Example of a hotplug invocation:
* SUBSYSTEM="block"
* DEVPATH="/sys" + "/devices/virtual/mtd/mtd3/mtdblock3"
* ("/sys" is added by mdev_main)
* - path does not contain subsystem
*/
subsystem_slash_devname = concat_path_file(G.subsystem, device_name);
path = subsystem_slash_devname;
}
#if ENABLE_FEATURE_MDEV_CONF
G.rule_idx = 0; /* restart from the beginning (think mdev -s) */
#endif
for (;;) {
const char *str_to_match;
regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];
char *command;
char *alias;
char aliaslink = aliaslink; /* for compiler */
const char *node_name;
const struct rule *rule;
str_to_match = "";
//# define next_rule() (&G.cur_rule)
rule = next_rule();
//检查rule
#if ENABLE_FEATURE_MDEV_CONF
if (rule->maj >= 0) { /* @maj,min rule */
if (major != rule->maj)
continue;
if (minor < rule->min0 || minor > rule->min1)
continue;
memset(off, 0, sizeof(off));
goto rule_matches;
}
if (rule->envvar) { /* $envvar=regex rule */
str_to_match = getenv(rule->envvar);
dbg("getenv('%s'):'%s'", rule->envvar, str_to_match);
if (!str_to_match)
continue;
} else {
/* regex to match [subsystem/]device_name */
str_to_match = (rule->regex_has_slash ? path : device_name);
}
if (rule->regex_compiled) {
int regex_match = regexec(&rule->match, str_to_match, ARRAY_SIZE(off), off, 0);
dbg("regex_match for '%s':%d", str_to_match, regex_match);
if (regex_match != 0
/* regexec returns whole pattern as "range" 0 */
|| off[0].rm_so != 0
|| (int)off[0].rm_eo != (int)strlen(str_to_match)
) {
continue; /* this rule doesn't match */
}
}
/* else: it's final implicit "match-all" rule */
rule_matches:
#endif
dbg("rule matched");
/* Build alias name */
alias = NULL;
if (ENABLE_FEATURE_MDEV_RENAME && rule->ren_mov) {
aliaslink = rule->ren_mov[0];
if (aliaslink == '!') {
/* "!": suppress node creation/deletion */
major = -2;
}
else if (aliaslink == '>' || aliaslink == '=') {
if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {
char *s;
char *p;
unsigned n;
/* substitute %1..9 with off[1..9], if any */
n = 0;
s = rule->ren_mov;
while (*s)
if (*s++ == '%')
n++;
p = alias = xzalloc(strlen(rule->ren_mov) + n * strlen(str_to_match));
s = rule->ren_mov + 1;
while (*s) {
*p = *s;
if ('%' == *s) {
unsigned i = (s[1] - '0');
if (i <= 9 && off[i].rm_so >= 0) {
n = off[i].rm_eo - off[i].rm_so;
strncpy(p, str_to_match + off[i].rm_so, n);
p += n - 1;
s++;
}
}
p++;
s++;
}
} else {
alias = xstrdup(rule->ren_mov + 1);
}
}
}
dbg("alias:'%s'", alias);
command = NULL;
IF_FEATURE_MDEV_EXEC(command = rule->r_cmd;)
if (command) {
const char *s = "$@*";
const char *s2 = strchr(s, command[0]);
/* Are we running this command now?
* Run $cmd on delete, @cmd on create, *cmd on both
*/
if (s2 - s != delete) {
/* We are here if: '*',
* or: '@' and delete = 0,
* or: '$' and delete = 1
*/
command++;
} else {
command = NULL;
}
}
dbg("command:'%s'", command);
/* "Execute" the line we found */
node_name = device_name;
if (ENABLE_FEATURE_MDEV_RENAME && alias) {
node_name = alias = build_alias(alias, device_name);
dbg("alias2:'%s'", alias);
}
if (!delete && major >= 0) {//创建设备节点
dbg("mknod('%s',%o,(%d,%d))", node_name, rule->mode | type, major, minor);
if (mknod(node_name, rule->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, rule->mode);
chown(node_name, rule->ugid.uid, rule->ugid.gid);
}
if (ENABLE_FEATURE_MDEV_RENAME && alias) {
if (aliaslink == '>') {
//TODO: on devtmpfs, device_name already exists and symlink() fails.
//End result is that instead of symlink, we have two nodes.
//What should be done?
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)
bb_perror_msg("can't run '%s'", command);
bb_unsetenv_and_free(s1);
bb_unsetenv_and_free(s);
}
if (delete && major >= -1) {//删除节点
if (ENABLE_FEATURE_MDEV_RENAME && alias) {
if (aliaslink == '>')
unlink(device_name);
}
unlink(node_name);
}
if (ENABLE_FEATURE_MDEV_RENAME)
free(alias);
/* We found matching line.
* Stop unless it was prefixed with '-'
*/
if (!ENABLE_FEATURE_MDEV_CONF || !rule->keep_matching)
break;
} /* for (;;) */
free(subsystem_slash_devname);
}
阅读(2632) | 评论(1) | 转发(1) |