Android系统启动流程

要求:掌握Android系统的启动流程,每个阶段的区别和作用。
一.  android系统架构

andorid系统架构从上到下分别有应用层、应用框架层、系统运行时库层、硬件抽象层、Linux内核层。应用层:包括系统应用比如闹钟、日历等这些在内的以及非系统级别的应用都属于应用层。负责用
户交互,也就是我们需要开发的东西。应用框架层:这一层主要是为咱们开发人员提供用来开发应用程序的API,平常我们开发程序大部分
都是调用这部分的API来进行开发。这一层主要提供一些ActivityManager 管理应用生命周期、
locationManager 地理位置服务、还有就是NotificationManager 消息通知管理等等系统运行库层:这一层主要分为两部分,分别是c/c++程序库和Android运行库c++库主要是能被Andorid系统不同组件所使用,并通过应用程序框架为开发者提供服务。它主
要功能有openggl 绘图方法库,多媒体库支持常用的音频 视频格式录制回访 ,还有我们常用会用到的一
个轻量级的数据库 sqlLite 等还有ssl网络协议等等    android运行时库:它主要又分为核心库和虚拟机ART,核心库主要包含Java核心库的大部分功能。
android 5.0系统之前使用的虚拟机是 dalvik,它相对于JVM来说,dalvik虚拟机是专门为移动设备定制的,
主要在有限的内存中同时运行多个虚拟机实例,每一个dalvik虚拟机就是一个独立的linux进程,这样独立
的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。  5.0之后,dalvik虚拟机被ART所取代,它们主
要区别就是dalvik虚拟机,应用每次在运行的时候,都需要把字节码文件转换为机器码来运行,这样应用
的运行效率就会比较低。  而art虚拟机,应用在第一次安装的时候,字节码文件就会预先编译成机器码,
让应用成为真正的本地应用。硬件抽象层:它是位于操作系统内核和硬件电路之间的接口层,主要目的在于将硬件抽象化,隐藏了
特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多平台上进行移植。linux内核层:Android的核心系统服务基于Linux内核,在此基础上添加了部分Android专用的驱动,
android系统的安全管理、内存管理、进程管理、网络协议等都依赖于该内核。二,系统的启动流程解析1.启动电源以及系统启动
当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序Bootloader到RAM,
然后执行。
2.引导程序BootLoader
引导程序BootLoader是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运
行。
3.Linux内核启动内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当内核完成系统设置,它首先在系统文件寻找
init.rc文件,并启动init进程。
4.init进程启动
init进程是系统空间内的第一个进程,进行初始化和启动属性服务,在main方法中进行,包括初始化资源文件
和启动一系列的属性服务。通过执行init.rc文件的脚本文件来启动Zygote进程。
5.Zygote进程启动所有的应用程序包括system系统进程 都是zygote进程负责创建,因此zygote进程也被称为进程孵化器,它创
建进程是通过复制自身来创建应用进程,它在启动过程中会在内部创建一个虚拟机实例,所以通过复制zygote
进程而得到的应用进程和系统服务进程都可以快速地在内部的获得一个虚拟机实例拷贝。创建JVM并注册JNI,创建服务端的socket,通过socket通信,启动systemServer1.创建AppRuntime并调用其start方法,启动Zygote进程。2.创建JavaVM并为JavaVM注册JNI.3.通过JNI调用ZygoteInit的main函数进入Zygote的Java框架层。4.通过registerZygoteSocket函数创建服务端Socket,并通过runSelectLoop函数等待
ActivityManagerService的请求。5.启动SystemServer进程。6.SystemServer进程启动
启动Binder线程池和SystemServiceManager,systemServiceManger主要是对系统服务进行创建、启动和
生命周期管理,就会启动各种系统服务。7.Launcher启动Launcher组件是由之前启动的systemServer所启动的ActivityManagerService启动,这也是andorid系统启动的最后一步,launcher是andorid系统home程序,主要是用来显示系统中已安装的
应用程序。    launcher应用程序的启动会通过请求packageManagerService返回系统中已经安装的应用
信息,并将这些应用信息通过封装处理成快捷列表显示在系统屏幕上,这样咱们就可以单击启动它们。
被SystemServer进程启动的ActivityManagerService会启动Launcher,Launcher启动后会将已安装应用
的快捷图标显示到界面上。上述七步是手机开机andorid系统启动的流程。三 应用启动流程首先,activity分为两种分别是根activity和子activity,根activity就是显示在手机屏幕上快捷应用图
标,在launcher应用程序启动器,根activity也就是app的第一个activity是由Launcher组件来启动,但
它又是通过activity管理服务ActivityManagerService来启动根activity。但是activity Launcher
activityManagerService分别运行在不同进程里面,这三个进程是通过binder进程间通信机制来完成进行
通信完成activity的启动。应用的启动也就是根activity的启动。ActivityManagerService 是一个系统关键服务,运行在SystemServer系统进程中,负责启动和调度应用
程序组件。luncher是andorid系统的home程序,管理和安装手机里的所有应用,(1)Launcher首先向activityManagerService发送一个启动activity的进程间通信请求(2)ams会先把要启动的activity信息保存下来,然后再想Launcher发送一个进入中止状态的进程间
通信请求。(3)Launcher组件进入终止状态后,就会给ams发送一个已进入终止状态的一个进程间通信请求,ams
收到后就会继续执行启动activity操作(4)ams如果发现用来运行运行activity的进程不存在,它就会给zygote进程发送一个进程间通信请
求,zaygote会调用fork()方法创建一个新的应用程序进程。zaygote进程在启动的时候在内部创建一个
虚拟机实例,它通过复制它本身得到一个应用程序进程。(5)新的应用程序进程启动完成之后,就会向ams发送一个启动完成的通信请求,(6)最后ams将第二步保存下来的Activity组件信息发送给zaygote进程创建的新的进程,进程创建好
之后,经过一系列调用就会调用startactivity方法,最后activity调用oncreate方法构建出页面至此我们
的应用正式启动完成。

init进程【1】——init启动过程

init启动过程
众所周知,Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动
init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。Android
是基于Linux的操作系统,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。下面先简单
的看一下init进程的启动过程。

/kernel/goodfish/init/main.cstatic int __init kernel_init(void * unused)
{/** Wait until kthreadd is all set-up.*/wait_for_completion(&kthreadd_done);/** init can allocate pages on any node*/set_mems_allowed(node_states[N_HIGH_MEMORY]);/** init can run on any cpu.*/set_cpus_allowed_ptr(current, cpu_all_mask);cad_pid = task_pid(current);smp_prepare_cpus(setup_max_cpus);do_pre_smp_initcalls();lockup_detector_init();smp_init();sched_init_smp();do_basic_setup();/* Open the /dev/console on the rootfs, this should never fail */if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)printk(KERN_WARNING "Warning: unable to open an initial console.\n");(void) sys_dup(0);(void) sys_dup(0);/** check if there is an early userspace init.  If yes, let it do all* the work*/if (!ramdisk_execute_command)ramdisk_execute_command = "/init";if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {ramdisk_execute_command = NULL;prepare_namespace();}/** Ok, we have completed the initial bootup, and* we're essentially up and running. Get rid of the* initmem segments and start the user-mode stuff..*/init_post();return 0;
}
/* This is a non __init function. Force it to be noinline otherwise gcc* makes it inline to init() and it becomes part of init.text section*/
static noinline int init_post(void)
{/* need to finish all async __init code before freeing the memory */async_synchronize_full();free_initmem();mark_rodata_ro();system_state = SYSTEM_RUNNING;numa_default_policy();current->signal->flags |= SIGNAL_UNKILLABLE;if (ramdisk_execute_command) {run_init_process(ramdisk_execute_command);printk(KERN_WARNING "Failed to execute %s\n",ramdisk_execute_command);}/** We try each of these until one succeeds.** The Bourne shell can be used instead of init if we are* trying to recover a really broken machine.*/if (execute_command) {run_init_process(execute_command);printk(KERN_WARNING "Failed to execute %s.  Attempting ""defaults...\n", execute_command);}run_init_process("/sbin/init");run_init_process("/etc/init");run_init_process("/bin/init");run_init_process("/bin/sh");panic("No init found.  Try passing init= option to kernel. ""See Linux Documentation/init.txt for guidance.");
}
static void run_init_process(const char *init_filename)
{argv_init[0] = init_filename;kernel_execve(init_filename, argv_init, envp_init);
}
在init_post()中会判断execute_command是否为空,如果不为空则执行run_init_process调用。
execute_command的赋值在init_setup()中,所以这里应该注意在设置内核启动选项时,应设置为
“init=/init”,以便正常启动init进程,因为编译完Android后生成的文件系统中,init位于最顶层目录。<span style="font-size:14px;">static const char * argv_init[MAX_INIT_ARGS+2] = { "init",
NULL, };</span>
static int __init init_setup(char *str)
{unsigned int i;execute_command = str;/** In case LILO is going to boot us with default command line,* it prepends "auto" before the whole cmdline which makes* the shell think it should execute a script with such name.* So we ignore all arguments entered _before_ init=... [MJ]*/for (i = 1; i < MAX_INIT_ARGS; i++)argv_init[i] = NULL;return 1;
}
__setup("init=", init_setup);

