mdev

uname -a

详细讲解了mdev的过程.

http://routeadd-net202.114.6.yulei.blog.chinaunix.net/uid-10928782-id-2182044.html

http://routeadd-net202.114.6.yulei.blog.chinaunix.net/uid-10928782-id-2182045.html

http://routeadd-net202.114.6.yulei.blog.chinaunix.net/uid-10928782-id-2182047.html

mdev - Mini udev for busybox

"mdev -s"扫描/sys/class/xxx,在目录中查找dev文件(它的格式为"M:m\n")。例如:/sys/class/tty0/dev,它的内容为"4:0\n"。目录名作为设备名。/sys/class/下的每个文件夹都代表着一个子系统。然后mdev创建/dev/设备名的设备节点。

如果/sys/class/.../dev文件不存在,mdev将按照配置文件执行相应的操作。

int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;

int mdev_main(int argc UNUSED_PARAM, char **argv)
{
    RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);

    /* We can be called as hotplug helper */
    /* Kernel cannot provide suitable stdio fds for us, do it ourself */
    bb_sanitize_stdio();  //阅读这个函数的源代码后,发现这里其实就是判断了/dev/null是否存在。如果打不开,那么就进入die状态。

    /* Force the configuration file settings exactly */
    umask(0);  //配置屏蔽位

    xchdir("/dev");  //切换到/dev目录

    if (argv[1] && strcmp(argv[1], "-s") == 0) {  //如果执行的是mdev -s,这是在shell里调用的。在系统启动时调用。创建所有设备驱动的节点。
        /* Scan:
         * mdev -s
         */

        struct stat st;

        xstat("/", &st);
        root_major = major(st.st_dev);  //"根文件系统所在的设备标识"
        root_minor = minor(st.st_dev);

        /* ACTION_FOLLOWLINKS is needed since in newer kernels
         * /sys/block/loop* (for example) are symlinks to dirs,
         * not real directories.
         * (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs,
         * but we can't enforce that on users)
         */

        if (access("/sys/class/block", F_OK) != 0) {  //判断/sys/class/block这个文件或者目录是否存在。存在返回0,否则返回-1
            /* 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",  //这个函数是递归函数,它会把/sys/block目录下的所有文件文件夹都去查看一遍,如果发现dev文件,那么将按照/etc/mdev.conf文件进行相应的配置。如果没有配置文件,那么直接创建设备节点。
                ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
                fileAction, dirAction, temp, 0);
        }
        recursive_action("/sys/class",
            ACTION_RECURSE | ACTION_FOLLOWLINKS,
            fileAction, dirAction, temp, 0);
    } else {
        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;

////////////////////////////////////////////////////

扫描二维码关注公众号,回复: 4294417 查看本文章

/* for arches where byte accesses generate larger code: */
typedef int smallint;
typedef unsigned smalluint;

////////////////////////////////////////////////////
        /* 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");
        subsystem = getenv("SUBSYSTEM");
        if (!action || !env_path /*|| !subsystem*/)
            bb_show_usage();
        fw = getenv("FIRMWARE");
        op = index_in_strings(keywords, action);
        /* If it exists, does /dev/mdev.seq match $SEQNUM?
         * If it does not match, earlier mdev is running
         * in parallel, and we need to wait */

        seq = getenv("SEQNUM");
        if (seq) {
            int timeout = 2000 / 32; /* 2000 msec */
            do {
                int seqlen;
                char seqbuf[sizeof(int)*+ 2];

                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);
        }

        snprintf(temp, PATH_MAX, "/sys%s", env_path);
        if (op == OP_remove) {
            /* Ignoring "remove firmware". It was reported
             * to happen and to cause erroneous deletion
             * of device nodes. */

            if (!fw)
                make_device(temp, 1);
        }
        else if (op == OP_add) {
            make_device(temp, 0);
            if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
                if (fw)
                    load_firmware(fw, temp);
            }
        }

        if (seq) {
            xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1));
        }
    }

    if (ENABLE_FEATURE_CLEAN_UP)
        RELEASE_CONFIG_BUFFER(temp);

    return EXIT_SUCCESS;
}

./include/autoconf.h:#define ENABLE_FEATURE_CLEAN_UP 0

int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;

/* We need to export XXX_main from libbusybox
 * only if we build "individual" binaries
 */

#if ENABLE_FEATURE_INDIVIDUAL
#define MAIN_EXTERNALLY_VISIBLE EXTERNALLY_VISIBLE
#else
#define MAIN_EXTERNALLY_VISIBLE
#endif

./include/autoconf.h:#define ENABLE_FEATURE_INDIVIDUAL 0
./include/libbb.h:#if ENABLE_FEATURE_INDIVIDUAL
./libbb/appletlib.c:#undef ENABLE_FEATURE_INDIVIDUAL
./libbb/appletlib.c:#define ENABLE_FEATURE_INDIVIDUAL 1
./libbb/appletlib.c:#if ENABLE_FEATURE_INDIVIDUAL
通过上面我们可以发现,应该定义的ENABLE_FEATURE_INDIVIDUAL是0。也就是说MAIN_EXTERNALLY_VISIBLE为空。

不过看看EXTERNALLY_VISIBLE是怎么定义的。

/include/platform.h

* -fwhole-program makes all symbols local. The attribute externally_visible
   forces a symbol global. */

#if __GNUC_PREREQ(4,1)  //如果编译器的版本高于4.1版本,那么返回1,否则返回0。
# define EXTERNALLY_VISIBLE __attribute__(( visibility("default")))
//__attribute__ ((__externally_visible__))

#else
# define EXTERNALLY_VISIBLE
#endif

那么还需要看看__GNU_PREREQ

/* Convenience macros to test the version of gcc. */
#undef __GNUC_PREREQ
#if defined __GNUC__ && defined __GNUC_MINOR__
# define __GNUC_PREREQ(maj, min) \
        ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
#else
# define __GNUC_PREREQ(maj, min) 0
#endif

这里解释一下宏定义__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__分别定义了gcc编译器的版本号,例如:gcc4.3.1
下面描述:
__attribute__((visibility("visibility_type")))

此函数属性影响 ELF 符号的可见性。

Note

此属性是 ARM 编译器支持的 GNU 编译器扩展。

语法

