init启动流程总结分析_第1页
init启动流程总结分析_第2页
init启动流程总结分析_第3页
init启动流程总结分析_第4页
init启动流程总结分析_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

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

文档简介

1、Init 启动流程Android:4.2.2Linux内核:3.1.10内核空间和应用空间是不能直接通过内存地址级别访问的,所以就需要建立某种通讯机制。目前Linux有很多通讯机制可以在用户空间和内核空间之间交互,例如设备驱动文件(位于/dev目录中)、内存文件(/proc、/sys目录等)这些与内 核空间交互的文件都在用户空间,所以在Linux内核装载完,需要首先建立这些文件所在的目录。而完成这些工作的程序就是本文要介绍的init。Init 是一个命令行程序。其主要工作之一就是建立这些与内核空间交互的文件所在的目录。当Linux内核加载完后,要做的第一件事就是调用init程序,也就是 说,i

2、nit是用户空间执行的第一个程序。在分析init的核心代码之前,还需要初步了解init除了建立一些目录外,还做了如下的工作1. 初始化属性2. 处理配置文件的命令(主要是init.rc文件),包括处理各种Action。3. 性能分析(使用bootchart工具)。4. 无限循环执行command(启动其他的进程)。尽管init完成的工作不算很多,不过代码还是非常复杂的。Init程序并不是由一个源代码文件组成的,而是由一组源代码文件的目标文件链接而成的。这些文件位于如下的目录。<Android源代码本目录>/system/core/init其中init.c是init的主文件,现在打开

3、该文件,看看其中的内容。由于init是命令行程序,所以分析init.c首先应从main函数开始,现在好到main函数,代码如下:int main(int argc, char *argv) int fd_count = 0; struct pollfd ufds4; char *tmpdev; char* debuggable; char tmp32; int property_set_fd_init = 0; int signal_fd_init = 0; int keychord_fd_init = 0; bool is_charger = false; / 启动ueventd if (!

4、strcmp(basename(argv0), "ueventd") return ueventd_main(argc, argv); / 启动watchdogd if (!strcmp(basename(argv0), "watchdogd") return watchdogd_main(argc, argv); /* clear the umask */ umask(0); / 下面的代码开始建立各种用户空间的目录,如/dev、/proc、/sys等 / 为rootfs创建并挂在启动所需的文件目录 mkdir("/dev", 07

5、55); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts&q

6、uot;, 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); /* 检测/dev/.booting文件是否可读写和创建 */ close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000); / 创建/dev/null 节点,需要在后面的程序中看到打印信息的话,需要屏蔽掉这个函数

7、 open_devnull_stdio(); / log初始化 / klog_init()用于初始化log,通过其实现可以看出log被打印到/dev/_kmsg_文件中。 / 主要在代码中最后通过fcntl和unlink使得/dev/_kmsg_不可被访问, / 这就保证了只有log程序才可以访问。 klog_init(); / 在/system/core/libcutils/Klog.c中 / 属性服务初始化 property_init(); / 从/proc/cpuinfo中读取Hardware名,在后面的mix_hwrng_into_linux_rng_action函数 / 中会将har

8、dware的值设置给属性ro.hardware / get_hardware_name()函数的作用是从/proc/cpuinfo中获取Hardware和Revision的值, / 并保存到全局变量hardware和revision中。 get_hardware_name(hardware, &revision); / 导入并设置内核变量,处理内核命令行 process_kernel_cmdline(); /selinux相关,暂不分析 union selinux_callback cb; cb.func_log = klog_write; selinux_set_callback(S

9、ELINUX_CB_LOG, cb); cb.func_audit = audit_callback; selinux_set_callback(SELINUX_CB_AUDIT, cb); selinux_initialize(); /* 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 p

10、opulated by ueventd. */ restorecon("/dev"); restorecon("/dev/socket"); restorecon("/dev/_properties_"); restorecon_recursive("/sys"); is_ffbm = !strncmp(bootmode, "ffbm", 4); if (!is_ffbm) is_charger = !strcmp(bootmode, "charger");/关机充电相关,暂

11、不做分析 INFO("property initn"); if (!is_charger) property_load_boot_defaults(); INFO("reading config filen"); / 解析/init.rc配置文件的内容 init_parse_config_file("/init.rc"); / 执行初始化文件中的动作 /*· *解析完init.rc后会得到一系列的action等,下面的代码将执行处于early-init阶段的action。· *init将action按照执行时间段的

12、不同分为early-init、init、early-boot、boot。· *进行这样的划分是由于有些动作之间具有依赖关系,某些动作只有在其他动作完成后才能执行,所以就有了先后的区别。· *具体哪些动作属于哪个阶段是在init.rc中的配置决定的· */ action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"