当根目录中不存在init时,或者未指定启动项“init=”时,内核会到/sbin、/etc、/bin目录下查找init。
了解了init进程的启动过程后,接下来看一下init进程都干了些什么?Android中的init进程与Linux不同,
其职责可以归结如下:作为守护进程
解析和执行init.rc文件
生成设备驱动节点
属性服务
init源码分析
init进程的入口函数是main,它的代码如下:/system/core/init/init.cint main(int argc, char **argv)
{int fd_count = 0;struct pollfd ufds[4];char *tmpdev;char* debuggable;char tmp[32];int property_set_fd_init = 0;int signal_fd_init = 0;int keychord_fd_init = 0;bool is_charger = false;//启动ueventdif (!strcmp(basename(argv[0]), "ueventd"))return ueventd_main(argc, argv);//启动watchdogdif (!strcmp(basename(argv[0]), "watchdogd"))return watchdogd_main(argc, argv);/* clear the umask */umask(0);/* Get the basic filesystem setup we need put* together in the initramdisk on / and then we'll* let the rc file figure out the rest.*///创建并挂在启动所需的文件目录mkdir("/dev", 0755);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", 0, NULL);mount("proc", "/proc", "proc", 0, NULL);mount("sysfs", "/sys", "sysfs", 0, NULL);/* indicate that booting is in progress to background fw loaders, etc */close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));//检测/dev/.booting文件是否
可读写和创建/* We must have some place other than / to create the* device nodes for kmsg and null, otherwise we won't* 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();//log初始化property_init();//属性服务初始化//从/proc/cpuinfo中读取Hardware名,在后面的mix_hwrng_into_linux_rng_action函数中会
将hardware的值设置给属性ro.hardwareget_hardware_name(hardware, &revision);//导入并设置内核变量process_kernel_cmdline();//selinux相关,暂不分析union selinux_callback cb;cb.func_log = klog_write;selinux_set_callback(SELINUX_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 populated by ueventd.*/restorecon("/dev");restorecon("/dev/socket");restorecon("/dev/__properties__");restorecon_recursive("/sys");is_charger = !strcmp(bootmode, "charger");//关机充电相关,暂不做分析INFO("property init\n");if (!is_charger)property_load_boot_defaults();INFO("reading config file\n");init_parse_config_file("/init.rc");//解析init.rc配置文件/** 解析完init.rc后会得到一系列的action等,下面的代码将执行处于early-init阶段的action。* init将action按照执行时间段的不同分为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");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_each_trigger("init", action_add_queue_tail);/* skip mounting filesystems in charger mode */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_tail);action_for_each_trigger("post-fs-data", action_add_queue_tail);}/* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random* wasn't ready immediately after wait_for_coldboot_done*/queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");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");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 */queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");#if BOOTCHARTqueue_builtin_action(bootchart_init_action, "bootchart_init");
#endiffor(;;) {//init进入无限循环int nr, i, timeout = -1;//检查action_queue列表是否为空。如果不为空则移除并执行列表头中的actionexecute_one_command();restart_processes();//重启已经死去的进程if (!property_set_fd_init && get_property_set_fd() > 0) {ufds[fd_count].fd = get_property_set_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;fd_count++;property_set_fd_init = 1;}if (!signal_fd_init && get_signal_fd() > 0) {ufds[fd_count].fd = get_signal_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;fd_count++;signal_fd_init = 1;}if (!keychord_fd_init && get_keychord_fd() > 0) {ufds[fd_count].fd = get_keychord_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;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;#if BOOTCHARTif (bootchart_count > 0) {if (timeout < 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 (ufds[i].revents == POLLIN) {if (ufds[i].fd == get_property_set_fd())//处理属性服务事件handle_property_set_fd();else if (ufds[i].fd == get_keychord_fd())//处理keychord事件handle_keychord();else if (ufds[i].fd == get_signal_fd())//处理handle_signal();//处理SIGCHLD信号}}}return 0;
}
main函数分析:if (!strcmp(basename(argv[0]), "ueventd"))return ueventd_main(argc, argv);
main函数一开始就会判断参数argv[0]的值是否等于“ueventd”,如果是就调用ueventd进程的入口函数
ueventd_main()启动ueventd进程。这是怎么回事呢?当前正在启动的进程不是init吗?它的名称怎么
可能会等于“ueventd”?所以这里有必要看一下ueventd的启动过程,ueventd是在init.rc中被启动的。on boot
service ueventd /sbin/ueventdclass corecriticalseclabel u:r:ueventd:s0
可以看出ueventd可执行文件位于/sbin/ueventd,在观察了/sbin/ueventd后我们发现,它只不过是 是
可执行文件/init的一个符号链接文件,即应用程序ueventd和init运行的是同一个可执行文件。

所以,整个过程是这样的:内核启动完成之后,可执行文件/init首先会被执行,即init进程会首先被
启动。init进程在启动的过程中,会对启动脚本/init.rc进行解析。在启动脚本/init.rc中,配置了
一个ueventd进程,它对应的可执行文件为/sbin/ueventd,即ueventd进程加载的可执行文件也为
/init(此时init中main函数的参数argv[0] = “/sbin/ueventd”)。因此,通过判断参数argv[0]的值,
就可以知道当前正在启动的是init进程还是ueventd进程。
PS:ueventd是一个守护进程,主要作用是接收uevent来创建或删除/dev/xxx(设备节点),其实现位于
eventd.c中。ueventd进程会通过一个socket接口来和内核通信,以便可以监控系统
设备事件。在开始所有的工作之前,main进程首先做的是创建并挂载启动所需的(其他的会在解析init.rc时创建)文  件目录,如下所示:/* Get the basic filesystem setup we need put* together in the initramdisk on / and then we'll* let the rc file figure out the rest.*///创建并挂在启动所需的文件目录mkdir("/dev", 0755);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", 0, NULL);mount("proc", "/proc", "proc", 0, NULL);mount("sysfs", "/sys", "sysfs", 0, NULL);
说明:
tmpfs是一种虚拟内存的文件系统,典型的tmpfs文件系统完全驻留在RAM中,读写速度远快于内存或硬盘
文件系统。/dev目录保存着硬件设备访问所需要的设备驱动程序。在Android中,将相关目录作用于tmpfs,可以大幅
度提高设备访问的速度。devpts是一种虚拟终端文件系统。proc是一种虚拟文件系统,只存在于内存中,不占用外存空间。借助此文件系统,应用程序可以与内核内
部数据结构进行交互。sysfs是一种特殊的文件系统,在Linux 2.6中引入,用于将系统中的设备组织成层次结构,并向用户模
式程序提供详细的内核数据结构信息,将proc、devpts、devfs三种文件系统统一起来。
编译Android系统源码时,在生成的根文件系统中,并不存在/dev、/proc、/sys这类目录,它们是系统
运行时的目录,有init进程在运行中生成,当系统终止时,它们就会消失。上面的代码所形成的的文件层
次结构为:

