Android系统启动概述

Android系统开机流程基于Linux系统,总体可分为三个阶段:

Boot Loader引导程序启动
Linux内核启动
Android系统启动,Launcher/app启动

启动流程如图1形象展示:

图1 Android开机启动一般性流程

图1只简单地描述了开机启动一般性流程,“正常开机”(注意,是正常模式,不是工厂模式、recovery模式)流程为:

1. 手机、TV等android设备上电或重启后,系统硬件进行相应的复位操作,然后CPU开始执行第一条指令,该指令固化在ROM或者flash中某地址处,不可更改,由芯片制造商确定,该指令的作用就是加载引导程序到RAM执行。

2. 引导程序(boot loader),顾名思义,引导操作系统(比如Linux、Android、windows等)启动的程序,
就是在操作系统运行之前运行的一段程序,其作用是初始化硬件设备、创建存储器空间的映射等软件运行时所需要的最小环境;加载Linux内核镜像文件(本文只针对Android、Linux)到RAM中某个地址处执行,此时引导程序的控制权就交给了内核。
各家厂商都有可能自行设计boot loader,常见的有:U-Boot、RedBoot、ARMBoot等。

3. 当内核镜像文件被加载到RAM时,通过汇编编写的程序初始化硬件、堆栈、进行一些必要的环境设置等,然后调用decompress_kernel函数解压内核镜像,再调用c语言编写的start_kernel函数启动内核,内核启动时,会进行一些列初始化工作,包括:初始化调度程序、内存管理区、日期时间、缓存、中断等,再创建init内核线程,最后调用可执行程序init执行。

4. init进程启动后,创建、挂载文件系统、设备节点,解析init.rc文件,再启动各种系统守护进程,包括Android部分最重要的Zygote、ServiceManager进程,由此进入到Android系统启动部分。无论什么Linux发行版本还是Android系统,init进程都是用户空间的第一个进程(不是内核空间),其进程号固定为1,init进程是通向Linux、Android文件系统的大门,其他用户级进程都由init进程直接、间接创建,本文主要关注init进程的来龙去脉。

启动init进程

在kernel/init/路径下,main.c文件中的start_kernel函数就是启动内核的入口,经过一些列的初始化操作后进入到rest_init:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
staticnoinline void__init_refok rest_init(void)
{
intpid;
rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init,NULL,CLONE_FS|CLONE_SIGHAND);
numa_default_policy();
pid=kernel_thread(kthreadd,NULL,CLONE_FS|CLONE_FILES);
rcu_read_lock();
kthreadd_task=find_task_by_pid_ns(pid,&init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}

kernel_thread函数调用do_fork创建了2号内核线程kthreadd、1号内核线程init,他们的父进程是内核0号进程,2号内核线程kthreadd作用是管理调度其他内核线程,而init内核线程通过调用init可执行程序转变成init进程,进程号还是1,kernel_thread函数第一个参数就是kernel_init函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
staticint__ref kernel_init(void*unused)
{
kernel_init_freeable();
/* 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();
flush_delayed_fput();
if(ramdisk_execute_command){
if(!run_init_process(ramdisk_execute_command))
return0;
pr_err("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){
if(!run_init_process(execute_command))
return0;
pr_err("Failed to execute %s.  Attempting defaults...\n",
execute_command);
}
if(!run_init_process("/sbin/init")||
   !run_init_process("/etc/init")||
   !run_init_process("/bin/init")||
   !run_init_process("/bin/sh"))
return0;
panic("No init found.  Try passing init= option to kernel. "
     "See Linux Documentation/init.txt for guidance.");
}

第一行就是kernel_init_freeable函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
staticnoinline void__init kernel_init_freeable(void)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/* Now the scheduler is fully set up and can do blocking allocations */
gfp_allowed_mask=__GFP_BITS_MASK;
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_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((constchar__user *)"/dev/console",O_RDWR,0)<0)
pr_err("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((constchar__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..
*/
/* rootfs is available now, try loading default modules */
load_default_modules();
}

wait_for_completion(&kthreadd_done);

kernel_init函数在内核线程init中执行,执行前必须等待kthreadd线程建立好后才能进行,随后进行内存页分配等工作。

do_basic_setup();

