Android ueventd浅析

platform
  mstar828
  android 5.0.1

在linux2.6之后,udev取代了devfs,但是在android中却没有udev或者mdev1,而是由ueventd进程实现了类似功能(管理设备节点权限、创建设备节点)。

ueventd通过两种方式创建设备节点:

  • 静态,ueventd启动时,根据在sysfs中预定义的uevent信息创建设备节点;
  • 动态,系统运行过程中,当接收到内核uevent事件时(如插入u盘),动态创建设备节点。

1. ueventd启动过程

在init.rc中,当触发条件为“early-init”时ueventd被启动:
system/core/rootdir/init.rc

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000

    # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.
    write /sys/fs/selinux/checkreqprot 0

    # Set the security context for the init process.
    # This should occur before anything else (e.g. ueventd) is started.
    setcon u:r:init:s0

    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

    start ueventd

在运行环境中查看命令“/sbin/ueventd”,其实它是”/init”的软链接:

shell@wwt:/ # ls sbin -l                                                 
-rwxr-x--- root     root    499152 1970-01-01 08:00 adbd
-rwxr-x--- root     root   3325472 1970-01-01 08:00 healthd
lrwxrwxrwx root     root           1970-01-01 08:00 ueventd -> ../init
lrwxrwxrwx root     root           1970-01-01 08:00 watchdogd -> ../init

通过分析Android.mk可知,ueventd.c、watchdog.c与init.c被编译成了同一个可执行文件“/init”,并创建了软链接“/sbin/ueventd”、“/sbin/watchdog”指向“/init”:

system/core/init/Android.mk

LOCAL_SRC_FILES:= \
    builtins.c \
    init.c \
    devices.c \
    property_service.c \
    util.c \
    parser.c \
    keychords.c \
    signal_handler.c \
    init_parser.c \
    ueventd.c \
    ueventd_parser.c \
    watchdogd.c

......

LOCAL_MODULE:= init

......

# Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init
SYMLINKS := \
    $(TARGET_ROOT_OUT)/sbin/ueventd \
    $(TARGET_ROOT_OUT)/sbin/watchdogd

原来在文件init.c的main()函数中有一个巧妙的处理:可以通过判断第一个运行参数来启动不同的进程:

  • 如果执行“./ueventd”,进入第一个条件分支,执行uevent_main()函数;
  • 如果执行“./watchdog”,进入第二个条件分支,执行watchdogd_main()函数;
  • 如果执行”./init”,跳过所有分支条件,继续执行main()函数。

system/core/init/init.c

int main(int argc, char **argv)
{

    ......

    if (!strcmp(basename(argv[0]), "ueventd"))
        return ueventd_main(argc, argv);

    if (!strcmp(basename(argv[0]), "watchdogd"))
        return watchdogd_main(argc, argv);

    ......

}

因此,脚本init.rc中的命令“start ueventd”最终执行的是ueventd_main()函数。

2. ueventd代码分析

2.1 main

ueventd_main()函数就是ueventd进程的主体,实现了以下几个功能:

  • 解析ueventd.rc文件,管理设备节点权限;
  • 递归扫描/sys目录,根据uevent文件,静态创建设备节点;
  • 通过netlink获取内核uevent事件,动态创建设备节点。

system/core/init/ueventd.c

int ueventd_main(int argc, char **argv)
{
    struct pollfd ufd;
    int nr;
    char tmp[32];

    INFO("starting ueventd\n");

    ......

    // 解析ueventd.rc文件
    ueventd_parse_config_file("/ueventd.rc");

    // 解析厂商相关的ueventd.$(TARGET_BOARD_PLATFORM).rc文件
    snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware);
    ueventd_parse_config_file(tmp);

    // 创建netlink sockfd(全局变量device_fd),用于监听uevent事件
    // 执行coldboot,递归扫描/sys目录下uevent文件,创建相应设备节点
    device_init();

    ufd.events = POLLIN;
    // 获取device_init()创建的sockfd
    ufd.fd = get_device_fd();

    while(1) {
        ufd.revents = 0;
        // 通过sockfd监听内核uevent事件
        nr = poll(&ufd, 1, -1);
        if (nr <= 0)
            continue;
        if (ufd.revents & POLLIN)
            // 当接收到内核uevent事件时,创建相应设备节点
            handle_device_fd();
    }
}

2.2 device_init

device_init()函数做了两件事:

  • uevent_open_socket(),创建netlink套接字,并赋值给全局变量device_fd,用于后续的uevent事件监听,uevent_open_socket()函数涉及到netlink机制与socket编程,具体分析请参考:uevent_open_socket()浅析

  • coldboot(),递归扫描/sys目录下的uevent节点,然后写入字符串“add”,强制触发内核uevent事件。

这里我们对coldboot()函数代码进行重点分析,调用关系如下:

main() -> device_init()-> coldboot() -> do_coldboot()

