服务器之家

服务器之家 > 正文

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

时间:2021-03-04 10:33     来源/作者:低调小一

概述

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

  •     init提供property service(属性服务)来管理Android系统的属性。
  •     init负责创建系统中的关键进程,包括zygote。

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

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

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

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

属性服务

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

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

?
1
adb shell getprop

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

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


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

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

接下来,我们分别看一下这两处代码的具体实现。
属性服务初始化
创建存储空间

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

?
1
2
3
4
void property_init(void)
{
  init_property_area();
}


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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
__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();
}

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

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

虽然属性内存区域是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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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创建的属性内存到本地进程空间,这样本地进程就可以使用这块共享内存了。
   * 注意:映射时制定了PROT_READ属性,所以客户端进程只能读属性,不能设置属性。
   */
  prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);
 
  if (pa == MAP_FAILED) {
    goto cleanup;
  }
 
  if ((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION && pa->version != PROP_AREA_VERSION_COMPAT)) {
    munmap(pa, pa_size);
    goto cleanup;
  }
 
  if (pa->version == PROP_AREA_VERSION_COMPAT) {
    compat_mode = true;
  }
 
  result = 0;
 
  __system_property_area__ = pa;
cleanup:
  if (fromFile) {
    close(fd);
  }
 
  return result;
}
 
int __system_properties_init()
{
  return map_prop_area();
}


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

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

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

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


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

服务端处理设置属性请求

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

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

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

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


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

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


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

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

?
1
2
3
4
int property_set(const char *key, const char *value)
{
  return __system_property_set(key, value);
}

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

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

 

标签:

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总 2020-11-13
返回顶部

608
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40