ueventd主要是负责设备节点的创建、权限设定等一些列工作。服务通过使用uevent,监控驱动发送的消息,做进一步处理。
ueventd实际和init是同一个binary,只是走了不同分支,可参看前一部分。
ueventd的整体代码比较简单,主要是三部分:
解析ueventd.rc 初始化设备信息 循环polling uevent消息
主函数及相关功能如下如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | int ueventd_main( int argc, char **argv) { // 和init一样,没有std输入输出 open_devnull_stdio(); // 初始化kernel log,让ueventd的log,通过kernel printk的log输出到串口中 klog_init(); //解析和处理ueventd的rc文件 import_kernel_cmdline( 0 , import_kernel_nv); get_hardware_name(hardware, &revision); ueventd_parse_config_file(/ueventd.rc); snprintf(tmp, sizeof(tmp), /ueventd.%s.rc, hardware); ueventd_parse_config_file(tmp); // 设备初始化 device_init(); // polling uevent消息,对设备进行管理 ufd.events = POLLIN; ufd.fd = get_device_fd(); while ( 1 ) { ufd.revents = 0 ; nr = poll(&ufd, 1 , - 1 ); if (nr <= 0 ) continue ; if (ufd.revents == POLLIN) handle_device_fd(); // polling到消息,处理event消息 } } |
处理和解析ueventd.rc
这部分相比init.rc来说,巨简单,没什么特别的。主要是通过rc文件,来控制目录节点的权限。如:
?
1 2 3 4 5 6 7 | /dev/ttyUSB2 0666 radio radio /dev/ts0710mux* 0640 radio radio /dev/ppp 0666 radio vpn # sysfs properties /sys/devices/virtual/input/input* enable 0666 system system /sys/devices/virtual/input/input* poll_delay 0666 system system |
详情应该不需要展开,基本都能了解。
设备初始化
kernel在加载设备时,会通过socket发送uevent事件给userspace, 在init里,通过接受这些uevent事件,来创建设备的节点。主要函数是device_init()
初始化函数为device_init,如下
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | void device_init( void ) { suseconds_t t0, t1; struct stat info; int fd; sehandle = NULL; if (is_selinux_enabled() > 0 ) { sehandle = selinux_android_file_context_handle(); } /* is 256K enough? udev uses 16MB! */ 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); if (stat(coldboot_done, &info) < 0 ) { t0 = get_usecs(); coldboot(/sys/ class ); coldboot(/sys/block); coldboot(/sys/devices); t1 = get_usecs(); fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000 ); close(fd); log_event_print(coldboot %ld uS , (( long ) (t1 - t0))); } else { log_event_print(skipping coldboot, already done ); } } |
open_uevent_socket();
这是打开uevent的socket。这里的uevent是用到netlink中的内核事件向用户态通知(NETLINK_KOBJECT_UEVENT)功能,是内核和用户态进行双向数据传输的非常好的方式,除了eventd外,netd和vold也是使用uevent的。
初始化函数中,比较要注意的coldboot这个函数,字面意思是冷启动,它的作用是,对那些在uventd启动前,已经add上的驱动,再重新操作一下,让他们再发一次event消息,上层号针对性处理。
这里对 /sys/class,/sys/block和/sys/devices下的设备遍历一遍:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | static void do_coldboot(DIR *d) { struct dirent *de; int dfd, fd; dfd = dirfd(d); fd = openat(dfd, uevent, O_WRONLY); if (fd >= 0 ) { write(fd, add , 4 ); close(fd); handle_device_fd(); } 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); } } } |
write(fd, add , 4)激活内核,重发add事件的uevent,handle_device_fd();针对event消息,做响应的处理。
uevent消息处理
初始化好了之后,daemon程序只要polling新的event事件即可,polling到了,就调用handle_device_fd();来处理,可以看看这个函数:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void handle_device_fd() { char msg[UEVENT_MSG_LEN+ 2 ]; int n; while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0 ) { if (n >= UEVENT_MSG_LEN) /* overflow -- discard */ continue ; msg[n] = '' ; msg[n+ 1 ] = '' ; struct uevent uevent; parse_event(msg, &uevent); handle_device_event(&uevent); handle_firmware_event(&uevent); } } |
功能就是接受内核发的event消息,然后parser此消息,处理对应的消息事件。
这里:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 | static void handle_device_event(struct uevent *uevent) { if (!strcmp(uevent->action,add) || !strcmp(uevent->action, change)) fixup_sys_perms(uevent->path); if (!strncmp(uevent->subsystem, block, 5 )) { handle_block_device_event(uevent); } else if (!strncmp(uevent->subsystem, platform, 8 )) { handle_platform_device_event(uevent); } else { handle_generic_device_event(uevent); } } |
主要功能,就是根据发过来的uevent,创建/删除设备节点,同时以ueventd.rc中描述的权限设置更新一些节点权限。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | static void handle_firmware_event(struct uevent *uevent) { pid_t pid; int ret; if (strcmp(uevent->subsystem, firmware)) return ; if (strcmp(uevent->action, add)) return ; /* we fork, to avoid making large memory allocations in init proper */ pid = fork(); if (!pid) { process_firmware_event(uevent); exit(EXIT_SUCCESS); } } |
如果有协处理器, 还要下载协处理器的firmware,这里是处理协处理器要下载firmware的指令,fork一个子进程处理。