最近项目组把Android5.0的开关机模块分给我,以前只是很简单的接触过Android的开关机流程,刚好借助这次机会,深入研究一下Android5.0的开关机模块。
Android的开关机流程可以按照整个手机设备的分层结构来理解:
1、硬件层(Hardware)。用户手指长按电源键触发硬件产生电信号
2、内核层(Kernel)。硬件的电信号会触发bootloader,bootloader会引导kernel的启动
3、系统层(System)。Kernel起来以后,才是真正的Android系统被引导起来
4、应用层(App)。Android系统起来后,用户最终看到的App才会显示在桌面(Launcher)上
这4步是对Android启动流程的简要概括。第一步和第二步主要由硬件工程师和驱动工程师来负责,我只关注第三、四步。
Kernel起来后,会启动Android系统的第一个进程——init进程
从init进程的ID号也可以看出,它的进程ID是1,说明是第一个进程
root 1 0 948 704 c01f80d0 00028100 S /init
就从init进程开始入手分析,init进程的源代码在system/core/init/init.c中
分析init进程从main函数入手
main函数在定义相关的变量之后,首先就是uevent和watchdog
<span style="font-size:18px;"> if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
if (!strcmp(basename(argv[0]), "watchdogd"))
return watchdogd_main(argc, argv);</span>
system/core/init/Android.mk是init进程的编译脚本,它有下面一段话
# Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init
SYMLINKS := \
$(TARGET_ROOT_OUT)/sbin/ueventd \
$(TARGET_ROOT_OUT)/sbin/watchdogd
这说明ueventd和watchdogd是init的两个软连接,当执行ueventd或者watchdogd这两个服务时,执行的还是init进程,init会根据命令行的参数名决定是否进入ueventd_main或者watchdogd_main
ueventd伺服程序将解析/ueventd.rc文件,并创建相应的设备结点。watchdogd伺服程序是一个看门狗程序,它的任务就是定期向看门狗设备文件执行写操作,以判断系统是否正常运行。
接下来就是创建几个必须的目录,并挂载tmpfs,devpts,proc,sysfs文件系统,将通过创建文件"/dev/.booting"来表示目前正处于启动中的状态。
/* 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));</span>
接下来,将创建两个设备结点:/dev/__null__以及/dev/__kmsg__,并打开标准输入流,输出流以及错误输出流,并将它们重定向到/dev/__null__,所以,此时是不能直接调用printf系列的函数直接打印Log输出,而是利用klog输出日志。
open_devnull_stdio();
klog_init();
property_init();</span>
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);
}
}
由上述实现看出内核的log输出是通过文件描述符log_fd写入的,那到底写入到什么设备呢?/dev/kmsg,这个设备则会把它收到的任何写入都作为printk的输出。printk函数是内核中运行的向控制台输出显示的函数。
接下来,property_init初始化属性服务所需要的基本空间。首先创建一个/dev/__properties__文件,然后通过对应的文件描述映射一块共享内存,大小PA_SIZE(49152),映射的地址和相应的文件描述符保存在struct workspace中。相当于在内存中初始化一块共享内存区域,用来存储系统属性,方便其他进程在执行过程中读取。
接着获取硬件信息,将处理传递给内核的命令行参数 get_hardware_name(hardware, &revision);
process_kernel_cmdline();
get_hardware_name()其实是把/proc/cpuinfo中的hardware和revision信息提取出来存在变量中,供后面使用
process_kernel_cmdline处理内存传递上来的参数,该参数存在/proc/cmdline中
如果启用了SELinux机制,接下来将加载selinux策略,并初始化文件安全上下文以及属性安全上下文。
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");
从内核参数中得出当前的bootmode,判断是不是充电模式
is_charger = !strcmp(bootmode, "charger");
接下来是property_load_boot_defaults();
INFO("property init\n");
property_load_boot_defaults();
这个函数是从PROP_PATH_RAMDISK_DEFAULT中加载系统属性
bionic\libc\include\sys\_system_properties.h
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
#define PROP_PATH_FACTORY "/factory/factory.prop"
接下来是解析init.rc配置文件,这是init进程的重头戏
INFO("reading config file\n");
init_parse_config_file("/init.rc");
在讲述函数init_parse_config_file()之前,先了解下init.rc的一些简要知识
init.rc文件基本组成单位是section, section分为三种类型,分别由三个关键字(所谓关键字即每一行的第一列)来区分,这三个关键字是on、service、import。
on类型的section表示一系列命令的组合, 例如:
on init
export PATH /sbin:/system/sbin:/system/bin
export ANDROID_ROOT /system
export ANDROID_DATA /data
这样一个section包含了三个export命令,命令的执行是以section为单位的,所以这三个命令是一起执行的,不会单独执行, 那什么时候执行呢? 这是由init.c的main()所决定的,main()里在某个时间会调用
action_for_each_trigger("init", action_add_queue_tail);
这就把on init开始的这样一个section里的所有命令加入到一个执行队列,在未来的某个时候会顺序执行队列里的命令,所以调用action_for_each_trigger的先后决定了命令执行的先后。
service类型的section表示一个可执行程序,例如:
service surfaceflinger /system/bin/surfaceflinger
class main
user system
group graphics drmrpc
onrestart restart zygote
surfaceflinger作为一个名字标识了这个service, /system/bin/surfaceflinger表示可执行文件的位置, class、user、group、onrestart这些关键字所对应的行都被称为options, options是用来描述的service一些特点,不同的service有着不同的options。
service类型的section标识了一个service(或者说可执行程序), 那这个service什么时候被执行呢?是在class_start这个命令被执行的时候,class_start命令行总是存在于某个on类型的section中,“class_start core”这样一条命令被执行,就会启动类型为core的所有service。
所以可以看出android的启动过程主要就是on类型的section被执行的过程。
import类型的section表示引入另外一个.rc文件,例如:
import init.test.rc
相当包含另外一些section, 在解析完init.rc文件后继续会调用init_parse_config_file来解析引入的.rc文件。
接着继续说,init_parse_config_file()函数,该函数会调用parse_config函数,这才是完成解析动作的函数
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
struct listnode import_list;
struct listnode *node;
char *args[INIT_PARSER_MAXARGS];
int nargs;
nargs = 0;
state.filename = fn;
state.line = 0;
state.ptr = s;
state.nexttoken = 0;
state.parse_line = parse_line_no_op;
list_init(&import_list);
state.priv = &import_list;
for (;;) {
switch (next_token(&state)) {
case T_EOF: //遍历到文件结尾,然后goto解析import的.rc文件,相当于解析另一个rc文件
state.parse_line(&state, 0, 0);
goto parser_done;
case T_NEWLINE:
state.line++;
if (nargs) {
int kw = lookup_keyword(args[0]); //找到这一行的关键字
if (kw_is(kw, SECTION)) { //如果这是一个section的第一行
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args);
} else { //如果这不是一个section的第一行
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break;
case T_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);
}
}
next_token() 解析完init.rc中一行之后,会返回T_NEWLINE,这时调用lookup_keyword函数来找出这一行的关键字, lookup_keyword返回的是一个整型值,对应keyword_info[]数组的下标,keyword_info[]存放的是keyword_info结构体类型的数据
static struct {
const char *name;
int (*func)(int nargs, char **args);
unsigned char nargs;
unsigned char flags;
} keyword_info
因此keyword_info[]中存放的是所有关键字的信息,每一项对应一个关键字。
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(seclabel, OPTION, 0, 0)
KEYWORD(service, SECTION, 0, 0)
根据每一项的flags就可以判断出关键字的类型,如新的一行是SECTION,就调用parse_new_section()来解析这一行, 如新的一行不是一个SECTION的第一行,那么调用state.parseline()来解析(state.parseline所对应的函数会根据section类型的不同而不同),在parse_new_section()中进行动态设置。
static 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:
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;
return;
}
break;
case K_on:
state->context = parse_action(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
case K_import:
parse_import(state, nargs, args);
break;
}
state->parse_line = parse_line_no_op;
}
三种类型的section: service、on、import,
service对应的state.parseline为parse_line_service,
on对应的state.parseline为parse_line_action,
import section中只有一行所以没有对应的state.parseline。
service的结构体为
struct service {
/* list of all services */
struct listnode slist;
const char *name;
const char *classname;
unsigned flags;
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;
struct svcenvinfo *envvars;
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! */
所有从init.rc中解析出来的service都用slist链接到service_list中。service结构体中其他变量代表了service的option选项
action的结构体、command结构体
struct command
{
/* list of commands in an action */
struct listnode clist;
int (*func)(int nargs, char **args);
int line;
const char *filename;
int nargs;
char *args[1];
};
struct action {
/* node in list of all actions */
struct listnode alist;
/* node in the queue of pending actions */
struct listnode qlist;
/* node in list of actions for a trigger */
struct listnode tlist;
unsigned hash;
const char *name;
struct listnode commands;
struct command *current;
};
commands用来链接很多command结构体,也就是说,一个action中,含有很多个command。
alist,用来链接action_list,action_list包含了所有从init.rc中解析出来的action结构体。
qlist用来链接action_queue,后面会讲到
command结构体中func指定的函数决定了这个command具体做什么动作,nargs,args是两个参数,传给func
至此,init.rc的解析完成了,其实就是把init.rc中的信息放置到对应的结构体链表中,供后续使用。一个action_list包含了所有的从init.rc中解析出来的action,每一个action包含了多个command,每个command有自己的func函数和参数。一个service_list包含了所有从init.rc解析出来的service,每个service包含了自己的很多选项。
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);
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
action_for_each_trigger是根据trigger字符串的名字,把对应的action放到action_queue队列中去
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); 把自定义的action(这些action没有在init.rc中被定义),放入action_queue中去
接下来是一个死循环,init进程就循环在这里
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;
}
execute_one_command();是执行action_queue队列中的每一个action中包含的command。action_queue是所有需要执行的action的集合,每次循环到函数execute_one_command时,就从action_queue中取出一个需要执行的command去执行。直到所有的action中的command都被执行完。当执行到class_start这个command时,会从service_list中取出service来执行。注意:如果service的option选项为disable,则不执行
restart_processes();是重启service_list队列中的所有标记为SVC_RESTARTING的service
在for循环中,注册了三个事件
get_property_set_fd (系统属性相关的事件)
get_signal_fd (信号相关事件)
get_keychord_fd (组合键相关事件)
nr = poll(ufds, fd_count, timeout);用来轮询这三个事件,如果没有发生事件,则nr<=0,当发生事件时,nr>0,代码会跳转到下面的几个处理函数中去
handle_property_set_fd
handle_keychord
handle_signal
至此,init进程就分析完了,init进程作为一个死循环一直运行。其中还有一些小的知识点,例如轮询消息事件中的架构是如何实现的?init.rc中的具体内容的分析等,后面会慢慢介绍。