<span style="color:#666666">__attribute__((visibility("<em><code>visibility_type</code></em>")))
</span>

其中,visibility_type 是下列值之一:

default

假定的符号可见性可通过其他选项进行更改。缺省可见性将覆盖此类更改。缺省可见性与外部链接对应。

hidden

该符号不存放在动态符号表中,因此,其他可执行文件或共享库都无法直接引用它。使用函数指针可进行间接引用。

internal

除非由 特定于处理器的应用二进制接口 (psABI) 指定,否则,内部可见性意味着不允许从另一模块调用该函数。

protected

该符号存放在动态符号表中,但定义模块内的引用将与局部符号绑定。也就是说,另一模块无法覆盖该符号。

用法

除指定 default 可见性外,此属性都可与在这些情况下具有外部链接的声明结合使用。

您可在 C 和 C++ 中使用此属性。在 C++ 中,还可将它应用于类型、成员函数和命名空间声明。

示例

<span style="color:#666666">void __attribute__((visibility(“internal”))) foo()
{
   ...
}</span>
<span style="color:#666666">RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);通过下面分析可知,分配一段缓存,名字为temp。


</span>

RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);

# define PATH_MAX 256

#define SCRATCH_SIZE 80

/* buffer allocation schemes */
#if ENABLE_FEATURE_BUFFERS_GO_ON_STACK   //定义在栈中
#define RESERVE_CONFIG_BUFFER(buffer,len) char buffer[len]
#define RESERVE_CONFIG_UBUFFER(buffer,len) unsigned char buffer[len]
#define RELEASE_CONFIG_BUFFER(buffer) ((void)0)
#else
#if ENABLE_FEATURE_BUFFERS_GO_IN_BSS     //定义在bss段中
#define RESERVE_CONFIG_BUFFER(buffer,len) static char buffer[len]
#define RESERVE_CONFIG_UBUFFER(buffer,len) static unsigned char buffer[len]
#define RELEASE_CONFIG_BUFFER(buffer) ((void)0)
#else                                    //定义在堆中。
#define RESERVE_CONFIG_BUFFER(buffer,len) char *buffer = xmalloc(len)
#define RESERVE_CONFIG_UBUFFER(buffer,len) unsigned char *buffer = xmalloc(len)
#define RELEASE_CONFIG_BUFFER(buffer) free(buffer)
#endif
#endif

bb_sanitize_stdio();

    /* We can be called as hotplug helper */
    /* Kernel cannot provide suitable stdio fds for us, do it ourself */
    bb_sanitize_stdio();

void FAST_FUNC bb_sanitize_stdio(void)
{
    bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE, NULL);
}

enum {
    DAEMON_CHDIR_ROOT = 1,
    DAEMON_DEVNULL_STDIO = 2,
    DAEMON_CLOSE_EXTRA_FDS = 4,
    DAEMON_ONLY_SANITIZE = 8, /* internal use */
};


void FAST_FUNC bb_daemonize_or_rexec(int flags, char **argv)
{
    int fd;

    if (flags & DAEMON_CHDIR_ROOT)
        xchdir("/");   //切换到根目录,如果切换失败,那么进入die状态

    if (flags & DAEMON_DEVNULL_STDIO) {  //是否使用DEVNULL设备作为输入输出设备
        close(0);  //文件描述符0,标准输入,缺省是键盘
        close(1);  //文件描述符1,标准输出,缺省是屏幕
        close(2);  //文件描述符2,错误输出,缺省是屏幕
    }

    fd = open(bb_dev_null, O_RDWR);  //bb_dev_null为"/dev/null"
    if (fd < 0) {
        /* NB: we can be called as bb_sanitize_stdio() from init
         * or mdev, and there /dev/null may legitimately not (yet) exist!
         * Do not use xopen above, but obtain _ANY_ open descriptor,
         * even bogus one as below. */

        fd = xopen("/", O_RDONLY); /* don't believe this can fail */
    }

    while ((unsigned)fd < 2)
        fd = dup(fd); /* have 0,1,2 open at least to /dev/null */

    if (!(flags & DAEMON_ONLY_SANITIZE)) {
        if (fork_or_rexec(argv))
            exit(EXIT_SUCCESS); /* parent */
        /* if daemonizing, make sure we detach from stdio & ctty */
        setsid();
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
    }
    while (fd > 2) {
        close(fd--);
        if (!(flags & DAEMON_CLOSE_EXTRA_FDS))
            return;
        /* else close everything after fd#2 */
    }
}

./include/libbb.h:extern const char bb_dev_null[];
./libbb/messages.c:const char bb_dev_null[] ALIGN1 = "/dev/null";


// Die if we can't chdir to a new path.

void FAST_FUNC xchdir(const char *path)
{
    if (chdir(path))   //切换到path目录。如果失败,那么进入die状态。
        bb_perror_msg_and_die("chdir(%s)", path);
}

void FAST_FUNC bb_perror_msg_and_die(const char *s, ...)
{
    va_list p;

    va_start(p, s);
    /* Guard against ": Success" */
    bb_verror_msg(s, p, errno ? strerror(errno) : NULL); //如果系统出错,那么errno将是非0,strerror(errno)将把错误代码转换成字符串信息
    va_end(p);
    xfunc_die();
}

void FAST_FUNC bb_verror_msg(const char *s, va_list p, const char* strerr)
{
    char *msg, *msg1;
    int applet_len, strerr_len, msgeol_len, used;

    if (!logmode)
        return;

    if (!s) /* nomsg[_and_die] uses NULL fmt */
        s = ""; /* some libc don't like printf(NULL) */

    used = vasprintf(&msg, s, p);  //字符串格式化
    if (used < 0)
        return;

    /* This is ugly and costs +60 bytes compared to multiple
     * fprintf's, but is guaranteed to do a single write.
     * This is needed for e.g. httpd logging, when multiple
     * children can produce log messages simultaneously. */


    applet_len = strlen(applet_name) + 2; /* "applet: " */
    strerr_len = strerr ? strlen(strerr) : 0;
    msgeol_len = strlen(msg_eol);
    /* can't use xrealloc: it calls error_msg on failure,
     * that may result in a recursion */

    /* +3 is for ": " before strerr and for terminating NUL */
    msg1 = realloc(msg, applet_len + used + strerr_len + msgeol_len + 3);  //重新分配内存
    if (!msg1) {
        msg[used++] = '\n'; /* overwrites NUL */
        applet_len = 0;
    } else {
        msg = msg1;
        /* TODO: maybe use writev instead of memmoving? Need full_writev? */
        memmove(msg + applet_len, msg, used);
        used += applet_len;
        strcpy(msg, applet_name);
        msg[applet_len - 2] = ':';
        msg[applet_len - 1] = ' ';
        if (strerr) {
            if (s[0]) { /* not perror_nomsg? */
                msg[used++] = ':';
                msg[used++] = ' ';
            }
            strcpy(&msg[used], strerr);
            used += strerr_len;
        }
        strcpy(&msg[used], msg_eol);
        used += msgeol_len;
    }
    //": "": "
    if (logmode & LOGMODE_STDIO) {
        fflush_all();
        full_write(STDERR_FILENO, msg, used);
    }
    if (logmode & LOGMODE_SYSLOG) {
        syslog(LOG_ERR, "%s", msg + applet_len);
    }
    free(msg);
}

