🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
init进程的入口函数是main,它的代码如下所示: **init.c** ~~~ int main(int argc, char **argv) { intdevice_fd = -1; intproperty_set_fd = -1; intsignal_recv_fd = -1; intkeychord_fd = -1; int fd_count; ints[2]; intfd; structsigaction act; chartmp[PROP_VALUE_MAX]; structpollfd ufds[4]; char*tmpdev; char*debuggable; //设置子进程退出的信号处理函数,该函数为sigchld_handler。 act.sa_handler = sigchld_handler; act.sa_flags= SA_NOCLDSTOP; act.sa_mask = 0; act.sa_restorer = NULL; sigaction(SIGCHLD, &act, 0); ......//创建一些文件夹,并挂载设备,这些是和Linux相关的,不拟做过多讨论。 mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0,NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); //重定向标准输入/输出/错误输出到/dev/_null_。 open_devnull_stdio(); /* 设置init的日志输出设备为/dev/__kmsg__,不过该文件打开后,会立即被unlink了, 这样,其他进程就无法打开这个文件读取日志信息了。 */ log_init(); //上面涉及很多和Linux系统相关的知识,不熟悉的读者可自行研究,它们不影响我们的分析 //解析init.rc配置文件 parse_config_file("/init.rc"); ...... //下面这个函数通过读取/proc/cpuinfo得到机器的Hardware名,我的HTCG7手机为bravo。 get_hardware_name(); snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware); //解析这个和机器相关的配置文件,我的G7手机对应文件为init.bravo.rc。 parse_config_file(tmp); /* 解析完上述两个配置文件后,会得到一系列的Action(动作),下面两句代码将执行那些处于 early-init阶段的Action。init将动作执行的时间划分为四个阶段:early-init、init、 early-boot、boot。由于有些动作必须在其他动作完成后才能执行,所以就有了先后之分。哪些 动作属于哪个阶段由配置文件决定。后面会介绍配置文件的相关知识。 */ action_for_each_trigger("early-init", action_add_queue_tail); drain_action_queue(); /* 创建利用Uevent和Linux内核交互的socket。关于Uevent的知识,第9章中对 Vold进行分析时会做介绍。 */ device_fd = device_init(); //初始化和属性相关的资源 property_init(); //初始化/dev/keychord设备,这和调试有关,本书不讨论它的用法。读者可以自行研究, //内容比较简单。 keychord_fd = open_keychord(); ...... /* INIT_IMAGE_FILE定义为”/initlogo.rle”,下面这个函数将加载这个文件作为系统的开机 画面,注意,它不是开机动画控制程序bootanimation加载的开机动画文件。 */ if(load_565rle_image(INIT_IMAGE_FILE) ) { /* 如果加载initlogo.rle文件失败(可能是没有这个文件),则会打开/dev/ty0设备,并 输出”ANDROID”的字样作为开机画面。在模拟器上看到的开机画面就是它。 */ ...... } } if(qemu[0]) import_kernel_cmdline(1); ...... //调用property_set函数设置属性项,一个属性项包括属性名和属性值。 property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown"); ......//执行位于init阶段的动作 action_for_each_trigger("init", action_add_queue_tail); drain_action_queue(); //启动属性服务 property_set_fd = start_property_service(); /* 调用socketpair函数创建两个已经connect好的socket。socketpair是Linux的系统调用, 不熟悉的读者可以利用man socketpair查询相关信息。后面就会知道它们的用处了。 */ if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) { signal_fd = s[0]; signal_recv_fd = s[1]; ...... } ...... //执行配置文件中early-boot和boot阶段的动作。 action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail); drain_action_queue(); ...... //init关注来自四个方面的事情。 ufds[0].fd= device_fd;//device_fd用于监听来自内核的Uevent事件 ufds[0].events = POLLIN; ufds[1].fd = property_set_fd;//property_set_fd用于监听来自属性服务器的事件 ufds[1].events= POLLIN; //signal_recv_fd由socketpair创建,它的事件来自另外一个socket。 ufds[2].fd = signal_recv_fd; ufds[2].events = POLLIN; fd_count = 3; if(keychord_fd > 0) { //如果keychord设备初始化成功,则init也会关注来自这个设备的事件。 ufds[3].fd = keychord_fd; ufds[3].events = POLLIN; fd_count++; } ...... #if BOOTCHART ......//与Boot char相关,不做讨论了。 /* Boot chart是一个小工具,它能对系统的性能进行分析,并生成系统启动过程的图表, 以提供一些有价值的信息,而这些信息最大的用处就是帮助提升系统的启动速度。 */ #endif for(;;) { //从此init将进入一个无限循环。 int nr, i, timeout = -1; for (i = 0; i < fd_count; i++) ufds[i].revents = 0; //在循环中执行动作 drain_action_queue(); restart_processes(); //重启那些已经死去的进程 ...... #if BOOTCHART ...... // Boot Chart相关 #endif //调用poll等待一些事情的发生 nr= poll(ufds, fd_count, timeout); ...... //ufds[2]保存的是signal_recv_fd,用于接收来自socket的消息。 if(ufds[2].revents == POLLIN) { //有一个子进程去世,init要处理这个事情 read(signal_recv_fd, tmp, sizeof(tmp)); while (!wait_for_one_process(0)) ; continue; } if(ufds[0].revents == POLLIN) handle_device_fd(device_fd);//处理Uevent事件 if(ufds[1].revents == POLLIN) handle_property_set_fd(property_set_fd);//处理属性服务的事件。 if(ufds[3].revents == POLLIN) handle_keychord(keychord_fd);//处理keychord事件。 } return0; } ~~~ 从上面的代码中可知,init的工作任务还是很重的。上面的代码虽已省略了不少行,可结果还是很长,不过从本章要分析的两个知识点来看,可将init的工作流程精简为以下四点: - 解析两个配置文件,其中,将分析对init.rc文件的解析。 - 执行各个阶段的动作,创建Zygote的工作就是在其中的某个阶段完成的。 - 调用property_init初始化属性相关的资源,并且通过property_start_service启动属性服务。 - init进入一个无限循环,并且等待一些事情的发生。重点关注init如何处理来自socket和来自属性服务器相关的事情。 >[info] **提示**:精简工作流程,是以后分析代码时常用的方法。读者在分析代码的过程中,也可使用这种方法。