首页 > 编程 > C++ > 正文

深入剖析Android中init进程实现的C语言源码

2020-05-23 14:16:52
字体:
来源:转载
供稿:网友

这篇文章主要介绍了Android中init进程实现的C语言源码,init属性服务在安卓中属于系统的底层Linux服务,需要的朋友可以参考下

概述

init是一个进程,确切的说,它是Linux系统中用户空间的第一个进程。由于Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程。init的进程号是1。作为天字第一号进程,init有很多重要的工作:

init提供property service(属性服务)来管理Android系统的属性。

init负责创建系统中的关键进程,包括zygote。

以往的文章一上来就介绍init的源码,但是我这里先从这两个主要工作开始。搞清楚这两个主要工作是如何实现的,我们再回头来看init的源码。

这篇文章主要是介绍init进程的属性服务。

跟init属性服务相关的源码目录如下:

 

 
  1. system/core/init/ 
  2. bionic/libc/bionic/ 
  3. system/core/libcutils/ 

属性服务

在windows平台上有一个叫做注册表的东西,它可以存储一些类似key/value的键值对。一般而言,系统或者某些应用程序会把自己的一些属性存储在注册表中,即使系统重启或应用程序重启,它还能根据之前在注册表中设置的属性值,进行相应的初始化工作。

Android系统也提供了类似的机制,称之为属性服务(property service)。应用程序可以通过这个服务查询或者设置属性。我们可以通过如下命令,获取手机中属性键值对。

 

 
  1. adb shell getprop 

例如红米Note手机的属性值如下:

 

 
  1. [ro.product.device]: [lcsh92_wet_jb9] 
  2. [ro.product.locale.language]: [zh] 
  3. [ro.product.locale.region]: [CN] 
  4. [ro.product.manufacturer]: [Xiaomi] 

在system/core/init/init.c文件的main函数中,跟属性服务的相关代码如下:

 

 
  1. property_init(); 
  2. queue_builtin_action(property_service_init_action, "property_service_init"); 

接下来,我们分别看一下这两处代码的具体实现。

属性服务初始化 创建存储空间

