a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 編程 > C > 正文

深入剖析Android中init進程實現的C語言源碼

2020-01-26 15:00:45
字體:
來源:轉載
供稿:網友

概述

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/

屬性服務

在windows平臺上有一個叫做注冊表的東西,它可以存儲一些類似key/value的鍵值對。一般而言,系統或者某些應用程序會把自己的一些屬性存儲在注冊表中,即使系統重啟或應用程序重啟,它還能根據之前在注冊表中設置的屬性值,進行相應的初始化工作。

Android系統也提供了類似的機制,稱之為屬性服務(property service)。應用程序可以通過這個服務查詢或者設置屬性。我們可以通過如下命令,獲取手機中屬性鍵值對。

adb shell getprop

例如紅米Note手機的屬性值如下:

[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函數中,跟屬性服務的相關代碼如下:

property_init();queue_builtin_action(property_service_init_action, "property_service_init");

接下來,我們分別看一下這兩處代碼的具體實現。
屬性服務初始化
創建存儲空間

首先,我們先來看一下property_init函數的源碼(/system/core/init/property_service.c):

void property_init(void){  init_property_area();}


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;}

從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();}

 
代碼比較好理解,主要內容是利用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;}

客戶端進程訪問屬性內存區域

雖然屬性內存區域是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創建的屬性內存到本地進程空間,這樣本地進程就可以使用這塊共享內存了。   * 注意:映射時制定了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):

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進程中,相關代碼如下所示:

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,我們來看一下這個函數的實現源碼:

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源碼實現如下:

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:

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文件中:

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;}

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 北条麻妃国产九九九精品小说 | 精品乱码一区二区 | 国产中文在线 | 无套内谢孕妇毛片免费看红桃影视 | 中文字幕精品三级久久久 | 国产精品毛片一区 | 欧美视频免费在线观看 | 日韩不卡一区二区 | 日韩三区| 久久亚洲春色中文字幕久久久 | 可以免费看av的网址 | 一区二区精品在线 | 91精品久久久久久久久久 | 亚洲一区欧美一区 | 黄视频入口 | 三级在线观看视频 | 久久久免费av | 久久久久久久久久毛片 | 夜夜草av| 色无欲天天天影视综合网 | 欧美一区永久视频免费观看 | 美女午夜视频 | 亚洲 精品 综合 精品 自拍 | 精品久久久久久久久久久久包黑料 | 涩涩视频在线看 | 午夜精品在线 | 亚洲一级生活片 | 一级毛片免费高清 | 久久精品视频免费观看 | 97人人人 | 色图综合| 国产一区二区免费 | 欧美日韩一区二区在线 | 亚洲精品99 | 一区二区三区回区在观看免费视频 | 欧美日韩综合精品 | 九九在线精品 | 国产精品亚洲一区 | 精品久久99 | 日韩欧美在线免费观看 | 中文字幕一区二区三区免费视频 |