int FAST_FUNC fflush_all(void)
{
    return fflush(NULL);  //这里为NULL的意思是,清空所有输出流。让它真正显示或者写入到硬盘中。
}

/*
 * Write all of the supplied buffer out to a file.
 * This does multiple writes as necessary.
 * Returns the amount written, or -1 on an error.
 */

ssize_t FAST_FUNC full_write(int fd, const void *buf, size_t len)
{
    ssize_t cc;
    ssize_t total;

    total = 0;

    while (len) {
        cc = safe_write(fd, buf, len);

        if (cc < 0) {
            if (total) {
                /* we already wrote some! */
                /* user can do another write to know the error code */
                return total;
            }
            return cc;    /* write() returns -1 on failure. */
        }

        total += cc;
        buf = ((const char *)buf) + cc;
        len -= cc;
    }

    return total;
}

ssize_t FAST_FUNC safe_write(int fd, const void *buf, size_t count)
{
    ssize_t n;

    do {
        n = write(fd, buf, count);
    } while (< 0 && errno == EINTR);  //errno=EINTR说明是由于信号打断了,那么就重写。

    return n;
}

void FAST_FUNC xfunc_die(void)
{
    if (die_sleep) {
        if ((ENABLE_FEATURE_PREFER_APPLETS || ENABLE_HUSH)
         && die_sleep < 0
        ) {
            /* Special case. We arrive here if NOFORK applet
             * calls xfunc, which then decides to die.
             * We don't die, but jump instead back to caller.
             * NOFORK applets still cannot carelessly call xfuncs:
             * p = xmalloc(10);
             * q = xmalloc(10); // BUG! if this dies, we leak p!
             */

            /* -2222 means "zero" (longjmp can't pass 0)
             * run_nofork_applet() catches -2222. */

            longjmp(die_jmp, xfunc_error_retval ? xfunc_error_retval : -2222);
        }
        sleep(die_sleep);
    }
    exit(xfunc_error_retval);
}

# define close(fd) do { \
    int dfd = (fd); \
    if (close(dfd) < 0) \
        bb_error_msg("bug on %d: closing %d(0x%x)", \
            __LINE__, dfd, dfd); \
} while (0)


// Die if we can't open an existing file and return a fd.

int FAST_FUNC xopen(const char *pathname, int flags)
{
    return xopen3(pathname, flags, 0666);
}

// Die if we can't open a file and return a fd.

int FAST_FUNC xopen3(const char *pathname, int flags, int mode)
{
    int ret;

    ret = open(pathname, flags, mode);
    if (ret < 0) {
        bb_perror_msg_and_die("can't open '%s'", pathname);
    }
    return ret;
}

 

recursive_action("/sys/block",
    ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
    fileAction, dirAction, temp, 0);

/* File callback for /sys/ traversal */
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;

    /* len check is for paranoid reasons */
    if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)  //这里的意思就是说文件名应该是dev,通过dev文件获取设备的主设备号和次设备号。
        return FALSE;

    strcpy(scratch, fileName);  //把文件名存储到srcatch,它就是刚开始传进来的temp缓存区。
    scratch[len] = '\0';
    make_device(scratch, 0);   //创建设备节点。

    return TRUE;
}

/* Directory callback for /sys/ traversal */
static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
        struct stat *statbuf UNUSED_PARAM,
        void *userData UNUSED_PARAM,
        int depth)
{
    /* Extract device subsystem -- the name of the directory
     * under /sys/class/ */

    if (== depth) {
        free(subsystem);
        subsystem = strrchr(fileName, '/');
        if (subsystem)
            subsystem = xstrdup(subsystem + 1);  //获取子系统名字。在执行mdev.conf配置的命令时,作为环境变量:SUBSYSTEM=subsystem。
    }

    return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
}