在执行此函数之前,CPU、内存管理、进程管理等已经初始化完成,但是和设备相关的工作还没有展开,而此函数就是进行设备相关的初始化,重点包括初始化设备驱动程序(编译进内核的),这由driver_init函数完成。由此可知,在前文讨论了诸多初始化,都没有涉及到驱动,一直等到内核线程init创建时才开始。

1
2
if(!ramdisk_execute_command)
   ramdisk_execute_command="/init";

一开始,ramdisk_execute_command为空,被初始化为”/init”,默认init进程的路径在根目录下

返回到kernel_init函数继续看

1
2
3
4
5
if(ramdisk_execute_command){
if(!run_init_process(ramdisk_execute_command))
return0;
pr_err("Failed to execute %s\n",ramdisk_execute_command);
}

ramdisk_execute_command已经被赋值为”/init”不为空,调用run_init_process处理init可执行程序,run_init_process函数中的do_execve就是系统调用execve的具体实现,作用是运行可执行程序。

1
2
3
4
5
6
7
8
if(!run_init_process("/sbin/init")||
   !run_init_process("/etc/init")||
   !run_init_process("/bin/init")||
   !run_init_process("/bin/sh"))
   return0;
   panic("No init found.  Try passing init= option to kernel. "
     "See Linux Documentation/init.txt for guidance.");

如果根目录下没有init可执行程序,就会在”/sbin”、”/etc”、”/bin”下查找,直到找到后执行,如果都没有找到,系统尝试建立一个交互的shell命令可执行程序,让管理员尝试修复。如果sh也没有,此时Android系统启动失败,调用panic把错误提示保存到磁盘中,待重启后显示。

注:本文关于内核部分基于3.1版本所述,与2.6有差别,比如没有init_post函数,但核心内容没有太大区别。

init进程执行过程

上文通过内核启动init可执行程序,内核把控制权交到了用户空间,开始真正的Android之旅!init进程源码所在路径:

system\core\init

查看Android.mk文件,有这两句:

LOCAL_MODULE:= init   // 编译后生成的模块的名字,具体是什么模块,看include关键字编译成什么

include $(BUILD_EXECUTABLE) //编译成可执行程序

init进程主要代码是init.c,标准c语言写的程序,其main函数是入口,init.c源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
intmain(intargc,char**argv)
{
   intfd_count=0;
   structpollfd ufds[4];
   char*tmpdev;
   char*debuggable;
   chartmp[32];
   intproperty_set_fd_init=0;
   intsignal_fd_init=0;
   intkeychord_fd_init=0;
   boolis_charger=false;
   if(!strcmp(basename(argv[0]),"ueventd"))
       returnueventd_main(argc,argv);
   if(!strcmp(basename(argv[0]),"watchdogd"))
       returnwatchdogd_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));
       /* 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();
   klog_init();
   property_init();
   get_hardware_name(hardware,&revision);
   process_kernel_cmdline();
   if(is_initselinux())
   {
       union selinux_callback cb;
       cb.func_log=log_callback;
       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");
   property_load_boot_defaults();
   INFO("reading config file\n");
   init_parse_config_file("/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);
   /* 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");
   /* Don't mount filesystems or start core system services if in charger mode. */
   if(is_charger){
       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(queue_property_triggers_action,"queue_property_triggers");
#if BOOTCHART
   queue_builtin_action(bootchart_init_action,"bootchart_init");
#endif
   for(;;){
       intnr,i,timeout=-1;
       execute_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 BOOTCHART
       if(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();
               elseif(ufds[i].fd==get_keychord_fd())
                   handle_keychord();
               elseif(ufds[i].fd==get_signal_fd())
                   handle_signal();
           }
       }
   }
   return0;
}

init进程主要做的事情都在main函数中,前两个语句:

1
2
3
4
5
if(!strcmp(basename(argv[0]),"ueventd"))
     returnueventd_main(argc,argv);
if(!strcmp(basename(argv[0]),"watchdogd"))
     returnwatchdogd_main(argc,argv);

libc库函数basename去除诸如:

/sbin/ueventd

前面的斜杠和前缀,获得字符串”ueventd”,如果argv数组包含该参数,就执行ueventd_main函数。ueventd也是一个可执行程序,在system\core\init\Android.mk中:

1
2
3
4
# Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init
SYMLINKS:=\
$(TARGET_ROOT_OUT)/sbin/ueventd\
$(TARGET_ROOT_OUT)/sbin/watchdogd

