深入理解“init”
一、概述
init是一个进程,是Linux系统中用户空间的第一个进程。由于Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。
init有很多重要的工作职责,如下:
- init进程负责创建系统中的几个关键进程,如zygote,它是Java世界的开创者。
- Android系统有很多属性,于是init就提供了一个property service(属性服务)来管理它们。
二、nit分析
init的工作流程精简为一下四点:
- 解析两个配置文件;
- 执行各个阶段的动作,创建zygote的工作就是在其中的某个阶段完成的。
- 调用property_init初始化属性相关的资源,并且通过property_start_service启动属性服务。
- init进入一个无限循环,并且等待一些事情的发生。重点关注init如何处理来自socket和来自属性服务器的相关事情。
1.解析配置文件
在init中会解析两个配置文件,一个是系统配置文件init.rc,另一个是与硬件平台相关的配置文件。对这两个配置文件进行解析,调用的是同一个parse_config_file函数。如下例子所示:
int parse_config_file(const char *fn)
{
char *data;
data = read_file(fn,0);//读取配置文件的内容
if(!data) return -1;
parse_config(fn,data);//调用parse_config做真正的解析。
return 0;
}
读取完文件的内容后,将调用parse_config进行解析,函数代码如下:
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]);
if(kw_is(kw,SECTION)){//判断关键字类型是不是SECTION。
state.parse_line(&state,0,0);
parse_new_section(&state,kw,nargs,args);//解析这个SECTION。
}else{
state.parse_line(&state,nargs,args);
}
nargs=0;
}
break;
case T_TEXT:
......
break;
}
}
}
parse_config首先会找到配置文件的一个section,然后针对不同的section使用不同的解析函数来解析。
1、关键字定义
keywords.h文件定义了init中使用的关键字。
keywords.h主要完成了两件事:
- 第一次包含keywords.h时,它声明了一些诸如do_class_start的函数,另外还定义了一个枚举,枚举值为K_class,K_mkdir等关键字。
- 第二次包含keywords.h后,得到了一个keyword_info结构体数组,这个keyword_info结构体数组以前面定义的枚举值为索引,存储对应的关键字信息,这些信息包括关键字名称、处理函数、处理函数的参数个数,以及属性。
2、解析init.rc
- 一个section的内容从这个标识section的关键字开始,到下一个标识section的地方结束。
- init.rc中出现了名为boot和init的section。也就是说,在boot阶段执行的动作都是由boot这个section定义的。
2.解析service
解析service时,用到了parse_service和parse_line_service这两个函数。
1、service结构体
init中使用了一个叫service的结构体来保存于service section相关的信息。代码如下:
struct service{
//listnode是一个特殊的结构体,在内核代码中用得非常多,主要用来将结构体链接成一个双向链表。
//init中有一个全局的service_list,专门用来保存解析配置文件后得到的service。
struct listnode slist;
const char *name;//service的名字
const char *classname;//service所属class的名字,默认是"defult"。
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;
/*
有些service需要使用socket,下面这个socketinfo用来描述socket的相关信息。
zygote也使用了socket,配置文件中的内容是socket zygote stream 666。
它表示创建一个AF_STREAM类型的socket,这个socket的名字为"zygote",读写权限是666。
*/
struct socketinfo *sockets;
//service一般运行在一个单独的进程中,envvars用来描述创建这个进程时所需的环境变量信息。
struct svcenvinfo *envvars;
/*
虽然关键字onrestart标识一个OPTION,可是这个OPTION后面跟着COMMAND,
下面的action结构体可用来存储command信息。
*/
struct action onrestart;
//与keychord相关的内容
int *keycodes;
int nkeycodes;
int keychord_id;
//io优先级设置
int ioprio_class;
int ioprio_pri;
//参数个数
int nargs;
//用于存储参数
char *args[1];
};
service中使用的action结构体的代码如下:
struct action{
/*
一个action结构体可存放在三个双向链表中,其中alist用于存储所有的action,
qlist用于链接那些等待执行的action,tlist用于链接那些待某些条件满足后就需要执行的action。
*/
struct listnode alist;
struct listnode qlist;
struct listnode tlist;
unsigned hash;
const char *name;
struct listnode commands;
struct command *current;
};
2、了解parse_service
parse_service函数只是搭建了一个service的架子,具体的内容尚需由后面的解析函数来填充。
3、了解parse_line_service
parse_line_service将根据配置文件的内容填充service结构体。zygote解析后的结果如下图所示:
从上图可知:
- service_list链表将解析后的service全部链接到了一起,并且是一个双向链表,前向节点用prev表示,后向节点用next表示。
- socketinfo也是一个双向链表,因为zygote只有一个socket,所以画了一个虚框socket作为链表的示范。
- onrestart通过commands指向一个commands链表,zygote有三个commands。
3.init控制service
1、启动zygote
class_start标识一个COMMAND,对应的处理函数为do_class_start,它位于boot section的范围内。当init进程执行到下面几句话时,do_class_start就会被执行了。
//将boot section节的command加入到执行队列。
action_for_each_trigger("boot",action_add_queue_tail);
//执行队列里的命令,class是一个COMMAND,所以它对应的do_class_start会被执行。
drain_action_queue();
下面是do_class_start函数:
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函数。
*/
service_for_each_class(args[1],service_start_if_not_disabled);
return 0;
}
zygote的这个service的classname的值就是"default",所以会针对这个service调用service_start_if_not_disabled函数。代码如下:
static void service_start_if_not_disabled(struct service *svc)
{
if(!(svc->flags&SVC_DISABLED)){
service_start(svc,NULL);//zygote可不设置SVC_DISABLED
}
}
2、重启zygote
zygote死后,它的父进程init会有如下操作:
static void sigchld_handler(int s)
{
//当子进程退出时,init的这个信号处理函数会被调用。
write(signal_fd,&s,1);//往signal_fd中写数据。
}
signal_fd就是在init中通过socketpair创建的两个socket中的一个,既然会往这个signal_fd中发送数据,那么另外一个socket就一定能接收到,这样就会导致init从poll函数中返回。
zygote的重启将在下面的代码中进行:
for(;;){
int nr,i,timeout=-1;
for(i=0;i<fd_count;i++)
ufds[i].revents = 0;
drain_action_queue();//poll函数返回后,会进入下一轮的循环。
restart_pocesses();//这里会重启所有flag标识为SVC_RESTARTING的service。
......
}
4.属性服务
应用程序可通过Android平台提供的属性服务机制,查询或设置属性。
init.c和属性服务相关的代码如下:
property_init();
property_set_fd = start_property_service();
1、属性服务初始化
- 创建存储空间:property_init函数代码如下所示:
void propert_init(void)
{
init_property_area();//初始化属性存储区域。
//加载default.prop文件
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
在property_init函数中,先调用init_property_area函数,创建一块用于存储属性的存储区域,然后加载default.prop文件中的内容。
虽然属性区域是由init进程创建的,但是Android系统希望其他进程也能读取这块内存里的东西。因此做了以下工作:
- 把属性区域创建在共享内存上,而共享内存是可以跨进程的。
- Android利用gcc的construstor属性指明了一个_libc_prenit函数,当bionic libc库被加载时,将自动调用这个_libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作。
- 客户端进程获取存储空间。
2、属性服务器的分析
- 启动属性服务器:init进程会启动一个属性服务器,而客户端只能通过与属性服务器交互来设置属性。
- 处理设置属性请求:接收请求的地方在init进程中,代码如下:
if(ufds[1].revents == POLLIN)
handle_property_set_fd(property_set_fd);
当属性服务器收到客户端请求时,init会调用handle_property_set_fd进行处理。
当客户端的权限满足要求时,init就调用property_set进行相关处理。
- 客户端发送请求:客户端通过property_set发送请求,property_set由libcutils库提供。
网友评论