Android -- Android Init进程的处理流程分析.doc_第1页
Android -- Android Init进程的处理流程分析.doc_第2页
Android -- Android Init进程的处理流程分析.doc_第3页
Android -- Android Init进程的处理流程分析.doc_第4页
Android -- Android Init进程的处理流程分析.doc_第5页
已阅读5页,还剩34页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Android - Android Init进程的处理流程分析最近在看Android Init进程的处理流程,现记录如下。在Android中,Init进程是Linux内核启动后创建的第一个用户进程,地位非常重要。Init进程的可执行文件在/system/core/init/目录下,我们直接看Init进程的main()函数,该函数的代码处理流程较长,我们分两大段来分析。首先看第一大段:cpp view plain copy 在CODE上查看代码片派生到我的代码片int main(int argc, char* argv) /检测启动程序的文件名,如果是ueventd或者watchdogd,则执行相应守护进程的主函数,然后退出 if (!strcmp(basename(argv0), ueventd) return ueventd_main(argc, argv); if (!strcmp(basename(argv0), watchdogd) return watchdogd_main(argc, argv); / Clear the umask. umask(0);/umask设置用户创建文件的默认属性;默认情况下文件的属性是022,这里参数为0,意为该进程创建的文件的属性值将为0777 add_environment(PATH, _PATH_DEFPATH); bool is_first_stage = (argc = 1) | (strcmp(argv1, -second-stage) != 0); / Get the basic filesystem setup we need put together in the initramdisk / on / and then well let the rc file figure out the rest. if (is_first_stage) /创建一些基本的目录,并将一些文件系统mount到对应的目录上. /tmpfs、devpts、proc和sysfs都是文件系统 mount(tmpfs, /dev, tmpfs, MS_NOSUID, mode=0755); mkdir(/dev/pts, 0755); mkdir(/dev/socket, 0755); mount(devpts, /dev/pts, devpts, 0, NULL); mount(proc, /proc, proc, 0, NULL); mount(sysfs, /sys, sysfs, 0, NULL); / We must have some place other than / to create the device nodes for / kmsg and null, otherwise we wont be able to remount / read-only / later on. Now that tmpfs is mounted on /dev, we can actually talk / to the outside world. open_devnull_stdio();/把标准输入、标准输出、标准错误重定向到空设备文件/dev/_null_ klog_init();/创建/dev/_kmsg_设备节点,让进程可以使用kernel的log系统来输出log klog_set_level(KLOG_NOTICE_LEVEL);/设置log等级 NOTICE(init%s started!n, is_first_stage ? : second stage); if (!is_first_stage) / Indicate that booting is in progress to background fw loaders, etc. /在/dev目录下创建.booting空文件,表示初始化正在进行;is_booting()函数会依靠这个文件判断进程是否正在初始化 /进程初始化结束后,.booting文件将会被删除 close(open(/dev/.booting, O_WRONLY | O_CREAT | O_CLOEXEC, 0000); property_init();/初始化Android属性系统,Android中的属性系统在各个进程间都可以访问,这里创建了一块共享区域来存储属性值 / If arguments are passed both on the command line and in DT, / properties set in DT always have priority over the command-line ones. process_kernel_dt(); process_kernel_cmdline();/解析/proc/cmdline文件,获得kernel的启动参数,将结果保存到几个属性中 / Propogate the kernel variables to internal variables / used by init as well as the current required properties. export_kernel_boot_props();/将一些系统属性发布到系统中 / Set up SELinux, including loading the SELinux policy if were in the kernel domain. selinux_initialize(is_first_stage);/初始化SELinux / If were in the kernel domain, re-exec init to transition to the init domain now / that the SELinux policy has been loaded. if (is_first_stage) if (restorecon(/init) = -1) ERROR(restorecon failed: %sn, strerror(errno); security_failure(); char* path = argv0; char* args = path, const_cast(-second-stage), nullptr ; if (execv(path, args) = -1) ERROR(execv(%s) failed: %sn, path, strerror(errno); security_failure(); / These directories were necessarily created before initial policy load / and therefore need their security context restored to the proper value. / This must happen before /dev is populated by ueventd. /SELinux初始化的一部分工作 INFO(Running restorecon.n); restorecon(/dev); restorecon(/dev/socket); restorecon(/dev/_properties_); restorecon_recursive(/sys); . return 0; 函数一开始,会首先判断当前启动程序名是否是ueventd或者watchdogd,如果是,则会走对应的初始化流程后,并终止程序。首先,主程序会创建一些需要的目录,并挂载几个文件系统到系统中;其次,会重定向标准输入、标准输出、标准错误流到/dev/_null_设备文件下,并初始化内核Log系统,使我们此时可以输出log(因为此时Android的Log系统还未初始化);接着,处理kernel启动参数,设置系统默认属性,并对SELinux的内容进行一些初始化操作等。我们这里只看一些重要的跟init.rc文件相关的处理内容,其他的部分可以参考代码中的注释加以理解。接下来分析第二段重要代码,它包含了init.rc文件解析和init进程如何变成守护进程的操作:cpp view plain copy 在CODE上查看代码片派生到我的代码片/epoll轮询与select机制类似,但它更高效;我们可以向epoll_fd中添加我们想要监听的一组fd,当有某个fd有事件产生时,它就会根据我们事先注册的结果 /根据获取到的epoll_event事件信息,调用epoll_event.data.ptr这个函数指针来处理监听到的事件 epoll_fd = epoll_create1(EPOLL_CLOEXEC);/创建epoll 句柄,并设置FD_CLOEXEC;后续会注册属性监听事件、组合键盘事件、信号处理事件的fd到该epoll_fd中 if (epoll_fd = -1) ERROR(epoll_create1 failed: %sn, strerror(errno); exit(1); signal_handler_init();/初始化signal信号事件处理,会signal_read_fd注册到epoll_fd中,通过epoll轮询检测事件,handle_signal()实际处理监听到的信号事件 property_load_boot_defaults();/解析p文件,把文件中的属性值解析并发布到系统中 start_property_service();/启动属性服务,会创建一个socket 句柄,并将该fd注册到epoll_fd中;通过epoll轮询查询属性请求,并注册handle_property_set_fd()为实际事件处理函数; init_parse_config_file(/init.rc);/解析init.rc文件 /将指定的action加入到action_queue(一个单向链表结构)中,每个action由一个函数指针和表示名字的字符串组成 action_for_each_trigger(early-init, action_add_queue_tail); /调用queue_builtin_action()函数动态生成一个action加入到action_queue中,每个action由一个函数指针和表示名字的字符串组成 / Queue an action that waits for coldboot done so we know ueventd has set up all of /dev. queue_builtin_action(wait_for_coldboot_done_action, wait_for_coldboot_done); / . so that we can start queuing up actions that require stuff from /dev. queue_builtin_action(mix_hwrng_into_linux_rng_action, mix_hwrng_into_linux_rng); queue_builtin_action(keychord_init_action, keychord_init);/注册组合键盘消息监听处理机制,会将/dev/keychord目录的一个fd注册到epoll_fd中,通过epoll轮询事件消息,注册handle_keychord()实际处理组合键盘事件 queue_builtin_action(console_init_action, console_init); / Trigger all the boot actions to get us started. action_for_each_trigger(init, action_add_queue_tail);/将指定的action加入到action_queue中 / Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random / wasnt ready immediately after wait_for_coldboot_done queue_builtin_action(mix_hwrng_into_linux_rng_action, mix_hwrng_into_linux_rng); / Dont mount filesystems or start core system services in charger mode. char bootmodePROP_VALUE_MAX; if (property_get(ro.bootmode, bootmode) 0 & strcmp(bootmode, charger) = 0) action_for_each_trigger(charger, action_add_queue_tail); else action_for_each_trigger(late-init, action_add_queue_tail); / Run all property triggers based on current state of the properties. /调用queue_builtin_action()函数动态生成一个action加入到action_queue中,每个action由一个函数指针和表示名字的字符串组成 queue_builtin_action(queue_property_triggers_action, queue_property_triggers); while (true) if (!waiting_for_exec) execute_one_command();/执行命令列表中的命令 restart_processes();/启动服务列表中的进程 int timeout = -1; if (process_needs_restart) timeout = (process_needs_restart - gettime() * 1000; if (timeout 0) timeout = 0; if (!action_queue_empty() | cur_action) timeout = 0; bootchart_sample(&timeout);/bootchart是一个用可视化方式对启动过程进行性能分析的工具;需要定时唤醒进程 epoll_event ev; int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout);/开始轮询,epoll_wait()等待事件产生 if (nr = -1) ERROR(epoll_wait failed: %sn, strerror(errno); else if (nr = 1) (void (*)() ev.data.ptr)();/调用epoll_event事件存储的函数指针处理事件 epoll机制跟select机制是类似的,两者都可以处理一组fd,监听它们的读写情况,当关注的fd有事件产生时,我们可以进行处理;只不过epoll机制比select机制更高效,所以这里采用了epoll机制进行轮询,而非select。cpp view plain copy 在CODE上查看代码片派生到我的代码片/epoll轮询与select机制类似,但它更高效;我们可以向epoll_fd中添加我们想要监听的一组fd,当有某个fd有事件产生时,它就会根据我们事先注册的结果 /根据获取到的epoll_event事件信息,调用epoll_event.data.ptr这个函数指针来处理监听到的事件 epoll_fd = epoll_create1(EPOLL_CLOEXEC);/创建epoll句柄,并设置FD_CLOEXEC;后续会注册属性监听事件、组合键盘事件、信号处理事件的fd到该epoll_fd中 if (epoll_fd = -1) ERROR(epoll_create1 failed: %sn, strerror(errno); exit(1); 我们调用epoll_create()函数创建了一个epoll句柄,并保存到全局变量epoll_fd中。cpp view plain copy 在CODE上查看代码片派生到我的代码片signal_handler_init();/初始化signal信号事件处理,会signal_read_fd注册到epoll_fd中,通过epoll轮询检测事件,handle_signal()实际处理监听到的信号事件 start_property_service();/启动属性服务,会创建一个socket 句柄,并将该fd注册到epoll_fd中;通过epoll轮询查询属性请求,并注册handle_property_set_fd()为实际事件处理函数; 接着,我们初始化signal和property处理系统,两函数的处理流程类似:创建、获取一个socket的文件描述符fd,通过epoll_ctl()将有兴趣的fd添加到epoll_fd进行监听。先看signal事件部分的处理:cpp view plain copy 在CODE上查看代码片派生到我的代码片static void reap_any_outstanding_children() while (wait_for_one_process() static void handle_signal() / Clear outstanding requests. char buf32; read(signal_read_fd, buf, sizeof(buf); reap_any_outstanding_children(); static void SIGCHLD_handler(int) if (TEMP_FAILURE_RETRY(write(signal_write_fd, 1, 1) = -1) ERROR(write(signal_write_fd) failed: %sn, strerror(errno); void signal_handler_init() / Create a signalling mechanism for SIGCHLD. int s2; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) = -1) ERROR(socketpair failed: %sn, strerror(errno); exit(1); signal_write_fd = s0; signal_read_fd = s1; / Write to signal_write_fd if we catch SIGCHLD. struct sigaction act; memset(&act, 0, sizeof(act); act.sa_handler = SIGCHLD_handler;/设置信号处理函数句柄,当有信号产生时,会向上面创建的socket写入数据,epoll监控到该socket对中的fd可读时,就会调用注册的函数去处理该事件 act.sa_flags = SA_NOCLDSTOP;/设置标志,表示只有当子进程终止时才接受SIGCHID信号 sigaction(SIGCHLD, &act, 0);/初始化SIGCHLD信号处理方式 reap_any_outstanding_children(); register_epoll_handler(signal_read_fd, handle_signal); 调用socketpair()创造一对未命名的、相互连接的UNIX域套接字,接着创建sigaction结构实例,初始化函数句柄、设置标志位,最终设置SIGCHLD信息处理方方式。reap_any_outstanding_children()会调用wait_for_one_process()循环等待有进程终止的信号,并对它进行处理,这部分后面再分析。register_epoll_handler(signal_read_fd, handle_signal)函数就是把我们关注的fd添加到epoll_fd中,让它轮询查询:cpp view plain copy 在CODE上查看代码片派生到我的代码片void register_epoll_handler(int fd, void (*fn)() epoll_event ev; ev.events = EPOLLIN;/对文件描述符可读 ev.data.ptr = reinterpret_cast(fn);/保存指定的函数指针,用于后续的事件处理 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) = -1) /向epoll_fd添加要监听的fd,比如property、keychord和signal事件监听 ERROR(epoll_ctl failed: %sn, strerror(errno); 注意,这里指定了signal事件处理的函数句柄:handle_signal()。属性服务事件的监听处理与signal类似:cpp view plain copy 在CODE上查看代码片派生到我的代码片void start_property_service() property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0666, 0, 0, NULL); if (property_set_fd = -1) ERROR(start_property_service socket creation failed: %sn, strerror(errno); exit(1); listen(property_set_fd, 8); register_epoll_handler(property_set_fd, handle_property_set_fd); cpp view plain copy 在CODE上查看代码片派生到我的代码片/* * create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR * (/dev/socket) as dictated in init.rc. This socket is inherited by the * daemon. We communicate the file descriptors value via the environment * variable ANDROID_SOCKET_ENV_PREFIX (ANDROID_SOCKET_foo). */ int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid, const char *socketcon) struct sockaddr_un addr; int fd, ret; char *filecon; if (socketcon) setsockcreatecon(socketcon); fd = socket(PF_UNIX, type, 0); if (fd 0) ERROR(Failed to open socket %s: %sn, name, strerror(errno); return -1; if (socketcon) setsockcreatecon(NULL); memset(&addr, 0 , sizeof(addr); addr.sun_family = AF_UNIX; snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR/%s, name); ret = unlink(addr.sun_path); if (ret != 0 & errno != ENOENT) ERROR(Failed to unlink old socket %s: %sn, name, strerror(errno); goto out_close; filecon = NULL; if (sehandle) ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK); if (ret = 0) setfscreatecon(filecon); ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr); if (ret) ERROR(Failed to bind socket %s: %sn, name, strerror(errno); goto out_unlink; setfscreatecon(NULL); freecon(filecon); chown(addr.sun_path, uid, gid); chmod(addr.sun_path, perm); INFO(Created socket %s with mode %o, user %d, group %dn, addr.sun_path, perm, uid, gid); return fd; out_unlink: unlink(addr.sun_path); out_close: close(fd); return -1; cpp view plain copy 在CODE上查看代码片派生到我的代码片void register_epoll_handler(int fd, void (*fn)() epoll_event ev; ev.events = EPOLLIN;/对文件描述符可读 ev.data.ptr = reinterpret_cast(fn);/注册指定的函数指针,用于后续的事件处理 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) = -1) /向epoll_fd添加要监听的fd,比如property、keychord和signal事件监听 ERROR(epoll_ctl failed: %sn, strerror(errno); 先按设置创建一个socket,并会在/dev/socket目录下创建一个对应的设备文件,接着在此socket上进行绑定并开始监听,这表明此处是服务端,会有客户端连接此socket,并发送属性设置、读取请求;epoll监听到该fd有数据可读时,就会调用注册的函数句柄handle_property_set_fd()处理这个请求。这一部分内容只是将signal、property要监听的fd加入到了epoll_fd中,还未真正开始轮询事件。再接着看:cpp view plain copy 在CODE上查看代码片派生到我的代码片init_parse_config_file(/init.rc);/解析init.rc文件 这是真正解析init.rc文件的函数:cpp view plain copy 在CODE上查看代码片派生到我的代码片int init_parse_config_file(const char* path) INFO(Parsing %s.n, path); Timer t; std:string data; if (!read_file(path, &data) /将init.rc配置文件读入内存 return -1; data.push_back(n); / TODO: fix parse_config. parse_config(path, data);/实际解析配置文件内容 dump_parser_state(); NOTICE(Parsing %s took %.2fs.)n, path, t.duration(); return 0; 先把init.rc配置文件的内容读到内存中,再调用parse_config()函数进行解析:cpp view plain copy 在CODE上查看代码片派生到我的代码片static void parse_config(const char *fn, const std:string& data) struct listnode import_list; struct listnode *node; char *argsINIT_PARSER_MAXARGS; int nargs = 0; parse_state state; state.filename = fn; state.line = 0; state.ptr = strdup(data.c_str(); / TODO: fix this code! state.nexttoken = 0; state.parse_line = parse_line_no_op; list_init(&import_list); state.priv = &import_list; for (;) switch (next_token(&state) case T_EOF: state.parse_line(&state, 0, 0); goto parser_done; case T_NEWLINE: state.line+; if (nargs) int kw = lookup_keyword(args0); if (kw_is(kw, SECTION) state.parse_line(&state, 0, 0); parse_new_section(&state, kw, nargs, args);/是一个section块,进行处理 else state.parse_line(&state, nargs, args);/否则接着处理下一行 nargs = 0; break; case T_TEXT: if (nargs filename); if (ret) ERROR(could not import file %s from %sn, import-filename, fn); init.rc文件的解析流程如下图描述:如果我们遇到了新的一行,并且它是一个section块,我们就调用parse_new_section()进行处理:cpp view plain copy 在CODE上查看代码片派生到我的代码片static void parse_new_section(struct parse_state *state, int kw, int nargs, char *args) printf( %s %s n, args0, nargs 1 ? args1 : ); switch(kw) case K_service:/service state-context = parse_service(state, nargs, args); if (state-context) state-parse_line = parse_line_service; return; break; case K_on:/action state-context = parse_action(state, nargs, args); if (state-context) state-parse_line = parse_line_action; return; break; case K_import:/import配置导入 parse_import(state, nargs, args); break; state-parse_line = parse_line_no_op; parse_new_section()根据三个关键字分别处理:service:调用parse_service()初始化一个结构service,并把它添加到service_list列表中去;把行处理函数设置为parse_line_service(),以解析它的Options。on:调用parse_action()初始化一个action结构,并把它添加到action_list列表中去;把行处理函数设置为parse_line_action(),以解析它的Commands。import:调用parse_import()初始化一个import结构,并把它添加到import_list列表中去。这里涉及到了几个链表结构,它们的定义、初始化过程是:cpp view plain copy 在CODE上查看代码片派生到我的代码片static list_declare(service_list);/init.rc中解析出的service保存到此列表中 static list_declare(action_list);/init.rc中解析出的action保存到此列表中 static list_declare(action_queue);/将要执行的actio

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论