system/core/init/devices.c

void device_init(void)
{

    ......
    // 创建netlink sockfd(全局变量device_fd),用于监听uevent事件
    device_fd = uevent_open_socket(256*1024, true);
    if(device_fd < 0)
        return;

    fcntl(device_fd, F_SETFD, FD_CLOEXEC);
    fcntl(device_fd, F_SETFL, O_NONBLOCK);

    // 递归扫描/sys目录下uevent文件,创建相应设备节点
    if (stat(coldboot_done, &info) < 0) {

        ......

        coldboot("/sys/class");
        coldboot("/sys/block");
        coldboot("/sys/devices");

        ......

    }

    ......

}
static void coldboot(const char *path)
{
    DIR *d = opendir(path);
    if(d) {
        do_coldboot(d);
        closedir(d);
    }
}
static void do_coldboot(DIR *d)
{
    struct dirent *de;
    int dfd, fd;

    // 获得目录文件描述符
    dfd = dirfd(d);

    // 打开目录中的uevent节点,写入“add\n”触发内核uevent事件并处理
    fd = openat(dfd, "uevent", O_WRONLY);
    if(fd >= 0) {
        write(fd, "add\n", 4);
        close(fd);
        handle_device_fd();
    }

    // 递归调用do_coldboot(),扫描uevent节点
    while((de = readdir(d))) {
        DIR *d2;

        if(de->d_type != DT_DIR || de->d_name[0] == '.')
            continue;

        fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
        if(fd < 0)
            continue;

        d2 = fdopendir(fd);
        if(d2 == 0)
            close(fd);
        else {
            do_coldboot(d2);
            closedir(d2);
        }
    }
}

2.3 handle_device_id

在main()函数中通过poll监听到内核uevent事件后,由handler_device_id()函数进行处理:

  • 解析uevent事件;
  • 动态创建设备节点。

这一部分代码的调用关系如下:

main() -> handle_device_id() -> handle_device_event() -> handle_generic_device_event() -> handle_device() -> make_device() -> mknode()

system/core/init/devices.c

void handle_device_fd()
{
    char msg[UEVENT_MSG_LEN+2];
    int n;

    // 通过sockfd调用recvmsg()获取内核uevent事件,以字符串形式存入msg
    while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {

        ......

        struct uevent uevent;
        // 将字符串msg解析成uevent
        parse_event(msg, &uevent);

        ......
        // 处理设备相关uevent事件
        handle_device_event(&uevent);
        // 处理固件相关uevent事件(暂不分析)
        handle_firmware_event(&uevent);
    }
}
static void handle_device_event(struct uevent *uevent)
{
    ......

        handle_generic_device_event(uevent);

    ......

}
static void handle_generic_device_event(struct uevent *uevent)
{
    ......

    // 根据uevent事件中子系统名称,创建/dev目录及其子目录
    } else if(!strncmp(uevent->subsystem, "input", 5)) {
        base = "/dev/input/";
        make_dir(base, 0755);
    } else if(!strncmp(uevent->subsystem, "mtd", 3)) {
        base = "/dev/mtd/";
        make_dir(base, 0755);
    } else if(!strncmp(uevent->subsystem, "sound", 5)) {
        base = "/dev/snd/";
        make_dir(base, 0755);
    } else if(!strncmp(uevent->subsystem, "misc", 4) &&
                 !strncmp(name, "log_", 4)) {
        kernel_logger();
        base = "/dev/log/";
        make_dir(base, 0755);
        name += 4;
    } else
        base = "/dev/";

    links = get_character_device_symlinks(uevent);

    if (!devpath[0])
        snprintf(devpath, sizeof(devpath), "%s%s", base, name);

    // 根据uevent事件中的信息创建/删除节点及链接
    handle_device(uevent->action, devpath, uevent->path, 0,
            uevent->major, uevent->minor, links);
}
static void handle_device(const char *action, const char *devpath,
        const char *path, int block, int major, int minor, char **links)
{
    ......

    // 当uevent事件中的atcion为“add”时,创建节点及链接
    if(!strcmp(action, "add")) {
        make_device(devpath, path, block, major, minor, (const char **)links);
        if (links) {
            for (i = 0; links[i]; i++)
                make_link(devpath, links[i]);
        }
    }

    // 当uevent事件中的atcion为“remove”,删除链接
    if(!strcmp(action, "remove")) {
        if (links) {
            for (i = 0; links[i]; i++)
                remove_link(devpath, links[i]);
        }
        unlink(devpath);
    }

    ......
}
static void make_device(const char *path,
                        const char *upath UNUSED,
                        int block, int major, int minor,
                        const char **links)
{
    ......

    // 合成设备号
    dev = makedev(major, minor);
    ......
    // 根据文件路径、权限、设备号创建节点
    mknod(path, mode, dev);

    ......
}

  1. 由busybox提供的简化版udev,适用于嵌入式应用场合
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值