这段代码编译后生成三个可执行文件:/init、/sbin/ueventd、/sbin/watchdogd。SYMLINKS关键字确定ueventd和watchdogd可执行程序文件作为init的软链接(也称符号链接)存在,查看运行环境:

1
2
3
4
5
6
root@soniq_v600:/# ls -l  sbin/                                              
-rwxr-xr-xcompass  radio      3098442015-12-3014:16adbd
-rwxr-xr-xcompass  radio      3247962015-12-3014:16healthd
-rwxr-xr-xcompass  radio      1699842015-12-3014:16mkfs.f2fs
lrwxrwxrwx compass  radio             2015-12-3014:17ueventd->../init
lrwxrwxrwx compass  radio             2015-12-3014:17watchdogd->../init

ueventd和watchdogd都指向了init程序。init程序运行时,实际上同时运行了三个程序,之所以把ueventd和watchdogd作为init进程的软链接,是因为这个三个进程共享了共同资源,放在同一份代码中即可,不用额外再写出分别针对ueventd和watchdogd的程序,这样造成了代码的冗余,也不便于维护。但是,放在同一份代码中如何区别当前进程是哪一个?这就是作者在main函数开头用了两个if语句的原因,通过进程名字判断到底是哪个进程。

ueventd进程用来管理设备,如果有新设备插入,就会在/dev创建对应的设备文件;watchdogd进程是看门狗程序,每隔一段时间通过系统调用write向内核看门狗设备发一个信息,以确保系统正常运行。

1
2
3
4
5
6
7
8
9
10
   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、devpts、profs、sysfs分别挂载/dev、/dev/pts、/proc、/sys目录下。

sysfs是一种基于内存的虚拟文件系统,在内核中产生,作用是把设备、驱动等内核信息从内核空间输出到用户空间,也可以对设备和驱动程序做设置。一般情况下,该文件系统挂在在/sys目录,但不是强制规定,也可以挂载在其他位置。sysfs文件内容以二进制、ASCII格式保存,一个文件只保存一个数据。

procfs文件系统(进程文件系统,procfs:process data filesystem),装载在/proc目录,也可以在其他位置,也是一种基于内存的虚拟文件系统,通过内核生成与系统状态、配置相关信息,其信息不能从块设备读取,只有在读取文件内容时,动态生成相应的内容。

所谓动态生成相应内容,就是在用户访问某信息时,实时生成相关信息供用户访问。
比如,ls -l proc/version 返回
-r-r-r- root root 0 2015-12-29 16:45 version
字节大小为0,没有任何内容
但是如果用cat proc/version 返回
Linux version 3.10.0_s5 (abc@H58M-SERVER) (gcc version 4.4.1 (Hisilicon_v200(gcc4.4-290+glibc-2.11+eabi+nptl)) ) #3 SMP Wed Dec 23 10:42:11 CST 2015

确实有数据的,这是内核实时产生的有关系统内核版本,系统固件版本名称等,该信息从内核内存的数据结构中实时获取而来,这就是动态生成信息的本意。

tmpfs也是一种虚拟内存文件系统,最大特点是其存储空间在VM(虚拟内存),而VM大小最大可达到实际内存+swap交换分区。
tmpfs的作用:linux中把一些程序的临时文件放在tmpfs中,利用tmpfs比硬盘速度快的特点提升系统性能。

devfs是一种设备文件系统,作用是提供一种高效率方式管理通常位于 /dev 的所有块设备和字符设备。

1
get_hardware_name(hardware,&revision);

既然procfs文件系统已经挂载,就可以使用了,get_hardware_name函数从/proc/cpuinfo中读取处理器、硬件版本号、序列号等信息。

1
process_kernel_cmdline

从/proc/cmdline中获取内核命令参数并保存到相应的属性中

1
2
3
4
property_get("ro.bootmode",tmp);
strlcpy(bootmode,tmp,sizeof(bootmode));
is_charger=!strcmp(bootmode,"charger");

property_get从属性ro.bootmode中获得值保存到tmp中,再用strlcpy库函数把tmp值拷贝到bootmode中,strcmp比较是否等于”charge”,如果是,is_charger为true,代表充电模式,否则,非充电模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
property_load_boot_defaults();
voidproperty_load_boot_defaults(void)
{
   load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT,NULL);
}
staticvoidload_properties_from_file(constchar*fn,constchar*filter)
{
   char*data;
   unsignedsz;
   data=read_file(fn,&sz);
   if(data!=0){
       load_properties(data,filter);
       free(data);
   }
}
staticvoidload_properties(char*data,constchar*filter)
{
   ...
   ...
   property_set(key,value);
}