enum {
    ACTION_RECURSE = (<< 0),
    ACTION_FOLLOWLINKS = %3

 

make_device(scratch, 0);

/* mknod in /dev based on a path like "/sys/block/hda/hda1"
 * NB1: path parameter needs to have SCRATCH_SIZE scratch bytes
 * after NUL, but we promise to not mangle (IOW: to restore if needed)
 * path string.
 * NB2: "mdev -s" may call us many times, do not leak memory/fds!
 */

static void make_device(char *path, int delete) //这里的path是不包含"/dev"的。例如/sys/block/loop0/dev。它传进来的是/sys/block/loop0。delete如果为0,那么说明要创建设备节点,如果为1,那么说明要删除设备节点。
{
    char *device_name;
    int major, minor, type, len;
    mode_t mode;
    parser_t *parser;

    /* Try to read major/minor string. Note that the kernel puts \n after
     * the data, so we don't need to worry about null terminating the string
     * because sscanf() will stop at the first nondigit, which \n is.
     * We also depend on path having writeable space after it.
     */

    major = -1;
    if (!delete) {
        char *dev_maj_min = path + strlen(path);

        strcpy(dev_maj_min, "/dev");
        len = open_read_close(path, dev_maj_min + 1, 64);  //打开path文件读取最大64字节到dev_maj_min+1缓存上。
        *dev_maj_min = '\0';
        if (len < 1) {   //也就是没有数据读取到,或者说读数据时发生错误。
            if (!ENABLE_FEATURE_MDEV_EXEC)  //由于它在/include/autoconf.h:#define ENABLE_FEATURE_MDEV_EXEC 1,所以继续执行。注释也说了,没有dev文件,但是可以基于设备名运行脚本
                return;
            /* no "dev" file, but we can still run scripts
             * based on device name */

        } else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2)  //如果读取的主设备号和次设备号有问题,那么主设备号赋值为-1。{
            major = -1;
        }
    }

    /* Determine device name, type, major and minor */
    device_name = (char*) bb_basename(path); //获取设备名。这个函数就是返回,例如:path = /sys/block/loop0,返回loop0
    /* http://kernel.org/doc/pending/hotplug.txt says that only
     * "/sys/block/..." is for block devices. "/sys/bus" etc is not.
     * But since 2.6.25 block devices are also in /sys/class/block,
     * we use strstr("/block/") to forestall future surprises. */

    type = S_IFCHR;  //假设这个设备是字符设备
    if (strstr(path, "/block/"))  //如果在path字符串中找到字符串"/block/",那么返回第一次找到的位置,如果没有找到,那么返回NULL,如果找到也就是说该设备是块设备。
        type = S_IFBLK;  //这个设备是块设备

    /* Make path point to "subsystem/device_name" */
    if (path[5] == 'b') /* legacy /sys/block? */
        path += sizeof("/sys/") - 1;
    else
        path += sizeof("/sys/class/") - 1;

    /* If we have config file, look up user settings */
    if (ENABLE_FEATURE_MDEV_CONF)  //是否使用了mdev的配置文件。这个配置文件是/etc/mdev.conf。
        parser = config_open2("/etc/mdev.conf", fopen_for_read); //通过zalloc分配了parser结构体。并把文件的打开句柄赋值给这个结构体的fp成员

    do {
        int keep_matching;
        struct bb_uidgid_t ugid;
        char *tokens[4];
        char *command = NULL;
        char *alias = NULL;
        char aliaslink = aliaslink; /* for compiler */

        /* Defaults in case we won't match any line */
        ugid.uid = ugid.gid = 0;
        keep_matching = 0;
        mode = 0660;

        if (ENABLE_FEATURE_MDEV_CONF
         && config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)  //读取配置文件的一行命令数据,并进行词的解析,参数parser传递进去了配置文件的句柄。4,标志着一行命令数据解析成的最多词(最后一个词可以有好几个词组成)。3,标志着一行命令行数据解析出来的词至少要有的数。"# \t"表示词间的界定符,PARSE_NORMAL标志着解析命令时的一些方式。返回值:parser的lineno为当前的命令行数据的行数。tokens解析出来的词的指针索引。函数返回值:标志着一行命令行数据解析出来的词的数,如果为0说明整个配置文件解析完毕,或者是发生了错误。
        ) {
            char *val;
            char *str_to_match;
            regmatch_t off[+ 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];//regmatch_t由#include 包含进来的。定义如下

typedef struct {

regoff_t rm_so;

regoff_t rm_eo;

}regmatch_t
pcreposix.h:typedef int regoff_t;
./include/autoconf.h:#define ENABLE_FEATURE_MDEV_RENAME_REGEXP 1


            val = tokens[0];
            keep_matching = ('-' == val[0]); //如果第一个词可以有'-'字符开头,它的意思是在分析完当前的行后,就算是匹配了,也可以继续配置文件的后续行。不过在分析的时候,它不起作用,把它滤除。
            val += keep_matching; /* swallow leading dash */ //如果第一个词有'-'开始,那么把它去掉。

            /* Match against either "subsystem/device_name"
             * or "device_name" alone */

            str_to_match = strchr(val, '/') ? path : device_name;  //如果val里有'/'字符,那么str_to_match=path,否则为device_name。

            /* Fields: regex uid:gid mode [alias] [cmd] */

            if (val[0] == '@') { //第一个词,如果第一个字符为@,说明是设备的主设备号和次设备号。其格式为@主设备号,次设备号(-次设备号的最大值)
                /* @major,minor[-minor2] */
                /* (useful when name is ambiguous:
                 * "/sys/class/usb/lp0" and
                 * "/sys/class/printer/lp0") */

                int cmaj, cmin0, cmin1, sc;
                if (major < 0)
                    continue; /* no dev, no match */
                sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
                if (sc < 1 || major != cmaj
                 || (sc == 2 && minor != cmin0)
                 || (sc == 3 && (minor < cmin0 || minor > cmin1))
                ) {
                    continue; /* this line doesn't match */  //本行数据不匹配
                }
                goto line_matches;  //说明匹配
            }
            if (val[0] == '$') {  第一个词,如果第一个字符为'$',说明是环境变量的匹配。格式为xx=xxx。它是一个正则表达式,用来匹配其环境变量
                /* regex to match an environment variable */
                char *eq = strchr(++val, '=');
                if (!eq)  //如果val里找不到'='字符
                    continue;
                *eq = '\0';  //如果找到'='字符,那么把'='字符改成'\0'字符。
                str_to_match = getenv(val);  //获取环境变量val。
                if (!str_to_match)           //如果没有
                    continue;
                str_to_match -= strlen(val) + 1;  //环境变量中,获取该环境变量的整个语句
                *eq = '=';  //重新连接回去。
            }
            /* else: regex to match [subsystem/]device_name */  //其它的就是[子系统/]设备名,注意这里使用的是正则表达式。

            {
                regex_t match; //regex_t在pcreposix.h中定义:
typedef struct {
  void *re_pcre;
  size_t re_nsub;
  size_t re_erroffset;
} regex_t;

                int result;

                xregcomp(&match, val, REG_EXTENDED); //val正则表达式是配置文件的第1个词,为设备名正则表达式,或者为环境变量正则表达式。
                result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0); //str_to_match是要去与正则表达式去匹配的字符串,它有三种可能:1,如果正则表达式里有'/',那么使用的是设备dev文件所在的目录字符串。2,如果没有'/',那么是设备dev文件所在的目录的目录名(不包括路径)。3,如果正则表达式中有'=',也就是说它是一个标示的是一个环境变量。那么这里就是通过getenv()获得的环境变量。不过它包括了“环境变量名=”。
                regfree(&match);
                //bb_error_msg("matches:");

                //for (int i = 0; i < ARRAY_SIZE(off); i++) {

                //    if (off[i].rm_so < 0) continue;

                //    bb_error_msg("match %d: '%.*s'\n", i,

                //        (int)(off[i].rm_eo - off[i].rm_so),

                //        device_name + off[i].rm_so);

                //}


                /* If no match, skip rest of line */
                /* (regexec returns whole pattern as "range" 0) */
                if (result || off[0].rm_so
                 || ((int)off[0].rm_eo != (int)strlen(str_to_match)) //说明不匹配。
                ) {
                    continue; /* this line doesn't match */
                }
            }
 line_matches:
            /* This line matches. Stop parsing after parsing
             * the rest the line unless keep_matching == 1 */


            /* 2nd field: uid:gid - device ownership */
            if (get_uidgid(&ugid, tokens[1], 1) == 0) //第2个词就是uid:gid。设备的所有者。tokens[1]-->ugid.
                bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno);

            /* 3rd field: mode - device permissions */
            /* mode = strtoul(tokens[2], NULL, 8); */
            bb_parse_mode(tokens[2], &mode);  //第3个词描述的是设备权限

            val = tokens[3];    //第4个词描述的是要做什么,这个词可是可以包括多个词。
            /* 4th field (opt): >|=alias */

            if (ENABLE_FEATURE_MDEV_RENAME && val) { //假如第4个词为:">ab%1sad%2 @bcd",那么,这个if语句的执行结果是alias="ab%2sad%1",这里的%2和%1是由str_to_match匹配第一个词而得的。i就是匹配的序号。val=@bcd
                aliaslink = val[0];
                if (aliaslink == '>' || aliaslink == '=') { //第一个字符为如果为'>'或'=',代表是要改变设备的名字。">ab%1sad%2 @bcd"
                    char *a, *s, *st;
                    char *p;
                    unsigned i, n;

                    a = val;
                    s = strchrnul(val, ' ');
                    st = strchrnul(val, '\t');
                    if (st < s)
                        s = st;   //第4个词的第一个分隔符' ','\t'的位置
                    val = (s[0] && s[1]) ? s+: NULL;  //val指向值。
                    s[0] = '\0';  //断句

                    if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {  //重命名使用了正则表达式。
                        /* substitute %1..9 with off[1..9], if any */
                        n = 0;
                        s = a;
                        while (*s)
                            if (*s++ == '%')
                                n++;

                        p = alias = xzalloc(strlen(a) + n * strlen(str_to_match));
                        s = a + 1;
                        while (*s) {
                            *= *s;
                            if ('%' == *s) {
                                i = (s[1] - '0');
                                if (<= 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(+ 1);  //重命名不使用正则表达式。
                    }
                }
            }

            if (ENABLE_FEATURE_MDEV_EXEC && val) {  //如果第4个词中的第2个小词存在,它必须含有字符'$',或'@',或'*'。
                const char *= "$@*";
                const char *s2 = strchr(s, val[0]);

                if (!s2) {  //如果val[0]不是$@*字符之一,那么就是说发生错误了。
                    bb_error_msg("bad line %u", parser->lineno);
                    if (ENABLE_FEATURE_MDEV_RENAME)
                        free(alias);
                    continue;
                }

                /* Are we running this command now?
                 * Run $cmd on delete, @cmd on create, *cmd on both
                 */

                if (s2-!= delete)  //如果第4个词的第2个小词的第一个字母是字符'@',或'*',那么备份。其实这里的注释已经比较清晰了,$,在删除的时候运行,@在创建时运行,*在创建时运行,及在删除时也运行。
                    command = xstrdup(val + 1);
            }
        }

        /* End of field parsing */  //发现的行匹配,且参数解析完毕。

                                    //如果没有配置文件,那么直接运行到这里。

        /* "Execute" the line we found */  //接下来就是要创建设备节点以及相应的命令执行。
        {
            const char *node_name;

            node_name = device_name;
            if (ENABLE_FEATURE_MDEV_RENAME && alias)  //如果第4个词的第一个小词是以'>','='字符开头,也就是说有别名。那么获取别名。
                node_name = alias = build_alias(alias, device_name);  //创建alias标示的文件夹,当然如果alias只是一个文件名时,当然不会创建文件夹,如果alias不是文件名,那么用使用alias标示文件夹路径,device_name作为文件名。

            if (!delete && major >= 0) {
                if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST)  //创建设备节点。注意,在前面已经执行了xchdir("/dev");也就是说当前路径就是在/dev下。
                    bb_perror_msg("can't create %s", node_name);
                if (major == root_major && minor == root_minor)
                    symlink(node_name, "root");  //如果这个dev就是根设备节点,那么创建一个软链接。root->node_name。这个
                if (ENABLE_FEATURE_MDEV_CONF) {
                    chmod(node_name, mode);      //dev设备权限设置
                    chown(node_name, ugid.uid, ugid.gid);  //dev设备属主设置
                }
                if (ENABLE_FEATURE_MDEV_RENAME && alias) {
                    if (aliaslink == '>')  //如果第4个词的第一个字母是'>',那么还要创建软链接:device_name->node_name
                        symlink(node_name, device_name);
                }
            }

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

            if (delete) {
                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 && !keep_matching)  //如果第一个词是以'-'开头的,那么还要执行该dev文件在mdev.conf配置文件的匹配处理。
            break;

    /* end of "while line is read from /etc/mdev.conf" */
    } while (ENABLE_FEATURE_MDEV_CONF);

    if (ENABLE_FEATURE_MDEV_CONF)
        config_close(parser);
}