13、;); queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for

14、_each_trigger("init", action_add_queue_tail); / 在charger模式下略过mount文件系统的工作 if (!is_charger) action_for_each_trigger("early-fs", action_add_queue_tail); action_for_each_trigger("fs", action_add_queue_tail); action_for_each_trigger("post-fs", action_add_queue_tai

15、l); action_for_each_trigger("post-fs-data", action_add_queue_tail); queue_builtin_action(property_service_init_action, "property_service_init"); queue_builtin_action(signal_init_action, "signal_init"); queue_builtin_action(check_startup_action, "check_startup"

16、); if (is_charger) action_for_each_trigger("charger", action_add_queue_tail); else action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail); /* run all property triggers based on current state of the properties

17、 */ queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");#if BOOTCHART queue_builtin_action(bootchart_init_action, "bootchart_init");#endif / 进入无限循环,建立init的子进程(init是所有进程的父进程) for(;) int nr, i, timeout = -1; / 执行命令(子进程对应的命令) / 检查action_queue列表是否为空。如果不为空则

18、移除并执行列表头中的action execute_one_command(); restart_processes();/ 重启已经死去的进程 if (!property_set_fd_init && get_property_set_fd() > 0) ufdsfd_count.fd = get_property_set_fd(); ufdsfd_count.events = POLLIN; ufdsfd_count.revents = 0; fd_count+; property_set_fd_init = 1; if (!signal_fd_init &&a

19、mp; get_signal_fd() > 0) ufdsfd_count.fd = get_signal_fd(); ufdsfd_count.events = POLLIN; ufdsfd_count.revents = 0; fd_count+; signal_fd_init = 1; if (!keychord_fd_init && get_keychord_fd() > 0) ufdsfd_count.fd = get_keychord_fd(); ufdsfd_count.events = POLLIN; ufdsfd_count.revents = 0

20、; fd_count+; keychord_fd_init = 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是一个性能统计工具,用于搜集硬件和系统的信息,并将其写入磁盘,以便其/ 他程序使用#if BOOTCHART if (bootchart_count > 0) if (ti

21、meout < 0 | timeout > BOOTCHART_POLLING_MS) timeout = BOOTCHART_POLLING_MS; if (bootchart_step() < 0 | -bootchart_count = 0) bootchart_finish(); bootchart_count = 0; #endif / 等待下一个命令的提交,事件的发生 nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i+) if (

