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

 
  1. on early-init
  2. # Set init and its forked children's oom_adj.
  3. write /proc/1/oom_score_adj -1000
  4.  
  5. # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.
  6. write /sys/fs/selinux/checkreqprot 0
  7.  
  8. # Set the security context for the init process.
  9. # This should occur before anything else (e.g. ueventd) is started.
  10. setcon u:r:init:s0
  11.  
  12. # Set the security context of /adb_keys if present.
  13. restorecon /adb_keys
  14.  
  15. start ueventd

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

 
  1. shell@wwt:/ # ls sbin -l
  2. -rwxr-x--- root root 499152 1970-01-01 08:00 adbd
  3. -rwxr-x--- root root 3325472 1970-01-01 08:00 healthd
  4. lrwxrwxrwx root root 1970-01-01 08:00 ueventd -> ../init
  5. 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

 
  1. LOCAL_SRC_FILES:= \
  2. builtins.c \
  3. init.c \
  4. devices.c \
  5. property_service.c \
  6. util.c \
  7. parser.c \
  8. keychords.c \
  9. signal_handler.c \
  10. init_parser.c \
  11. ueventd.c \
  12. ueventd_parser.c \
  13. watchdogd.c
  14.  
  15. ......
  16.  
  17. LOCAL_MODULE:= init
  18.  
  19. ......
  20.  
  21. # Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init
  22. SYMLINKS := \
  23. $(TARGET_ROOT_OUT)/sbin/ueventd \
  24. $(TARGET_ROOT_OUT)/sbin/watchdogd

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

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

system/core/init/init.c

 
  1. int main(int argc, char **argv)
  2. {
  3.  
  4. ......
  5.  
  6. if (!strcmp(basename(argv[0]), "ueventd"))
  7. return ueventd_main(argc, argv);
  8.  
  9. if (!strcmp(basename(argv[0]), "watchdogd"))
  10. return watchdogd_main(argc, argv);
  11.  
  12. ......
  13.  
  14. }