typedef struct parser_t {
    FILE *fp;        //mdev.conf配置文件句柄
    char *line;      //指向读取一行数据的起始地址。
    char *data;      //备份行数据到data。注意,这里的行是非空的,且不是注释行。
    int lineno;      //在配置文件中读取一行的行号值。
} parser_t;

 

ssize_t FAST_FUNC open_read_close(const char *filename, void *buf, size_t size)
{
    int fd = open(filename, O_RDONLY);
    if (fd < 0)
        return fd;
    return read_close(fd, buf, size);
}

ssize_t FAST_FUNC read_close(int fd, void *buf, size_t size)
{
    /*int e;*/
    size = full_read(fd, buf, size);
    /*e = errno;*/
    close(fd);
    /*errno = e;*/
    return size;
}

/*
 * Read all of the supplied buffer from a file.
 * This does multiple reads as necessary.
 * Returns the amount read, or -1 on an error.
 * A short read is returned on an end of file.
 */

ssize_t FAST_FUNC full_read(int fd, void *buf, size_t len)
{
    ssize_t cc;
    ssize_t total;

    total = 0;

    while (len) {  //如果已经读取了要读取的字节数,那么退出while循环。
        cc = safe_read(fd, buf, len);

        if (cc < 0) {
            if (total) {
                /* we already have some! */
                /* user can do another read to know the error code */
                return total;  //读时发生错误,但是已经有读取数据,那么返回有读取的数据
            }
            return cc; /* read() returns -1 on failure. */  //返回读取失败
        }
        if (cc == 0)  //说明文件已被读完。
            break;
        buf = ((char *)buf) + cc; //缓存偏移
        total += cc;              //获取的字节数
        len -= cc;                //还剩下的要读字节数
    }

    return total;
}