首先,我们先来看一下property_init函数的源码(/system/core/init/property_service.c):

 

 
  1. void property_init(void
  2. init_property_area(); 

property_init函数中只是简单的调用了init_property_area方法,接下来我们看一下这个方法的具体实现:

 

 
  1. static int property_area_inited = 0; 
  2. static workspace pa_workspace; 
  3. static int init_property_area(void
  4. // 属性空间是否已经初始化 
  5. if (property_area_inited) 
  6. return -1; 
  7.  
  8. if (__system_property_area_init()) 
  9. return -1; 
  10.  
  11. if (init_workspace(&pa_workspace, 0)) 
  12. return -1; 
  13.  
  14. fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); 
  15.  
  16. property_area_inited = 1; 
  17. return 0; 

从init_property_area函数,我们可以看出,函数首先判断属性内存区域是否已经初始化过,如果已经初始化,则返回-1。如果没有初始化,我们接下来会发现有两个关键函数__system_property_area_init和init_workspace应该是跟内存区域初始化相关。那我们分别分析一下这两个函数具体实现。

 

 
  1. __system_property_area_init 
  2.  
  3. __system_property_area_init函数位于/bionic/libc/bionic/system_properties.c文件中,具体代码实现如下: 
  4.  
  5. struct prop_area { 
  6. unsigned bytes_used; 
  7. unsigned volatile serial; 
  8. unsigned magic; 
  9. unsigned version; 
  10. unsigned reserved[28]; 
  11. char data[0]; 
  12. }; 
  13. typedef struct prop_area prop_area; 
  14. prop_area *__system_property_area__ = NULL; 
  15.  
  16. #define PROP_FILENAME "/dev/__properties__" 
  17. static char property_filename[PATH_MAX] = PROP_FILENAME;  
  18.  
  19. #define PA_SIZE (128 * 1024) 
  20.  
  21.  
  22. static int map_prop_area_rw() 
  23. prop_area *pa; 
  24. int fd; 
  25. int ret; 
  26.  
  27. /** 
  28. * O_RDWR ==> 读写 
  29. * O_CREAT ==> 若不存在,则创建 
  30. * O_NOFOLLOW ==> 如果filename是软链接,则打开失败 
  31. * O_EXCL ==> 如果使用O_CREAT是文件存在,则可返回错误信息 
  32. */ 
  33. fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444); 
  34. if (fd < 0) { 
  35. if (errno == EACCES) { 
  36. abort(); 
  37. return -1; 
  38.  
  39. ret = fcntl(fd, F_SETFD, FD_CLOEXEC); 
  40. if (ret < 0) 
  41. goto out; 
  42.  
  43. if (ftruncate(fd, PA_SIZE) < 0) 
  44. goto out; 
  45.  
  46. pa_size = PA_SIZE; 
  47. pa_data_size = pa_size - sizeof(prop_area); 
  48. compat_mode = false
  49.  
  50. // mmap映射文件实现共享内存 
  51. pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
  52. if (pa == MAP_FAILED) 
  53. goto out; 
  54.  
  55. /*初始化内存地址中所有值为0*/ 
  56. memset(pa, 0, pa_size); 
  57. pa->magic = PROP_AREA_MAGIC; 
  58. pa->version = PROP_AREA_VERSION; 
  59. pa->bytes_used = sizeof(prop_bt); 
  60.  
  61. __system_property_area__ = pa; 
  62.  
  63. close(fd); 
  64. return 0; 
  65.  
  66. out: 
  67. close(fd); 
  68. return -1; 
  69.  
  70. int __system_property_area_init() 
  71. return map_prop_area_rw(); 

代码比较好理解,主要内容是利用mmap映射property_filename创建了一个共享内存区域,并将共享内存的首地址赋值给全局变量__system_property_area__。

关于mmap映射文件实现共享内存IPC通信机制,可以参考这篇文章:mmap实现IPC通信机制

init_workspace

接下来,我们来看一下init_workspace函数的源码(/system/core/init/property_service.c):

 

 
  1. typedef struct { 
  2. void *data; 
  3. size_t size; 
  4. int fd; 
  5. }workspace; 
  6.  
  7. static int init_workspace(workspace *w, size_t size) 
  8. void *data; 
  9. int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW); 
  10. if (fd < 0) 
  11. return -1; 
  12.  
  13. w->size = size; 
  14. w->fd = fd; 
  15. return 0; 

客户端进程访问属性内存区域

虽然属性内存区域是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函数代码的相关实现:

 

 
  1. void __attribute__((constructor)) __libc_prenit(void); 
  2. void __libc_prenit(void
  3. // ... 
  4. __libc_init_common(elfdata); // 调用这个函数 
  5. // ... 
  6.  
  7.  
  8. __libc_init_common函数为: 
  9.  
  10. void __libc_init_common(uintptr_t *elfdata) 
  11. // ... 
  12. __system_properties_init(); // 初始化客户端的属性存储区域 
  13.  
  14.  
  15. __system_properties_init函数有回到了我们熟悉的/bionic/libc/bionic/system_properties.c文件: 
  16.  
  17. static int get_fd_from_env(void
  18. char *env = getenv("ANDROID_PROPERTY_WORKSPACE"); 
  19.  
  20. if (! env) { 
  21. return -1; 
  22.  
  23. return atoi(env); 
  24.  
  25. static int map_prop_area() 
  26. bool formFile = true
  27. int result = -1; 
  28. int fd; 
  29. int ret; 
  30.  
  31. fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); 
  32. if (fd >= 0) { 
  33. /* For old kernels that don't support O_CLOEXEC */ 
  34. ret = fcntl(fd, F_SETFD, FD_CLOEXEC); 
  35. if (ret < 0) 
  36. goto cleanup; 
  37.  
  38. if ((fd < 0) && (error == ENOENT)) { 
  39. fd = get_fd_from_env(); 
  40. fromFile = false
  41.  
  42. if (fd < 0) { 
  43. return -1; 
  44.  
  45. struct stat fd_stat; 
  46. if (fstat(fd, &fd_stat) < 0) { 
  47. goto cleanup; 
  48.  
  49. if ((fd_stat.st_uid != 0) 
  50. || (fd_stat.st_gid != 0) 
  51. || (fd_stat.st_mode & (S_IWGRP | S_IWOTH) != 0) 
  52. || (fd_stat.st_size < sizeof(prop_area))) { 
  53. goto cleanup; 
  54.  
  55. pa_size = fd_stat.st_size; 
  56. pa_data_size = pa_size - sizeof(prop_area); 
  57.  
  58. /*  
  59. * 映射init创建的属性内存到本地进程空间,这样本地进程就可以使用这块共享内存了。 
  60. * 注意:映射时制定了PROT_READ属性,所以客户端进程只能读属性,不能设置属性。 
  61. */ 
  62. prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0); 
  63.  
  64. if (pa == MAP_FAILED) { 
  65. goto cleanup; 
  66.  
  67. if ((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION && pa->version != PROP_AREA_VERSION_COMPAT)) { 
  68. munmap(pa, pa_size); 
  69. goto cleanup; 
  70.  
  71. if (pa->version == PROP_AREA_VERSION_COMPAT) { 
  72. compat_mode = true
  73.  
  74. result = 0; 
  75.  
  76. __system_property_area__ = pa; 
  77. cleanup: 
  78. if (fromFile) { 
  79. close(fd); 
  80.  
  81. return result; 
  82.  
  83. int __system_properties_init() 
  84. return map_prop_area(); 

通过对源码的阅读,可以发现,客户端通过mmap映射,可以读取属性内存的内容,但是没有权限设置属性。那客户端是如何设置属性的呢?这就涉及到下面要将的属性服务器了。

属性服务器的分析

init进程会启动一个属性服务器,而客户端只能通过与属性服务器的交互来设置属性。

启动属性服务器

先来看一下属性服务器的内容,它由property_service_init_action函数启动,源码如下(/system/core/init/init.c&&property_service.c):

 

 
  1. static int property_service_init_action(int nargs, char **args) 
  2. start_property_service(); 
  3. return 0; 
  4.  
  5. static void load_override_properties() 
  6. #ifdef ALLOW_LOCAL_PROP_OVERRIDE 
  7. char debuggable[PROP_VALUE_MAX]; 
  8. int ret; 
  9.  
  10. ret = property_get("ro.debuggable", debuggable); 
  11. if (ret && (strcmp(debuggable, "1") == 0)) { 
  12. load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE); 
  13. #endif 
  14.  
  15. static void load_properties(char *data) 
  16. char *key, *value, *eol, *sol, *tmp; 
  17.  
  18. sol = data; 
  19. while ((eol = strchr(sol, '/n'))) { 
  20. key = sol; 
  21. // 赋值下一行的指针给sol 
  22. *eol ++ = 0; 
  23. sol = eol; 
  24.  
  25. value = strchr(key, '='); 
  26. if (value == 0) continue
  27. *value++ = 0; 
  28.  
  29. while (isspace(*key)) key ++; 
  30. if (*key == '#'continue
  31. tmp = value - 2; 
  32. while ((tmp > key) && isspace(*tmp)) *tmp-- = 0; 
  33.  
  34. while (isspace(*value)) value ++; 
  35. tmp = eol - 2; 
  36. while ((tmp > value) && isspace(*tmp)) *tmp-- = 0; 
  37.  
  38. property_set(key, value); 
  39.  
  40. int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid) 
  41. struct sockaddr_un addr; 
  42. int fd, ret; 
  43. char *secon; 
  44.  
  45. fd = socket(PF_UNIX, type, 0); 
  46. if (fd < 0) { 
  47. ERROR("Failed to open socket '%s': %s/n", name, strerror(errno)); 
  48. return -1; 
  49.  
  50. memset(&addr, 0, sizeof(addr)); 
  51. addr.sun_family = AF_UNIX; 
  52. snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s", name); 
  53.  
  54. ret = unlink(addr.sun_path); 
  55. if (ret != 0 && errno != ENOENT) { 
  56. goto out_close; 
  57.  
  58. ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); 
  59. if (ret) { 
  60. goto out_unlink; 
  61. chown(addr.sun_path, uid, gid); 
  62. chmod(addr.sun_path, perm); 
  63.  
  64. return fd; 
  65.  
  66. out_unlink: 
  67. unlink(addr.sun_path); 
  68. out_close: 
  69. close(fd); 
  70. return -1; 
  71.  
  72. #define PROP_PATH_SYSTEM_BUILD "/system/build.prop" 
  73. #define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop" 
  74. #define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop" 
  75. #define PROP_PATH_FACTORY "/factory/factory.prop" 
  76.  
  77. void start_property_service(void
  78. int fd; 
  79.  
  80. load_properties_from_file(PROP_PATH_SYSTEM_BUILD); 
  81. load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); 
  82. load_override_properties(); 
  83. /*Read persistent properties after all default values have been loaded.*/ 
  84. load_persistent_properties(); 
  85.  
  86. fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); 
  87. if (fd < 0) return
  88. fcntl(fd, F_SETFD, FD_CLOEXEC); 
  89. fcntl(fd, F_SETFL, O_NONBLOCK); 
  90.  
  91. listen(fd, 8); 
  92. property_set_fd = fd; 

从上述代码可以看到,init进程除了会预写入指定文件(例如:system/build.prop)属性外,还会创建一个UNIX Domain Socket,用于接受客户端的请求,构建属性。那这个socket请求是再哪里被处理的呢?

答案是:在init中的for循环处已经进行了相关处理。

服务端处理设置属性请求

接收属性设置请求的地方是在init进程中,相关代码如下所示:

 

 
  1. int main(int argc, char **argv) 
  2. // ...省略不相关代码 
  3.  
  4. for (;;) { 
  5. // ... 
  6. for (i = 0; i < fd_count; i ++) { 
  7. if (ufds[i].fd == get_property_set_fd()) 
  8. handle_property_set_fd(); 

从上述代码可以看出,当属性服务器收到客户端请求时,init进程会调用handle_property_set_fd函数进行处理,函数位置是:system/core/init/property_service.c,我们来看一下这个函数的实现源码:

 

 
  1. void handle_property_set_fd() 
  2. prop_msg msg; 
  3. int s; 
  4. int r; 
  5. int res; 
  6. struct ucred cr; 
  7. struct sockaddr_un addr; 
  8. socklen_t addr_size = sizeof(addr); 
  9. socklen_t cr_size = sizeof(cr); 
  10. char *source_ctx = NULL; 
  11.  
  12. // 接收TCP连接 
  13. if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) { 
  14. return
  15.  
  16. // 接收客户端请求数据 
  17. r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0)); 
  18. if (r != sizeof(prop_msg)) { 
  19. ERROR("sys_prop: mis-match msg size received: %d expected : %d errno: %d/n", r, sizeof(prop_msg), errno); 
  20. close(s); 
  21. return
  22.  
  23. switch(msg.cmd) { 
  24. case PROP_MSG_SETPROP: 
  25. msg.name[PROP_NAME_MAX - 1] = 0; 
  26. msg.value[PROP_VALUE_MAX - 1] = 0; 
  27.  
  28. if (memcmp(msg.name, "ctl.", 4) == 0) { 
  29. close(s); 
  30. if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) { 
  31. handle_control_message((char*) msg.name + 4, (char*) msg.value); 
  32. else { 
  33. 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); 
  34. else { 
  35. if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) { 
  36. property_set((char *) msg.name, (char*) msg.value); 
  37. close(s); 
  38. break
  39. default
  40. close(s); 
  41. break

当客户端的权限满足要求时,init就调用property_set进行相关处理。property_set源码实现如下:

 

 
  1. int property_set(const char *name, const char *value) 
  2. prop_info *pi; 
  3. int ret; 
  4.  
  5. size_t namelen = strlen(name); 
  6. size_t valuelen = strlen(value); 
  7.  
  8. if (! is_legal_property_name(name, namelen)) return -1; 
  9. if (valuelen >= PROP_VALUE_MAX) return -1; 
  10.  
  11. // 从属性空间中寻找是否已经存在该属性值 
  12. pi = (prop_info*) __system_property_find(name); 
  13. if (pi != 0) { 
  14. // ro开头的属性被设置后,不允许再被修改 
  15. if (! strncmp(name, "ro.", 3)) return -1; 
  16.  
  17. __system_property_update(pi, value, valuelen); 
  18. else { 
  19. ret = __system_property_add(name, namelen, value, valuelen); 
  20.  
  21. // 有一些特殊的属性需要特殊处理,例如net.和persist.开头的属性 
  22. if (strncmp("net.", name, strlen("net.")) == 0) { 
  23. if (strcmp("net.change", name) == 0) { 
  24. return 0; 
  25. property_set("net.change", name); 
  26. else if (persistent_properties_loaded && strncmp("persist.", name, strlen("persist.")) == 0) { 
  27. write_persistent_property(name, value); 
  28. property_changed(name, value); 
  29. return 0; 

属性服务器端的工作基本到这里就完成了。最后,我们来看一下客户端是如何发送设置属性的socket请求。

客户端发送请求

客户端设置属性时是调用了property_set(“sys.istest”, “true”)方法。从上述分析可知,该方法实现跟服务器端的property_set方法不同,该方法一定是发送了socket请求,该方法源码位置为:/system/core/libcutils/properties.c:

 

 
  1. int property_set(const char *key, const char *value) 
  2. return __system_property_set(key, value); 

可以看到,property_set调用了__system_property_set方法,这个方法位于:/bionic/libc/bionic/system_properties.c文件中:

 

 
  1. struct prop_msg 
  2. unsigned cmd; 
  3. char name[PROP_NAME_MAX]; 
  4. char value[PROP_VALUE_MAX]; 
  5. }; 
  6. typedef struct prop_msg prop_msg; 
  7.  
  8. static int send_prop_msg(prop_msg *msg) 
  9. struct pollfd pollfds[1]; 
  10. struct sockaddr_un addr; 
  11. socklen_t alen; 
  12. size_t namelen; 
  13. int s; 
  14. int r; 
  15. int result = -1; 
  16.  
  17. s = socket(AF_LOCAL, SOCK_STREAM, 0); 
  18. if (s < 0) { 
  19. return result; 
  20.  
  21. memset(&addr, 0, sizeof(addr)); 
  22. namelen = strlen(property_service_socket); 
  23. strlcpy(addr.sun_path, property_service_socket, sizeof(addr.sun_path)); 
  24. addr.sun_family = AF_LOCAL; 
  25. alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1; 
  26.  
  27. if (TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) { 
  28. close(s); 
  29. return result; 
  30.  
  31. r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0)); 
  32.  
  33. close(s); 
  34. return result; 
  35.  
  36. int __system_property_set(const char *key, const char *value) 
  37. int err; 
  38. prop_msg msg; 
  39.  
  40. if (key == 0) return -1; 
  41. if (value == 0) value = ""
  42. if (strlen(key) >= PROP_NAME_MAX) return -1; 
  43. if (strlen(value) >= PROP_VALUE_MAX) return -1; 
  44.  
  45. memset(&msg, 0, sizeof(msg)); 
  46. msg.cmd = PROP_MSG_SETPROP; 
  47. strlcpy(msg.name, key, sizeof(msg.name)); 
  48. strlcpy(msg.value, value, sizeof(msg.value)); 
  49.  
  50. err = send_prop_msg(&msg); 
  51. if (err < 0) { 
  52. return err; 
  53. return 0; 

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表