Android ueventd浅析

来源:互联网 发布:c语言标识符由什么组成 编辑:程序博客网 时间:2024/05/22 13:01

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 healthdlrwxrwxrwx root     root           1970-01-01 08:00 ueventd -> ../initlrwxrwxrwx 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 /initSYMLINKS := \    $(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,适用于嵌入式应用场合 ↩
原创粉丝点击