ssize_t FAST_FUNC safe_read(int fd, void *buf, size_t count)
{
    ssize_t n;

    do {
        n = read(fd, buf, count);
    } while (< 0 && errno == EINTR); //如果是由于信号中断引起的读失败,那么再读。

    return n;
}

 

FILE* FAST_FUNC fopen_for_read(const char *path)
{
    return fopen(path, "r");
}

parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
{
    FILE* fp;
    parser_t *parser;

    fp = fopen_func(filename);
    if (!fp)
        return NULL;
    parser = xzalloc(sizeof(*parser));
    parser->fp = fp;
    return parser;
}

// Die if we can't allocate and zero size bytes of memory.

void* FAST_FUNC xzalloc(size_t size)
{
    void *ptr = xmalloc(size);
    memset(ptr, 0, size);
    return ptr;
}

// Die if we can't allocate size bytes of memory.

void* FAST_FUNC xmalloc(size_t size)
{
    void *ptr = malloc(size);
    if (ptr == NULL && size != 0)
        bb_error_msg_and_die(bb_msg_memory_exhausted);
    return ptr;
}

struct bb_uidgid_t {
    uid_t uid;
    gid_t gid;
};

/include/linux/types.h
typedef __kernel_uid32_t uid_t;
typedef __kernel_gid32_t gid_t;

typedef unsigned int __kernel_uid32_t;
typedef unsigned int __kernel_gid32_t;

/include/asm-arm/posix_types.h

 

/*
 * Config file parser
 */

enum {
    PARSE_COLLAPSE = 0x00010000, // treat consecutive delimiters as one

    PARSE_TRIM = 0x00020000, // trim leading and trailing delimiters

// TODO: COLLAPSE and TRIM seem to always go in pair

    PARSE_GREEDY = 0x00040000, // last token takes entire remainder of the line

    PARSE_MIN_DIE = 0x00100000, // die if < min tokens found

    // keep a copy of current line

    PARSE_KEEP_COPY = 0x00200000 * ENABLE_FEATURE_CROND_D,
// PARSE_ESCAPE = 0x00400000, // process escape sequences in tokens

    // NORMAL is:

    // * remove leading and trailing delimiters and collapse

    // multiple delimiters into one

    // * warn and continue if less than mintokens delimiters found

    // * grab everything into last token

    PARSE_NORMAL = PARSE_COLLAPSE | PARSE_TRIM | PARSE_GREEDY,
};

 

#define config_read(parser, tokens, max, min, str, flags) \
    config_read(parser, tokens, ((flags) | (((min) & 0xFF) << 8) | ((max) & 0xFF)), str)

 

/*
0. If parser is NULL return 0.
1. Read a line from config file. If nothing to read then return 0.
   Handle continuation character. Advance lineno for each physical line.
   Discard everything past comment character.
2. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
3. If resulting line is empty goto 1.
4. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
   remember the token as empty.
5. Else (default) if number of seen tokens is equal to max number of tokens
   (token is the last one) and PARSE_GREEDY is set then the remainder
   of the line is the last token.
   Else (token is not last or PARSE_GREEDY is not set) just replace
   first delimiter with '\0' thus delimiting the token.
6. Advance line pointer past the end of token. If number of seen tokens
   is less than required number of tokens then goto 4.
7. Check the number of seen tokens is not less the min number of tokens.
   Complain or die otherwise depending on PARSE_MIN_DIE.
8. Return the number of seen tokens.

mintokens > 0 make config_read() print error message if less than mintokens
(but more than 0) are found. Empty lines are always skipped (not warned about).
*/

