Android -- Init进程对属性系统的处理流程分析
在Android中,我们大量使用属性来保存、传递一些简单的标志、判断信息。属性包含属性的名称和属性值两部分,属性的名称一般是由"."分割的字符串组成,但这些名称的前缀都有特定的含义,我们不能随意改动;前缀后面的字符串则可以有我们自己指定。属性的值则只能是字符串形式。每个进程都可以使用属性,我们对属性的操作有读写之分。每个进程都可以读取某个属性而不受限制,直接靠本进程从属性共享内存区去读取;但写属性则是有限制的,在应用进程中调用属性设置方法,最终属性的写入操作都是在Init进程中处理。为了安全性,Init进程会检查发送修改属性请求的进程有无权限来修改属性值。
我们在之前分析到,Init.rc中有一种<property>:<value>形式的Trigger,它依赖属性的值来决定什么时候需要执行它对应的Command。Android中有一些特殊的属性前缀,它们具有一些指定的意义:
- "ro.":以"ro."为前缀的属性被视为只读的,即一旦设置,属性值不允许被更改。
- "persist.":以"persist."为前缀的属性是常保留的,在设置这类属性值时,它同时也会被写入/data/property/下与属性对应的文件中。再次开机时,这些属性值会被Init进程加载并发布到系统中。
- "net.":以"net."为前缀的属性,当它被设置时,属性"net.change"将会自动设置为最后修改的属性名。
- "ctl.":这类是控制属性,包含三种:ctl.start、ctl.stop、ctl.restart,用于启动、停止某个服务。使用ctl.start启动服务时,系统会将服务启动结果存入到"init.svc.<servicename>中(在服务启动过程由NotifyStateChange()函数发布该属性),我们可以查询此属性,获取启动结果。System和Root用户可以使用这类属性。
接着看Init进程中对属性服务的具体处理:
property_init();//初始化Android属性系统,Android中的属性系统在各个进程间都可以访问,这里创建了一块共享区域来存储属性值
property_load_boot_defaults();//解析default.prop文件,把文件中的属性值解析并发布到系统中
start_property_service();//启动属性服务,会创建一个socket 句柄,并将该fd注册到epoll_fd中;通过epoll轮询查询属性请求,并注册handle_property_set_fd()为实际事件处理函数;
init_parse_config_file("/init.rc");//解析init.rc文件
//将指定的action加入到action_queue(一个单向链表结构)中,每个action由一个函数指针和表示名字的字符串组成
action_for_each_trigger("early-init", action_add_queue_tail);
action_for_each_trigger("init", action_add_queue_tail);//将指定的action加入到action_queue中
// Run all property triggers based on current state of the properties.
//调用queue_builtin_action()函数动态生成一个action加入到action_queue中,每个action由一个函数指针和表示名字的字符串组成
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
下面一步一步分析,看它们的具体处理是什么样的。
property_init()会初始化属性的共享内存区域,这块内存区域只有Init进程可以写入,其他的用户进程则只能读:
void property_init() {
if (property_area_initialized) {
return;
}
property_area_initialized = true;
if (__system_property_area_init()) {//创建并初始化属性的共享内存空间,该内存空间映射的设备文件是/dev/__properties__
return;
}
//初始化workspace对象;在service_start()函数中,会有地方将/dev/__properties__文件对应的fd发布到系统中
pa_workspace.size = 0;
pa_workspace.fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);//并设置了只读
if (pa_workspace.fd == -1) {
ERROR("Failed to open %s: %s\n", PROP_FILENAME, strerror(errno));
return;
}
}
int __system_property_area_init()
{
return map_prop_area_rw();
}
static int map_prop_area_rw()
{
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
const int fd = open(property_filename,/*以读写方式打开/dev/__properties__设备文件*/
O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
if (fd < 0) {
if (errno == EACCES) {
/* for consistency with the case where the process has already
* mapped the page in and segfaults when trying to write to it
*/
abort();
}
return -1;
}
if (ftruncate(fd, PA_SIZE) < 0) {
close(fd);
return -1;
}
pa_size = PA_SIZE;//设置内存映射区表的长度,128kb
pa_data_size = pa_size - sizeof(prop_area);//数据大小设置
compat_mode = false;
//将/dev/__properties__设备文件映射到内存中,可读写,MAP_SHARED:对映射区域的写入数据会复制回文件内,与其它所有映射这个文件的进程共享映射空间
void *const memory_area = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (memory_area == MAP_FAILED) {
close(fd);
return -1;
}
//设置属性共享区域的标志和版本号,此时共享区域没有任何数据;
prop_area *pa = new(memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);
/* plug into the lib property services */
__system_property_area__ = pa;//保存pa变量
close(fd);
return 0;
}
系统会把将/dev/__properties__设备文件映射到共享内存中,并会在该内存起始位置设置共享区域的标志和版本号;最后,会以只读方式再打开一次/dev/__properties__设备文件,并将它的fd保存到workspace结构对象中。在service_start()函数中,会将该fd发布到系统中:
if (properties_initialized()) {//属性系统初始化完成后,将/dev/__properties__设备文件的描述符发布到系统中
get_property_workspace(&fd, &sz);
snprintf(tmp, sizeof(tmp), "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);//ANDROID_PROPERTY_WORKSPACE : fd
}
bool properties_initialized() {
return property_area_initialized;
}
这样处理的意图是:如果用户进程不能直接打开/dev/__properties__设备文件时,可以通过该环境变量获取到这个设备文件的fd并使用mmap()获得属性系统的共享内存地址。
属性系统的初始化工作完成后,就要将系统中已有的属性解析、发布到系统中。
property_load_boot_defaults()会去加载default.prop文件,把文件中的属性解析出来、并发布到系统中供其他模块使用:
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_VENDOR_BUILD "/vendor/build.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
#define PROP_PATH_FACTORY "/factory/factory.prop"
void property_load_boot_defaults() {
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties_from_file(const char* filename, const char* filter) {
Timer t;
std::string data;
if (read_file(filename, &data)) {
data.push_back('\n');
load_properties(&data[0], filter);
}
NOTICE("(Loading properties from %s took %.2fs.)\n", filename, t.duration());
}
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties(char *data, const char *filter)
{
char *key, *value, *eol, *sol, *tmp, *fn;
size_t flen = 0;
if (filter) {
flen = strlen(filter);
}
sol = data;
while ((eol = strchr(sol, '\n'))) {
key = sol;
*eol++ = 0;
sol = eol;
while (isspace(*key)) key++;
if (*key == '#') continue;
tmp = eol - 2;
while ((tmp > key) && isspace(*tmp)) *tmp-- = 0;
if (!strncmp(key, "import ", 7) && flen == 0) {
fn = key + 7;
while (isspace(*fn)) fn++;
key = strchr(fn, ' ');
if (key) {
*key++ = 0;
while (isspace(*key)) key++;
}
load_properties_from_file(fn, key);
} else {
value = strchr(key, '=');
if (!value) continue;
*value++ = 0;
tmp = value - 2;
while ((tmp > key) && isspace(*tmp)) *tmp-- = 0;
while (isspace(*value)) value++;
if (flen > 0) {
if (filter[flen - 1] == '*') {
if (strncmp(key, filter, flen - 1)) continue;
} else {
if (strcmp(key, filter)) continue;
}
}
property_set(key, value);
}
}
}
主要的处理过程就是解析/default.prop文件中的属性,最后会把这些属性发布到系统中。我们平时使用的操作属性的方法的native实现是:
int property_set(const char *key, const char *value)
{
return __system_property_set(key, value);
}
int property_get(const char *key, char *value, const char *default_value)
{
int len;
len = __system_property_get(key, value);
if(len > 0) {
return len;
}
if(default_value) {
len = strlen(default_value);
if (len >= PROPERTY_VALUE_MAX) {
len = PROPERTY_VALUE_MAX - 1;
}
memcpy(value, default_value, len);
value[len] = '\0';
}
return len;
}
这对方法最后会调用Android系统内核库Bionic中,并将属性设置请求发送给Init进程。
从上面的代码中我们看到这里只加载了"/default.prop"文件中的属性,另外几个属性文件这里都没有去解析。那这些属性文件的解析是在哪触发的呢?这就要看Init.rc中的定义了,它是由trigger触发的。我们以解析"persist."为前缀的属性为例:
# Load properties from /system/ + /factory after fs mount.
on load_system_props_action
load_system_props
on load_persist_props_action
load_persist_props
start logd
start logd-reinit
...
# Mount filesystems and start core system services.
on late-init
trigger early-fs
trigger fs
trigger post-fs
# Load properties from /system/ + /factory after fs mount. Place
# this in another action so that the load will be scheduled after the prior
# issued fs triggers have completed.
trigger load_system_props_action
# Now we can mount /data. File encryption requires keymaster to decrypt
# /data, which in turn can only be loaded when system properties are present
trigger post-fs-data
trigger load_persist_props_action
load_persist_props是persist属性的处理函数,当load_persist_props_action这个trigger被触发时,对应属性的解析、发布操作就会进行:
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_VENDOR_BUILD "/vendor/build.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
#define PROP_PATH_FACTORY "/factory/factory.prop"
/* When booting an encrypted system, /data is not mounted when the
* property service is started, so any properties stored there are
* not loaded. Vold triggers init to load these properties once it
* has mounted /data.
*/
void load_persist_props(void) {
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
}
static void load_override_properties() {
if (ALLOW_LOCAL_PROP_OVERRIDE) {
char debuggable[PROP_VALUE_MAX];
int ret = property_get("ro.debuggable", debuggable);
if (ret && (strcmp(debuggable, "1") == 0)) {
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE, NULL);
}
}
}
#define PERSISTENT_PROPERTY_DIR "/data/property"
static void load_persistent_properties() {
persistent_properties_loaded = 1;
std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(PERSISTENT_PROPERTY_DIR), closedir);
if (!dir) {
ERROR("Unable to open persistent property directory \"%s\": %s\n",
PERSISTENT_PROPERTY_DIR, strerror(errno));
return;
}
struct dirent* entry;
while ((entry = readdir(dir.get())) != NULL) {
if (strncmp("persist.", entry->d_name, strlen("persist."))) {
continue;
}
if (entry->d_type != DT_REG) {
continue;
}
// Open the file and read the property value.
int fd = openat(dirfd(dir.get()), entry->d_name, O_RDONLY | O_NOFOLLOW);
if (fd == -1) {
ERROR("Unable to open persistent property file \"%s\": %s\n",
entry->d_name, strerror(errno));
continue;
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
ERROR("fstat on property file \"%s\" failed: %s\n", entry->d_name, strerror(errno));
close(fd);
continue;
}
// File must not be accessible to others, be owned by root/root, and
// not be a hard link to any other file.
if (((sb.st_mode & (S_IRWXG | S_IRWXO)) != 0) || (sb.st_uid != 0) || (sb.st_gid != 0) ||
(sb.st_nlink != 1)) {
ERROR("skipping insecure property file %s (uid=%u gid=%u nlink=%u mode=%o)\n",
entry->d_name, (unsigned int)sb.st_uid, (unsigned int)sb.st_gid,
(unsigned int)sb.st_nlink, sb.st_mode);
close(fd);
continue;
}
char value[PROP_VALUE_MAX];
int length = read(fd, value, sizeof(value) - 1);
if (length >= 0) {
value[length] = 0;
property_set(entry->d_name, value);
} else {
ERROR("Unable to read persistent property file %s: %s\n",
entry->d_name, strerror(errno));
}
close(fd);
}
}
与前面的介绍一致,开机时"persist."前缀的属性会去解析/data/property目录下的属性文件,并将属性发布到系统中;保证开机后这些属性仍然可用。
start_property_service()函数会启动property服务,开始监听用户进程的属性设置请求:
void start_property_service() {
//SOCK_CLOEXEC:如果fork出子进程,则子进程中会关闭这个fd
//SOCK_NONBLOCK:这个fd不会阻塞
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
static void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
char * source_ctx = NULL;
struct pollfd ufds[1];
const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */
int nr;
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/* Check socket options here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
return;
}
ufds[0].fd = s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
if (nr == 0) {
ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
close(s);
return;
} else if (nr < 0) {
ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
close(s);
return;
}
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
r, sizeof(prop_msg), strerror(errno));
close(s);
return;
}
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
if (check_perms(msg.name, source_ctx)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}
属性数据的传输是靠socket来完成的。首先会按指定配置构建一个socket,并再其基础上进行绑定、监听,看创建socket的代码:
/*
* create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR
* ("/dev/socket") as dictated in init.rc. This socket is inherited by the
* daemon. We communicate the file descriptor's value via the environment
* variable ANDROID_SOCKET_ENV_PREFIX<name> ("ANDROID_SOCKET_foo").
*/
int create_socket(const char *name, int type, mode_t perm, uid_t uid,
gid_t gid, const char *socketcon)
{
struct sockaddr_un addr;
int fd, ret;
char *filecon;
if (socketcon)
setsockcreatecon(socketcon);
fd = socket(PF_UNIX, type, 0);
if (fd < 0) {
ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
return -1;
}
if (socketcon)
setsockcreatecon(NULL);
memset(&addr, 0 , sizeof(addr));
addr.sun_family = AF_UNIX;
//ANDROID_SOCKET_DIR:/dev/socket;这里传入的name是:property_service;
//这里为socket设置了设备文件:/dev/socket/property_service
snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",
name);
ret = unlink(addr.sun_path);//删掉之前的设备文件
if (ret != 0 && errno != ENOENT) {
ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno));
goto out_close;
}
filecon = NULL;
if (sehandle) {
ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK);
if (ret == 0)
setfscreatecon(filecon);
}
ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));//将addr与socket绑定起来
if (ret) {
ERROR("Failed to bind socket '%s': %s\n", name, strerror(errno));
goto out_unlink;
}
setfscreatecon(NULL);
freecon(filecon);
//设置权限
chown(addr.sun_path, uid, gid);
chmod(addr.sun_path, perm);
INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n",
addr.sun_path, perm, uid, gid);
return fd;
out_unlink:
unlink(addr.sun_path);
out_close:
close(fd);
return -1;
}
通过分析代码,我们在初始化property服务时创建了一个socket,并将该socket与/dev/socket/property_service这个设备文件进行绑定。这一点很重要,在我们设置属性时,会首先拿到该文件的fd,向里写数据;这时epoll就会检测到该socket可读,从而调用注册的事件处理函数来进行处理;然后在socket进行监听,等待对该socket的连接请求。
最后会向epoll_fd注册这个socket,同时也注册了事件处理函数handle_property_set_fd()。
void register_epoll_handler(int fd, void (*fn)()) {
epoll_event ev;
ev.events = EPOLLIN;//对文件描述符可读
ev.data.ptr = reinterpret_cast<void*>(fn);//保存指定的函数指针,用于后续的事件处理
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {//向epoll_fd添加要监听的fd,比如property、keychord和signal事件监听
ERROR("epoll_ctl failed: %s\n", strerror(errno));
}
}
当epoll轮询发现此socket有数据到来,即有属性设置请求时,就会调用handle_property_set_fd()去处理该事件。属性的设置处理过程放在后面介绍。
static int queue_property_triggers_action(int nargs, char **args)
{
queue_all_property_triggers();
/* enable property triggers */
property_triggers_enabled = 1;
return 0;
}
void queue_all_property_triggers()
{
queue_property_triggers(NULL, NULL);
}
//检测init.rc中定义的trigger:on property:<property>=<value>,判断当前的property这个属性的值是否等于value
//如果是,则将这个action加入到action_queue执行列表中
void queue_property_triggers(const char *name, const char *value)
{
struct listnode *node, *node2;
struct action *act;
struct trigger *cur_trigger;
bool match;
int name_length;
list_for_each(node, &action_list) {
act = node_to_item(node, struct action, alist);
match = !name;
list_for_each(node2, &act->triggers) {
cur_trigger = node_to_item(node2, struct trigger, nlist);
if (!strncmp(cur_trigger->name, "property:", strlen("property:"))) {
const char *test = cur_trigger->name + strlen("property:");
if (!match) {
name_length = strlen(name);
if (!strncmp(name, test, name_length) &&
test[name_length] == '=' &&
(!strcmp(test + name_length + 1, value) ||
!strcmp(test + name_length + 1, "*"))) {
match = true;
continue;
}
}
const char* equals = strchr(test, '=');
if (equals) {
char prop_name[PROP_NAME_MAX + 1];
char value[PROP_VALUE_MAX];
int length = equals - test;
if (length <= PROP_NAME_MAX) {
int ret;
memcpy(prop_name, test, length);
prop_name[length] = 0;
/* does the property exist, and match the trigger value? */
ret = property_get(prop_name, value);
if (ret > 0 && (!strcmp(equals + 1, value) ||
!strcmp(equals + 1, "*"))) {
continue;
}
}
}
}
match = false;
break;
}
if (match) {
action_add_queue_tail(act);
}
}
}
Init.rc中允许定义这样一种Action:“on property:<property>=<value>”;如果property属性在运行时设定成了这里指定的value值,则这个Action将会执行。在前面的分析中,存在属性的解析与发布操作,这时就会出现某个Action触发条件成立的情况。queue_property_triggers_action()就是完成这样的工作的:检查Init.rc中定义的<property>=<value>形式的Action是否已经满足触发条件,是否需要执行。如果检测到匹配项,则将该action加入到action_queue中。根据之前介绍的内容,Init进程最后会通过epoll轮询我们注册的property socket fd,如果该fd可读,则会处理这此epoll事件,开始处理这次属性的设置操作,这块具体的流程后面会分析到。至此,Android属性系统的整个初始化部分就完成了。
每个子进程都能使用属性服务,是因为它们都会去链接Bionic库。而在Bionic库的初始化函数__libc_init_common()中会为每个进程初始化系统属性服务:
void __libc_init_common(KernelArgumentBlock& args) {
// Initialize various globals.
environ = args.envp;
errno = 0;
__libc_auxv = args.auxv;
__progname = args.argv[0] ? args.argv[0] : "<unknown>";
__abort_message_ptr = args.abort_message_ptr;
// AT_RANDOM is a pointer to 16 bytes of randomness on the stack.
__stack_chk_guard = *reinterpret_cast<uintptr_t*>(getauxval(AT_RANDOM));
// Get the main thread from TLS and add it to the thread list.
pthread_internal_t* main_thread = __get_thread();
__pthread_internal_add(main_thread);
__system_properties_init(); // Requires 'environ'.
__libc_init_vdso();
}
int __system_properties_init()
{
return map_prop_area();
}
static int map_prop_area()
{
int fd = open(property_filename, O_CLOEXEC | O_NOFOLLOW | O_RDONLY);//只读
bool close_fd = true;
if (fd == -1 && errno == ENOENT) {
/*
* For backwards compatibility, if the file doesn't
* exist, we use the environment to get the file descriptor.
* For security reasons, we only use this backup if the kernel
* returns ENOENT. We don't want to use the backup if the kernel
* returns other errors such as ENOMEM or ENFILE, since it
* might be possible for an external program to trigger this
* condition.
*/
fd = get_fd_from_env();
close_fd = false;
}
if (fd < 0) {
return -1;
}
const int map_result = map_fd_ro(fd);
if (close_fd) {
close(fd);
}
return map_result;
}
这里的初始化操作就是再次打开/dev/__properties__设备文件,如果这种方式无法打开设备文件,则以环境变量的形式获取该设备文件的fd:
static int get_fd_from_env(void)
{
// This environment variable consistes of two decimal integer
// values separated by a ",". The first value is a file descriptor
// and the second is the size of the system properties area. The
// size is currently unused.
char *env = getenv("ANDROID_PROPERTY_WORKSPACE");
if (!env) {
return -1;
}
return atoi(env);
}
然后调用map_fd_ro()获取/dev/__properties__设备文件的共享区域的指针:
static int map_fd_ro(const int fd) {
struct stat fd_stat;
if (fstat(fd, &fd_stat) < 0) {
return -1;
}
if ((fd_stat.st_uid != 0)
|| (fd_stat.st_gid != 0)
|| ((fd_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0)
|| (fd_stat.st_size < static_cast<off_t>(sizeof(prop_area))) ) {
return -1;
}
pa_size = fd_stat.st_size;
pa_data_size = pa_size - sizeof(prop_area);
//只读;获取设备文件的共享区域指针
void* const map_result = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);
if (map_result == MAP_FAILED) {
return -1;
}
prop_area* pa = reinterpret_cast<prop_area*>(map_result);
//判断共享区域的标志和版本号;property_init()中进行设置
if ((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION &&
pa->version != PROP_AREA_VERSION_COMPAT)) {
munmap(pa, pa_size);
return -1;
}
if (pa->version == PROP_AREA_VERSION_COMPAT) {//兼容模式
compat_mode = true;
}
__system_property_area__ = pa;//保存到全局变量
return 0;
}
这样,每个子进程都为自己初始化了属性服务,可以自行从该共享内存中读取属性,而属性的设置则由init进程处理。那么,我们再看init进程是如何处理属性设置请求的。
之前介绍过,init进程最后会化身一个守护进程,它的一个任务就是处理属性设置的请求。我们先看设置属性时调用的property_set()函数的处理:
int property_set(const char *key, const char *value)
{
return __system_property_set(key, value);
}
层级调用__system_property_set():
int __system_property_set(const char *key, const char *value)
{
if (key == 0) return -1;
if (value == 0) value = "";
if (strlen(key) >= PROP_NAME_MAX) return -1;
if (strlen(value) >= PROP_VALUE_MAX) return -1;
prop_msg msg;//属性的结构体,包含key, value, cmd
memset(&msg, 0, sizeof msg);
msg.cmd = PROP_MSG_SETPROP;//设置命令操作码:PROP_MSG_SETPROP
//将要设置的key和value保存到prop_msg结构中
strlcpy(msg.name, key, sizeof msg.name);
strlcpy(msg.value, value, sizeof msg.value);
const int err = send_prop_msg(&msg);//通过socket向init进程发送属性的设置数据
if (err < 0) {
return err;
}
return 0;
}
prop_msg结构是属性设置信息的代码封装,它保存了需要设置的属性名称即对应的属性值;这里也为它设置了操作码:PROP_MSG_SETPROP。
最后进入send_prop_msg()函数通过socket向init进程发送数据:
static int send_prop_msg(const prop_msg *msg)
{
const int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);//新建一个socket
if (fd == -1) {
return -1;
}
//property_service_socket:/dev/socket/property_service;
//根据之前的介绍,这个就是在init进程注册属性服务时创建的socket的设备文件地址
const size_t namelen = strlen(property_service_socket);
sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
strlcpy(addr.sun_path, property_service_socket, sizeof(addr.sun_path));//将该socket的地址设置/dev/socket/property_service
addr.sun_family = AF_LOCAL;
socklen_t alen = namelen + offsetof(sockaddr_un, sun_path) + 1;
//注册属性服务时,/dev/socket/property_service设备文件关联的socket已经处于listen状态,等待客户端的连接请求
if (TEMP_FAILURE_RETRY(connect(fd, reinterpret_cast<sockaddr*>(&addr), alen)) < 0) {
close(fd);
return -1;
}
//与服务端socket连接成功后,将要设置的属性内容发送到服务端,即init进程中
const int num_bytes = TEMP_FAILURE_RETRY(send(fd, msg, sizeof(prop_msg), 0));
int result = -1;
//数据发送成功后,我们要等待init进程中处理完该请求;
//这里使用poll轮询,当该socket关闭时,就认为该请求已经处理完毕了;
//这里也将处理超时看成请求处理成功。
if (num_bytes == sizeof(prop_msg)) {
// We successfully wrote to the property server but now we
// wait for the property server to finish its work. It
// acknowledges its completion by closing the socket so we
// poll here (on nothing), waiting for the socket to close.
// If you 'adb shell setprop foo bar' you'll see the POLLHUP
// once the socket closes. Out of paranoia we cap our poll
// at 250 ms.
pollfd pollfds[1];
pollfds[0].fd = fd;
pollfds[0].events = 0;
const int poll_result = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
if (poll_result == 1 && (pollfds[0].revents & POLLHUP) != 0) {
result = 0;
} else {
// Ignore the timeout and treat it like a success anyway.
// The init process is single-threaded and its property
// service is sometimes slow to respond (perhaps it's off
// starting a child process or something) and thus this
// times out and the caller thinks it failed, even though
// it's still getting around to it. So we fake it here,
// mostly for ctl.* properties, but we do try and wait 250
// ms so callers who do read-after-write can reliably see
// what they've written. Most of the time.
// TODO: fix the system properties design.
result = 0;
}
}
close(fd);
return result;
}
这里会去连接/dev/socket/property_service设备文件代表的socket,并向他发送数据;最后通过poll轮询等待属性设置请求处理完毕,然后就关闭此socket。这里发送完数据后,init进程中的epoll轮询检测到property注册的socket的文件描述符可读,就会调用注册的事件处理函数处理该设置请求。向epoll_fd中注册property socket fd的过程之前已经介绍过,这里就不再赘述。直接看事件处理函数:
static void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
char * source_ctx = NULL;
struct pollfd ufds[1];
const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */
int nr;
//等待、处理客户端的socket连接请求
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/* Check socket options here */
//通过设置SO_PEERCRED返回连接到此套接字的进程的凭据,用于检测对端进程(客户端进程)的身份
//获取对端进程的pid、uid、gid
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
return;
}
ufds[0].fd = s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));//等待客户端socket发送数据
if (nr == 0) {
ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
close(s);
return;
} else if (nr < 0) {
ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
close(s);
return;
}
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));//获取客户端发送的属性设置数据
//判断数据的大小是否合法
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
r, sizeof(prop_msg), strerror(errno));
close(s);
return;
}
switch(msg.cmd) {
//识别操作码
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
if (check_perms(msg.name, source_ctx)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}
首先,会等待客户端的连接请求;当有客户端请求连接时,还会去获取该客户端进程的ucred结构信息(pid、uid、gid)。最后等待客户端发送数据,并同时准备接收这些数据;数据收到后,先判断PROP_MSG_SETPROP操作码,这是客户端封装数据时设置的。接着会检测请求的属性名称是否是合法的:
static bool is_legal_property_name(const char* name, size_t namelen)
{
size_t i;
if (namelen >= PROP_NAME_MAX) return false;
if (namelen < 1) return false;
if (name[0] == '.') return false;
if (name[namelen - 1] == '.') return false;
/* Only allow alphanumeric, plus '.', '-', or '_' */
/* Don't allow ".." to appear in a property name */
for (i = 0; i < namelen; i++) {
if (name[i] == '.') {
// i=0 is guaranteed to never have a dot. See above.
if (name[i-1] == '.') return false;
continue;
}
if (name[i] == '_' || name[i] == '-') continue;
if (name[i] >= 'a' && name[i] <= 'z') continue;
if (name[i] >= 'A' && name[i] <= 'Z') continue;
if (name[i] >= '0' && name[i] <= '9') continue;
return false;
}
return true;
}
这里检测的依据有:
- 属性名称的长度必须大于等于1而小于32
- 属性名称不能以"."开头和结尾
- 属性名称不能出现连续的"."
- 属性的名称必须以"."为分隔符,且只能使用:'0'-'9'、'a'-'z'、'A'-'Z'、'-'及'_'等字符
检测完属性名称的合法性,如果此时使用的是控制类指令"ctl.",则先关掉此次socket,然后检测客户端socket的进程是否有权限设置控制类属性;最后调用 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_restart(arg);
} else {
ERROR("unknown control msg '%s'\n", msg);
}
}
以"ctl.start"启动服务为例:
static void msg_start(const char *name)
{
struct service *svc = NULL;
char *tmp = NULL;
char *args = NULL;
if (!strchr(name, ':'))
svc = service_find_by_name(name);
else {
tmp = strdup(name);
if (tmp) {
args = strchr(tmp, ':');
*args = '\0';
args++;
svc = service_find_by_name(tmp);
}
}
if (svc) {
service_start(svc, args);
} else {
ERROR("no such service '%s'\n", name);
}
if (tmp)
free(tmp);
}
首先从传入的参数中获取到要启动的服务名称,最后在service_list中找到符合该名称的服务service结构实例,然后再以解析出的服务启动参数去调用servcie_start()函数启动这个服务,该函数的处理过程之前已经分析过了。"ctl.stop"和"ctl.restart"的处理类似,也比较简单,就不再分析了。
再回到handle_property_set_fd()中,如果设置的属性不是控制类属性,则判断完权限后,调用property_set()函数处理属性设置:
int property_set(const char* name, const char* value) {
int rc = property_set_impl(name, value);
if (rc == -1) {
ERROR("property_set(\"%s\", \"%s\") failed\n", name, value);
}
return rc;
}
层级调用property_set_impl()进行属性设置操作:
static int property_set_impl(const char* name, const char* value) {
size_t namelen = strlen(name);//属性设置的名称
size_t valuelen = strlen(value);//属性设置的值
if (!is_legal_property_name(name, namelen)) return -1;//判断属性名称是否合法
if (valuelen >= PROP_VALUE_MAX) return -1;//属性值的长度判断
//SELinux相关
if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) {
if (selinux_reload_policy() != 0) {
ERROR("Failed to reload policy\n");
}
} else if (strcmp("selinux.restorecon_recursive", name) == 0 && valuelen > 0) {
if (restorecon_recursive(value) != 0) {
ERROR("Failed to restorecon_recursive %s\n", value);
}
}
prop_info* pi = (prop_info*) __system_property_find(name);//属性系统维护了一张属性类别,先找到该名称的属性结构;
if(pi != 0) {//如果列表中之前存在该名称的属性,则执行值的更新操作
/* ro.* properties may NEVER be modified once set */
if(!strncmp(name, "ro.", 3)) return -1;//"ro."前缀的属性设置后不能修改
__system_property_update(pi, value, valuelen);
} else {//否则,则是新添加一个该名称的属性
int rc = __system_property_add(name, namelen, value, valuelen);
if (rc < 0) {
return rc;
}
}
/* If name starts with "net." treat as a DNS property. */
if (strncmp("net.", name, strlen("net.")) == 0) {//如果是"net."前缀的属性,还要将"net.change"属性的值设置为当前设置的属性名称
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
property_set("net.change", name);
} else if (persistent_properties_loaded &&
strncmp("persist.", name, strlen("persist.")) == 0) {//处理常保留属性
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);//也会将该属性设置信息写入到/data/property/目录下对应的属性名称文件中
}
property_changed(name, value);//检查是否存在与这次属性相匹配的触发器;如果存在,则将对应的action加入到action_queue中
return 0;
}
首先会判断属性名称和属性值是否合法,这部分前面已经分析过。然后在维护的属性列表中按名称查找属性,如果有这个属性,那这部分操作是属性值更新;此处也排除了对"ro."前缀属性的修改操作。这时将属性值更新到共享内存中:
int __system_property_update(prop_info *pi, const char *value, unsigned int len)
{
prop_area *pa = __system_property_area__;//获取到共享内存的指针
if (len >= PROP_VALUE_MAX)
return -1;
uint32_t serial = atomic_load_explicit(&pi->serial, memory_order_relaxed);
serial |= 1;
atomic_store_explicit(&pi->serial, serial, memory_order_relaxed);
// The memcpy call here also races. Again pretend it
// used memory_order_relaxed atomics, and use the analogous
// counterintuitive fence.
atomic_thread_fence(memory_order_release);
memcpy(pi->value, value, len + 1);//将属性的值写入到共享内存中
atomic_store_explicit(
&pi->serial,
(len << 24) | ((serial + 1) & 0xffffff),
memory_order_release);
__futex_wake(&pi->serial, INT32_MAX);
atomic_store_explicit(
&pa->serial,
atomic_load_explicit(&pa->serial, memory_order_relaxed) + 1,
memory_order_release);
__futex_wake(&pa->serial, INT32_MAX);
return 0;
}
首先获取到保存的共享内存地址,然后通过通过院子存储操作将新的属性值保存到共享内存中。
如果之前没有找到这个名称的属性,则操作则是在系统中添加一个属性:
int __system_property_add(const char *name, unsigned int namelen,
const char *value, unsigned int valuelen)
{
prop_area *pa = __system_property_area__;
const prop_info *pi;
if (namelen >= PROP_NAME_MAX)
return -1;
if (valuelen >= PROP_VALUE_MAX)
return -1;
if (namelen < 1)
return -1;
pi = find_property(root_node(), name, namelen, value, valuelen, true);
if (!pi)
return -1;
// There is only a single mutator, but we want to make sure that
// updates are visible to a reader waiting for the update.
atomic_store_explicit(
&pa->serial,
atomic_load_explicit(&pa->serial, memory_order_relaxed) + 1,
memory_order_release);
__futex_wake(&pa->serial, INT32_MAX);
return 0;
}
如果是"net."前缀的属性,这时会把设置的属性名称设置到"net.change"属性中;如果是"persist."开头的常保留属性,还会将属性写入/data/property目录下对应的文件中:
/* If name starts with "net." treat as a DNS property. */
if (strncmp("net.", name, strlen("net.")) == 0) {//如果是"net."前缀的属性,还要将"net.change"属性的值设置为当前设置的属性名称
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
property_set("net.change", name);
} else if (persistent_properties_loaded &&
strncmp("persist.", name, strlen("persist.")) == 0) {//处理常保留属性
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);//也会将该属性设置信息写入到/data/property目录下的属性对应的文件中
}
static void write_persistent_property(const char *name, const char *value)
{
char tempPath[PATH_MAX];
char path[PATH_MAX];
int fd;
snprintf(tempPath, sizeof(tempPath), "%s/.temp.XXXXXX", PERSISTENT_PROPERTY_DIR);
fd = mkstemp(tempPath);
if (fd < 0) {
ERROR("Unable to write persistent property to temp file %s: %s\n", tempPath, strerror(errno));
return;
}
write(fd, value, strlen(value));
fsync(fd);
close(fd);
snprintf(path, sizeof(path), "%s/%s", PERSISTENT_PROPERTY_DIR, name);
if (rename(tempPath, path)) {
unlink(tempPath);
ERROR("Unable to rename persistent property file %s to %s\n", tempPath, path);
}
}
最后会调用property_changed()函数,检测此时是否有存在与此次属性设置相匹配的trigger,如果有,则将这个Action加入到action_queue执行列表中:
void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled)
queue_property_triggers(name, value);
}
//检测init.rc中定义的trigger:on property:<property>=<value>,判断当前的property这个属性的值是否等于value
//如果是,则将这个action加入到action_queue执行列表中
void queue_property_triggers(const char *name, const char *value)
{
struct listnode *node, *node2;
struct action *act;
struct trigger *cur_trigger;
bool match;
int name_length;
list_for_each(node, &action_list) {
act = node_to_item(node, struct action, alist);
match = !name;
list_for_each(node2, &act->triggers) {
cur_trigger = node_to_item(node2, struct trigger, nlist);
if (!strncmp(cur_trigger->name, "property:", strlen("property:"))) {
const char *test = cur_trigger->name + strlen("property:");
if (!match) {
name_length = strlen(name);
if (!strncmp(name, test, name_length) &&
test[name_length] == '=' &&
(!strcmp(test + name_length + 1, value) ||
!strcmp(test + name_length + 1, "*"))) {
match = true;
continue;
}
}
const char* equals = strchr(test, '=');
if (equals) {
char prop_name[PROP_NAME_MAX + 1];
char value[PROP_VALUE_MAX];
int length = equals - test;
if (length <= PROP_NAME_MAX) {
int ret;
memcpy(prop_name, test, length);
prop_name[length] = 0;
/* does the property exist, and match the trigger value? */
ret = property_get(prop_name, value);
if (ret > 0 && (!strcmp(equals + 1, value) ||
!strcmp(equals + 1, "*"))) {
continue;
}
}
}
}
match = false;
break;
}
if (match) {
action_add_queue_tail(act);
}
}
}
而更新了action_queue列表后,Init进程会重复查询action_queue中可执行的action,并执行它,这部分是在Init进程中处理的:
while (true) {
if (!waiting_for_exec) {
execute_one_command();//执行action_queue命令列表中的命令
restart_processes();//启动服务列表中的进程
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action) {
timeout = 0;
}
bootchart_sample(&timeout);//bootchart是一个用可视化方式对启动过程进行性能分析的工具;需要定时唤醒进程
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));//开始轮询,epoll_wait()等待事件产生
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();//调用epoll_event事件存储的函数指针处理事件
}
}
这块内容之前已经做过说明。
属性设置的处理流程这里就分析完毕,接下来再看下属性读取的路程是怎样的。
查询属性值时的调用方法是:
int property_get(const char *key, char *value, const char *default_value)
{
int len;
len = __system_property_get(key, value);
if(len > 0) {
return len;
}
if(default_value) {
len = strlen(default_value);
if (len >= PROPERTY_VALUE_MAX) {
len = PROPERTY_VALUE_MAX - 1;
}
memcpy(value, default_value, len);
value[len] = '\0';
}
return len;
}
该函数会传入一个默认值,当此属性获取的结果长度出错时,就会返回这个默认值。看__system_property_get()函数:
int __system_property_get(const char *name, char *value)
{
const prop_info *pi = __system_property_find(name);//查询这个属性名称的结构实例
if (pi != 0) {//如果不为空,则说明系统中已经有这个属性,就去读它的值
return __system_property_read(pi, 0, value);//从共享内存中读取这个属性的值,并返回
} else {//否则,返回0
value[0] = 0;
return 0;
}
}
int __system_property_read(const prop_info *pi, char *name, char *value)
{
if (__predict_false(compat_mode)) {
return __system_property_read_compat(pi, name, value);
}
while (true) {
uint32_t serial = __system_property_serial(pi); // acquire semantics
size_t len = SERIAL_VALUE_LEN(serial);
memcpy(value, pi->value, len + 1);
// TODO: Fix the synchronization scheme here.
// There is no fully supported way to implement this kind
// of synchronization in C++11, since the memcpy races with
// updates to pi, and the data being accessed is not atomic.
// The following fence is unintuitive, but would be the
// correct one if memcpy used memory_order_relaxed atomic accesses.
// In practice it seems unlikely that the generated code would
// would be any different, so this should be OK.
atomic_thread_fence(memory_order_acquire);
if (serial ==
load_const_atomic(&(pi->serial), memory_order_relaxed)) {
if (name != 0) {
strcpy(name, pi->name);
}
return len;
}
}
}
通过原子操作从共享内存中读取属性的值;获取到属性的值后,返回该结果。属性的读取过程就结束了。
到此,Init进程中属性系统的初始化和用户进程属性的设置和读取流程就分析完了。