property_load_boot_defaults对系统属性进行初始化,一开始调用了load_properties_from_file,第一个参数PROP_PATH_RAMDISK_DEFAULT的位置在

1
2
3
/bionic/libc/include/sys/_system_properties.h
#define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"

函数load_properties读取属性文件default.prop中每一个属性key和value,通过property_set把值都保存到相应的key中。default.prop中都是系统属性,系统编译后会copy到root/default.prop中,其原始位置在/build/core/main.mk中。

1
2
3
4
5
6
7
8
9
10
11
12
13
init_parse_config_file("/init.rc");
intinit_parse_config_file(constchar*fn)
{
char*data;
data=read_file(fn,0);
if(!data)return-1;
parse_config(fn,data);
DUMP();
return0;
}

解析init.rc文件,read_file读取init.rc文件内容,保存到缓冲区,返回缓冲区指针。

1
parse_config(fn,data);

parse_config中有一个for循环,循环读取每一行行内容,parse_new_section里面的parse_service、parse_line_service解析service;parse_action、parse_line_action解析Action。解析的最后一句是:

1
2
3
list_add_tail(&service_list,&svc->slist);
list_add_tail(&action_list,&act->alist);

分别把解析得到的service段和action段的地址放入到service_list列表和action_list列表保存起来,待后面使用时取出来。action段就是以on关键字开头的一部分命令组成的区域,比如:on early-init、on init及其后面跟随的各种命令,service段以service关键字开头的及其属性选项构成了一个section段,比如:

1
2
3
4
5
6
7
8
9
10
11
service ueventd/sbin/ueventd
   classcore
   critical
   seclabelu:r:ueventd:s0
service logd/system/bin/logd
   classcore
   socket logd stream0666logd logd
   socket logdr seqpacket0666logd logd
   socket logdw dgram0222logd logd
   seclabelu:r:logd:s0

分别表示uevented服务段、logd服务段,其他依次类推。

1
action_for_each_trigger("early-init",action_add_queue_tail);

action_for_each_trigger函数根据给定的action段字符串如early-init、init、late-init、boot等,在action_list中查询相应的action段,把qlist插入到action_queue尾部,就是把action段放到即将要执行的列表中,待执行。action_for_each_trigger把相应的段按顺序加入到队列中,其先后执行顺序就决定了段action段的执行顺序,因此,on early-init段最先执行,其次是on init、on charge(必须在充电模式下才能起作用)、on late-init等。

queue_builtin_action的作用和action_for_each_trigger差不多,主要处理以on property开头的属性服务相关的action。

进程执行到此,init.rc文件已经解析完毕,解析过程中把action段、service段、属性段等放到了“即将执行队列”待执行。

进程继续执行到一个for无限循环,从“即将执行队列”中取出action段来执行。执行者是execute_one_command函数,该函数先取出action中的command,再调用:

1
ret=cur_command->func(cur_command->nargs,cur_command->args);

func函数是parse_line_action中得到的,如下所示,把func赋值为kw_func(kw)

1
cmd->func=kw_func(kw);

kw_func是一个宏

1
#define kw_func(kw) (keyword_info[kw].func)