#undef config_read
int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
{
    char *line;
    int ntokens, mintokens;
    int t, len;

    ntokens = flags & 0xFF;
    mintokens = (flags & 0xFF00) >> 8;

    if (parser == NULL)
        return 0;

again:
    memset(tokens, 0, sizeof(tokens[0]) * ntokens);
    config_free_data(parser);

    /* Read one line (handling continuations with backslash) */
    line = bb_get_chunk_with_continuation(parser->fp, &len, &parser->lineno); //函数功能,读取配置文件的一行数据,如果一行数据通过连接符'\'分成两行,那么把它合并起来。参数parser->fp就是配置文件的句柄,&len返回的是读取一行数据的长度,&parser->lineno返回的从配置文件中已经读取的命令行行数。函数返回值:就是读取的命令行数据的起始地址。
    if (line == NULL)
        return 0;           //说明配置文件已经读取完了,还有一种可能是内存不够(当然这种情况,一般是不存在的)
    parser->line = line;

    /* Strip trailing line-feed if any */
    if (len && line[len-1] == '\n')  //去掉换行符。
        line[len-1] = '\0';

    /* Skip token in the start of line? */
    if (flags & PARSE_TRIM)
        line += strspn(line, delims + 1);  //去除行数据前面的' '或'\t'字符。

    if (line[0] == '\0' || line[0] == delims[0])  //如果line指向的字符串为空或者line[0] = '#',也就说刚读取的一行,要么是空的,要么就是注释。丢弃,重新读取一行。
        goto again;

    if (flags & PARSE_KEEP_COPY)  //这里的flagsPARSE_NORMAL    = PARSE_COLLAPSE | PARSE_TRIM | PARSE_GREEDY,也就是说,这里不会执行。其实,这里的意思就是是否备份。
        parser->data = xstrdup(line);

    /* Tokenize the line */
    for (= 0; *line && *line != delims[0] && t < ntokens; t++) { //退出条件,1,line指向的地方已经没有数据了。2,*line = "#",即后面是注释。也就是说这一行从这里开始就是注释了。3,一行语句只能有ntokens个词。多余的也略去。
        /* Pin token */
        tokens[t] = line;   //tokens字符串指针数组保存了词。

        /* Combine remaining arguments? */
        if ((!= (ntokens-1)) || !(flags & PARSE_GREEDY)) { //如果t小于ntokens-1,也就是需要分析的前一个词,如果flags的PARSE_GREEDY位为0,那么就算是t等于ntokens-1也还是通过下面进行词的识别。而在我们这里的flags=PARSE_NORMAL,它包括了PARSE_GREEDY。
            /* Vanilla token, find next delimiter */
            line += strcspn(line, delims[0] ? delims : delims + 1);  //查找下一个界定符,如果没有找到,那么返回字符串的尾巴'\0'位置
        } else {
            /* Combining, find comment char if any */
            line = strchrnul(line, delims[0]);    //查找'#'字符,如果没有找到,那么返回字符串的尾巴'\0'位置,这里和上面的有一个显著的区别是,最后一个tokens[ntokens-1],它可以包括多个词。

            /* Trim any extra delimiters from the end */
            if (flags & PARSE_TRIM) {  //在我们这里flags=PARSE_NORMAL,它包含了PARSE_TRIM
                while (strchr(delims + 1, line[-1]) != NULL)  //把最后一个词(这个词可以包括好几个词)后面的无效字符滤除。
                    line--;
            }
        }

        /* Token not terminated? */  //给每个词添加结束符。
        if (line[0] == delims[0])
            *line = '\0';
        else if (line[0] != '\0')
            *(line++) = '\0';

#if 0 /* unused so far */
        if (flags & PARSE_ESCAPE) {
            const char *from;
            char *to;

            from = to = tokens[t];
            while (*from) {
                if (*from == '\\') {
                    from++;
                    *to++ = bb_process_escape_sequence(&from);
                } else {
                    *to++ = *from++;
                }
            }
            *to = '\0';
        }
#endif

        /* Skip possible delimiters */
        if (flags & PARSE_COLLAPSE)
            line += strspn(line, delims + 1);  //去掉前面的"# \t"的字符。
    }

    if (< mintokens) {  //如果一条命令语句的词少于mintokens,那么继续读取配置文件的下一行目录,或者(如果flags的位PARSE_MIN_DIE被置一)就进入die状态。
        bb_error_msg("bad line %u: %d tokens found, %d needed",
                parser->lineno, t, mintokens);
        if (flags & PARSE_MIN_DIE)
            xfunc_die();
        goto again;
    }

    return t;
}

static void config_free_data(parser_t *parser)
{
    free(parser->line);
    parser->line = NULL;
    if (PARSE_KEEP_COPY) { /* compile-time constant */
        free(parser->data);
        parser->data = NULL;
    }
}

/* This function reads an entire line from a text file, up to a newline
 * or NUL byte, inclusive. It returns a malloc'ed char * which
 * must be free'ed by the caller. If end is NULL '\n' isn't considered
 * end of line. If end isn't NULL, length of the chunk is stored in it.
 * If lineno is not NULL, *lineno is incremented for each line,
 * and also trailing '\' is recognized as line continuation.
 *
 * Returns NULL if EOF/error. */

char* FAST_FUNC bb_get_chunk_with_continuation(FILE *file, int *end, int *lineno)
{
    int ch;
    int idx = 0;
    char *linebuf = NULL;
    int linebufsz = 0;

下面的while循环,阅读分析后可知:
end和lineno都不为NULL
退出while循环条件
1,已经到了文件的末尾
2,文件有数据为0
3,如果end不为NULL,ch='\n',lineno为NULL。
4,如果end不为NULL,ch='\n',lineno不为NULL,ch的前一个字符是'\\'。
也就是说,当遇到换行符,而这时又没有使用'\'行间连接符,那么退出。

linebuf和文件内容基本一致,但是还是做了些改变:
如果在文件中有通过'\'连接两行,那么去掉'\'字符和换行符。

也就是说,这个while循环主要是为了获取一行数据,包括使用'\'连接符的多行合成一行。
    while ((ch = getc(file)) != EOF) {  //从文件中读取一字节数据,如果到文章的尾,会返回EOF,于是退出这个while循环。
        /* grow the line buffer as necessary */
        if (idx >= linebufsz) {
            linebufsz += 256;
            linebuf = xrealloc(linebuf, linebufsz);  //分配一个linebufsz大小的内存(如果linebuf本来能够向后扩展满足容量,那么不会释放原先的内存,也不会另外申请内存,而是扩展原来已分配的内存。),把linebuf字符串的内容拷贝到新分配的缓存内,并返回新分配的缓存首地址。
        }
        linebuf[idx++] = (char) ch;  //把从文件中读取到得值赋值给linebuf字符串数组中。
        if (!ch)  //如果数据是空的,那么退出while循环。
            break;
        if (end && ch == '\n') {  //如果end不为NULL,且ch='\n'。
            if (lineno == NULL)   //如果lineno为NULL,不过在config_read调用这个函数时,lineo不是NULL
                break;            //那么退出
            (*lineno)++;          //增加配置文件已经读取的行数。
            if (idx < 2 || linebuf[idx-2] != '\\') //如果有转义字符,那么去除转义字符,也就是说,比如,文件中的内容有"abcd\efg",经过这个if语句及下面的idx -= 2后,linebuf中的内容为"abcdfg"。不过这里是不可能的了,前面还有一个if(end && ch == '\n'),也就是说只有这种情况下"abcd\\nefg"会变成"abcdefg"。哦,终于明白了,这里的意思就是说一行有时写不下时会使用'\'作为标记下一行和这一行在语法分析上而言它们是同一行的。这里就是去除"\\\n"这两个字符。
                break;
            idx -= 2;             //
        }
    }
    if (end)
        *end = idx;  //一行数据的结束位置。
    if (linebuf) {
        // huh, does fgets discard prior data on error like this?

        // I don't think so....

        //if (ferror(file)) {

        //    free(linebuf);

        //    return NULL;

        //}

        linebuf = xrealloc(linebuf, idx + 1);  //最终的数据例如:"abcdef\n\0"
        linebuf[idx] = '\0';
    }
    return linebuf;
}

 

