本文先说明init的大致流程,然后择重点说明其重要部分
一.Init整体流程
Init进程主函数main定义于system/core/init/init.c中,从该目录的Android.mk中我们知道该文件最终被编译为Init命令,也就是内核启动的第一个命令。它的进程号是1,用于系统的用户层初始化和启动各种服务进程。
1)配置规则
1)配置规则
Init读取两个配置文件,/init.rc和/init.goldfish.rc,这两个配置文件存储在ramdisk的根目录中,在android的源代码结构中的位置是system/core/rootdir和system/core/rootdir/etc,配置文件中包含了若干条规则,有两种类型的规则,他们的定义如下:
1.action
on <trigger>
<command>
<command>
<command>
<command>
<command>
<command>
表示在发生某个trigger条件的时候,执行下面的command
2.service
service <name> <pathname> [ <argument> ]*
<option>
<option>
启动服务,服务的名称为name,执行路径是pathname,参数为argument,option是对服务的进一步说明
service <name> <pathname> [ <argument> ]*
<option>
<option>
启动服务,服务的名称为name,执行路径是pathname,参数为argument,option是对服务的进一步说明
system/core/init/readme.txt文件中有规则的详细说明
2)流程伪代码
创建并绑定不同的文件系统
解析并加载init.rc和init.goldfish.rc配置文件
加载内置规则
Init进程执行这两个文件解析出来的内容,配置文件中的trigger按照early-init,init,early-boot,boot的顺序进入执行序列中执行。排序后的action triger如下:
early-init
wait_for_coldboot_done
property_init
keychord_init
console_init
set_init_properties
init
property_service_init
signal_init
check_startup
early-boot
boot
queue_propety_trigger
进入以下的循环事件等待,该循环实际上是等待三个不同的事件,property,signal,keychord,然后分别处理
for(;;) {
execute_one_command();//一次执行一个action_queue中的命令
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;
}
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();
}
}
}
2)流程伪代码
创建并绑定不同的文件系统
解析并加载init.rc和init.goldfish.rc配置文件
加载内置规则
Init进程执行这两个文件解析出来的内容,配置文件中的trigger按照early-init,init,early-boot,boot的顺序进入执行序列中执行。排序后的action triger如下:
early-init
wait_for_coldboot_done
property_init
keychord_init
console_init
set_init_properties
init
property_service_init
signal_init
check_startup
early-boot
boot
queue_propety_trigger
进入以下的循环事件等待,该循环实际上是等待三个不同的事件,property,signal,keychord,然后分别处理
for(;;) {
execute_one_command();//一次执行一个action_queue中的命令
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;
}
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();
}
}
}
二.rc文件的解析
init_parser.c
init_parser.c
init_parse_config_file
->parse_config该函数是解析的主函数,循环解析token,然后设置进一步解析action或者service的函数
->parse_new_section
->parse_service负责解析service后面的名字和执行路径,参数等信息,并填写到service结构中,最后放入到service_list全局链表中,并设置parse_line_service作为解析service后续的option的函数
-> parse_action负责解析action中on后面的触发条件,创建并填写action结构,并把这个结构放入到action_list中,同时设置parse_line_action作为解析action后续的command的函数
action,service,command的结构定义于system/core/init/init.h
struct command
{
/* list of commands in an action */
struct listnode clist;
int (*func)(int nargs, char **args);
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;
};
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;
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! */
特别需要注意的是在system/core/init/init_parser.c中定义的如下变量:
->parse_config该函数是解析的主函数,循环解析token,然后设置进一步解析action或者service的函数
->parse_new_section
->parse_service负责解析service后面的名字和执行路径,参数等信息,并填写到service结构中,最后放入到service_list全局链表中,并设置parse_line_service作为解析service后续的option的函数
-> parse_action负责解析action中on后面的触发条件,创建并填写action结构,并把这个结构放入到action_list中,同时设置parse_line_action作为解析action后续的command的函数
action,service,command的结构定义于system/core/init/init.h
struct command
{
/* list of commands in an action */
struct listnode clist;
int (*func)(int nargs, char **args);
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;
};
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;
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! */
特别需要注意的是在system/core/init/init_parser.c中定义的如下变量:
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);
service_list把所有rc中所有的服务都加入到这个list中,在main的for循环中依次启动
static list_declare(action_list);
static list_declare(action_queue);
service_list把所有rc中所有的服务都加入到这个list中,在main的for循环中依次启动
action_list把rc中所有的action都加入到这个list,但是由于编写rc的时候,可能不是按照init期望运行的顺序编写的,所以这里的list都是乱序的
action_queue按照init期望启动的顺序early-init,init,early-boot,boot,把action加入到这个list中,同时也加入了一些builtin的action,然后在main的for循环中依次启动.
三.service分析
1.service的启动时机
在进入循环之前按照early-init,init,early-boot,boot的triger排序action,查看rc文件,在on boot的action中有
class_start core
class_start main
class_start对应的是system/core/init/builtins.c文件的do_class_start函数
int do_class_start(int nargs, char **args)
{
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individually.
*/
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
static void service_start_if_not_disabled(struct service *svc)
{
if (!(svc->flags & SVC_DISABLED)) {
service_start(svc, NULL);
}
}
其中service_start定义于system/core/init.c中
也就是说,上述action,启动了service队列中的core和main两个class的service。
2.启动service
system/core/init/init.c中的service_start函数是用来启动rc文件中指定的服务的,启动一个服务的基本流程如下:
在进入循环之前按照early-init,init,early-boot,boot的triger排序action,查看rc文件,在on boot的action中有
class_start core
class_start main
class_start对应的是system/core/init/builtins.c文件的do_class_start函数
int do_class_start(int nargs, char **args)
{
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individually.
*/
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
static void service_start_if_not_disabled(struct service *svc)
{
if (!(svc->flags & SVC_DISABLED)) {
service_start(svc, NULL);
}
}
其中service_start定义于system/core/init.c中
也就是说,上述action,启动了service队列中的core和main两个class的service。
2.启动service
system/core/init/init.c中的service_start函数是用来启动rc文件中指定的服务的,启动一个服务的基本流程如下:
1)fork一个子进程
2)在子进程中
if (properties_inited()) {
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
//向环境变量表中加入 ANDROID_PROPERTY_WORKSPACE=fd,size 的环境变量
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
}
//根据service的envvars向环境变量表中添加service指定的环境变量
for (ei = svc->envvars; ei; ei = ei->next)
add_environment(ei->name, ei->value);
//根据service中的socket options指定的name,type,perm,uid,gid,创建socket,并向环境变量表中添加ANDROID_SOCKET_name=socket_id的环境变量
for (si = svc->sockets; si; si = si->next) {
int socket_type = (
!strcmp(si->type, "stream") ? SOCK_STREAM :
(!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
int s = create_socket(si->name, socket_type,
si->perm, si->uid, si->gid);
if (s >= 0) {
//添加环境变量,特别注意在其中调用了fcntl(fd, F_SETFD, 0);表示没有设置socket id的CLOSE_ON_EXEC,也就是说着个id会被子进程继承的。
publish_socket(si->name, s);
}
}
这里所谓的create_socket是创建一个AF_UNIX的socket,该类型socket用于进程间通信,它绑定一个/dev/socket/name的路径,并返回一个socket id。service一般会在这个socket上监听,客户端通过连接这个socket与service通信。
3)在子进程中执行execve,并把环境变量作为启动服务的参数
if (properties_inited()) {
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
//向环境变量表中加入 ANDROID_PROPERTY_WORKSPACE=fd,size 的环境变量
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
}
//根据service的envvars向环境变量表中添加service指定的环境变量
for (ei = svc->envvars; ei; ei = ei->next)
add_environment(ei->name, ei->value);
//根据service中的socket options指定的name,type,perm,uid,gid,创建socket,并向环境变量表中添加ANDROID_SOCKET_name=socket_id的环境变量
for (si = svc->sockets; si; si = si->next) {
int socket_type = (
!strcmp(si->type, "stream") ? SOCK_STREAM :
(!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
int s = create_socket(si->name, socket_type,
si->perm, si->uid, si->gid);
if (s >= 0) {
//添加环境变量,特别注意在其中调用了fcntl(fd, F_SETFD, 0);表示没有设置socket id的CLOSE_ON_EXEC,也就是说着个id会被子进程继承的。
publish_socket(si->name, s);
}
}
这里所谓的create_socket是创建一个AF_UNIX的socket,该类型socket用于进程间通信,它绑定一个/dev/socket/name的路径,并返回一个socket id。service一般会在这个socket上监听,客户端通过连接这个socket与service通信。
3)在子进程中执行execve,并把环境变量作为启动服务的参数
4)父进程也就是init进程,会在属性服务器中加入该服务器的启动状态的属性条目
总体来说就是,设置环境变量,创建socket,运行服务程序
总体来说就是,设置环境变量,创建socket,运行服务程序
三.属性服务分析
property service运行于init进程
1.属性服务的初始化
在action排序的时候我们可以看到property_init_action函数,该函数初始化一块属性区域用来存放系统属性,并载入缺省属性/default.prop,对应源代码目录中的out/target/product/generic/root/default.prop.
property_init_action函数最终会调用到system/core/init/property_service.c文件中的如下函数
static int init_property_area(void)
{
prop_area *pa;
if(pa_info_array)
return -1;
//初始化一块内存空间用来存放属性
if(init_workspace(&pa_workspace, PA_SIZE))
return -1;
//设置CLOSE_ON_EXEC,保证了子进程不会直接得到这块空间的fd
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;
/* plug into the lib property services */
__system_property_area__ = pa;//全局变量用来保存这块属性区域,它定义在bionic/libc/bionic/system_properties.c
property_area_inited = 1;//设置属性空间初始化的标志
return 0;
}
property_init_action函数最终会调用到system/core/init/property_service.c文件中的如下函数
static int init_property_area(void)
{
prop_area *pa;
if(pa_info_array)
return -1;
//初始化一块内存空间用来存放属性
if(init_workspace(&pa_workspace, PA_SIZE))
return -1;
//设置CLOSE_ON_EXEC,保证了子进程不会直接得到这块空间的fd
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;
/* plug into the lib property services */
__system_property_area__ = pa;//全局变量用来保存这块属性区域,它定义在bionic/libc/bionic/system_properties.c
property_area_inited = 1;//设置属性空间初始化的标志
return 0;
}
__system_property_area__是struct prop_area类型,它定义于bionic/libc/include/sys/_system_properties.h,prop_area和prop_info的结构如下,其中结构末尾的toc[1],被用来作为变长数组:
/* (8 header words + 247 toc words) = 1020 bytes */
/* 1024 bytes header and toc + 247 prop_infos @ 128 bytes = 32640 bytes */
/* 1024 bytes header and toc + 247 prop_infos @ 128 bytes = 32640 bytes */
struct prop_area {
unsigned volatile count;
unsigned volatile serial;
unsigned magic;
unsigned version;
unsigned reserved[4];
unsigned toc[1];
};
struct prop_info {
char name[PROP_NAME_MAX];
unsigned volatile serial;
char value[PROP_VALUE_MAX];
};
unsigned volatile count;
unsigned volatile serial;
unsigned magic;
unsigned version;
unsigned reserved[4];
unsigned toc[1];
};
struct prop_info {
char name[PROP_NAME_MAX];
unsigned volatile serial;
char value[PROP_VALUE_MAX];
};
实际上代码会分配一块32768长的内存块,其结构如下:
-----------------------------------------------------------------------------------------------------
| Header =8 * 4= 32 bytes | Toc = 247 * 4 = 988 bytes | Array = 247 * prop_info |
-----------------------------------------------------------------------------------------------------
其中Header对应prop_area中除了toc以外的各个字段
Toc是247个4字节,每个4字节用来存储后面Array中对应索引item的信息,其结构为:
最高的一个字节用来表示对应item中name字符串的长度,后面三个字节用来存储这个item的首地址到prop_area首地址的偏移量
这样设计的目的是为了在根据name查找prop_info的时候,能够实现快速查找,根据prop_area的count,遍历所有的toc字段,首先比较name的长度,然后再根据偏移量找到prop_info,再比较name
Array是一个247个item的数组,用来存放name和value对
上述初始化完成之后,__system_property_area__全局变量将指向这块属性区域
如下的代码是用来创建属性的workspace的,它使用mmap创建一块可读写和共享的内存块,并返回fd,这个fd会保存在ANDROID_PROPERTY_WORKSPACE环境变量中。
static int init_workspace(workspace *w, size_t size)
{
void *data;
int fd;
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);
if (fd < 0)
return -1;
if (ftruncate(fd, size) < 0)
goto out;
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(data == MAP_FAILED)
goto out;
close(fd);
fd = open("/dev/__properties__", O_RDONLY);
if (fd < 0)
return -1;
unlink("/dev/__properties__");
w->data = data;
w->size = size;
w->fd = fd;
return 0;
out:
close(fd);
return -1;
}
{
void *data;
int fd;
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);
if (fd < 0)
return -1;
if (ftruncate(fd, size) < 0)
goto out;
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(data == MAP_FAILED)
goto out;
close(fd);
fd = open("/dev/__properties__", O_RDONLY);
if (fd < 0)
return -1;
unlink("/dev/__properties__");
w->data = data;
w->size = size;
w->fd = fd;
return 0;
out:
close(fd);
return -1;
}
除此之外,特别需要注意的是system/core/init/property_service.c中定义的property_set函数,该函数用来根据name前缀设置name,value,如果属性值发生了改变,还会向执行队列中加入触发的action。在属性初始化结束后,在init进程中的执行队列中的set_init_properties会被执行,主要用来设置一些ro属性
3)属性服务的启动
3)属性服务的启动
属性服务不同于其他服务,它是单独启动的,在action列表中,我们可以找到property_service_init triger对应,property_service_init_action函数,该函数会依次载入
/system/build.prop
/system/default.prop
/data/local.prop
然后会载入/data/property目录下的属性文件,这些属性文件以属性名为文件名,属性值为文件内容
最后在/dev/socket目录下会创建一个的socket,这个socket是一个AF_UNIX socket,它用来进行进程间通信,它会绑定/dev/socket/property_service路径,创建完成后会在这个socket上listen,等待接收请求,这个socket主要用于其他进程设置属性的时候,与init进程通信。
4)获取属性
其他进程是如何获取property的值,在上述属性初始化的过程中,我们知道亮点:
1.init进程已经把保存属性的内存块设置为shared,
2.把ANDROID_PROPERTY_WORKSPACE=fd,size作为环境变量传递给子进程
同时android还做了如下的事情:
1.init进程已经把保存属性的内存块设置为shared,
2.把ANDROID_PROPERTY_WORKSPACE=fd,size作为环境变量传递给子进程
同时android还做了如下的事情:
在libc_init_dynamic.c文件中声明和定义了__libc_preinit函数
/* We flag the __libc_preinit function as a constructor to ensure
* that its address is listed in libc.so's .init_array section.
* This ensures that the function is called by the dynamic linker
* as soon as the shared library is loaded.
*/
void __attribute__((constructor)) __libc_preinit(void);
使用constructor表示要把__libc_preinit加入libc.so的init_array section,这样在子进程加载这个so文件的时候,该函数会被调用,libc.so是C runtime库。
* that its address is listed in libc.so's .init_array section.
* This ensures that the function is called by the dynamic linker
* as soon as the shared library is loaded.
*/
void __attribute__((constructor)) __libc_preinit(void);
使用constructor表示要把__libc_preinit加入libc.so的init_array section,这样在子进程加载这个so文件的时候,该函数会被调用,libc.so是C runtime库。
__libc_preinit会调用__system_properties_init函数,该函数会读取ANDROID_PROPERTY_WORKSPACE环境变量,并根据其中的fd,使用mmap函数在本地创建一块只读的内存块,并使用变量__system_property_area__指向它。代码如下:
int __system_properties_init(void)
{
prop_area *pa;
int s, fd;
unsigned sz;
char *env;
//确保在init进程的时候不会调用后续代码
if(__system_property_area__ != ((void*) &dummy_props)) {
return 0;
}
env = getenv("ANDROID_PROPERTY_WORKSPACE");
if (!env) {
return -1;
}
fd = atoi(env);
env = strchr(env, ',');
if (!env) {
return -1;
}
sz = atoi(env + 1);
pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
if(pa == MAP_FAILED) {
return -1;
}
if((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION)) {
munmap(pa, sz);
return -1;
}
__system_property_area__ = pa;
return 0;
}
mmap使用了PROT_READ和MAP_SHARED参数,表明这是一块共享内存,数据来源是fd,数据内容与init进程中的内存块相同,且返回的内存区域是只读的。从前面的代码中我们知道这个fd是init进程设置给ANDROID_PROPERTY_WORKSPACE环境变量的,这样子进程就有了一块包含当前属性的内存块,重要的是,当init进程中这块内存发生变化的时候,子进程的属性也会同步变化。子进程在调用system/core/libcutils/properties.c文件中的property_get函数获取属性的时候,就可以直接从本进程的__system_property_area__指向的内存块中获取,而不用进行进程间通信。
但是这里有两个问题
1.这个libc.so是在新创建的进程中载入的,它为什么能够使用init进程的fd呢?
2.在启动服务的时候,会添加ANDROID_PROPERTY_WORKSPACE等诸多环境变量给子进程,这些环境变量都是存储在一个指针数组中
static const char *ENV[32];
因为要反复启动不同的服务,这个数组不会填满么?
对于问题1,要知道每个进程都有自己的文件描述符表,只要对应的文件描述符没有设置CLOSE_ON_EXEC标志,那么这个文件描述符都会被子进程所继承。
对于问题2,由于添加环境变量的操作是在子进程中执行的,根据Linux的copy on write的特性,ENV这个静态变量在刚刚fork的时候,子进程和父进程是共享一个内存地址的,但是在进行写操作的时候,Linux会为子进程的ENV重新分配页空间,这样,对于每个进程都是在他们的进程空间,重新添加环境变量,与父进程无关了。
{
prop_area *pa;
int s, fd;
unsigned sz;
char *env;
//确保在init进程的时候不会调用后续代码
if(__system_property_area__ != ((void*) &dummy_props)) {
return 0;
}
env = getenv("ANDROID_PROPERTY_WORKSPACE");
if (!env) {
return -1;
}
fd = atoi(env);
env = strchr(env, ',');
if (!env) {
return -1;
}
sz = atoi(env + 1);
pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
if(pa == MAP_FAILED) {
return -1;
}
if((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION)) {
munmap(pa, sz);
return -1;
}
__system_property_area__ = pa;
return 0;
}
mmap使用了PROT_READ和MAP_SHARED参数,表明这是一块共享内存,数据来源是fd,数据内容与init进程中的内存块相同,且返回的内存区域是只读的。从前面的代码中我们知道这个fd是init进程设置给ANDROID_PROPERTY_WORKSPACE环境变量的,这样子进程就有了一块包含当前属性的内存块,重要的是,当init进程中这块内存发生变化的时候,子进程的属性也会同步变化。子进程在调用system/core/libcutils/properties.c文件中的property_get函数获取属性的时候,就可以直接从本进程的__system_property_area__指向的内存块中获取,而不用进行进程间通信。
但是这里有两个问题
1.这个libc.so是在新创建的进程中载入的,它为什么能够使用init进程的fd呢?
2.在启动服务的时候,会添加ANDROID_PROPERTY_WORKSPACE等诸多环境变量给子进程,这些环境变量都是存储在一个指针数组中
static const char *ENV[32];
因为要反复启动不同的服务,这个数组不会填满么?
对于问题1,要知道每个进程都有自己的文件描述符表,只要对应的文件描述符没有设置CLOSE_ON_EXEC标志,那么这个文件描述符都会被子进程所继承。
对于问题2,由于添加环境变量的操作是在子进程中执行的,根据Linux的copy on write的特性,ENV这个静态变量在刚刚fork的时候,子进程和父进程是共享一个内存地址的,但是在进行写操作的时候,Linux会为子进程的ENV重新分配页空间,这样,对于每个进程都是在他们的进程空间,重新添加环境变量,与父进程无关了。
5)设置属性
其他进程在设置属性的时候,会调用system/core/libcutils/properties.c文件中的property_set函数,它实际上会调用bionic/libc/bionic/system_properties.c中的send_prop_msg函数,该函数会创建一个AF_LOCAL socket,并组装msg,发送给/dev/socket/property_service。这个socket文件是在启动property服务的时候创建的,这个fd会返回给init.c的main函数,并在main的for循环中等待事件,在等待到数据后,调用handle_property_set_fd函数,该函数会从socket中获取msg,这样就在init进程中设置属性
我们看到子进程读写属性的API函数是定义在system/core/libcutils/properties.c中的,而init进程则是在system/core/init/property_service.c文件中的。
6)属性API
system/core/libcutils/properties.c中对属性的设置和获取进行了封装,在init进程以外的进程中设置属性的时候,要使用这些函数
在shell中使用到的几个命令:
printenv 打印环境变量
getprop 打印所有属性
setprop 设置属性
四.参考文档
android系统启动过程
android开机启动流程初探
android开机图片修改
分析Android 根文件系统启动过程(init守护进程分析)
浅析android下propt怎么通过init进程传递和glibc库函数的添加