深入剖析Android中init進(jìn)程實(shí)現(xiàn)的C語(yǔ)言源碼
概述
init是一個(gè)進(jìn)程,確切的說,它是Linux系統(tǒng)中用戶空間的第一個(gè)進(jìn)程。由于Android是基于Linux內(nèi)核的,所以init也是Android系統(tǒng)中用戶空間的第一個(gè)進(jìn)程。init的進(jìn)程號(hào)是1。作為天字第一號(hào)進(jìn)程,init有很多重要的工作:
- init提供property service(屬性服務(wù))來管理Android系統(tǒng)的屬性。
- init負(fù)責(zé)創(chuàng)建系統(tǒng)中的關(guān)鍵進(jìn)程,包括zygote。
以往的文章一上來就介紹init的源碼,但是我這里先從這兩個(gè)主要工作開始。搞清楚這兩個(gè)主要工作是如何實(shí)現(xiàn)的,我們?cè)倩仡^來看init的源碼。
這篇文章主要是介紹init進(jìn)程的屬性服務(wù)。
跟init屬性服務(wù)相關(guān)的源碼目錄如下:
system/core/init/ bionic/libc/bionic/ system/core/libcutils/
屬性服務(wù)
在windows平臺(tái)上有一個(gè)叫做注冊(cè)表的東西,它可以存儲(chǔ)一些類似key/value的鍵值對(duì)。一般而言,系統(tǒng)或者某些應(yīng)用程序會(huì)把自己的一些屬性存儲(chǔ)在注冊(cè)表中,即使系統(tǒng)重啟或應(yīng)用程序重啟,它還能根據(jù)之前在注冊(cè)表中設(shè)置的屬性值,進(jìn)行相應(yīng)的初始化工作。
Android系統(tǒng)也提供了類似的機(jī)制,稱之為屬性服務(wù)(property service)。應(yīng)用程序可以通過這個(gè)服務(wù)查詢或者設(shè)置屬性。我們可以通過如下命令,獲取手機(jī)中屬性鍵值對(duì)。
adb shell getprop
例如紅米Note手機(jī)的屬性值如下:
[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函數(shù)中,跟屬性服務(wù)的相關(guān)代碼如下:
property_init(); queue_builtin_action(property_service_init_action, "property_service_init");
接下來,我們分別看一下這兩處代碼的具體實(shí)現(xiàn)。
屬性服務(wù)初始化
創(chuàng)建存儲(chǔ)空間
首先,我們先來看一下property_init函數(shù)的源碼(/system/core/init/property_service.c):
void property_init(void) { init_property_area(); }
property_init函數(shù)中只是簡(jiǎn)單的調(diào)用了init_property_area方法,接下來我們看一下這個(gè)方法的具體實(shí)現(xiàn):
static int property_area_inited = 0; static workspace pa_workspace; static int init_property_area(void) { // 屬性空間是否已經(jīng)初始化 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函數(shù),我們可以看出,函數(shù)首先判斷屬性內(nèi)存區(qū)域是否已經(jīng)初始化過,如果已經(jīng)初始化,則返回-1。如果沒有初始化,我們接下來會(huì)發(fā)現(xiàn)有兩個(gè)關(guān)鍵函數(shù)__system_property_area_init和init_workspace應(yīng)該是跟內(nèi)存區(qū)域初始化相關(guān)。那我們分別分析一下這兩個(gè)函數(shù)具體實(shí)現(xiàn)。
__system_property_area_init __system_property_area_init函數(shù)位于/bionic/libc/bionic/system_properties.c文件中,具體代碼實(shí)現(xiàn)如下: 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 ==> 若不存在,則創(chuàng)建 * O_NOFOLLOW ==> 如果filename是軟鏈接,則打開失敗 * O_EXCL ==> 如果使用O_CREAT是文件存在,則可返回錯(cuò)誤信息 */ 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映射文件實(shí)現(xiàn)共享內(nèi)存 pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (pa == MAP_FAILED) goto out; /*初始化內(nèi)存地址中所有值為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(); }
代碼比較好理解,主要內(nèi)容是利用mmap映射property_filename創(chuàng)建了一個(gè)共享內(nèi)存區(qū)域,并將共享內(nèi)存的首地址賦值給全局變量__system_property_area__。
關(guān)于mmap映射文件實(shí)現(xiàn)共享內(nèi)存IPC通信機(jī)制,可以參考這篇文章:mmap實(shí)現(xiàn)IPC通信機(jī)制
init_workspace
接下來,我們來看一下init_workspace函數(shù)的源碼(/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; }
客戶端進(jìn)程訪問屬性內(nèi)存區(qū)域
雖然屬性內(nèi)存區(qū)域是init進(jìn)程創(chuàng)建的,但是Android系統(tǒng)希望其他進(jìn)程也能夠讀取這塊內(nèi)存區(qū)域里的內(nèi)容。為了做到這一點(diǎn),init進(jìn)程在屬性區(qū)域初始化過程中做了如下兩項(xiàng)工作:
把屬性內(nèi)存區(qū)域創(chuàng)建在共享內(nèi)存上,而共享內(nèi)存是可以跨進(jìn)程的。這一點(diǎn),在上述代碼中是通過mmap映射/dev/__properties__文件實(shí)現(xiàn)的。pa_workspace變量中的fd成員也保存了映射文件的句柄。
如何讓其他進(jìn)程知道這個(gè)共享內(nèi)存句柄呢?Android先將文件映射句柄賦值給__system_property_area__變量,這個(gè)變量屬于bionic_lic庫(kù)中的輸出的一個(gè)變量,然后利用了gcc的constructor屬性,這個(gè)屬性指明了一個(gè)__lib_prenit函數(shù),當(dāng)bionic_lic庫(kù)被加載時(shí),將自動(dòng)調(diào)用__libc_prenit,這個(gè)函數(shù)內(nèi)部完成共享內(nèi)存到本地進(jìn)程的映射工作。
只講原理是不行的,我們直接來看一下__lib_prenit函數(shù)代碼的相關(guān)實(shí)現(xiàn):
void __attribute__((constructor)) __libc_prenit(void); void __libc_prenit(void) { // ... __libc_init_common(elfdata); // 調(diào)用這個(gè)函數(shù) // ... } __libc_init_common函數(shù)為: void __libc_init_common(uintptr_t *elfdata) { // ... __system_properties_init(); // 初始化客戶端的屬性存儲(chǔ)區(qū)域 } __system_properties_init函數(shù)有回到了我們熟悉的/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創(chuàng)建的屬性內(nèi)存到本地進(jìn)程空間,這樣本地進(jìn)程就可以使用這塊共享內(nèi)存了。 * 注意:映射時(shí)制定了PROT_READ屬性,所以客戶端進(jìn)程只能讀屬性,不能設(shè)置屬性。 */ 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(); }
通過對(duì)源碼的閱讀,可以發(fā)現(xiàn),客戶端通過mmap映射,可以讀取屬性內(nèi)存的內(nèi)容,但是沒有權(quán)限設(shè)置屬性。那客戶端是如何設(shè)置屬性的呢?這就涉及到下面要將的屬性服務(wù)器了。
屬性服務(wù)器的分析
init進(jìn)程會(huì)啟動(dòng)一個(gè)屬性服務(wù)器,而客戶端只能通過與屬性服務(wù)器的交互來設(shè)置屬性。
啟動(dòng)屬性服務(wù)器
先來看一下屬性服務(wù)器的內(nèi)容,它由property_service_init_action函數(shù)啟動(dòng),源碼如下(/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進(jìn)程除了會(huì)預(yù)寫入指定文件(例如:system/build.prop)屬性外,還會(huì)創(chuàng)建一個(gè)UNIX Domain Socket,用于接受客戶端的請(qǐng)求,構(gòu)建屬性。那這個(gè)socket請(qǐng)求是再哪里被處理的呢?
答案是:在init中的for循環(huán)處已經(jīng)進(jìn)行了相關(guān)處理。
服務(wù)端處理設(shè)置屬性請(qǐng)求
接收屬性設(shè)置請(qǐng)求的地方是在init進(jìn)程中,相關(guān)代碼如下所示:
int main(int argc, char **argv) { // ...省略不相關(guān)代碼 for (;;) { // ... for (i = 0; i < fd_count; i ++) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); } } }
從上述代碼可以看出,當(dāng)屬性服務(wù)器收到客戶端請(qǐng)求時(shí),init進(jìn)程會(huì)調(diào)用handle_property_set_fd函數(shù)進(jìn)行處理,函數(shù)位置是:system/core/init/property_service.c,我們來看一下這個(gè)函數(shù)的實(shí)現(xiàn)源碼:
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; } // 接收客戶端請(qǐng)求數(shù)據(jù) 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; } }
當(dāng)客戶端的權(quán)限滿足要求時(shí),init就調(diào)用property_set進(jìn)行相關(guān)處理。property_set源碼實(shí)現(xiàn)如下:
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; // 從屬性空間中尋找是否已經(jīng)存在該屬性值 pi = (prop_info*) __system_property_find(name); if (pi != 0) { // ro開頭的屬性被設(shè)置后,不允許再被修改 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; }
屬性服務(wù)器端的工作基本到這里就完成了。最后,我們來看一下客戶端是如何發(fā)送設(shè)置屬性的socket請(qǐng)求。
客戶端發(fā)送請(qǐng)求
客戶端設(shè)置屬性時(shí)是調(diào)用了property_set(“sys.istest”, “true”)方法。從上述分析可知,該方法實(shí)現(xiàn)跟服務(wù)器端的property_set方法不同,該方法一定是發(fā)送了socket請(qǐng)求,該方法源碼位置為:/system/core/libcutils/properties.c:
int property_set(const char *key, const char *value) { return __system_property_set(key, value); }
可以看到,property_set調(diào)用了__system_property_set方法,這個(gè)方法位于:/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; }
欄 目:C語(yǔ)言
下一篇:學(xué)習(xí)C語(yǔ)言要掌握的幾個(gè)庫(kù)
本文標(biāo)題:深入剖析Android中init進(jìn)程實(shí)現(xiàn)的C語(yǔ)言源碼
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2927.html
您可能感興趣的文章
- 01-10深入理解約瑟夫環(huán)的數(shù)學(xué)優(yōu)化方法
- 01-10深入二叉樹兩個(gè)結(jié)點(diǎn)的最低共同父結(jié)點(diǎn)的詳解
- 01-10深入理解C++中常見的關(guān)鍵字含義
- 01-10深入Main函數(shù)中的參數(shù)argc,argv的使用詳解
- 01-10深入第K大數(shù)問題以及算法概要的詳解
- 01-10深入解析最長(zhǎng)公共子串
- 01-10深入理解鏈表的各類操作詳解
- 01-10深入N皇后問題的兩個(gè)最高效算法的詳解
- 01-10深入理解二叉樹的非遞歸遍歷
- 01-10深入全排列算法及其實(shí)現(xiàn)方法


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 04-02jquery與jsp,用jquery
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10delphi制作wav文件的方法
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文