<span style="font-size:14px;">        /* We must have some place other than / to create
the* device nodes for kmsg and null, otherwise we won't 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_</span>open_devnull_stdio()函数的作用是重定向标准输入/输出/错误输出到/dev/_null_,至于为什么要重定向
的原因在注释中已经写明。open_devnull_stdio()的实现如下:
system/core/init/util.cvoid open_devnull_stdio(void)
{int fd;static const char *name = "/dev/__null__";if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {fd = open(name, O_RDWR);unlink(name);if (fd >= 0) {dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);if (fd > 2) {close(fd);}return;}}exit(1);
}<span style="font-size:14px;">klog_init();//log初始化</span>
klog_init()用于初始化log,通过其实现可以看出log被打印到/dev/__kmsg__文件中。主要在代码中最后
通过fcntl和unlink使得/dev/__kmsg__不可被访问,这就保证了只有log程序才可以访问。
void klog_init(void)
{static const char *name = "/dev/__kmsg__";if (klog_fd >= 0) return; /* Already initialized */if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {klog_fd = open(name, O_WRONLY);if (klog_fd < 0)return;fcntl(klog_fd, F_SETFD, FD_CLOEXEC);unlink(name);}
}property_init
属性服务初始化,这里先不深究,接下来会单独分析。<span style="font-size:14px;">    </span>//从/proc/cpuinfo中读取Hardware名,在后面的
mix_hwrng_into_linux_rng_action函数中会将hardware的值设置给属性ro.hardwareget_hardware_name(hardware, &revision);
get_hardware_name()函数的作用是从/proc/cpuinfo中获取Hardware和Revision的值,并保持到全局
变量hardware和revision中。
下面的截图是在我的手机上的CPU info信息:

这里获取hardware信息有什么用呢?在main()函数后面的代码中,我们可以看见这样一句://导入并设置内核变量process_kernel_cmdline();
下面看一下process_kernel_cmdline的实现:
system/core/init/init.cstatic void process_kernel_cmdline(void)
{/* don't expose the raw commandline to nonpriv processes */chmod("/proc/cmdline", 0440);/* first pass does the common stuff, and finds if we are in qemu.* second pass is only necessary for qemu to export all kernel params* as props.*/import_kernel_cmdline(0, import_kernel_nv);if (qemu[0])import_kernel_cmdline(1, import_kernel_nv);/* now propogate the info given on command line to internal variables* used by init as well as the current required properties*/export_kernel_boot_props();
}
static void export_kernel_boot_props(void)
{char tmp[PROP_VALUE_MAX];....../* if this was given on kernel command line, override what we read* before (e.g. from /proc/cpuinfo), if anything */ret = property_get("ro.boot.hardware", tmp);if (ret)strlcpy(hardware, tmp, sizeof(hardware));property_set("ro.hardware", hardware);snprintf(tmp, PROP_VALUE_MAX, "%d", revision);property_set("ro.revision", tmp);......
}
process_kernel_cmdline()函数用于导入和设置一些内核变量,在export_kernel_boot_props()
中我们看见将hardware的值赋值给了属性"ro.hardware"。那这个赋值又是干什么的呢?我们再看一
下main()函数,在解析init.rc配置文件的时候,有没有发现少了点什么?INFO("reading config file\n");init_parse_config_file("/init.rc");//解析init.rc配置文件
是的,在以前比较老的代码中(例如2.3和4.0)这里除了init.rc以外还会有一个与硬件相关的rc脚本,
如下:snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
init_parse_config_file(tmp);
那现在这段代码跑去哪里了呢?我们在init.rc中找到了它:

所以,之前设置的ro.hardware的值是在这里用的,在init.rc中用来导入init.${ro.hardware}.rc脚本,
然后一起进行解析。与之前相比,这里只是方式变了,本质上还是一样的。INFO("reading config file\n");init_parse_config_file("/init.rc");//解析init.rc配置文件action_for_each_trigger("early-init", action_add_queue_tail);queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");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_each_trigger("init", action_add_queue_tail);/* skip mounting filesystems in charger mode */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_tail);action_for_each_trigger("post-fs-data", action_add_queue_tail);}/* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random* wasn't ready immediately after wait_for_coldboot_done*/queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");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");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 */queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");#if BOOTCHARTqueue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
这部分代码用于解析init.rc脚本,并触发执行解析生成的action。这部分后面单独进行分析。在main()函数的最后,init进入了一个无限循环,并等待一些事情的发生。即:在执行完前面的初始化
工作以后,init变为一个守护进程。init所关心的事件有三类:属性服务事件、keychord事件和SIGNAL,
当有这三类事件发生时,init进程会调用相应的handle函数进行处理。

init进程【2】——解析配置文件

init.rc脚本语法
init.rc文件不同于init进程,init进程仅当编译完Android后才会生成,而init.rc文件存在于Android
平台源代码中。init.rc在源代码中的位置为:@system/core/rootdir/init.rc。init.rc文件的大致结
构如下图所示:

关于init.rc脚本的介绍,在system/core/init/readme.txt中有完整的介绍,这里不再赘述,不想看
英文的朋友也可以看下面的部分,这个部分关于rc脚本的介绍转自
http://blog.csdn.net/nokiaguy/article/details/9109491。相当于readme的翻译吧。init.rc文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,
这里简称为AIL)的脚本写成的文件。在了解init如何解析init.rc文件之前,先了解AIL非常必要,否
则机械地分析init.c及其相关文件的源代码毫无意义。为了学习AIL,读者可以到自己Android手机的根目录寻找init.rc文件,最好下载到本地以便查看,如果
有编译好的Android源代码,在<Android源代码根目录>out/target/product/geneic/root目录也可找
到init.rc文件。AIL由如下4部分组成。1.  动作(Actions)2.  命令(Commands)3.服务(Services)4.  选项(Options)这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多
个符号(Tokens)表示。可以使用反斜杠转义符在Token中插入空格。双引号可以将多个由空格分隔的
Tokens合成一个Tokens。如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是说,可以用
反斜杠将多行代码连接成一行代码。AIL的注释与很多Shell脚本一行,以#开头。AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。
也就是说,每一个Actions或Services确定一个Section。而所有的Commands和Options只能属于最近定
义的Section。如果Commands和Options在第一个Section之前被定义,它们将被忽略。Actions和Services的名称必须唯一。如果有两个或多个Action或Service拥有同样的名称,那么init在
执行它们时将抛出错误,并忽略这些Action和Service。下面来看看Actions、Services、Commands和Options分别应如何设置。Actions的语法格式如下:
on <trigger>  <command>  <command>  <command>
也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标
准的Action
on boot  ifup lo  hostname localhost  domainname localdomain
其中boot是触发器,下面三行是command那么init.rc到底支持哪些触发器呢?目前init.rc支持如下5类触发器。1.  boot这是init执行后第一个被触发Trigger,也就是在 /init.rc被装载之后执行该Trigger
2.  <name>=<value>当属性<name>被设置成<value>时被触发。例如,on property:vold.decrypt=trigger_reset_mainclass_reset main3.  device-added-<path>当设备节点被添加时触发4.  device-removed-<path>当设备节点被移除时添加5. service-exited-<name>会在一个特定的服务退出时触发Actions后需要跟若干个命令,这些命令如下:1.  exec <path> [<argument> ]*创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量
避免使用exec ,它可能会引起init执行超时。2.  export <name> <value>在全局环境中将 <name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)3.  ifup <interface>启动网络接口4.  import <filename>指定要解析的其他配置文件。常被用于当前配置文件的扩展5.  hostname <name>设置主机名6.  chdir <directory>改变工作目录7.  chmod <octal-mode><path>改变文件的访问权限8.  chown <owner><group> <path>更改文件的所有者和组9.  chroot <directory>改变处理根目录10.  class_start<serviceclass>启动所有指定服务类下的未运行服务。11  class_stop<serviceclass>停止指定服务类下的所有已运行的服务。12.  domainname <name>设置域名13.  insmod <path>加载<path>指定的驱动模块14.  mkdir <path> [mode][owner] [group]创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,
并属于root用户和 root组。15. mount <type> <device> <dir> [<mountoption> ]*试图在目录<dir>挂载指定的设备。<device> 可以是mtd@name的形式指定一个mtd块设备。
<mountoption>包括 "ro"、"rw"、"re16.  setkey保留,暂时未用17.  setprop <name><value>将系统属性<name>的值设为<value>。18. setrlimit <resource> <cur> <max>设置<resource>的rlimit (资源限制)19.  start <service>启动指定服务(如果此服务还未运行)。20.stop<service>停止指定服务(如果此服务在运行中)。21. symlink <target> <path>创建一个指向<path>的软连接<target>。22. sysclktz <mins_west_of_gmt>设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)23.  trigger <event>触发一个事件。用于Action排队24.  wait <path> [<timeout> ]等待一个文件是否存在,当文件存在时立即返回,或到<timeout>指定的超时时间后返回,如果不指定
<timeout>,默认超时时间是5秒。25. write <path> <string> [ <string> ]*向<path>指定的文件写入一个或多个字符串。  Services (服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。Services (服务)的
形式如下:
service <name> <pathname> [ <argument> ]*  <option>  <option>
例如,下面是一个标准的Service用法
service servicemanager /system/bin/servicemanager  class core  user system  group system  critical  onrestart restart zygote  onrestart restart media  onrestart restart surfaceflinger  onrestart restart drm  Services的选项是服务的修饰符,可以影响服务如何以及怎样运行。服务支持的选项如下:
1.  critical表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入
Recovery (恢复)模式。2. disabled表明这个服务不会同与他同trigger (触发器)下的服务自动启动。该服务必须被明确的按名启动。3.  setenv <name><value>在进程启动时将环境变量<name>设置为<value>。4.  socket <name><type> <perm> [ <user> [ <group> ] ]Create a unix domain socketnamed /dev/socket/<name> and passits fd to the launchedprocess.  <type> must be"dgram", "stream" or "seqpacket".User and group default to0.创建一个unix域的名为/dev/socket/<name> 的套接字,并传递它的文件描述符给已启动的进程。
<type> 必须是 "dgram","stream" 或"seqpacket"。用户和组默认是0。5.  user <username>在启动这个服务前改变该服务的用户名。此时默认为 root。6.  group <groupname> [<groupname> ]*在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的
补充组(通过setgroups函数),档案默认是root。7.  oneshot服务退出时不重启。8.  class <name>指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认
为"default"类服务。9. onrestart当服务重启,执行一个命令(下详)。init.rc脚本分析
在上一篇文章中说过,init将动作执行的时间划分为几个阶段,按照被执行的先后顺序依次为:early-init、
init、early-boot、boot。在init进程的main()方法中被调用的先后顺序决定了它们的先后(直接原因),
根本原因是:各个阶段的任务不同导致后面的对前面的有依赖,所以这里的先后顺序是不能乱调整的。system/core/init/init.c

early-init主要用于设置init进程(进程号为1)的oom_adj的值,以及启动ueventd进程。oom_adj是Linux和Android中用来表示进程重要性的一个值,取值范围为[-17, 15]。在Android中系统在杀死进程时会根据oom_adj和空闲内存大小作为依据,oom_adj越大越容易被杀死。Android将程序分成以下几类,按照重要性依次降低的顺序:
名 称              oom_adj                 解释
FOREGROUD_APP           0               前 台程序,可以理解为你正在使用的程序
VISIBLE_APP             1               用户可见的程序
SECONDARY_SERVER        2               后 台服务,比如说QQ会在后台运行服务
HOME_APP                4               HOME,就是主界面
HIDDEN_APP              7               被 隐藏的程序
CONTENT_PROVIDER        14              内容提供者,
EMPTY_APP               15              空程序,既不提供服务,也不提供内容
on early-init# Set init and its forked children's oom_adj.write /proc/1/oom_adj -16# Set the security context for the init process.# This should occur before anything else (e.g. ueventd) is started.setcon u:r:init:s0start ueventd
这里设置init进程的oom_adj的值为-16.这里要说明的是,我们现在分析的是init.rc文件,在文件头部
我们发现还导入了其他的rc脚本。其他rc脚本中的文件结构与init.rc是类似的。在init进程解析rc脚本
时,会将所有rc脚本中的配置安装执行阶段一并解析。即:init.rc中的early-init与init,
${hardware}.rc中的early-init是一并解析的。在执行完early-init以后,接下来就是init阶段。在init阶段主要用来:设置环境变量,创建和挂在
文件节点。下面是init接的的不分代码截选:system/core/rootdir/init.environ.rc.in# set up the global environment
on initexport PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbinexport LD_LIBRARY_PATH /vendor/lib:/system/libexport ANDROID_BOOTLOGO 1export ANDROID_ROOT /systemexport ANDROID_ASSETS /system/appexport ANDROID_DATA /dataexport ANDROID_STORAGE /storageexport ASEC_MOUNTPOINT /mnt/asecexport LOOP_MOUNTPOINT /mnt/obbexport BOOTCLASSPATH %BOOTCLASSPATH%
以前设置环境变量这一段时在init.rc中的,现在放到了init.environ.rc.in,这样代码也更清晰一些。@system/core/rootdir/init.rcon initsysclktz 0loglevel 3# Backward compatibilitysymlink /system/etc /etcsymlink /sys/kernel/debug /d# Right now vendor lives on the same filesystem as system,
# but someday that may change.symlink /system/vendor /vendor# Create cgroup mount point for cpu accountingmkdir /acctmount cgroup none /acct cpuacctmkdir /acct/uidmkdir /systemmkdir /data 0771 system systemmkdir /cache 0770 system cachemkdir /config 0500 root root接下来是fs相关的几个过程,它们主要用于文件系统的挂载,下面是截取的一小部分代码:on post-fs# once everything is setup, no need to modify /mount rootfs rootfs / ro remount# mount shared so changes propagate into child namespacesmount rootfs rootfs / shared recmount tmpfs tmpfs /mnt/secure private rec# We chown/chmod /cache again so because mount is run as root + defaultschown system cache /cachechmod 0770 /cache# We restorecon /cache in case the cache partition has been reset.restorecon /cache
如果你看过以前的版本的init.rc脚本,看到这里会想起,应该还有几行:mount yaffs2 mtd@system /systemmount yaffs2 mtd@userdata /data
这两行用于挂载/system分区和/data分区到yaffs2文件系统。手机领域有多种不同的内存设备,其中
NAND闪存设备以其低功耗、重量轻、性能佳等优良特性,受到绝大多数厂商的青睐。NAND闪存采用
yaffs2文件系统。
可以看出在Android 4.4中默认已经不再使用yaffs2。在完成文件系统的创建和挂载后,完整的Android
根文件系统结构如下:

接下来看一下boot部分,该部分主要用于设置应用程序终止条件,应用程序驱动目录及文件权限等。
下面是一部分代码片段:on boot
# basic network initifup lohostname localhostdomainname localdomain# set RLIMIT_NICE to allow priorities from 19 to -20setrlimit 13 40 40# Memory management.  Basic kernel parameters, and allow the high
# level system server to be able to adjust the kernel OOM driver
# parameters to match how it is managing things.write /proc/sys/vm/overcommit_memory 1write /proc/sys/vm/min_free_order_shift 4chown root system /sys/module/lowmemorykiller/parameters/adjchmod 0664 /sys/module/lowmemorykiller/parameters/adjchown root system /sys/module/lowmemorykiller/parameters/minfreechmod 0664 /sys/module/lowmemorykiller/parameters/minfreeclass_start coreclass_start main
在on boot部分,我们可以发现许多”on property:<name> = <value>"的代码片段,这些是根据属性的
触发器,但相应的属性满足一定的条件时,就会触发相应的动作。此外,还有许多service字段,service
后面第一项表示服务的名称,第二项表示服务的路径,接下来的第2行等是服务的附加内容,配合服务使用,
主要包含运行权限、条件以及重启等相关选项。解析配置文件
前面了解了init.rc脚本的相关内容,接下来我们分析一下init进程是如何解析rc脚本的。首先,在init
进程的main()函数中调用init_parse_config_file()函数对属性进行解析,下面就来看一下这个函数:
init_parse_config_file("/init.rc");//解析init.rc配置文件
@system/core/init/init_parser.c
int init_parse_config_file(const char *fn)
{char *data;data = read_file(fn, 0);if (!data) return -1;parse_config(fn, data);DUMP();return 0;
}
read_file(fn, 0)函数将fn指针指向的路径(这里即:/init.rc)所对应的文件读取到内存中,保存为
字符串形式,并返回字符串在内存中的地址;然后parse_config会对文件进行解析,生成动作列表
(Action List)和服务列表(Service List)。关于read_file()函数的实现在
@system/core/init/util.c中。下面是parse_config()的实现:static void parse_config(const char *fn, char *s)
{struct parse_state state;struct listnode import_list;//导入链表,用于保持在init.rc中通过import导入的其他rc文件struct listnode *node;char *args[INIT_PARSER_MAXARGS];int nargs;nargs = 0;state.filename = fn;//初始化filename的值为init.rc文件state.line = 0;//初始化行号为0state.ptr = s;//初始化ptr指向s,即read_file读入到内存中的init.rc文件的首地址state.nexttoken = 0;//初始化nexttoken的值为0state.parse_line = parse_line_no_op;//初始化行解析函数list_init(&import_list);state.priv = &import_list;for (;;) {switch (next_token(&state)) {case T_EOF://如果返回为T_EOF,表示init.rc已经解析完成,则跳到parser_done解析
import进来的其他rc脚本state.parse_line(&state, 0, 0);goto parser_done;case T_NEWLINE:state.line++;//一行读取完成后,行号加1if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对
这一行进行语法解析int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个
keyword开头的,因此args[0]即表示这一行的keywordif (kw_is(kw, SECTION)) {state.parse_line(&state, 0, 0);parse_new_section(&state, kw, nargs, args);} else {state.parse_line(&state, nargs, args);}nargs = 0;//复位}break;case T_TEXT://将nexttoken解析的一个text保存到args字符串数组中,nargs的最大值为
INIT_PARSER_MAXARGS(64),即init.rc中一行最多不能超过INIT_PARSER_MAXARGS个text(单词)if (nargs < INIT_PARSER_MAXARGS) {args[nargs++] = state.text;}break;}}parser_done:list_for_each(node, &import_list) {struct import *import = node_to_item(node, struct import, list);int ret;INFO("importing '%s'", import->filename);ret = init_parse_config_file(import->filename);if (ret)ERROR("could not import file '%s' from '%s'\n",import->filename, fn);}
}
parse_config()函数,代码虽然很短,实际上却比较复杂。接下来将对其进行详细分析。首先看一
下struct parse_state的定义:struct parse_state
{char *ptr;//指针,指向剩余的尚未被解析的数据(即:ptr指向当前解析到的位置)char *text;//一行文本int line; //行号 int nexttoken;//下一行的标示,T_EOF标示文件结束,T_TEXT表示需要进行解释的文本,
T_NEWLINE标示一个空行或者是注释行void *context;//一个action或者servicevoid (*parse_line)(struct parse_state *state, int nargs, char **args);//函数指针,
指向当前行的解析函数const char *filename;//解析的rc文件void *priv;//执行import链表的指针
};next_token()以行为单位分割参数传递过来的字符串。
@system/core/init/parser.cint next_token(struct parse_state *state)
{char *x = state->ptr;char *s;if (state->nexttoken) {//nexttoken的值为0int t = state->nexttoken;state->nexttoken = 0;return t;}for (;;) {switch (*x) {case 0://到底末尾,解析完成state->ptr = x;return T_EOF;case '\n'://换行符,返回T_NEWLINE,表示下一个token是新的一行x++;state->ptr = x;return T_NEWLINE;case ' '://忽略空格、制表符等case '\t':case '\r':x++;continue;case '#'://在当前解析到的字符为#号时,将指针一直移动到#行的末尾,然后判断下一个字符
是T_NEWLINE还是T_EOFwhile (*x && (*x != '\n')) x++;//注意x++,当指针移动到#行末尾时,x执行末尾的
下一个字符if (*x == '\n') {state->ptr = x+1;return T_NEWLINE;} else {state->ptr = x;return T_EOF;}default:goto text;//解析的为普通文本}}textdone://x指向一个单词的开头位置,s指向末尾位置,将s设置为0(C字符串末尾为0),即表示单词结束state->ptr = x;*s = 0;return T_TEXT;
text:state->text = s = x;
textresume:for (;;) {switch (*x) {case 0:goto textdone;case ' ':case '\t':case '\r':x++;goto textdone;case '\n':state->nexttoken = T_NEWLINE;x++;goto textdone;case '"':x++;for (;;) {switch (*x) {case 0:/* unterminated quoted thing */state->ptr = x;return T_EOF;case '"':x++;goto textresume;default:*s++ = *x++;}}break;case '\\':x++;switch (*x) {case 0:goto textdone;case 'n':*s++ = '\n';break;case 'r':*s++ = '\r';break;case 't':*s++ = '\t';break;case '\\':*s++ = '\\';break;case '\r':/* \ <cr> <lf> -> line continuation */if (x[1] != '\n') {x++;continue;}case '\n':/* \ <lf> -> line continuation */state->line++;x++;/* eat any extra whitespace */while((*x == ' ') || (*x == '\t')) x++;continue;default:/* unknown escape -- just copy */*s++ = *x++;}continue;default:*s++ = *x++;}}return T_EOF;
}
在parse_config()中通过next_token从rc脚本中解析出一行行的rc语句,下面看一下另一个重要的函数
lookup_keyword()的实现:int lookup_keyword(const char *s)
{switch (*s++) {case 'c':if (!strcmp(s, "opy")) return K_copy;if (!strcmp(s, "apability")) return K_capability;if (!strcmp(s, "hdir")) return K_chdir;if (!strcmp(s, "hroot")) return K_chroot;if (!strcmp(s, "lass")) return K_class;if (!strcmp(s, "lass_start")) return K_class_start;if (!strcmp(s, "lass_stop")) return K_class_stop;if (!strcmp(s, "lass_reset")) return K_class_reset;if (!strcmp(s, "onsole")) return K_console;if (!strcmp(s, "hown")) return K_chown;if (!strcmp(s, "hmod")) return K_chmod;if (!strcmp(s, "ritical")) return K_critical;break;case 'd':if (!strcmp(s, "isabled")) return K_disabled;if (!strcmp(s, "omainname")) return K_domainname;break;case 'e':if (!strcmp(s, "xec")) return K_exec;if (!strcmp(s, "xport")) return K_export;break;case 'g':if (!strcmp(s, "roup")) return K_group;break;case 'h':if (!strcmp(s, "ostname")) return K_hostname;break;case 'i':if (!strcmp(s, "oprio")) return K_ioprio;if (!strcmp(s, "fup")) return K_ifup;if (!strcmp(s, "nsmod")) return K_insmod;if (!strcmp(s, "mport")) return K_import;break;case 'k':if (!strcmp(s, "eycodes")) return K_keycodes;break;case 'l':if (!strcmp(s, "oglevel")) return K_loglevel;if (!strcmp(s, "oad_persist_props")) return K_load_persist_props;break;case 'm':if (!strcmp(s, "kdir")) return K_mkdir;if (!strcmp(s, "ount_all")) return K_mount_all;if (!strcmp(s, "ount")) return K_mount;break;case 'o':if (!strcmp(s, "n")) return K_on;if (!strcmp(s, "neshot")) return K_oneshot;if (!strcmp(s, "nrestart")) return K_onrestart;break;case 'p':if (!strcmp(s, "owerctl")) return K_powerctl;case 'r':if (!strcmp(s, "estart")) return K_restart;if (!strcmp(s, "estorecon")) return K_restorecon;if (!strcmp(s, "mdir")) return K_rmdir;if (!strcmp(s, "m")) return K_rm;break;case 's':if (!strcmp(s, "eclabel")) return K_seclabel;if (!strcmp(s, "ervice")) return K_service;if (!strcmp(s, "etcon")) return K_setcon;if (!strcmp(s, "etenforce")) return K_setenforce;if (!strcmp(s, "etenv")) return K_setenv;if (!strcmp(s, "etkey")) return K_setkey;if (!strcmp(s, "etprop")) return K_setprop;if (!strcmp(s, "etrlimit")) return K_setrlimit;if (!strcmp(s, "etsebool")) return K_setsebool;if (!strcmp(s, "ocket")) return K_socket;if (!strcmp(s, "tart")) return K_start;if (!strcmp(s, "top")) return K_stop;if (!strcmp(s, "wapon_all")) return K_swapon_all;if (!strcmp(s, "ymlink")) return K_symlink;if (!strcmp(s, "ysclktz")) return K_sysclktz;break;case 't':if (!strcmp(s, "rigger")) return K_trigger;break;case 'u':if (!strcmp(s, "ser")) return K_user;break;case 'w':if (!strcmp(s, "rite")) return K_write;if (!strcmp(s, "ait")) return K_wait;break;}return K_UNKNOWN;
}
lookup_keyword()主要用解析出args中的关键字,这个函数本身没有什么特别,也非常简单,但是其实
现方法在我们自己实现类似通过switch等的查找判断时是值得借鉴的,即:先通过单词的首字母将内容分组,
在定位到哪一个组后再依次比较。这样就减少了程序中比较的次数,提高了效率。case T_NEWLINE:state.line++;//一行读取完成后,行号加1if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对这
一行进行语法解析int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个keyword
开头的,因此args[0]即表示这一行的keywordif (kw_is(kw, SECTION)) {state.parse_line(&state, 0, 0);parse_new_section(&state, kw, nargs, args);} else {state.parse_line(&state, nargs, args);}nargs = 0;//复位}break;
在parse_config()中,在找的keyword以后,接下来会判断这个keyword是否是section,是则走解析
section的逻辑,否则走其他逻辑。下面我们看一下kw_is的实现:#define kw_is(kw, type) (keyword_info[kw].flags & (type))
可以看出kw_is只不过是一个宏定义,这里又引出了keyword_info,下面让我们一起来看一下keyword的相
关定义:
关键字定义
@system/core/init/keywords.h
#ifndef KEYWORD//如果没有定义KEYWORD则执行下面的分支
//声明一些函数,这些函数即Action的执行函数
int do_chroot(int nargs, char **args);
int do_chdir(int nargs, char **args);
int do_class_start(int nargs, char **args);
int do_class_stop(int nargs, char **args);
int do_class_reset(int nargs, char **args);
int do_domainname(int nargs, char **args);
int do_exec(int nargs, char **args);
int do_export(int nargs, char **args);
int do_hostname(int nargs, char **args);
int do_ifup(int nargs, char **args);
int do_insmod(int nargs, char **args);
int do_mkdir(int nargs, char **args);
int do_mount_all(int nargs, char **args);
int do_mount(int nargs, char **args);
int do_powerctl(int nargs, char **args);
int do_restart(int nargs, char **args);
int do_restorecon(int nargs, char **args);
int do_rm(int nargs, char **args);
int do_rmdir(int nargs, char **args);
int do_setcon(int nargs, char **args);
int do_setenforce(int nargs, char **args);
int do_setkey(int nargs, char **args);
int do_setprop(int nargs, char **args);
int do_setrlimit(int nargs, char **args);
int do_setsebool(int nargs, char **args);
int do_start(int nargs, char **args);
int do_stop(int nargs, char **args);
int do_swapon_all(int nargs, char **args);
int do_trigger(int nargs, char **args);
int do_symlink(int nargs, char **args);
int do_sysclktz(int nargs, char **args);
int do_write(int nargs, char **args);
int do_copy(int nargs, char **args);
int do_chown(int nargs, char **args);
int do_chmod(int nargs, char **args);
int do_loglevel(int nargs, char **args);
int do_load_persist_props(int nargs, char **args);
int do_wait(int nargs, char **args);
#define __MAKE_KEYWORD_ENUM__//定义一个宏
/** 定义KEYWORD宏,这里KEYWORD宏中有四个参数,其各自的含义如下:* symbol表示keyword的名称(即init.rc中的关键字);* flags表示keyword的类型,包括SECTION、COMMAND和OPTION三种类型,其定义在init_parser.c中;* nargs表示参数的个数,即:该keyword需要几个参数* func表示该keyword所对应的处理函数。** KEYWORD宏虽然有四个参数,但是这里只用到了symbol,其中K_##symbol中的##表示连接的意思,* 即最后的得到的值为K_symbol。*/
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {K_UNKNOWN,
#endifKEYWORD(capability,  OPTION,  0, 0)//根据上面KEYWORD的宏定义,这一行就变成了K_capability,KEYWORD(chdir,       COMMAND, 1, do_chdir)//key_chdir,后面的依次类推KEYWORD(chroot,      COMMAND, 1, do_chroot)KEYWORD(class,       OPTION,  0, 0)KEYWORD(class_start, COMMAND, 1, do_class_start)KEYWORD(class_stop,  COMMAND, 1, do_class_stop)KEYWORD(class_reset, COMMAND, 1, do_class_reset)KEYWORD(console,     OPTION,  0, 0)KEYWORD(critical,    OPTION,  0, 0)KEYWORD(disabled,    OPTION,  0, 0)KEYWORD(domainname,  COMMAND, 1, do_domainname)KEYWORD(exec,        COMMAND, 1, do_exec)KEYWORD(export,      COMMAND, 2, do_export)KEYWORD(group,       OPTION,  0, 0)KEYWORD(hostname,    COMMAND, 1, do_hostname)KEYWORD(ifup,        COMMAND, 1, do_ifup)KEYWORD(insmod,      COMMAND, 1, do_insmod)KEYWORD(import,      SECTION, 1, 0)KEYWORD(keycodes,    OPTION,  0, 0)KEYWORD(mkdir,       COMMAND, 1, do_mkdir)KEYWORD(mount_all,   COMMAND, 1, do_mount_all)KEYWORD(mount,       COMMAND, 3, do_mount)KEYWORD(on,          SECTION, 0, 0)KEYWORD(oneshot,     OPTION,  0, 0)KEYWORD(onrestart,   OPTION,  0, 0)KEYWORD(powerctl,    COMMAND, 1, do_powerctl)KEYWORD(restart,     COMMAND, 1, do_restart)KEYWORD(restorecon,  COMMAND, 1, do_restorecon)KEYWORD(rm,          COMMAND, 1, do_rm)KEYWORD(rmdir,       COMMAND, 1, do_rmdir)KEYWORD(seclabel,    OPTION,  0, 0)KEYWORD(service,     SECTION, 0, 0)KEYWORD(setcon,      COMMAND, 1, do_setcon)KEYWORD(setenforce,  COMMAND, 1, do_setenforce)KEYWORD(setenv,      OPTION,  2, 0)KEYWORD(setkey,      COMMAND, 0, do_setkey)KEYWORD(setprop,     COMMAND, 2, do_setprop)KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)KEYWORD(setsebool,   COMMAND, 2, do_setsebool)KEYWORD(socket,      OPTION,  0, 0)KEYWORD(start,       COMMAND, 1, do_start)KEYWORD(stop,        COMMAND, 1, do_stop)KEYWORD(swapon_all,  COMMAND, 1, do_swapon_all)KEYWORD(trigger,     COMMAND, 1, do_trigger)KEYWORD(symlink,     COMMAND, 1, do_symlink)KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)KEYWORD(user,        OPTION,  0, 0)KEYWORD(wait,        COMMAND, 1, do_wait)KEYWORD(write,       COMMAND, 2, do_write)KEYWORD(copy,        COMMAND, 2, do_copy)KEYWORD(chown,       COMMAND, 2, do_chown)KEYWORD(chmod,       COMMAND, 2, do_chmod)KEYWORD(loglevel,    COMMAND, 1, do_loglevel)KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)KEYWORD(ioprio,      OPTION,  0, 0)
#ifdef __MAKE_KEYWORD_ENUM__KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD//取消KEYWORD宏的定义
#endif
看一下keyword在init_parse.c中是如何被使用的:#include "keywords.h"#define KEYWORD(symbol, flags, nargs, func) \[ K_##symbol ] = { #symbol, func, nargs + 1, flags, },struct {const char *name;//关键字的名称int (*func)(int nargs, char **args);//对应关键字的处理函数unsigned char nargs;//参数个数,每个关键字的参数个数是固定的unsigned char flags;//关键字属性,包括:SECTION、OPTION和COMMAND,其中COMMAND有对应的处理
函数,见keyword的定义。
} keyword_info[KEYWORD_COUNT] = {[ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
#include "keywords.h"
};
#undef KEYWORD#define kw_is(kw, type) (keyword_info[kw].flags & (type))
#define kw_name(kw) (keyword_info[kw].name)
#define kw_func(kw) (keyword_info[kw].func)
#define kw_nargs(kw) (keyword_info[kw].nargs)
从上面的代码我们看到一个很有意思的地方,keyword.h头文件被包含引用了两次。
第一次包含keywords.h时,它声明了一些诸如do_class_start的函数,另外还定义了一个枚举,枚举值为
K_class、K_mkdir等关键字。
第二次包含keywords.h后,得到了keyword_info结构体数组,这个keyword_info结构体数组以前定义的枚
举值为索引,存储对应的关键字信息。
flags的取值也在init_parse.c中定义:
#define SECTION 0x01
#define COMMAND 0x02
#define OPTION  0x04在了解了keyword后,下面我们继续来分析rc脚本的解析,让我们回到之前的代码,继续分析。case T_NEWLINE:state.line++;//一行读取完成后,行号加1if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对
这一行进行语法解析int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个keyword
开头的,因此args[0]即表示这一行的keywordif (kw_is(kw, SECTION)) {state.parse_line(&state, 0, 0);parse_new_section(&state, kw, nargs, args);} else {state.parse_line(&state, nargs, args);}nargs = 0;//复位}break;
解析section的函数为parse_new_section,其实现为:
void parse_new_section(struct parse_state *state, int kw,int nargs, char **args)
{printf("[ %s %s ]\n", args[0],nargs > 1 ? args[1] : "");switch(kw) {case K_service://解析Servicestate->context = parse_service(state, nargs, args);//当service_list中不存在同名
service时,执行新加入service_list中的serviceif (state->context) {//service为新增加的service时,即:<span style="font-family:
Arial, Helvetica, sans-serif;">service_list中不存在同名service</span>state->parse_line = parse_line_service;//制定解析service行的函数为<span
style="font-family: Arial, Helvetica, sans-serif;">parse_line_service</span>return;}break;case K_on://解析sectionstate->context = parse_action(state, nargs, args);if (state->context) {state->parse_line = parse_line_action;return;}break;case K_import://解析importparse_import(state, nargs, args);break;}state->parse_line = parse_line_no_op;
}
先看一下service的解析:static void *parse_service(struct parse_state *state, int nargs, char **args)
{struct service *svc;//保持Service相关信息if (nargs < 3) {parse_error(state, "services must have a name and a program\n");return 0;}if (!valid_name(args[1])) {parse_error(state, "invalid service name '%s'\n", args[1]);return 0;}//service_list中是否已存在同名service<span style="white-space:pre"> </span>svc = service_find_by_name(args[1]);if (svc) {//<span style="font-family: Arial, Helvetica, sans-serif;">如果已存在同名
service则直接返回,不再做其他操作</span>parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);return 0;}nargs -= 2;svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);if (!svc) {parse_error(state, "out of memory\n");return 0;}svc->name = args[1];svc->classname = "default";//设置classname为“default”memcpy(svc->args, args + 2, sizeof(char*) * nargs);svc->args[nargs] = 0;svc->nargs = nargs;svc->onrestart.name = "onrestart";list_init(&svc->onrestart.commands);list_add_tail(&service_list, &svc->slist);//将service添加到全局链表service_list中return svc;
}
init中使用了一个叫做service的结构体来保存与service相关的信息。
@system/core/init/init.h
struct service {/* list of all services */struct listnode slist;//双向链表const char *name;//service的名字const char *classname;//service所属class的名字,默认是“default”unsigned flags;//service的属性pid_t pid;//进程号time_t time_started;    /* time of last start 上一次启动的时间*/time_t time_crashed;    /* first crash within inspection window 第一次死亡的时间*/int nr_crashed;         /* number of times crashed within window 死亡次数*/uid_t uid;gid_t gid;gid_t supp_gids[NR_SVC_SUPP_GIDS];size_t nr_supp_gids;char *seclabel;struct socketinfo *sockets;//有些service需要使用socket,socketinfo用来描述socket相关
信息struct svcenvinfo *envvars;//service一般运行在一个单独的进程中,envvars用来描述创建这
个进程时所需的环境变量信息//关键字onrestart标示一个OPTION,可是onrestart后面一般跟着COMMAND,下面这个action结构体
可用来存储command信息struct action onrestart;  /* Actions to execute on restart. *//* keycodes for triggering this service via /dev/keychord */int *keycodes;int nkeycodes;int keychord_id;int ioprio_class;int ioprio_pri;int nargs;//参数个数/* "MUST BE AT THE END OF THE STRUCT" */char *args[1];//用于存储参数
}; /*     ^-------'args' MUST be at the end of this struct! */
从parse_service函数可以看出,它的作用就是讲service添加到service_list列表中,并制定解析函数
为parse_line_service,也就是说具体的service的解析靠的是parse_line_service方法。static void parse_line_service(struct parse_state *state, int nargs, char **args)
{struct service *svc = state->context;struct command *cmd;int i, kw, kw_nargs;if (nargs == 0) {return;}svc->ioprio_class = IoSchedClass_NONE;kw = lookup_keyword(args[0]);switch (kw) {case K_capability:break;case K_class:if (nargs != 2) {parse_error(state, "class option requires a classname\n");} else {svc->classname = args[1];}break;case K_console:svc->flags |= SVC_CONSOLE;break;case K_disabled:svc->flags |= SVC_DISABLED;svc->flags |= SVC_RC_DISABLED;break;case K_ioprio:if (nargs != 3) {parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");} else {svc->ioprio_pri = strtoul(args[2], 0, 8);if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {parse_error(state, "priority value must be range 0 - 7\n");break;}if (!strcmp(args[1], "rt")) {svc->ioprio_class = IoSchedClass_RT;} else if (!strcmp(args[1], "be")) {svc->ioprio_class = IoSchedClass_BE;} else if (!strcmp(args[1], "idle")) {svc->ioprio_class = IoSchedClass_IDLE;} else {parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");}}break;case K_group:if (nargs < 2) {parse_error(state, "group option requires a group id\n");} else if (nargs > NR_SVC_SUPP_GIDS + 2) {parse_error(state, "group option accepts at most %d supp. groups\n",NR_SVC_SUPP_GIDS);} else {int n;svc->gid = decode_uid(args[1]);for (n = 2; n < nargs; n++) {svc->supp_gids[n-2] = decode_uid(args[n]);}svc->nr_supp_gids = n - 2;}break;case K_keycodes:if (nargs < 2) {parse_error(state, "keycodes option requires atleast one keycode\n");} else {svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));if (!svc->keycodes) {parse_error(state, "could not allocate keycodes\n");} else {svc->nkeycodes = nargs - 1;for (i = 1; i < nargs; i++) {svc->keycodes[i - 1] = atoi(args[i]);}}}break;case K_oneshot:svc->flags |= SVC_ONESHOT;break;case K_onrestart:nargs--;args++;kw = lookup_keyword(args[0]);if (!kw_is(kw, COMMAND)) {parse_error(state, "invalid command '%s'\n", args[0]);break;}kw_nargs = kw_nargs(kw);if (nargs < kw_nargs) {parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,kw_nargs > 2 ? "arguments" : "argument");break;}cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);cmd->func = kw_func(kw);cmd->nargs = nargs;memcpy(cmd->args, args, sizeof(char*) * nargs);list_add_tail(&svc->onrestart.commands, &cmd->clist);break;case K_critical:svc->flags |= SVC_CRITICAL;break;case K_setenv: { /* name value */struct svcenvinfo *ei;if (nargs < 2) {parse_error(state, "setenv option requires name and value arguments\n");break;}ei = calloc(1, sizeof(*ei));if (!ei) {parse_error(state, "out of memory\n");break;}ei->name = args[1];ei->value = args[2];ei->next = svc->envvars;svc->envvars = ei;break;}case K_socket: {/* name type perm [ uid gid ] */struct socketinfo *si;if (nargs < 4) {parse_error(state, "socket option requires name, type, perm arguments\n");break;}if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")&& strcmp(args[2],"seqpacket")) {parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");break;}si = calloc(1, sizeof(*si));if (!si) {parse_error(state, "out of memory\n");break;}si->name = args[1];si->type = args[2];si->perm = strtoul(args[3], 0, 8);if (nargs > 4)si->uid = decode_uid(args[4]);if (nargs > 5)si->gid = decode_uid(args[5]);si->next = svc->sockets;svc->sockets = si;break;}case K_user:if (nargs != 2) {parse_error(state, "user option requires a user id\n");} else {svc->uid = decode_uid(args[1]);}break;case K_seclabel:if (nargs != 2) {parse_error(state, "seclabel option requires a label string\n");} else {svc->seclabel = args[1];}break;default:parse_error(state, "invalid option '%s'\n", args[0]);}
}
可以看出parse_line_service中会根据keyword找的对应的keyword的处理函数,具体进程处理。
section的处理与service类似,通过分析init.rc的解析过程,我们知道,所谓的解析就是将rc脚本中
的内容通过解析,填充到service_list和action_list中去。那他们是在哪里进行调用的呢,让我们回
忆一下init进程中main函数的实现。INFO("reading config file\n");init_parse_config_file("/init.rc");//解析init.rc配置文件action_for_each_trigger("early-init", action_add_queue_tail);queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");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_each_trigger("init", action_add_queue_tail);/* skip mounting filesystems in charger mode */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_tail);action_for_each_trigger("post-fs-data", action_add_queue_tail);}OK,到这里init.rc脚本的解析就完了。