// Die if we can't resize previously allocated memory. (This returns a pointer

// to the new memory, which may or may not be the same as the old memory.

// It'll copy the contents to a new chunk and free the old one if necessary.)

void* FAST_FUNC xrealloc(void *ptr, size_t size)
{
    ptr = realloc(ptr, size);
    if (ptr == NULL && size != 0)
        bb_error_msg_and_die(bb_msg_memory_exhausted);
    return ptr;
}

/* Builds an alias path.
 * This function potentionally reallocates the alias parameter.
 * Only used for ENABLE_FEATURE_MDEV_RENAME
 */

static char *build_alias(char *alias, const char *device_name)
{
    char *dest;

    /* ">bar/": rename to bar/device_name */
    /* ">bar[/]baz": rename to bar[/]baz */
    dest = strrchr(alias, '/');
    if (dest) { /* ">bar/[baz]" ? */
        *dest = '\0'; /* mkdir bar */
        bb_make_directory(alias, 0755, FILEUTILS_RECUR);  //创建文件夹
        *dest = '/';
        if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */这里也就是说,如果alias不包括文件名,也就是说它只是文件夹路径。那么把device_name作为文件名,添加进来。
            dest = alias;
            alias = concat_path_file(alias, device_name);
            free(dest);
        }
    }

    return alias;
}

 

 

 

------------- MDEV Primer

------------- MDEV 入门

-------------

For those of us who know how to use mdev, a primer might seem lame.  For everyone else, mdev is a weird black box that they hear is awesome, but can't seem to get their head around how it works.  Thus, a primer.

对于知道如何使用mdev的人来说,这份文档是比较肤浅的。但对于其他人,mdev可能是一个神秘的黑匣子,以至让人敬畏。而这份文档不足以让他们知道mdev是如何工作的。因此这是一份入门文档。

----------- Basic Use

基本使用方法

-----------

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.

Mdev有两个主要的应用:整体初始化和动态更新。它们都需要内核的sysfs支持,并且是挂载在/sys下。动态更新还需要内核支持热插拔功能。

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

以下是系统初始化脚本中使用mdev的典型代码片段:

[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

另外,没有procfs的话,上面得改成这样

[1] mount –t sysfs sysfs /sys

[2] sysctl –w kernel.hotplug=/sbin/mdev

[3] mdec –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

当然,一个完整的安装程序,在执行前面的代码片段之前执行下面的命令:

[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.

简单的解释一下上面的代码:

[1]在执行mdev之前要先挂载sys。

[2]随后告诉内核在添加或删除设备时执行/sbin/mdev,增加或设备节点。

[3]然后,设置mdev,让它在系统启动时创建所有的设备节点。

 

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配置,必须确保/dev是一个tmpfs文件系统(假设在flash上运行)[4]。然后创建/dev/pts [5],然后把devpts文件系统挂载到/dev/pts上。

 

------------- MDEV Config   (/etc/mdev.conf)

------------- MDEV配置    (/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.

如果你的系统需要一个不是默认的root/root 660权限的话,Mdev有一个可选的配置文件,用来控制设备节点的所有者和权限。

The file has the format:        : or @ :

For example: hd[a-z][0-9]* 0:3 660

文件格式:

<设备的正则表达式>  : <8进制权限>

或者:

@ : <8进制权限>

 

The config file parsing stops at the first matching line.  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. : [=path] 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".

配置文件在第一个匹配行处停止解析。如果没有匹配行,使用默认0:0 660。在配置文件的最后一行,设置如.* 1:1 777来创建自己的默认值。你可以重命名或者删除设备节点,通过使用下一个域。

<设备正则表达式> : <8进制权限> [=path]。

所以如果你想重新命名设备节点,只要设置名字。

Had 0:3 660 =driver/

这样”had”将移动到”driver”目录下。

Hdb 0:3 660 =cdrom

Hdb将被重命名为cdrom。

Similarly, ">path" renames/moves the device but it also creates a direct symlink /dev/DEVNAME to the renamed/moved device.

类似,”>path”重命名/移动的设备,但是也创建了符号链接/dev/DEVNAME到重命名/移动的设备

If you also enable support for executing your own commands, then the file has the format: : [=path] [@|$|*] or : [>path] [@|$|*]

如果你时能支持执行命令,那么文件的格式为:

: [=path] [@|$|*]

或者

: [>path] [@|$|*]

 

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<---

例如:

([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]+)

 

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.

命令是通过system()函数调用执行的,也就是说使用的命令时shell,所以要确保安装了/bin/sh。

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".

为了方便,shell的环境env变量$MDEV被设置为设备名。所以,如果设备”hdc”匹配,MDEC将被设置为”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.

一些内核设备驱动在运行时需要一些固件,实现设备正确的初始化。把所有固件文件都放在/lib/firmware/目录下。在运行时,内核将会按固件文件名调用 mdev ,之后 mdev 会通过 sysfs 接口将固件从 /lib/firmware/装载到内核。确定的文件名固化在内核中,如有必要,你必须知道如何在用户空间命名这个文件。

------------ 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.

内核没有串行化热插拔事件。当正确调用哪个热插拔后,它增加了环境变量SENUM的值。通常情况下,mdev不关心这个。这将重新排列热插和热拔事件,典型的的设备节点有时创建的不是所期望的。

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.

然而,如果/dev/mdev.seq文件找到,mdev将会比较SEQNUM。它将重试最多两秒钟,等待它们匹配。如果精确匹配(不包括尾巴’\n’),或者如果超过2秒,mdev普通运行,然后重新写/dev/mdev.seq的SEQNUM+1

IOW: this will serialize concurrent mdev invocations.

IOW:这将序列号调用mdev

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.

如果你想激活这个特性。执行”echo>/dev/mdev.seq”,然后设置mdev。写’\n’到文件。注意:mdev识别 /dev/mdev.seq,以’\n’作为特殊字符。IOW:这不会使你的第一个热拔插事件拖延两秒钟。

猜你喜欢

转载自blog.csdn.net/lydh123456/article/details/84620609