22、ufdsi.revents = POLLIN) if (ufdsi.fd = get_property_set_fd() / 处理属性服务事件 handle_property_set_fd(); else if (ufdsi.fd = get_keychord_fd() / 处理keychord事件 handle_keychord(); else if (ufdsi.fd = get_signal_fd() handle_signal(); /处理SIGCHLD信号 return 0;*在main()函数的最后,init进入了一个无限循环,并等待一些事情的发生。即:在执行完前面的初始化工作以后

23、,init变为一个守护进程。init 所关心的事件有三类:属性服务事件、keychord事件和SIGNAL,当有这三类事件发生时,init进程会调用相应的handle函数进行处理。 *我们可以看到main函数是非常复杂的,不过我们也不需要每条语句都弄得非常清楚(因为这样弄是非常困难的),通常只需要了解init的主线即可。其实从init的main函数可以看出。*main函数一开始就会判断参数argv0的值是否等于“ueventd”,如果是就调用ueventd进程的入口函数ueventd_main()启动ueventd进程。这是怎么回事呢?当前正在启动的进程不是init吗?它的名称怎么可能会等于“

24、ueventd”?所以这里有必要看一下ueventd的启动过程,ueventd是在init.rc中被启动的。If (!strcmp(basename(argv0), "ueventd") return ueventd_main(argc, argv);on boot· service ueventd /sbin/ueventd· class core· critical· seclabel u:r:ueventd:s0可以看出ueventd可执行文件位于/sbin/ueventd,在观察了/sbin/ueventd后我们发现,它只不过

25、是是可执行文件/init的一个符号链接文件,即应用程序ueventd和init运行的是同一个可执行文件。所以,整个过程是这样的:内核启动完成之后,可执行文件/init首先会被执行,即init进程会首先被启动。init进程在启动的过程中,会对启动脚本/init.rc进行解析。 在启动脚本/init.rc中,配置了一个ueventd进程,它对应的可执行文件为/sbin/ueventd,即ueventd进程加载的可执行文件也为/init(此时init中main函数的参数argv0 = “/sbin/ueventd”)。因此,通过判断参数argv0的值,就可以知道当前正在启动的是init进程还是uev

26、entd进程。PS:ueventd是一个守护进程,主要作用是接收uevent来创建或删除/dev/xxx(设备节点),其实现位于system/core/init/ueventd.c中。ueventd进程会通过一个socket接口来和内核通信,以便可以监控系统设备事件。*Init实际上就分为如下两部分。1. 初始化(包括建立/dev、/proc等目录、初始化属性、执行init.rc等初始化文件中的action等)。2. 使用for循环无限循环建立子进程。*/创建并挂在启动所需的文件目录  · mkdir("/dev", 0755);·

27、mkdir("/proc", 0755);· mkdir("/sys", 0755);· mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");· mkdir("/dev/pts", 0755);· mkdir("/dev/socket", 0755);· mount("devpts", "/dev/

28、pts", "devpts", 0, NULL);· mount("proc", "/proc", "proc", 0, NULL);· mount("sysfs", "/sys", "sysfs", 0, NULL);说明: tmpfs是一种虚拟内存的文件系统,典型的tmpfs文件系统完全驻留在RAM中,读写速度远快于内存或硬盘文件系统。/dev目录保存着硬件设备访问所需要的设备驱动程序。在Android中,将相关目录作用于

29、tmpfs,可以大幅度提高设备访问的速度。devpts是一种虚拟终端文件系统。proc是一种虚拟文件系统,只存在于内存中,不占用外存空间。借助此文件系统,应用程序可以与内核内部数据结构进行交互。sysfs是一种特殊的文件系统,在Linux 2.6中引入,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息,将proc、devpts、devfs三种文件系统统一起来。编译Android系统源码时,在生成的根文件系统中,并不存在/dev、/proc、/sys这类目录,它们是系统运行时的目录,有init进程在运行中生成,当系统终止时,它们就会消失。上面的代码所形成的的文件层次结

30、构为:* 第一项工作很好理解。而第二项工作是init中的核心。在Linux系统中init是一切应用空间进程的父进程。所以我们平常在Linux终端执行的命 令,并建立进程。实际上都是在这个无限的for循环中完成的。也就是说,在Linux终端执行ps e 命令后,看到的所有除了init外的其他进程,都是由init负责创建的。而且init也会常驻内容。当然,如果init挂了,Linux系统基本上就崩 溃了。由于init比较复杂,所以本文只分析其中的一部分,在后续文章中将详细分析init的各个核心组成部分。对于main函数最开始完成的建立目录的工作比较简单,这部分也没什么可以分析的。就是调用了一些普通

31、的API(mkdir)建立一些目录。现在说一些题 外话,由于Android的底层源代码(包括init)实际上是属于Linux应用编程领域,所以要想充分理解Android源代码,除了Linux的基 本结构要了解外,Linux应用层的API需要熟悉。为了满足这些读者的需要,后续我会写一些关于Linux应用编程的文章。Ok,现在言归正传,接下来 分析一个比较重要的部分:配置文件的解析。这里的配置文件主要指init.rc。读者可以进到Android的shell,会看到根目录有一个init.rc文件。该文件是只读的,即使有了 root权限,可以修改该文件也没用。因为我们在根目录看到的文件只是内存文件的镜

32、像。也就是说,android启动后,会将init.rc文件装载到内 存。而修改init.rc文件的内容实际上只是修改内存中的init.rc文件的内容。一旦重启android,init.rc文件的内容又会恢复到最初 的装载。想彻底修改init.rc文件内容的唯一方式是修改Android的ROM中的内核镜像(boot.img)。其实boot.img名曰内核镜 像,不过该文件除了包含完整的Linux内核文件(zImage)外,还包括另外一个镜像文件(ramdisk.img)。ramdisk.img就包含 了init.rc文件和init命令。所以只有修改ramdisk.img文件中的init.rc文件

33、,并且重新打包boot.img文件,并刷机,才能 彻底修改init.rc文件。如果读者有Android源代码,编译后,就会看到out目录中的相关子目录会生成一个root目录,该目录实际上就是 ramdisk.img解压后的内容。会看到有init命令和init.rc文件。在后续的文章中将会讨论具体如何修改init.rc文件,如何刷机。现在回到main函数,在创建完目录后,会看到执行了如下3个函数。 property_init(); get_hardware_name(hardware, &revision); process_kernel_cmdline();其中property_ini

34、t主要是为属性分配一些存储空间,该函数并不是核心。不过当我们查看init.rc文件时会发现该文件开始部分用一些 import语句导入了其他的配置文件,例如,/init.usb.rc。大多数配置文件都直接使用了确定的文件名,只有如下的代码使用了一个变量 ($ro.hardware)执行了配置文件名的一部分。那么这个变量值是从哪获得的呢?import /init.$ro.hardware.rc首先要了解init.$ro.hardware.rc配置文件的内容通常与当前的硬件有关。现在我们先来关注get_hardware_name函数,代码如下:void get_hardware_name(char

35、*hardware, unsigned int *revision) char data1024; int fd, n; char *x, *hw, *rev; /* 如果hardware已经有值了,说明hardware通过内核命令行提供,直接返回 */ if (hardware0) return; / 打开/proc/cpuinfo文件 fd = open("/proc/cpuinfo", O_RDONLY); if (fd < 0) return; / 读取/proc/cpuinfo文件的内容 n = read(fd, data, 1023); close(fd)

36、; if (n < 0) return; datan = 0; / 从/proc/cpuinfo文件中获取Hardware字段的值 hw = strstr(data, "nHardware"); rev = strstr(data, "nRevision"); / 成功获取Hardware字段的值 if (hw) x = strstr(hw, ": "); if (x) x += 2; n = 0; while (*x && *x != 'n') if (!isspace(*x) / 将Hardw

37、are字段的值都转换为小写,并更新hardware参数的值 / hardware也就是在init.c文件中定义的hardware数组 hardwaren+ = tolower(*x); x+; if (n = 31) break; hardwaren = 0; if (rev) x = strstr(rev, ": "); if (x) *revision = strtoul(x + 2, 0, 16); 从get_hardware_name方法的代码可以得知,该方法主要用于确定hardware和revision的变量的值。Revision这里先不讨论,只要研究hardwa

38、re。获取hardware的来源是从Linux内核命令行或/proc/cpuinfo文件中的内容。Linux内核命令行 暂且先不讨论(因为很少传递该值),先看看/proc/cpuinfo,该文件是虚拟文件(内存文件),执行cat /proc/cpuinfo命令会看到该文件中的内容, 例如:在白框中就是Hardware字段的值。由于该设备是Nexus 7,所以值为grouper。如果程序就到此位置,那么与硬件有关的配置文件名是init.grouper.rc。有Nexus 7的读者会看到在根目录下确实有一个init.grouper.rc文件。说明Nexus 7的原生ROM并没有在其他的地方设置配置

39、文件名,所以配置文件名就是从/proc/cpuinfo文件的Hardware字段中取的值。现在来看在get_hardware_name函数后面调用的process_kernel_cmdline函数,代码如下:static void process_kernel_cmdline(void) /* don't expose the raw commandline to nonpriv processes */ chmod("/proc/cmdline", 0440); / 导入内核命令行参数 import_kernel_cmdline(0, import_kernel_

40、nv); if (qemu0) import_kernel_cmdline(1, import_kernel_nv); / 用属性值设置内核变量 export_kernel_boot_props();在process_kernel_cmdline函数中除了使用import_kernel_cmdline函数导入内核变量外,主要的功能就是调用 export_kernel_boot_props函数通过属性设置内核变量,例如,通过ro.boot.hardware属性设置hardware变 量,也就是说通过ro.boot.hardware属性值可以修改get_hardware_name函数中从/proc

41、/cpuinfo文件中得到 的hardware字段值。* init.rc中文件中会通过import /init.$ro.hardware.rc文件,这个ro.hardware应该是某个具体的属性,而这个ro.hardware赋值应该是在Init进程中赋值的。这个ro.hardware值设置是在/system/core/init.c中实现的,其通过hardware来赋值,hardware首先被/proc/cpuinfo赋值,然后会检测comandline。如果comandline中有参数为androidboot.hardware,那这个参数在init进程的解析中会用到,并会赋值给hardware

42、。也就是说如果cmdline中有androidboot.hardware,则ro.hardware的值就是这个值,如果没有则ro.hardware的值就是/proc/cpuinfo*例如我参与的这个项目comandline中的androidboot.hardware = qilianp1,那么在init.rc中import的具体文件对应为,init.qilianp1.rc在device/qcom/msm8909 目录下的BoardConfig.mk文件中有BOARD_KERNEL_CMDLINE := console=ttyHSL0,115200,n8 androidboot.console=

43、ttyHSL0 androidboot.hardware=qcom user_debug=31 msm_rtb.filter=0x3F ehci-hcd.park=3 androidboot.bootdevice=7824900.sdhci lpm_levels.sleep_disabled=1 earlyprintk*下面看一下export_kernel_boot_props函数的代码。static void export_kernel_boot_props(void) char tmpPROP_VALUE_MAX; const char *pval; unsigned i; struct

44、const char *src_prop; const char *dest_prop; const char *def_val; prop_map = "ro.boot.serialno", "ro.serialno", "", , "ro.boot.mode", "ro.bootmode", "unknown", , "ro.boot.baseband", "ro.baseband", "unknown", ,

45、"ro.boot.bootloader", "ro.bootloader", "unknown", , ; / 通过内核的属性设置应用层配置文件的属性 for (i = 0; i < ARRAY_SIZE(prop_map); i+) pval = property_get(prop_mapi.src_prop); property_set(prop_mapi.dest_prop, pval ?: prop_mapi.def_val); / 根据ro.boot.console属性的值设置console变量 pval = prop

46、erty_get("ro.boot.console"); if (pval) strlcpy(console, pval, sizeof(console); /* save a copy for init's usage during boot */ strlcpy(bootmode, property_get("ro.bootmode"), sizeof(bootmode); /* if this was given on kernel command line, override what we read * before (e.g. fro

47、m /proc/cpuinfo), if anything */ / 获取ro.boot.hardware属性的值 pval = property_get("ro.boot.hardware"); if (pval) / 这里通过ro.boot.hardware属性再次改变hardware变量的值 strlcpy(hardware, pval, sizeof(hardware); / 利用hardware变量的值设置设置ro.hardware属性 / 这个属性就是前面提到的设置初始化文件名的属性,实际上是通过hardware变量设置的 property_set("

48、ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp); /* TODO: these are obsolete. We should delete them */ if (!strcmp(bootmode,"factory") property_set("ro.factorytest", "1"); else if (!strcmp

49、(bootmode,"factory2") property_set("ro.factorytest", "2"); else property_set("ro.factorytest", "0"); 从export_kernel_boot_props函数的代码可以看出,该函数实际上就是来回设置一些属性值,并且利用某些属性值修改 console、hardware等变量。其中hardware变量(就是一个长度为32的字符数组)在get_hardware_name函数中已经从 /proc/cpuin

50、fo文件中获得过一次值了,在export_kernel_boot_props函数中又通过ro.boot.hardware属性设置了一次值,不过在Nexus 7中并没有设置该属性,所以hardware的值仍为grouper。最后用hardware变量设置ro.hardware属性,所以最后的初始化文件名为init.grouper.rc。 这里还有一个问题,前面多次提到属性或属性文件,那么这些属性文件指的是什么呢?是init.rc?当然不是。实际上这些属性文件是一些列位于不同目录,系统依次读取的配置文件。属性服务(Property Service) 在研究这些配置文件之前应先了解init是如何处理

51、这些属性的。编写过Windows本地应用的读 者都应了解,在windows中有一个注册表机制,在注册表中提供了大量的属性。在Linux中也有类似的机制,这就是属性服务。init在启动的过程中 会启动属性服务(Socket服务),并且在内存中建立一块存储区域,用来存储这些属性。当读取这些属性时,直接从这一内存区域读取,如果修改属性值,需要通过Socket连接属性服务完成。在init.c文件中的一个action函数中调用了start_property_service函数来启动属性 服务,action是init.rc及其类似文件中的一种执行机制,由于内容比较多,所以关于init.rc文件中的执行机制

52、将在下一篇文章中详细讨论。 现在顺藤摸瓜,找到start_property_service函数,该函数在Property_service.c文件中,该文件与init.c文件中同一个目录。void start_property_service(void) int fd; / 装载不同的属性文件 load_properties_from_file(PROP_PATH_SYSTEM_BUILD); load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); /add hu_chao 红色部分为自添加:添加供应商的属性 /* Read vendor-specific property runtime overrides. */ vendor_load_properties(); load_override_properties(); /* Read persistent properties after all default values have been loaded. */ load_persistent_p

温馨提示

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

评论

0/150

提交评论