init,是linux系统中用户空间的第一个进程,也是Android系统中用户空间的第一个进程。
位于/system/core/init目录下。
分析init
int main(int argc, char **argv)
{
//设置子进程退出的信号处理函数 sigchld_handler
act.sa_handler = sigchld_handler;
//解析init.rc文件
parse_config_file("/init.rc");
//通过这个函数读取/proc/cpuinfo得到机器的HardWare名,
get_hardware_name();
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
//解析这个和机器相关的配置文件
parse_config_file(tmp);
//执行各个阶段的工作 共分为4个阶段
/**
* ① early-init阶段完成的动作
*/
action_for_each_trigger("early-init", action_add_queue_tail);
drain_action_queue();
//初始化和属性相关的资源
property_init();
/**INIT_IMAGE_FILE定义为“initlogo.rle”
* 这个函数将加载这个文件作为系统开机的画面
* 如果加载文件initlogo.rle失败就会打开/dev/tty0设备,将输出“ANDROID”的字样作为开机画面
*/
if( load_565rle_image(INIT_IMAGE_FILE) ) {
fd = open("/dev/tty0", O_WRONLY);
if (fd >= 0) {
const char *msg;
msg = "\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n" // console is 40 cols x 30 lines
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
" A N D R O I D ";
write(fd, msg, strlen(msg));
close(fd);
}
}
/**
* ②init
*/
action_for_each_trigger("init", action_add_queue_tail);
drain_action_queue();
//启动属性服务
property_set_fd = start_property_service();
/**
* ③early-boot ④boot
*/
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
drain_action_queue();
//boot char是一个小工具 对系统性能进行分析 帮助提升系统的启动速度
#if BOOTCHART
bootchart_count = bootchart_init();
if (bootchart_count < 0) {
……
#endif
/**
* 进入init循环
*/
for(;;) {
int nr, i, timeout = -1;
for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;
drain_action_queue();
//重启死亡的进程
restart_processes();
//调用poll等待一些事情的发生
nr = poll(ufds, fd_count, timeout);
总结:init工作主要先初始化两个配置文件,然后执行四个阶段的动作,接下来调用property_init相关的属性资源,并通过property_start_service启动属性服务,然后init将进入一个无限循环,并等待一些事情的发生。
解析配置文件
init会解析两个配置文件一个是系统配置文件init_rc,另一个是与硬件平台相关的配置文件,解析调用的是parse_config_file函数,然后看看这个函数里做了什么?
/system/core/init下的parser.c文件
int parse_config_file(const char *fn)
{
parse_config(fn, data);
return 0;
}
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
char *args[SVC_MAXARGS];
int nargs;
nargs = 0;
state.filename = fn;
state.line = 1;
state.ptr = s;
state.nexttoken = 0;
//设置解析函数 不同的内容用不同的解析函数
state.parse_line = parse_line_no_op;
for (;;) {
switch (next_token(&state)) {
case T_EOF:
state.parse_line(&state, 0, 0);
return;
case T_NEWLINE:
if (nargs) {
//得到关键字类型
int kw = lookup_keyword(args[0]);
//判断关键字是不是SECTION
if (kw_is(kw, SECTION)) {
state.parse_line(&state, 0, 0);
//解析这个SECTION
parse_new_section(&state, kw, nargs, args);
} else {
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break;
case T_TEXT:
if (nargs < SVC_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
}
实际上,parse_config首先会找到配置文件中的section,然后根据不同的section使用不同的解析函数去解析。section是什么?在parse.h里包含了keywords.h脚本文件
第一次包含keywords.h时声明了一些函数,定义了一个枚举值是K_class等关键字
第二次包含keywords.h后,得到了一个keyword_info的结构体数组,以前面对应的枚举值为索引,存储对应的关键字信息,这些信息包括关键字名称、处理函数、函数参数个数以及属性。根据keywords.h中的定义,on和service就可以表示成section。
init.rc的解析:
1 一个section的内容的从这个标识section的关键字开始,到下一个标识section的关键字结束。
2 init.rc中出现的名字为boot和init的section就是前面介绍的四个动作执行阶段中的boot和init,也就是说,在boot这个阶段执行的动作都是由boot这个section定义的。
然后再来解析init.rc文件
总结:
1. 一个section的内容是从这个标识section的关键字开始,直到下一个标识section的地方结束。on和service都是section开始的标识。
2. 这里出现了名为boot和init的section,这就是前面四个动作中执行的init和boot,init和boot阶段执行的动作都是在这里定义的。
Zygote对应的是service,zygote的section是这样的:
service zygote /system/bin/app_process -Xzygote /system/bin =zygote \
--start system server
socket zygote stream 666
onrestart write
onrestart write
onrestart restart media
解析section的入口函数是parse_new_section,在这里,解析service时用到了parse_service和parse_line_service这两个函数,
先来看Service结构体,是什么样子的?
struct service {
struct listnode slist;//将结构体连接成一个双向链表
const char *name; //service的名字 这里就是zygote
const char *classname; //service所属的class的名字
unsigned flags;//service属性
pid_t pid;//进程号
time_t time_started; //上一次启动时间
time_t time_crashed; //上一次死亡的时间
int nr_crashed; //死亡的次数
uid_t uid;
gid_t gid;
gid_t supp_gids[NR_SVC_SUPP_GIDS];
size_t nr_supp_gids;
struct socketinfo *sockets; //用来描述socket信息的结构体
//service一般运行在一个单独的线程中,envvars表示创建这个进程需要的环境变量
struct svcenvinfo *envvars;
struct action onrestart; /* Actions to execute on restart. */
/* 与keywords相关的信息 */
int *keycodes;
int nkeycodes;
int keychord_id;
//IO优先级
int ioprio_class;
int ioprio_pri;
int nargs; //参数个数
char *args[1];
}
现在已经了解了service中的结构体,再来看zygote中的三个onrestart是什么,先看action结构体:
struct action {
/*一个action结构体可以存放在三个双向链表中 */
struct listnode alist; //存放所有的action
struct listnode qlist;//链接等待执行的action
struct listnode tlist; //链接那些待某些条件满足后需要执行的action
unsigned hash;
const char *name;
/*对应的是command列表 zygote有三个onrestart就会创建三个command结构体*/
struct listnode commands;
struct command *current;
};
接下来,在parse_service和parse_line_service里,parse_service搭建了一个service的架子,parse_line_service里:
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_oneshot: //service的属性
svc->flags |= SVC_ONESHOT;/*有五个属性 SVC_ONESHOT代表退出后不需要重启*/
//SVC_DISABLED 不随class自动启动;SVC_RUNNING 正在运行;SVC_RESTARING:等待重启;
//SVC_CONSOLE 该service需要使用控制台;SVC_CRITICAL 如果在规定时间内,该service不断重启则系统会重启进入恢复模式。
......
List_add_tail(&svc->onrestart.commands, &cmd->clist);// 把新建的command加入到双向链表中
......
由此可见,parse_line_service是根据配置文件的内容填充service结构体。
总结:
service_list解析后将service连接到一起,是一个双向链表的形式。
socket_info也是一个双向链表。
onrestart通过command指向一个command链表,zygote有三个command。
init控制service
Init.rc目录是system/core/rootdir/init,rc
在init.rc的解析中,包含了这样的内容:
class_start标识一个COMMAND,对应的处理函数是do_class_start,位于boot section里,在init的main函数里,boot阶段是这样的,
//将boot的section节的的COMMAND加入到执行队列
action_for_each_trigger("boot", action_add_queue_tail);
//执行队列里的命令,class是一个COMMAND,所以他对应的do_class_start会被执行
drain_action_queue();
do_class_start函数位于system/core/init/builtins.c中,
int do_class_start(int nargs, char **args)
{
/*args是do_class_start的参数,init.rc中只有一个参数,就是default,下面这个函数将从service_list中找到classname为default的service,然后调用service_start_if_not_disabled*/
//zygote的classname 就是default
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
service_start_if_not_disabled里调用了service_start函数,代码分析如下:
1 启动zygote,Zygote是由fork和execve共同创建的
void service_start(struct service *svc, const char *dynamic_args)
{
...
if (svc->flags & SVC_RUNNING) {
return; //service已经在运行,不用处理
}
/*service一般运行在另外一个进程中,这个进程也是init的子进程,*/
if (stat(svc->args[0], &s) != 0) {
ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
svc->flags |= SVC_DISABLED;
return;
}
//调用fork创建子进程
pid = fork();
//Pid为0的时候,表明运行在子线程中
if (pid == 0) {//pid为0 表明运行在子进程中
......
//添加环境变量信息 socket等
if (!dynamic_args) {
if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
}
}
}
//父进程的init的处理
svc->time_started = gettime();
svc->pid = pid;
svc->flags |= SVC_RUNNING;
......
}
2 重启zygote
onrestart方法顾名思义是在zygote重启的时候调用,zygote死亡后,父进程init:
static void sigchld_handler(int s)
{ //子进程退出后 init这个信号函数会被调用
write(signal_fd, &s, 1); // 往signal_fd写数据
}
signal_fd就是在init中通过socketpair创建的两个当中的一个,会往这个socket里发送数据另一个socket就一定能收到,这样就会导致init从poll函数中返回,在init.c中,
for(;;) {
int nr, i, timeout = -1;
for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;
drain_action_queue();//poll函数返回后 会有下一轮的循环
restart_processes(); // 重启所有flag标识为SVC_RESTARTING的service
}
那么zygote在哪里重启呢?答案是init.c的main函数里.
static int wait_for_one_process(int block)
{
......
svc = service_find_by_pid(pid);//找到死掉的service
//杀掉zygote创建的所有子进程,这就是zygote死后Java世界崩溃的原因
if (!(svc->flags & SVC_ONESHOT)) {
kill(-pid, SIGKILL);
NOTICE("process '%s' killing any children in process group\n", svc->name);
}
//如果设置了SVC_CRITICAL模式,则四分钟内重启的次数不能超过四次否则机器
//会在重启后进入recovery模式
if (svc->flags & SVC_CRITICAL) {
if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
ERROR("critical process '%s' exited %d times in %d minutes; "
"rebooting into recovery mode\n", svc->name,
CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
sync();
__reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, "recovery");
return 0;
}
} else {
svc->time_crashed = now;
svc->nr_crashed = 1;
}
//设置标识为SVC_RESTARTING,然后执行service onrestart中的COMMAND
svc->flags |= SVC_RESTARTING;
}
属性服务
Windows平台有个叫注册表的东西,可以存储一些类似key/value的键值对,包含了系统的属性,即使系统在重启后也能根据注册表中的属性进行初始化工作,Android平台这种类似的机制叫做属性服务---property service 如下是该手机的部分属性
属性服务的初始化
init.c中有个函数初始化属性服务函数 property_init(),这里先调用init_property_area()函数进行初始化属性存储区域,
static int init_property_area(void)
{
prop_area *pa;
if(pa_info_array)
return -1;
/*initworkspace是一块内存 PA_SIZE是存储空间的总大小 a_workspace是workspace的结构体*/
if(init_workspace(&pa_workspace, PA_SIZE))
return -1;
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);
pa = pa_workspace.data;
memset(pa, 0, PA_SIZE);
pa->magic = PROP_AREA_MAGIC;
pa->version = PROP_AREA_VERSION;
/*这个变量由bionic libc库输出 */
__system_property_area__ = pa;
return 0;
}
为什么在最后要给变量赋值呢?属性虽然由init创建,但是Android希望其他进程也能读取到这块内存里的东西所以做了以下工作:
- 把属性区域建共享内存上,共享内存是可以跨进程的。
- 利用gcc的constructor属性,这个属性指明了一个_libc_prenit函数,当bionic libc这个库被加载时,就会自动调用_libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作
客户端进程获取存储空间
在bionic/libc/bionic/libc_init_common.c的libc_init_dynamic.c函数中,constructor属性指示加载器加载该库后,首先调用_libc_prenit函数,然后调用libc_init_common函数,最后一行就是初始化客户端的属性存储区域。
int __system_properties_init(void)
{
...
//zygote中添加的环境变量 在这里取出
env = getenv("ANDROID_PROPERTY_WORKSPACE");
/*映射init创建的那块内存到本地进程空间,本地进程就可以使用这个共享内存了 指定了PROT_READ属性是只读不允许设置*/
pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
}
属性服务器的分析
init进程会启动一个属性服务器来与与客户端交互设置客户端属性,属性服务器由start_property_service函数启动
int start_property_service(void)
{
int fd;
//加载属性文件,解析出来设置到属性空间中
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
//创建socket用于IPC通信
fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
if(fd < 0) return -1;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
listen(fd, 8);
return fd;
}
属性服务器接受到客户端请求后会在init进程中,调用handle_property_set_fd进行处理。检查客户端是否有足够的权限,有则调用property_set(位于properties_service.c下)函数进行相关处理。
那么客户端是如何设置属性的?
客户端调用property_set(位于properties.c下)进行处理,在libcutils库下,
static int send_prop_msg(prop_msg *msg)
{ //建立和属性服务器的socket链接
s = socket_local_client(PROP_SERVICE_NAME,
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM);
//通过socket发出去
while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
if((errno == EINTR) || (errno == EAGAIN)) continue;
break;
}
}
int property_set(const char *key, const char *value)
{ ......
msg.cmd = PROP_MSG_SETPROP;//设置消息码为PROP_MSG_SETPROP
return send_prop_msg(&msg);......
}
至此,讲解了init如何解析配置文件,解析zygote,以及属性服务。