1 Property service initialization {property_init}
The process of property_init focuses on 2 things: creating the mapping & making other processes aware of the mapping space
1.1 Create a memory-mapped space of shared memory {this space can be accessed by other processes through mmap}, open it again after initialization
//property_init() -just call->:init_property_area(),实现如下:
static int property_area_inited = 0;
static int init_property_area(void)//初始化属性存储区域
{
if (property_area_inited)//通过全局变量控制,仅执行一次,之后执行无效
return -1;
if(__system_property_area_init())//-just call->:map_prop_area_rw(),打开节点并初始化一块区域并关闭
return -1;
if(init_workspace(&pa_workspace, 0))//重新打开设备节点"/dev/__properties__"
return -1;
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);//在exec中使用该程序该文件描述符不可用,但在fork中可以
property_area_inited = 1;//表示已经初始化完毕
return 0;
}
Among them, the key method map_prop_area_rw is implemented as follows:
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...
//这里打开的关键节点为"/dev/__properties__"
const int fd = open(property_filename,O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
...
// TODO: Is this really required ? Does android run on any kernels that don't support O_CLOEXEC ?
const int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);//在exec中使用该程序该文件描述符不可用,但在fork中可以
...
(ftruncate(fd, PA_SIZE)//ftruncate会将参数fd指定的文件大小改为参数length指定的大小。
pa_size = PA_SIZE; // PA_SIZE被定义为(128 * 1024)
pa_data_size = pa_size - sizeof(prop_area);//这里的prop_area实际上就是数据头而已
compat_mode = false;
void *const memory_area = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//mmap
...
prop_area *pa = new(memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);
__system_property_area__ = pa; //plug into the lib property services
close(fd);
return 0;
}
1.2 How do other processes know about the shared memory
Android adopts the constructor attribute of gcc, which specifies a _libc_preinit function. When the bionic library is loaded, _libc_preinit will be called automatically. The key call process is as follows:
__libc_preinit -call-> __libc_init_common(*args)->__system_properties_init->map_prop_area:
Here is a more critical analysis of map_prop_area, the implementation is as follows:
static int map_prop_area()
{
int fd(open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC));
if (fd >= 0) {
/* For old kernels that don't support O_CLOEXEC */
const int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
...
}
bool close_fd = true;
if ((fd < 0) && (errno == ENOENT)) {
fd = get_fd_from_env();
close_fd = false;
}
...
const int map_result = map_fd_ro(fd);
if (close_fd) {
close(fd);
}
return map_result;
}
The implementation of get_fd_from_env is as follows:
static int get_fd_from_env(void)
{
char *env = getenv("ANDROID_PROPERTY_WORKSPACE");//一定有某个地方添加了该环境变量,这里取出
if (!env) {
return -1;
}
return atoi(env);
}
The implementation of map_fd_ro is as follows:
static int map_fd_ro(const int fd) {
struct stat fd_stat;
(fstat(fd, &fd_stat);
...
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);
...
prop_area* pa = reinterpret_cast<prop_area*>(map_result);
...
if (pa->version == PROP_AREA_VERSION_COMPAT) {
compat_mode = true;
}
__system_property_area__ = pa;
return 0;
Summary: The purpose of this process is to map the property attribute space initialized by init to the local process for easy use
2 Property service default item initialization {property_load_boot_defaults()}
The loading process of attributes is as follows:
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
//property_load_boot_defaults->load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);实现如下
static void load_properties_from_file(const char *fn, const char *filter)
{
char *data;
unsigned sz;
data = read_file(fn, &sz);
if(data != 0) {
load_properties(data, filter);->//这里最终会循环调用property_set(key, value);来设置各个属性
free(data);
}
}
3 Start property service {queue_builting_action(property_service_init_action, “property_service_init”)}
Here we focus on analyzing property_service_init_action, which is implemented as follows:
//property_service_init_action()-just call ->start_property_service();实现如下:
//主要工作为,创建socket,但此时并未接收客户端
void start_property_service(void)
{
int fd;
//creates a Unix domain socket in ANDROID_SOCKET_DIR* ("/dev/socket") as dictated in init.rc
fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL);
if(fd < 0) return;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
listen(fd, 8);
property_set_fd = fd;
}
4. On property processing in action {if the property meets the condition}
{queue_builtin_action(queue_property_triggers_action, “queue_property_triggers”)}
Here we focus on analyzing queue_property_triggers_action, the implementation is as follows:
//queue_property_triggers_action->queue_all_property_triggers,实现如下:
void queue_all_property_triggers()
{
struct listnode *node;
struct action *act;
list_for_each(node, &action_list) {
act = node_to_item(node, struct action, alist);
if (!strncmp(act->name, "property:", strlen("property:"))) {
/* parse property name and value syntax is property:<name>=<value> */
const char* name = act->name + strlen("property:");
const char* equals = strchr(name, '=');
if (equals) {
char prop_name[PROP_NAME_MAX + 1];
char value[PROP_VALUE_MAX];
int length = equals - name;
if (length > PROP_NAME_MAX) {
ERROR("property name too long in trigger %s", act->name);
} else {
int ret;
memcpy(prop_name, name, 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, "*"))) {
action_add_queue_tail(act);
}
}
}
}
}
}
In this process, if the property condition satisfies on property:=, it will be placed in the corresponding action execution queue
5 The execution of load_all_props_action {on late-init is processed by trigger load_all_props_action}
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.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 load_all_props(void)
{
load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT, NULL);
load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
load_override_properties(); //{本地属性会覆盖之前的默认属性}
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties(); //读取"/data/property"下的persist属性并执行property_set
}
6 Property setting change processing
6.1 Property setting process
6.1.1 When the upper layer {java layer or native layer} of the system executes the property_set operation, perform the following operations:
//property_set-just call->__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;
//结构体msg初始化
prop_msg msg;
memset(&msg, 0, sizeof msg);
msg.cmd = PROP_MSG_SETPROP;
strlcpy(msg.name, key, sizeof msg.name);
strlcpy(msg.value, value, sizeof msg.value);
//发送msg消息
const int err = send_prop_msg(&msg);
if (err < 0) {
return err;
}
return 0;
}
Continue to analyze send_prop_msg, the implementation is as follows:
static int send_prop_msg(const prop_msg *msg)
{
const int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);//创建socket并初始化
...
const size_t namelen = strlen(property_service_socket);
...
(connect(fd, reinterpret_cast<sockaddr*>(&addr), alen)) < 0) //连接property service
...
(send(fd, msg, sizeof(prop_msg), 0));//发送msg消息
...
if (num_bytes == sizeof(prop_msg)) {
//写入成功后还要进行poll操作,保证服务端收到请求并正确处理
pollfd pollfds[1];
pollfds[0].fd = fd;
pollfds[0].events = 0;
//如果server端已经close fd,则客户端pollup挂起,这里会检测到
const int poll_result = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
if (poll_result == 1 && (pollfds[0].revents & POLLHUP) != 0) {
result = 0;
}
}
close(fd);
return result;
}
The whole process is actually sending a message to the property service service and waiting for the end of processing
6.1.2 The analysis mainly focuses on this piece of code in init {the processing flow after receiving the attribute change}:
for(;;) {
...
if (!property_set_fd_init && get_property_set_fd() > 0) {
//只有第一次会进入,ufds初始化工作
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 (!action_queue_empty() || cur_action)
timeout = 0;
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();//处理属性事件
...
}
}
}
The processing of handle_property_set_fd is implemented as follows:
void handle_property_set_fd()
{
...
((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size))//accept操作
(getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) //检测socket options
ufds[0].fd = s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
...
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
...
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) {
//如果属性是以ctl.开头,启动后面的服务
// Keep the old close-socket-early behavior when handling ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx)) {
//SELinux校验相关
/**handle_control_message
分支(!strcmp(msg,"start"))->msg_start(arg);//service_find_by_name->service_start,启动服务
分支(!strcmp(msg,"stop")) ->msg_stop(arg);//service_find_by_name->service_stop,关闭服务
分支(!strcmp(msg,"restart"))->msg_restart(arg);//service_find_by_name->service_restart,重启服务
*/
handle_control_message((char*) msg.name + 4, (char*) msg.value);
}
...
} else {
if (check_perms(msg.name, source_ctx)) {
//SELinux校验相关
property_set((char*) msg.name, (char*) msg.value);//设置属性
}
...
close(s);
}
freecon(source_ctx);
break;
...
}
}
6.2 Attribute Acquisition Process
//int property_get-call->system_property_get(key, value)
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 {
value[0] = 0;
return 0;
}
}
Continue to analyze __system_property_find, the implementation is as follows:
//__system_property_find-call-> __system_property_find_compat(name)
__LIBC_HIDDEN__ const prop_info *__system_property_find_compat(const char *name)
{
prop_area_compat *pa = (prop_area_compat *)__system_property_area__;//共享内存头地址
unsigned count = pa->count;
unsigned *toc = pa->toc;
unsigned len = strlen(name);
prop_info_compat *pi;
if (len >= PROP_NAME_MAX)
return 0;
if (len < 1)
return 0;
while(count--) {
//从共享内存中找到相应属性结构体
unsigned entry = *toc++;
if(TOC_NAME_LEN(entry) != len) continue;
pi = TOC_TO_INFO(pa, entry);
if(memcmp(name, pi->name, len)) continue;
return (const prop_info *)pi;//找到则直接返回
}
return 0;//未找到返回0
}
Explanation: The process of obtaining attributes no longer goes through the socket, but directly reads from the shared memory