概述
init是一个进程,确切的说,它是Linux系统中用户空间的第一个进程。由于Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程。init的进程号是1。作为天字第一号进程,init有很多重要的工作:
- init提供property service(属性服务)来管理Android系统的属性。
- init负责创建系统中的关键进程,包括zygote。
以往的文章一上来就介绍init的源码,但是我这里先从这两个主要工作开始。搞清楚这两个主要工作是如何实现的,我们再回头来看init的源码。
这篇文章主要是介绍init进程的属性服务。
跟init属性服务相关的源码目录如下:
system/core/init/
bionic/libc/bionic/
system/core/libcutils/
</div>
属性服务
在windows平台上有一个叫做注册表的东西,它可以存储一些类似key/value的键值对。一般而言,系统或者某些应用程序会把自己的一些属性存储在注册表中,即使系统重启或应用程序重启,它还能根据之前在注册表中设置的属性值,进行相应的初始化工作。
Android系统也提供了类似的机制,称之为属性服务(property service)。应用程序可以通过这个服务查询或者设置属性。我们可以通过如下命令,获取手机中属性键值对。
adb shell getprop</div>
例如红米Note手机的属性值如下:
[ro.product.device]: [lcsh92_wet_jb9] [ro.product.locale.language]: [zh] [ro.product.locale.region]: [CN] [ro.product.manufacturer]: [Xiaomi]</div>
在system/core/init/init.c文件的main函数中,跟属性服务的相关代码如下:
property_init(); queue_builtin_action(property_service_init_action, "property_service_init");</div>
接下来,我们分别看一下这两处代码的具体实现。
属性服务初始化
创建存储空间
首先,我们先来看一下property_init函数的源码(/system/core/init/property_service.c):
void property_init(void)
{
init_property_area();
}
</div>
property_init函数中只是简单的调用了init_property_area方法,接下来我们看一下这个方法的具体实现:
static int property_area_inited = 0;
static workspace pa_workspace;
static int init_property_area(void)
{
// 属性空间是否已经初始化
if (property_area_inited)
return -1;
if (__system_property_area_init())
return -1;
if (init_workspace(&pa_workspace, 0))
return -1;
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
property_area_inited = 1;
return 0;
}
</div>
从init_property_area函数,我们可以看出,函数首先判断属性内存区域是否已经初始化过,如果已经初始化,则返回-1。如果没有初始化,我们接下来会发现有两个关键函数__system_property_area_init和init_workspace应该是跟内存区域初始化相关。那我们分别分析一下这两个函数具体实现。
__system_property_area_init
__system_property_area_init函数位于/bionic/libc/bionic/system_properties.c文件中,具体代码实现如下:
struct prop_area {
unsigned bytes_used;
unsigned volatile serial;
unsigned magic;
unsigned version;
unsigned reserved[28];
char data[0];
};
typedef struct prop_area prop_area;
prop_area *__system_property_area__ = NULL;
#define PROP_FILENAME "/dev/__properties__"
static char property_filename[PATH_MAX] = PROP_FILENAME;
#define PA_SIZE (128 * 1024)
static int map_prop_area_rw()
{
prop_area *pa;
int fd;
int ret;
/**
* O_RDWR ==> 读写
* O_CREAT ==> 若不存在,则创建
* O_NOFOLLOW ==> 如果filename是软链接,则打开失败
* O_EXCL ==> 如果使用O_CREAT是文件存在,则可返回错误信息
*/
fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
if (fd < 0) {
if (errno == EACCES) {
abort();
}
return -1;
}
ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
if (ret < 0)
goto out;
if (ftruncate(fd, PA_SIZE) < 0)
goto out;
pa_size = PA_SIZE;
pa_data_size = pa_size - sizeof(prop_area);
compat_mode = false;
// mmap映射文件实现共享内存
pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pa == MAP_FAILED)
goto out;
/*初始化内存地址中所有值为0*/
memset(pa, 0, pa_size);
pa->magic = PROP_AREA_MAGIC;
pa->version = PROP_AREA_VERSION;
pa->bytes_used = sizeof(prop_bt);
__system_property_area__ = pa;
close(fd);
return 0;
out:
close(fd);
return -1;
}
int __system_property_area_init()
{
return map_prop_area_rw();
}
</div>
代码比较好理解,主要内容是利用mmap映射property_filename创建了一个共享内存区域,并将共享内存的首地址赋值给全局变量__system_property_area__。
关于mmap映射文件实现共享内存IPC通信机制,可以参考这篇文章:mmap实现IPC通信机制
init_workspace
接下来,我们来看一下init_workspace函数的源码(/system/core/init/property_service.c):
typedef struct {
void *data;
size_t size;
int fd;
}workspace;
static int init_workspace(workspace *w, size_t size)
{
void *data;
int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);
if (fd < 0)
return -1;
w->size = size;
w->fd = fd;
return 0;
}
</div>
客户端进程访问属性内存区域
虽然属性内存区域是init进程创建的,但是Android系统希望其他进程也能够读取这块内存区域里的内容。为了做到这一点,init进程在属性区域初始化过程中做了如下两项工作:
把属性内存区域创建在共享内存上,而共享内存是可以跨进程的。这一点,在上述代码中是通过mmap映射/dev/__properties__文件实现的。pa_workspace变量中的fd成员也保存了映射文件的句柄。
如何让其他进程知道这个共享内存句柄呢?Android先将文件映射句柄赋值给__system_property_area__变量,这个变量属于bionic_lic库中的输出的一个变量,然后利用了gcc的constructor属性,这个属性指明了一个__lib_prenit函数,当bionic_lic库被加载时,将自动调用__libc_prenit,这个函数内部完成共享内存到本地进程的映射工作。
只讲原理是不行的,我们直接来看一下__lib_prenit函数代码的相关实现:
void __attribute__((constructor)) __libc_prenit(void);
void __libc_prenit(void)
{
// ...
__libc_init_common(elfdata); // 调用这个函数
// ...
}
__libc_init_common函数为:
void __libc_init_common(uintptr_t *elfdata)
{
// ...
__system_properties_init(); // 初始化客户端的属性存储区域
}
__system_properties_init函数有回到了我们熟悉的/bionic/libc/bionic/system_properties.c文件:
static int get_fd_from_env(void)
{
char *env = getenv("ANDROID_PROPERTY_WORKSPACE");
if (! env) {
return -1;
}
return atoi(env);
}
static int map_prop_area()
{
bool formFile = true;
int result = -1;
int fd;
int ret;
fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (fd >= 0) {
/* For old kernels that don't support O_CLOEXEC */
ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
if (ret < 0)
goto cleanup;
}
if ((fd < 0) && (error == ENOENT)) {
fd = get_fd_from_env();
fromFile = false;
}
if (fd < 0) {
return -1;
}
struct stat fd_stat;
if (fstat(fd, &fd_stat) < 0) {
goto cleanup;
}
if ((fd_stat.st_uid != 0)
|| (fd_stat.st_gid != 0)
|| (fd_stat.st_mode & (S_IWGRP | S_IWOTH) != 0)
|| (fd_stat.st_size < sizeof(prop_area))) {
goto cleanup;
}
pa_size = fd_stat.st_size;
pa_data_size = pa_size - sizeof(prop_area);
/*
* 映射init创建的属性内存到本地进

