android 开机启动流程分析(03)init启动中关键进程 uevent & watchdog

    xiaoxiao2022-07-03  112

    该系列文章总纲链接:专题分纲目录 android 开机启动流程分析


    本章关键点总结 & 说明:

    说明:思维导图是基于之前文章不断迭代的,本章内容我们关注➕uevent & watchdog部分即可

    1 Uevent入口

    Uevent是接收uevent的守护进程,这里它的主要作用根据kernel接收到的uevent事件来创建或删除/dev/xxx(xxx设备名),主函数实现如下:

    int ueventd_main(int argc, char **argv) { struct pollfd ufd; int nr; char tmp[32]; umask(000); signal(SIGCHLD, SIG_IGN); open_devnull_stdio();//输入输出重定向 klog_init(); #if LOG_UEVENTS /* Ensure we're at a logging level that will show the events */ if (klog_get_level() < KLOG_INFO_LEVEL) { klog_set_level(KLOG_INFO_LEVEL); } #endif //selinux相同,同init union selinux_callback cb; cb.func_log = log_callback; selinux_set_callback(SELINUX_CB_LOG, cb); INFO("starting ueventd\n"); //----1 解析和处理ueventd的rc文件,start import_kernel_cmdline(0, import_kernel_nv); get_hardware_name(hardware, &revision); ueventd_parse_config_file("/ueventd.rc");//<关键点1,解析配置文件> snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware); ueventd_parse_config_file(tmp); //----1 解析和处理ueventd的rc文件,end //----2 polling uevent消息,对设备进行管理,start device_init();//设备初始化 ufd.events = POLLIN; ufd.fd = get_device_fd();//获取由device_init中uevent_open_socket打开的device_fd while(1) { ufd.revents = 0; nr = poll(&ufd, 1, -1);//poll监听ufd if (nr <= 0) continue; if (ufd.revents & POLLIN) // polling到消息,处理event消息 handle_device_fd(); } //----2 polling uevent消息,对设备进行管理,end }

    1.1  解析和处理uevent的rc文件

    解析流程参考后面的init.rc的解析流程,流程如下:

    /** ueventd_parse_config_file {根据rc文件,生成一个参考数据结构图} ->parse_config -->loop循环 -->int token = next_token(&state); -->根据token执行parse_line --->lookup_keyword,查找关键字 --->分支kw_is(kw, SECTION) ---->parse_new_section(state, kw, nargs, args); ----->根据kw获取不同的parse_line,这里设置为parse_line_subsystem或no_op{空操作} ------>parse_line_subsystem中调用lookup_keyword();->根据kw 获取s->devname_src和s->dirname --->分支kw_is(kw, OPTION) ---->state->parse_line(state, nargs, args); --->分支,其他,即表示根据rc文件创建数据结构,为后期创建节点做数据参考图 ---->parse_line_device(state, nargs, args);->set_device_permission() ----->获取(name, attr, perm, uid, gid, prefix, wildcard)参数 ----->add_dev_perms(name, attr, perm, uid, gid, prefix, wildcard); */

    注意:ueventd_parse_config_file并不创建设备节点,它的作用是提供数据库,当有设备节点生成的时候,eventd会参考这个数据库设置设备节点的权限。处理和解析ueventd.rc这部分相比init.rc简单,主要是通过解析rc文件,实现控制目录节点的权限,如:

    /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

    1.2 polling uevent消息,对设备进行管理

    这里主要针对device_init(),get_device_fd()与handle_device_fd()进行分析。

    1.2.1 device_init()实现如下:

    /** 主要是创建了uevent的socket handle,同时触发/sys/clas,/sys/block,/sys/devices 这三个目录及其子目录下的uevent,然后接受并创建设备节点 */ void device_init(void) { suseconds_t t0, t1; struct stat info; int fd; ...//SELinux相关 /* is 256K enough? udev uses 16MB! */ //打开uevent的socket。这里的uevent是用到netlink中的内核事件向用户态通知(NETLINK_KOBJECT_UEVENT)功能 //是内核和用户态进行双向数据传输的非常好的方式,除了eventd外,netd和vold也是使用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); 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\n", ((long) (t1 - t0))); } else { log_event_print("skipping coldboot, already done\n"); } }

    这里关键分析coldboot进而分析,代码实现如下:

    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); fd = openat(dfd, "uevent", O_WRONLY); if(fd >= 0) { write(fd, "add\n", 4);//激活内核且重发add事件的uevent close(fd); handle_device_fd();//针对event消息做响应的处理 } 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); } } }

    继续,关键分析handle_device_fd()的处理,实现如下:

    void handle_device_fd() { char msg[UEVENT_MSG_LEN+2]; int n; //接收kernel通过netlink的socket方式发送的msg事件 while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) { if(n >= UEVENT_MSG_LEN) /* overflow -- discard */ continue; msg[n] = '\0'; msg[n+1] = '\0'; struct uevent uevent; parse_event(msg, &uevent);//将msg解析成uevent ...//SELinux相关 handle_device_event(&uevent); handle_firmware_event(&uevent); } }

    1.2.2 关注➕handle_device_event 和handle_firmware_event的分析:

    handle_device_event的处理流程如下所示(部分给出伪代码,主要分析流程):

    static void handle_device_event(struct uevent *uevent) { if (!strcmp(uevent->action,"add") || !strcmp(uevent->action, "change") || !strcmp(uevent->action, "online")) fixup_sys_perms(uevent->path); //通过之前的数据库查表,重新修正节点权限 if (!strncmp(uevent->subsystem, "block", 5)) { /** handle_block_device_event(struct uevent *uevent)的处理流程如下: {根据数据库创建对应的节点} ->parse_device_name(),根据uevent解析出name ->make_dir(),根据解析出的name创建文件夹 ->对links类型文件进行处理 ->handle_device() 处理 -->make_device() --->get_device_perm()获取设备的perm链表节点 --->mknod创建设备节点+setegid变更节点权限+chown设置节点权限 */ handle_block_device_event(uevent); } else if (!strncmp(uevent->subsystem, "platform", 8)) { /** handle_platform_device_event(struct uevent *uevent) ->分支add_platform_device(path); -->platform节点添加操作 ->分支remove_platform_device(path); -->platform节点删除操作 */ handle_platform_device_event(uevent); } else { /** handle_generic_device_event(struct uevent *uevent) {根据subsystem创建各种类型文件夹} ->分支 subsystem 不为空 -->parse_device_name(),根据uevent解析出name -->ueventd_subsystem_find_by_name -->mkdir_recursive_for_devpath{call mkdir_recursive->call make_dir} ->分支 uevent->subsystem为usb -->mkdir_recursive_for_devpath{call mkdir_recursive->call make_dir} -->make_dir ->分支 uevent->subsystem为drm,input,adsp等等 -->make_dir ->get_character_device_symlinks()做链接处理 ->handle_device() 处理 -->make_device() --->get_device_perm()获取设备的perm链表节点 --->mknod创建设备节点+setegid变更节点权限+chown设置节点权限 */ handle_generic_device_event(uevent); } }

    如果有协处理器, 还要下载协处理器的firmware,这里是处理协处理器要下载firmware的指令,fork一个子进程处理,代码实现如下:

    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(struct uevent *uevent) ->确保isbooting ->通过uevent->firmware和uevent->path获取文件路径path ->open path,firmware,loading,data ->load_firmware(从firmware中读取数据到data中,且向loading_fd中写入状态) ->close path,firmware,loading,data */ process_firmware_event(uevent); exit(EXIT_SUCCESS); } }

    1.3 总结{uevent功能}

    @1 uevent本质上是解析对应的uevent.rc以及硬件相关rc,生成一张数据库表 @2 监听来自kernel的socket信息,将其转换成uevent,调用事件处理方法来处理 @3 处理的事件

    处理的事件如下图所示:

    2  watchdog分支分析

    2.1 watchdog逻辑说明:

    @1 init进程中会解析/init.rc,init.rc文件中有启用watchdog的服务,service watchdogd /sbin/watchdogd 10 20进行喂狗,即当系统出现崩溃或死机达到30s时会进行重启;watchdogd <argv1,argv2> 参数1为间隔的喂狗时间,argv1+argv2为超时时间,当argv2设置为0则到达argv1时间后系统会进行重启

    2.2 watchdog进程的主函数实现如下:

    #define DEV_NAME "/dev/watchdog" //设备节点,实际上上层仅仅是通过设备节点对硬件进行控制 int watchdogd_main(int argc, char **argv) { int fd; int ret; int interval = 10; int margin = 10; int timeout; open_devnull_stdio(); klog_init(); INFO("Starting watchdogd\n"); if (argc >= 2) interval = atoi(argv[1]);//喂狗时间,即每间隔internal喂狗一次 if (argc >= 3) margin = atoi(argv[2]);//internal+margin,过了超时时间还没有喂狗,则重新启动系统 timeout = interval + margin; fd = open(DEV_NAME, O_RDWR);//打开看门狗设备 if (fd < 0) { ERROR("watchdogd: Failed to open %s: %s\n", DEV_NAME, strerror(errno)); return 1; } ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout);//通过ioctl设置超时时间 if (ret) {//如果超时设置失败,则获取超时 ERROR("watchdogd: Failed to set timeout to %d: %s\n", timeout, strerror(errno)); ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout); if (ret) {//设置和获取都失败就不管了,直接报错 ERROR("watchdogd: Failed to get timeout: %s\n", strerror(errno)); } else {//如果第一次设置失败但获取成功,根据timeout和margin重新设置interval,保证看门狗有效 if (timeout > margin) interval = timeout - margin; else interval = 1; ERROR("watchdogd: Adjusted interval to timeout returned by driver: timeout %d, interval %d, margin %d\n", timeout, interval, margin); } } while(1) {//无限循环,不断喂狗,除非整个系统挂掉,否则会每隔interval,喂狗一次 write(fd, "", 1); sleep(interval); } }

    至此,除了init,另外两个和init相关的进程 uevent和watchdog也分析完毕;这里说明下,实际上通过system/core/init/ 目录下的android.mk文件可以知道,三者就是同一个程序,仅仅是名字不同而已;而通过名字的不同而在开始时执行不同的函数调用。

     

     

    最新回复(0)