然后继续找下去直到core\init\Keywords.h,在keywords.h中找到每个action中的command都有对应执行函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum{
   K_UNKNOWN,
#endif
   KEYWORD(capability,  OPTION,  0,0)
   KEYWORD(chdir,       COMMAND,1,do_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(enable,      COMMAND,1,do_enable)
   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(restorecon_recursive,  COMMAND,1,do_restorecon_recursive)
   KEYWORD(rm,          COMMAND,1,do_rm)
   KEYWORD(rmdir,       COMMAND,1,do_rmdir)
   KEYWORD(tsfastboot,  COMMAND,0,do_tsfastboot)
   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(load_all_props,        COMMAND,0,do_load_all_props)
   KEYWORD(ioprio,      OPTION,  0,0)

比如,在init.rc中开头

1
2
3
4
5
6
7
on early-init
# Set init and its forked children's oom_adj.
write/proc/1/oom_score_adj-1000
...
start ueventd
# create mountpoints
mkdir/mnt0775root system
1
2
3
on boot
...
class_start core

start ueventd对应这do_start、mkdir对应着do_mkdir函数,class_start core对应着do_class_start等。在start命令对应的do_start函数中,当service_start函数执行到如下语句时:

1
2
3
if(execve(svc->args[0],(char**)svc->args,(char**)ENV)<0){
   ERROR("cannot execve('%s'): %s\n",svc->args[0],strerror(errno));
}

系统会调用execve函数执行可执行程序。对于start ueventd来说,就是执行 ueventd程序,开始启动 ueventd进程。除此之外,chmod、mount都是action中的command,分别对应do_write、do_mkdir函数,以此类推,其他action也是如此执行。

在执行action的command时就有部分服务被启动,这部分服务并没有以service关键字开头执行,那么init.rc中那些以service关键字开头的服务又是在哪里启动?

一般来说,大部分服务(在init.rc中以service关键字开头的)进程是在执行class_start时被启动的,因为这些服务进程是属于某一类别的,比如,class core、class main类别,只要在含有class_start core或class_start main的action被执行时,就会启动所有类别标识为core、main的服务。可以看到在on boot的这个动作段中含有class_start core,那么相应被启动的服务进程有logd、netd、adbd、healthd、lm、servicemanager、vold、surfaceflinger、bootanimation、netd、debuggerd、rild、drmserver、mediaserver、installd、racoon、mtpd、keystore、dumpstate、mdnsd、uncrypt、zygote等(zygote通过import命令import /init.${ro.zygote}.rc引用进来,在init.rc开始部分),查看这些服务,发现其option选项中含有class core、class main这样的类别,用ps命令查看当前系统那些进程是init启动,以验证上文分析是否正确:

如红色字体所示,第一行就是init进程,进程号PID为1,PPID是父进程号,查看所有PPID为1的进程可知,ueventd、logd、servicemanager、zygote等都是有init进程fork并启动的,是其子进程。

除了这些android通用服务进程外,还有一些与具体硬件产品、厂商相关的服务进程等,在init.rc开头处看到如下内容:

1
2
3
4
5
import/init.environ.rc
import/init.usb.rc
import/init.${ro.hardware}.rc
import/init.${ro.zygote}.rc
import/init.trace.rc

与usb相关的服务进程都在init.usb.rc中,与厂商、硬件相关的都在init.<hardware>.rc中,比如,与wifi相关的wpa_supplicant进程、与ppoe相关的ppp进程、与电视厂商相关的显示进程、tv进程等、虚拟按键板等进程,由厂商自行创建。

init.rc文件解析完毕后,会继续解析由import导入的其他rc文件,函数调用流程:

init_parse_config_file—>parse_config—>init_parse_config_file

import关键字也是一个段second,解析过程和init.rc大致相同,不再赘述。

init进程主要做了三件事:

a. 创建/dev、/dev/pts、/proc、/sys目录,并挂载虚拟文件系统tmpfs、devpts、profs、sysfs

b. 解析init.rc等rc文件,启动系统服务进程,包括ServiceManager、Zygote、adbd、logd等

开机启动流程图

经过上文分析,本文最后给出开机启动流程图,主要以进程创建的方式描述:

图2 开机启动流程图

zygote是android部分的第一个进程,创建了ART,包括虚拟机以及各种库,最后创建了SystemServer,SystemServer用来创建系统最关键性服务包括ActivityManagerService、PowerManagerService、DisplayManagerService,随后创建了核心服务LightsService、BatteryService、AccountManagerService、VibratorService、WindowManagerService等,这些核心服务必须在ServiceManager中注册方可使用。当所有这些java层服务创建后,系统启动Launcher,系统启动过程结束。

Android系统开机启动流程及init进程浅析的更多相关文章

  1. Android的开机启动流程

    1.Android的开机启动流程 Android的层次框架图,如下所示: 图片清晰地展示了Android的五层架构,从上到下依次是:应用层.应用框架层.库层.运行时层以及Linux内核层.Androi ...

  2. 关于Android系统的启动流程

    当按下Android设备电源键时究竟发生了什么?Android的启动过程是怎么样的?什么是Linux内核?桌面系统linux内核与Android系统linux内核有什么区别?什么是引导装载程序?什么是 ...

  3. Linux系统开机启动流程

    (来源学习Linux时,自己做的笔记) Linux系统有7个运行级别(runlevel)运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动运行级别1:单用户工作状态,root权限, ...

  4. Android系统的启动流程

    手机启动后首先会通过执行BootLoader来启动Linux内核,BootLoader是所有嵌入式设备开机启动执行的第一行代码,linux内核在启动过程中会加载各种设备的驱动同时初始化数据结构,并且开 ...

  5. Android系统开机启动画面显示过程简要说明

    开机启动会显示三个画面: Linux内核的启动画面,静态画面 Init进程启动过程中出现的静态画面 系统服务启动过程中出现的动态画面 这些画面的显示的过程不同,但最终是通过framebuffer显示的 ...

  6. Android N 的开机启动流程概述

    原地址:https://blog.csdn.net/h655370/article/details/77727554 图片展示了Android的五层架构,从上到下依次是:应用层,应用框架层,库层,运行 ...

  7. android开机启动流程说明

    android开机启动流程说明 第一步:启动linux 1.Bootloader 2.Kernel 第二步android系统启动:入口为init.rc(system\core\rootdir) 1./ ...

  8. (转)CentOS 7系统详细开机启动流程和关机流程

    CentOS 7系统详细开机启动流程和关机流程 原文:http://blog.csdn.net/yuesichiu/article/details/51350654 名称 bootup - 系统启动流 ...

  9. Linux系统入门---开机启动流程

    目录 Linux系统入门---开机启动流程 一.centos6 二.systemd管理进程 1.查看级别 三.centos7实践案例: 1.案例1:centos7系统,单用户修改root密码 案例2: ...

随机推荐

  1. [HEOI2014]大工程

    题目描述 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建一条新通道需要的代价为树上 ...

  2. ●UOJ58 [WC2013]糖果公园

    题链: http://uoj.ac/problem/58题解: 树上带修莫队. 每个块的大小为$n^{\frac{2}{3}}$,在dfs时,把点集分为若干块. 然后类似序列带修莫队,三个关键字:be ...

  3. 【luogu P4005 清华集训2017】小Y和地铁

    题目描述 小 Y 是一个爱好旅行的 OIer.一天,她来到了一个新的城市.由于不熟悉那里的交通系统,她选择了坐地铁. 她发现每条地铁线路可以看成平面上的一条曲线,不同线路的交点处一定会设有 换乘站 . ...

  4. [Russian Code Cup 2017 - Finals [Unofficial Mirror]]简要题解

    来自FallDream的博客,未经允许,请勿转载,谢谢. Div1难度+ACM赛制  和几个大佬组队逛了逛 A.给一个大小为n的集合ai(1<=ai<=1000000),要求你构造一个大小 ...

  5. [BZOJ]1042 硬币购物(HAOI2008)

    失踪OJ回归. 小C通过这道题mark一下容斥一类的问题. Description 硬币购物一共有4种硬币.面值分别为c1,c2,c3,c4.某人去商店买东西,去了tot次.每次带di枚ci硬币,买s ...

  6. 阿里 & 酷家乐:实习生面试

    最近海投了十家公司,暂时有阿里两面(已凉).酷家乐两面(大概凉了).网易一面.前两个都是基础知识发挥得还可以,两家公司二面都凉凉. 阿里一面(3.21 26min) 刚好买了中饭回宿舍打开正准备吃的时 ...

  7. [ Java学习基础 ] Java的抽象类与接口

    一.抽象类 1. 抽象类 Java语言提供了两种类:一种是具体类:另一种是抽象子类. 2. 抽象类概念: 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的 ...

  8. linux常用命令随记

    常用指令 ls 显示文件或目录 -l 列出文件详细信息l(list) -a 列出当前目录下所有文件及目录,包括隐藏的a(all) mkdir 创建目录 -p 创建目录,若无父目录,则创建p(paren ...

  9. Linux上rpm实战搭建FTP服务器

    1.检测是否已安装FTP服务 # rpm -qa|grep vsftpd 2.未安装ftp服务的前提进行使用rpm安装 # yum install vsftpd -y Loaded plugins: ...

  10. Hibernate更新数据(不用update也可以)

    在介绍hibernate的更新之前,我们先来看看session的两个方法.load和get方法:这两个方法是获取数据的根据对象的id值: 先看两段代码.load和get的方法都含有两个参数,前者是得到 ...