Android ueventd浅析
在linux2.6之后,udev取代了devfs,但是在android中却没有udev或者mdev[1],而是由ueventd进程实现了类似功能(管理设备节点权限、创建设备节点)。ueventd通过两种方式创建设备节点:静态,ueventd启动时,根据在sysfs中预定义的uevent信息创建设备节点;动态,系统运行过程中,当接收到内核uevent事件时(如插入u盘),动态创建设备节点。1. ue
0.备模型中任何设备有事件需要上报时,会触发 uevent提供的接口,uevent模块准备好上报事件的格式后,可以通过两个途径上报到用户空间:
- 通过 kmod 模块,直接调动用户空间的可执行文件
- 通过 netlink 通信机制,将事件从内核空间传递给用户空间
PS: 目前多采用 netlink 通信机制,ueventd 也是采用 netlink机制创建 socket 与 kernel 进行通信。

在linux2.6之后,udev取代了devfs,但是在android中却没有udev或者mdev[1],而是由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_keysstart 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()创建的sockfdufd.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事件,以字符串形式存入msgwhile ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {......struct uevent uevent;// 将字符串msg解析成ueventparse_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;} elsebase = "/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);......}
更多推荐



所有评论(0)