因此,脚本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

 
  1. int ueventd_main(int argc, char **argv)
  2. {
  3. struct pollfd ufd;
  4. int nr;
  5. char tmp[32];
  6.  
  7. INFO("starting ueventd\n");
  8.  
  9. ......
  10.  
  11. // 解析ueventd.rc文件
  12. ueventd_parse_config_file("/ueventd.rc");
  13.  
  14. // 解析厂商相关的ueventd.$(TARGET_BOARD_PLATFORM).rc文件
  15. snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware);
  16. ueventd_parse_config_file(tmp);
  17.  
  18. // 创建netlink sockfd(全局变量device_fd),用于监听uevent事件
  19. // 执行coldboot,递归扫描/sys目录下uevent文件,创建相应设备节点
  20. device_init();
  21.  
  22. ufd.events = POLLIN;
  23. // 获取device_init()创建的sockfd
  24. ufd.fd = get_device_fd();
  25.  
  26. while(1) {
  27. ufd.revents = 0;
  28. // 通过sockfd监听内核uevent事件
  29. nr = poll(&ufd, 1, -1);
  30. if (nr <= 0)
  31. continue;
  32. if (ufd.revents & POLLIN)
  33. // 当接收到内核uevent事件时,创建相应设备节点
  34. handle_device_fd();
  35. }
  36. }

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

 
  1. void device_init(void)
  2. {
  3.  
  4. ......
  5. // 创建netlink sockfd(全局变量device_fd),用于监听uevent事件
  6. device_fd = uevent_open_socket(256*1024, true);
  7. if(device_fd < 0)
  8. return;
  9.  
  10. fcntl(device_fd, F_SETFD, FD_CLOEXEC);
  11. fcntl(device_fd, F_SETFL, O_NONBLOCK);
  12.  
  13. // 递归扫描/sys目录下uevent文件,创建相应设备节点
  14. if (stat(coldboot_done, &info) < 0) {
  15.  
  16. ......
  17.  
  18. coldboot("/sys/class");
  19. coldboot("/sys/block");
  20. coldboot("/sys/devices");
  21.  
  22. ......
  23.  
  24. }
  25.  
  26. ......
  27.  
  28. }
 
  1. static void coldboot(const char *path)
  2. {
  3. DIR *d = opendir(path);
  4. if(d) {
  5. do_coldboot(d);
  6. closedir(d);
  7. }
  8. }
 
  1. static void do_coldboot(DIR *d)
  2. {
  3. struct dirent *de;
  4. int dfd, fd;
  5.  
  6. // 获得目录文件描述符
  7. dfd = dirfd(d);
  8.  
  9. // 打开目录中的uevent节点,写入“add\n”触发内核uevent事件并处理
  10. fd = openat(dfd, "uevent", O_WRONLY);
  11. if(fd >= 0) {
  12. write(fd, "add\n", 4);
  13. close(fd);
  14. handle_device_fd();
  15. }
  16.  
  17. // 递归调用do_coldboot(),扫描uevent节点
  18. while((de = readdir(d))) {
  19. DIR *d2;
  20.  
  21. if(de->d_type != DT_DIR || de->d_name[0] == '.')
  22. continue;
  23.  
  24. fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
  25. if(fd < 0)
  26. continue;
  27.  
  28. d2 = fdopendir(fd);
  29. if(d2 == 0)
  30. close(fd);
  31. else {
  32. do_coldboot(d2);
  33. closedir(d2);
  34. }
  35. }
  36. }

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

 
  1. void handle_device_fd()
  2. {
  3. char msg[UEVENT_MSG_LEN+2];
  4. int n;
  5.  
  6. // 通过sockfd调用recvmsg()获取内核uevent事件,以字符串形式存入msg
  7. while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {
  8.  
  9. ......
  10.  
  11. struct uevent uevent;
  12. // 将字符串msg解析成uevent
  13. parse_event(msg, &uevent);
  14.  
  15. ......
  16. // 处理设备相关uevent事件
  17. handle_device_event(&uevent);
  18. // 处理固件相关uevent事件(暂不分析)
  19. handle_firmware_event(&uevent);
  20. }
  21. }
 
  1. static void handle_device_event(struct uevent *uevent)
  2. {
  3. ......
  4.  
  5. handle_generic_device_event(uevent);
  6.  
  7. ......
  8.  
  9. }
 
  1. static void handle_generic_device_event(struct uevent *uevent)
  2. {
  3. ......
  4.  
  5. // 根据uevent事件中子系统名称,创建/dev目录及其子目录
  6. } else if(!strncmp(uevent->subsystem, "input", 5)) {
  7. base = "/dev/input/";
  8. make_dir(base, 0755);
  9. } else if(!strncmp(uevent->subsystem, "mtd", 3)) {
  10. base = "/dev/mtd/";
  11. make_dir(base, 0755);
  12. } else if(!strncmp(uevent->subsystem, "sound", 5)) {
  13. base = "/dev/snd/";
  14. make_dir(base, 0755);
  15. } else if(!strncmp(uevent->subsystem, "misc", 4) &&
  16. !strncmp(name, "log_", 4)) {
  17. kernel_logger();
  18. base = "/dev/log/";
  19. make_dir(base, 0755);
  20. name += 4;
  21. } else
  22. base = "/dev/";
  23.  
  24. links = get_character_device_symlinks(uevent);
  25.  
  26. if (!devpath[0])
  27. snprintf(devpath, sizeof(devpath), "%s%s", base, name);
  28.  
  29. // 根据uevent事件中的信息创建/删除节点及链接
  30. handle_device(uevent->action, devpath, uevent->path, 0,
  31. uevent->major, uevent->minor, links);
  32. }
 
  1. static void handle_device(const char *action, const char *devpath,
  2. const char *path, int block, int major, int minor, char **links)
  3. {
  4. ......
  5.  
  6. // 当uevent事件中的atcion为“add”时,创建节点及链接
  7. if(!strcmp(action, "add")) {
  8. make_device(devpath, path, block, major, minor, (const char **)links);
  9. if (links) {
  10. for (i = 0; links[i]; i++)
  11. make_link(devpath, links[i]);
  12. }
  13. }
  14.  
  15. // 当uevent事件中的atcion为“remove”,删除链接
  16. if(!strcmp(action, "remove")) {
  17. if (links) {
  18. for (i = 0; links[i]; i++)
  19. remove_link(devpath, links[i]);
  20. }
  21. unlink(devpath);
  22. }
  23.  
  24. ......
  25. }
 
  1. static void make_device(const char *path,
  2. const char *upath UNUSED,
  3. int block, int major, int minor,
  4. const char **links)
  5. {
  6. ......
  7.  
  8. // 合成设备号
  9. dev = makedev(major, minor);
  10. ......
  11. // 根据文件路径、权限、设备号创建节点
  12. mknod(path, mode, dev);
  13.  
  14. ......
  15. }
Logo

更多推荐