Android内核开发:图解Android系统的启动过程

第一阶段:Android设备上电后,首先会从处理器片上ROM的启动引导代码开始执行,片上ROM会寻找
Bootloader代码,并加载到内存。(这一步由“芯片厂商”负责设计和实现)第二阶段:Bootloader开始执行,首先负责完成硬件的初始化,然后找到Linux内核代码,并加载到内存。
(这一步由“设备厂商”负责设计和实现)第三阶段:Linux内核开始启动,初始化各种软硬件环境,加载驱动程序,挂载根文件系统,并执行init程序,
由此开启Android的世界。(这一步则是Android内核开发过程中需要涉及的地方)Android系统以及各大Linux的发行版,他们的Linux内核部分启动过程都是差不多的,他们之间最大的区别
就在于init程序的不同,因为init程序决定了系统在启动过程中,究竟会启动哪些守护进程和服务,以及呈
现出怎样的一个用户UI界面。因此,init程序是分析Android启动过程中最核心的程序。对应的代码位于:system/core/init/init.c,工作内容如图所示

init程序最核心的工作主要有3点:(1) 创建和挂载一些系统目录/设备节点,设置权限,如:/dev, /proc, and /sys(2) 解析 init.rc 和 init.<hardware>.rc,并启动属性服务,以及一系列的服务和进程。(3) 显示boot logo,默认是“Android”字样其中,最重要的步骤是第二步,一系列的Android服务在这时被启动起来,其实Android系统的启动最重要
的过程也就是各个系统服务的启动,因为系统所有的功能都是依赖这些服务来完成的,比如启动应用程序,
拨打电话,使用WIFI或者蓝牙,播放音视频等等,只要这些服务都能正常地启动起来并且正常工作,整个
Android系统也就完成了自己的启动。这些服务包含2部分,一部分是本地服务,另一部分是Android服务,所有的这些服务都会向ServiceManager
进程注册,由它统一管理,这些服务的启动过程介绍如下:(1)本地服务本地服务是指运行在C++层的系统守护进程,一部分本地服务是init进程直接启动的,它们定义在init.rc
脚本和init.<hardware>.rc中,如 ueventd、servicemanager、debuggerd、rild、mediaserver等。
还有一部分本地服务,是由这些本地服务进一步创建的,如mediaserver服务会启动AudioFlinger,
MediaPlayerService, 以及 CameraService 等本地服务。我们可以通过查看init.rc和init.<hardware>.rc文件找出具体有哪些本地服务被init进程直接启动了,
这些文件的位置:system/core/rootdir/注意,每一个由init直接启动的本地服务都是一个独立的Linux进程,在系统启动以后,我们通过
adb shell命令进入手机后,输入top命令就可以查看到这些本地进程的存在:

