Android的内核是linux,因此,此时讨论的其实是linux的启动过程,虽然这并不是纯粹的linux启动过程
此次讨论的源码基于Android4.1.1,主要涉及到的文件是init.c,init_parser.c,init.rc
Android的开机就是不断的启动必要的系统进程服务的过程,而这些大部分进程都被写死在**init.rc**的配置文件中,那么由哪个进程来读取配置文件?
这个重任就落在init进程身上了,它是Android启动的第一个进程,主要任务就是读取配置文件,然开启其他进程。
open_devnull_stdio();//重定向标准输出输入
klog_init();//创建设备文件节点,写入该节点的均以printk输出
property_init();//初始化系统属性共享内存区域
//获取硬件平台名称及版本。若没在cmdline中提供的话,
get_hardware_name(hardware, &revision);//将从/proc/cpuinfo中的Hardware获取
//读出内核启动参数中的配置项的值
process_kernel_cmdline();
...
...
init_parse_config_file("/init.rc");//遍历解析init.rc文件!!!
而init_parse_config_file()在init_parser.c文件中,具体函数如下
//fn是.rc文件的名称
int init_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);//将文件内容读到内存中
if (!data) return -1;
parse_config(fn, data);//遍历解析.rc文件
DUMP();
return 0;
}
parse_config()函数关键部分如下:
for (;;) {
switch (next_token(&state)) {
case T_EOF:
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,这部分会关联到keyword.h的头文件
state.parse_line(&state, 0, 0);//第一次为空操作
parse_new_section(&state, kw, nargs, args);//解析Section的第一行,并为state.parse_line赋值
} else {
state.parse_line(&state, nargs, args);//下个循环则执行到此行,调用上面的赋值
}
nargs = 0;
}
break;
case T_TEXT:
if (nargs < INIT_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
init_parser.c文件中包含了keyword.h头文件,这个头文件中定义了一种数据结构,方便查找和读取init.rc文件的数据(有兴趣的人可以看看)。而parse_new_section(&state, kw, nargs, args);函数则是开始解析配置文件中的数据了,源码如下:
void parse_new_section(struct parse_state *state, int kw,
int nargs, char **args)
{
printf("[ %s %s ]\n", args[0],
nargs > 1 ? args[1] : "");
........
.........
case K_service://遇到服务Section,下行将返回service结构体变量指针
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;//指定遍历状态机的行遍历函数
return;
}
break;
......
......
这里主要启动了两个进程Runtime和zygote进程。但是parse_service函数并不启动进程,只是“搭架子”,完成一个service结构体指针,然后添加到一个列表中。但是具体的填充工作是由parse_line_service()函数完成。最后得到一个复杂的,填充内容的结构体指针。但是,进程还是没有启动,init进程怎么启动zygote等进程呢?这是最关键的部分,而且最关键的是,我找不到啊!!!
问题在这里似乎已经走进了死胡同,结果,我尝试寻找fork函数的时候,却碰巧找到了整个init的C文件中唯一一个fork函数,处在service_start()这个函数中
好的,现在先不管init进程什么时候启动了这个service_start函数,先进入函数来看
void service_start(struct service *svc, const char *dynamic_args)
显然,svc指针就是之前构架的那个service结构体指针。
......
......
pid = fork();
if (pid == 0) {
struct socketinfo *si;
struct svcenvinfo *ei;
char tmp[32];
int fd, sz;
umask(077);
if (properties_inited()) {
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
}
for (ei = svc->envvars; ei; ei = ei->next)
add_environment(ei->name, ei->value);
.....
....
这段程序fork了一个子进程,并通过 add_environment()函数添加了一些环境变量,add_environment()函数最终把环境变量放到ENV数组中。到这里,即使已经fork了其实还没有真正启动进程,因为fork是复制进程。往下看
if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
}
exec系列的系统调用,是将复制的进程替换为新的进程,可以说,某种程度上,fork和exec是配合使用的,对于上面那段代码,args是字符串数组,读取自init.rc中,原本args[0]="service",agr[1]='zygote'(目前只讨论启动zygote的情况),但是当复制到svc结构体上args数组的时候,偏移了两个单位,即args[0]=args[2],那么此时args[0]="/system/bin/app_process"
init.rc文件中zygote部分是这么写的:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
所以有关于args字符串数组其实一目了然。
好了,那么fork复制出来的进程在经过execve()之后,传入“/system/bin/app_process”,就会去启动这个目录下的程序了,原先有init进程复制出来的一模一样的子进程就变成zygote进程了!!!!
接下来,我们在追问,是谁启动了service_start这个函数?
经过往上追踪,发现msg_start调用了service_start函数,然后handle_control_message又调用了msg_start函数,所以最终锁定在
handle_control_message()函数上
void handle_control_message(const char *msg, const char *arg)
{
if (!strcmp(msg,"start")) {
msg_start(arg);
} else if (!strcmp(msg,"stop")) {
msg_stop(arg);
} else if (!strcmp(msg,"restart")) {
msg_stop(arg);
msg_start(arg);
} else {
ERROR("unknown control msg '%s'\n", msg);
}
}
说实话,目前找不到系统是如何调用handle_control_message函数的(知道的同学欢迎交流)。看起来感觉有点想handle机制,通过一个消息队列的方式(之前在parse_service函数中讲到过,搭出service结构体的架子之后添加到一个列表中)然后取出消息,调用函数。。。。