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进程的来龙去脉。
在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
|
static
noinline
void
__init_refok
rest_init
(
void
)
{
int
pid
;
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
|
static
int
__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
)
)
return
0
;
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
)
)
return
0
;
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"
)
)
return
0
;
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
|
static
noinline
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
(
(
const
char
__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
(
(
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..
*/
/* 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
)
)
return
0
;
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"
)
)
return
0
;
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
|
int
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
;
if
(
!
strcmp
(
basename
(
argv
[
0
]
)
,
"ueventd"
)
)
return
ueventd_main
(
argc
,
argv
)
;
if
(
!
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
)
)
;
/* 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
(
;
;
)
{
int
nr
,
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
(
)
;
else
if
(
ufds
[
i
]
.
fd
==
get_keychord_fd
(
)
)
handle_keychord
(
)
;
else
if
(
ufds
[
i
]
.
fd
==
get_signal_fd
(
)
)
handle_signal
(
)
;
}
}
}
return
0
;
}
|
init进程主要做的事情都在main函数中,前两个语句:
1
2
3
4
5
|
if
(
!
strcmp
(
basename
(
argv
[
0
]
)
,
"ueventd"
)
)
return
ueventd_main
(
argc
,
argv
)
;
if
(
!
strcmp
(
basename
(
argv
[
0
]
)
,
"watchdogd"
)
)
return
watchdogd_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
-
x
compass
radio
309844
2015
-
12
-
30
14
:
16
adbd
-
rwxr
-
xr
-
x
compass
radio
|