(2)Android服务Android服务是指运行在Dalvik虚拟机进程中的服务,这些服务的创建过程描述如下:init进程会执行app_process程序,创建Zygote进程,它是Android系统最重要的进程,所有后续的
Android应用程序都是由它fork出来的。Zygote进程会首先fork出"SystemServer"进程,"SystemServer"进程的全部任务就是将所有的Android
核心服务启动起来,这些服务包括:

当所有的服务都启动完毕后,SystemServer会打印出“Making services ready”,然后通过
ActivityManager启动Home界面,并发送“ACTION_BOOT_COMPLETED”广播消息。注意,这些Android服务并没有各种运行在独立的进程中,它们由SystemServer以线程的方式创建,
所以都运行在同一个进程中,即SystemServer进程中。(3) 小结上面的几张图我总觉得还不够尽兴,因此文章最后,再给出一张图帮助大家加深一下理解

关于图解Android系统的启动过程就介绍到这里了

8.学习Camera之——Android开机流程相关推荐

  1. Android 开机流程

    Android 开机流程 Android 系统启动流程 1.启动电源即系统启动: 当电源键按下时引导芯片代码从预定义的地方(固化在ROM)开始执行.加载引导程序BootLoader到RAM中,然后执行 ...

  2. Android开机流程(一)

    参考文章 Android系统开篇 一.概述 简单梳理下Andorid启动流程. 一般操作系统启动流程如下图: Android系统启动流程概览: system_server服务启动流程 启动过程: Lo ...

  3. Android:开机流程

    流程顺序: 开机 -> BootLoader(引导芯片) -> Linux Kernel -> init进程 -> JVM和SystemServer等 -> Binder ...

  4. 13.学习Camera之——Android Treble架构解析

    本文主要介绍Treble架构下的HAL&HIDL&Binder相关技术原理. 1. Treble 简介 Android 8.0 版本的一项新元素是 Project Treble.这是 ...

  5. Android 开机流程解析

    目录 第一章 概述 第二章 Init启动 第三章 Android关键进程启动 第一章 概述 开机作为使用手机的第一步操作,在长按电源键之后到我们可视化可操作的界面中间包含了很多任务,诸如文件系统挂载. ...

  6. Android开机流程

    Zygote进程 在init.rc中有 service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-sys ...

  7. Android开机时长优化

    文章目录 一.背景说明 二.开机流程介绍 三.分析方法&工具 3.1 手动秒表计时 3.2 bootchart 3.2.1 生成log文件 3.2.2 生成bootchart.png 3.2. ...

  8. Android开机启动流程简析

    Android开机启动流程简析 (一) 文章目录 Android开机启动流程简析 (一) 前言 一.开机启动的流程概述 二.Android的启动过程分析 (1).总体流程 init简述 Zygote简 ...

  9. Android上电开机流程

    Android的开机流程  分类: Android  转:  1.    体系勾引bootloader  1)        源码:bootable/bootloader/*  2)        申 ...

最新文章

  1. 模板 - C++ STL
  2. 20172319 实验二《树》实验报告
  3. GNN笔记: random walk
  4. 信息系统项目管理师学习方法
  5. linux 创建匿名ftp,Linux使用pure-ftpd建立匿名ftp的方法
  6. echars显示折点数据_数据可视化的基础语法
  7. POJ 1804 Brainman (归并排序 -- 求逆序对数)
  8. 从单体架构迁移到微服务,8个关键的思考、实践和经验
  9. java字符排序_如何按字母顺序对字符串进行排序java
  10. Android组件化demo实现以及遇坑分享
  11. oracle清除过期备份,rman delete obsolete删除过期备份集问题
  12. 嵌入式蓝桥杯G431RBT6 串口配置及使用方法cube MX
  13. 安卓Vitamio播放课程视频
  14. 正和游戏:加密协议正在重塑公共产品 |链捕手
  15. MS中Perl脚本实现原子随机掺杂(或生成空位)
  16. 去掉每行行首空格及行尾空格,无空行的方法
  17. HCIE(华为Eth-trunk和E-trunk)
  18. 【分享】这些年,我使用的一些工具[非开发类]
  19. Gazebo-Ros搭建小车和场景并运行slam算法进行建图0--整体描述和资源
  20. 打造亚秒级页面加载速度网店实践经验

热门文章

  1. 物联网安全知识点总结--第一章 物联网概述
  2. iOS msgSend消息发送流程
  3. 基于pytorch的人脸识别脚本(python示例)
  4. 宅家自学量化投资 - 建立股票数据库 (上)
  5. Outlook将邮件内容中的数据以表格形式发送
  6. 【Flutter】动画学习(二) Animation
  7. 格杀勿论无法连接计算机,弱电工程双绞线使用注意点及常见问题汇总
  8. 2021级高考成绩查询四川绵阳,2021绵阳市地区高考成绩排名查询,绵阳市高考各高中成绩喜报榜单...
  9. kernel下制作动态logo
  10. WEB安全防御总结 : 列举漏洞及修复建议