我們知道,Windows平臺上有一個叫注冊表的東西。注冊表可以存儲一些類似key/value的鍵值對。一般而言,系統或某些應用程序會把自己的一些屬性存儲在注冊表中,即使下次系統重啟或應用程序重啟,它還能夠根據之前在注冊表中設置的屬性,進行相應的初始化工作。Android平臺也提供了一個類型機制,可稱之為屬性服務(property service)。應用程序可通過這個屬性機制,查詢或設置屬性。讀者可以用adb shell登錄到真機或模擬器上,然后用getprop命令查看當前系統中有哪些屬性。即如我的HTC G7測試結果,如圖3-2所示:(圖中只顯示了部分屬性)
:-: 
圖3-2 HTC G7屬性示意圖
這個屬性服務是怎么實現的呢?下面來看代碼,其中與init.c和屬性服務有關的代碼有下面兩行:
~~~
property_init();
property_set_fd = start_property_service();
~~~
分別來看看它們。
1. 屬性服務初始化
(1)創建存儲空間
先看property_init函數,代碼如下所示:
**property_service.c**
~~~
void property_init(void)
{
init_property_area();//初始化屬性存儲區域
//加載default.prop文件
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
~~~
在properyty_init函數中,先調用init_property_area函數,創建一塊用于存儲屬性的存儲區域,然后加載default.prop文件中的內容。再看init_property_area是如何工作的,它的代碼如下所示:
**property_service.c**
~~~
static int init_property_area(void)
{
prop_area *pa;
if(pa_info_array)
return -1;
/*
初始化存儲空間,PA_SIZE是這塊存儲空間的總大小,為32768字節,pa_workspace
為workspace類型的結構體,下面是它的定義:
typedef struct {
void *data; //存儲空間的起始地址
size_tsize; //存儲空間的大小
int fd; //共享內存的文件描述符
} workspace;
init_workspace函數調用Android系統提供的ashmem_create_region函數創建一塊
共享內存。關于共享內存的知識我們在第7章會接觸,這里,只需把它當做一塊普通的內存就
可以了。
*/
if(init_workspace(&pa_workspace, PA_SIZE))
return -1;
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
//在32768個字節的存儲空間中,有PA_INFO_START(1024)個字節用來存儲頭部信息
pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);
pa =pa_workspace.data;
memset(pa, 0, PA_SIZE);
pa->magic = PROP_AREA_MAGIC;
pa->version = PROP_AREA_VERSION;
//__system_property_area__這個變量由bionic libc庫輸出,有什么用呢?
__system_property_area__ = pa;
return0;
}
~~~
上面的內容比較簡單,不過最后的賦值語句可是大有來頭。__system_property_area__是bionic libc庫中輸出的一個變量,為什么這里要給它賦值呢?
原來,雖然屬性區域是由init進程創建,但Android系統希望其他進程也能讀取這塊內存里的東西。為做到這一點,它便做了以下兩項工作:
- 把屬性區域創建在共享內存上,而共享內存是可以跨進程的。這一點,已經在上面的代碼中見到了,init_workspace函數內部將創建這個共享內存。
- 如何讓其他進程知道這個共享內存呢?Android利用了gcc的constructor屬性,這個屬性指明了一個__libc_prenit函數,當bionic libc庫被加載時,將自動調用這個__libc_prenit,這個函數內部就將完成共享內存到本地進程的映射工作。
(2)客戶端進程獲取存儲空間
關于上面的內容,來看相關代碼:
**libc_init_dynamic.c**
~~~
//constructor屬性指示加載器加載該庫后,首先調用__libc_prenit函數。這一點和Windows上
//動態庫的DllMain函數類似
void __attribute__((constructor))__libc_prenit(void);
void __libc_prenit(void)
{
......
__libc_init_common(elfdata); //調用這個函數
......
}
~~~
__libc_init_common函數為:
**libc_init_common.c**
~~~
void __libc_init_common(uintptr_t *elfdata)
{
......
__system_properties_init();//初始化客戶端的屬性存儲區域
}
~~~
**system_properties.c**
~~~
int __system_properties_init(void)
{
prop_area *pa;
int s,fd;
unsigned sz;
char*env;
.....
//還記得在啟動zygote一節中提到的添加環境變量的地方嗎?屬性存儲區域的相關信息
//就是在那兒添加的,這里需要取出來使用了。
env =getenv("ANDROID_PROPERTY_WORKSPACE");
//取出屬性存儲區域的文件描述符。關于共享內存的知識,第7章中將會進行介紹。
fd =atoi(env);
env =strchr(env, ',');
if(!env) {
return -1;
}
sz =atoi(env + 1);
//映射init創建的那塊內存到本地進程空間,這樣本地進程就可以使用這塊共享內存了。
//注意,映射的時候指定了PROT_READ屬性,所以客戶端進程只能讀屬性,而不能設置屬性。
pa =mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
if(pa== MAP_FAILED) {
return -1;
}
if((pa->magic != PROP_AREA_MAGIC) || (pa->version !=PROP_AREA_VERSION)) {
munmap(pa, sz);
return -1;
}
__system_property_area__ = pa;
return0;
}
~~~
上面代碼中很多地方和共享內存有關,在第7章中會對與共享內存有關問題進行介紹,讀者也可先行學習有關共享內存的知識。
總之,通過這種方式,客戶端進程可以直接讀取屬性空間,但沒有權限設置屬性。客戶端進程又是如何設置屬性呢?
2. 啟動屬性服務器
(1)啟動屬性服務器
init進程會啟動一個屬性服務器,而客戶端只能通過和屬性服務器交互才能設置屬性。先來看屬性服務器的內容,它由start_property_service函數啟動,代碼如下所示:
**Property_servie.c**
~~~
int start_property_service(void)
{
intfd;
/*
加載屬性文件,其實就是解析這些文件中的屬性,然后把它設置到屬性空間中去。Android系統
一共提供了四個存儲屬性的文件,它們分別是:
#definePROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
*/
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
//有一些屬性是需要保存到永久介質上的,這些屬性文件則由下面這個函數加載,這些文件
//存儲在/data/property目錄下,并且這些文件的文件名必須以persist.開頭。這個函數
//很簡單,讀者可自行研究。
load_persistent_properties();
//創建一個socket,用于IPC通信。
fd =create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
if(fd< 0) return -1;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
listen(fd, 8);
returnfd;
}
~~~
屬性服務創建了一個用來接收請求的socket,可這個請求在哪里被處理呢?事實上,在init中的for循環那里已經進行相關處理了。
(2)處理設置屬性請求
接收請求的地方是在init進程中,代碼如下所示:
**init.c::main函數片斷**
~~~
if (ufds[1].revents == POLLIN)
handle_property_set_fd(property_set_fd);
~~~
當屬性服務器收到客戶端請求時,init會調用handle_property_set_fd進行處理。這個函數的代碼如下所示:
**property_service.c**
~~~
void handle_property_set_fd(int fd)
{
prop_msg msg;
int s;
int r;
intres;
structucred cr;
structsockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
//先接收TCP連接
if ((s= accept(fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
//取出客戶端進程的權限等屬性。
if(getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
......
return;
}
//接收請求數據
r = recv(s,&msg, sizeof(msg), 0);
close(s);
......
switch(msg.cmd) {
casePROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
/*
如果是ctl開頭的消息,則認為是控制消息,控制消息用來執行一些命令,例如用
adb shell登錄后,輸入setprop ctl.start bootanim就可以查看開機動畫了,
關閉的話就輸入setpropctl.stop bootanim,是不是很有意思呢?
*/
if(memcmp(msg.name,"ctl.",4) == 0) {
if (check_control_perms(msg.value, cr.uid, cr.gid)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
}
......
}else {
//檢查客戶端進程是否有足夠的權限
if (check_perms(msg.name, cr.uid, cr.gid)) {
//然后調用property_set設置。
property_set((char*) msg.name, (char*) msg.value);
}
......
}
break;
default:
break;
}
}
~~~
當客戶端的權限滿足要求時,init就調用property_set進行相關處理,這個函數比較簡單,代碼如下所示:
**property_service.c**
~~~
int property_set(const char *name, const char*value)
{
prop_area *pa;
prop_info *pi;
intnamelen = strlen(name);
intvaluelen = strlen(value);
......
//從屬性存儲空間中尋找是否已經存在該屬性
pi =(prop_info*) __system_property_find(name);
if(pi!= 0) {
//如果屬性名以ro.開頭,則表示是只讀的,不能設置,所以直接返回。
if(!strncmp(name, "ro.", 3)) return -1;
pa= __system_property_area__;
//更新該屬性的值
update_prop_info(pi, value, valuelen);
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
}else {
//如果沒有找到對應的屬性,則認為是增加屬性,所以需要新創建一項。注意,Android支持
//最多247項屬性,如果目前屬性的存儲空間中已經有247項,則直接返回。
pa= __system_property_area__;
if(pa->count == PA_COUNT_MAX) return -1;
pi= pa_info_array + pa->count;
pi->serial = (valuelen << 24);
memcpy(pi->name, name, namelen + 1);
memcpy(pi->value, value, valuelen +1);
pa->toc[pa->count] =
(namelen << 24) | (((unsigned) pi) - ((unsigned) pa));
pa->count++;
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
}
//有一些特殊的屬性需要特殊處理,這里,主要是以net.change開頭的屬性。
if(strncmp("net.", name, strlen("net.")) == 0) {
if(strcmp("net.change", name) == 0) {
return 0;
}
property_set("net.change", name);
} elseif (persistent_properties_loaded &&
strncmp("persist.", name,strlen("persist.")) == 0) {
//如果屬性名以persist.開頭,則需要把這些值寫到對應文件中去。
write_persistent_property(name, value);
}
/*
還記得init.rc中的下面這句話嗎?
on property:persist.service.adb.enable=1
startadbd
當persist.service.adb.enable屬性置為1后,就會執行start adbd這個command,
這是通過property_changed函數來完成的,它非常簡單,讀者可以自己閱讀。
*/
property_changed(name, value);
return0;
}
~~~
好,屬性服務端的工作已經了解了,下面看客戶端是如何設置屬性的。
(3)客戶端發送請求
客戶端通過property_set發送請求,property_set由libcutils庫提供,代碼如下所示:
**properties.c**
~~~
int property_set(const char *key, const char*value)
{
prop_msg msg;
unsigned resp;
......
msg.cmd = PROP_MSG_SETPROP;//設置消息碼為PROP_MSG_SETPROP。
strcpy((char*) msg.name, key);
strcpy((char*) msg.value, value);
//發送請求
returnsend_prop_msg(&msg);
}
static int send_prop_msg(prop_msg *msg)
{
int s;
int r;
//建立和屬性服務器的socket連接
s =socket_local_client(PROP_SERVICE_NAME,
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM);
if(s< 0) return -1;
//通過socket發送出去
while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
if((errno == EINTR) || (errno == EAGAIN)) continue;
break;
}
if(r== sizeof(prop_msg)) {
r= 0;
} else{
r= -1;
}
close(s);
returnr;
}
~~~
至此,屬性服務器就介紹完了。總體來說,還算比較簡單。
- 前言
- 第1章 閱讀前的準備工作
- 1.1 系統架構
- 1.1.1 Android系統架構
- 1.1.2 本書的架構
- 1.2 搭建開發環境
- 1.2.1 下載源碼
- 1.2.2 編譯源碼
- 1.3 工具介紹
- 1.3.1 Source Insight介紹
- 1.3.2 Busybox的使用
- 1.4 本章小結
- 第2章 深入理解JNI
- 2.1 JNI概述
- 2.2 學習JNI的實例:MediaScanner
- 2.3 Java層的MediaScanner分析
- 2.3.1 加載JNI庫
- 2.3.2 Java的native函數和總結
- 2.4 JNI層MediaScanner的分析
- 2.4.1 注冊JNI函數
- 2.4.2 數據類型轉換
- 2.4.3 JNIEnv介紹
- 2.4.4 通過JNIEnv操作jobject
- 2.4.5 jstring介紹
- 2.4.6 JNI類型簽名介紹
- 2.4.7 垃圾回收
- 2.4.8 JNI中的異常處理
- 2.5 本章小結
- 第3章 深入理解init
- 3.1 概述
- 3.2 init分析
- 3.2.1 解析配置文件
- 3.2.2 解析service
- 3.2.3 init控制service
- 3.2.4 屬性服務
- 3.3 本章小結
- 第4章 深入理解zygote
- 4.1 概述
- 4.2 zygote分析
- 4.2.1 AppRuntime分析
- 4.2.2 Welcome to Java World
- 4.2.3 關于zygote的總結
- 4.3 SystemServer分析
- 4.3.1 SystemServer的誕生
- 4.3.2 SystemServer的重要使命
- 4.3.3 關于 SystemServer的總結
- 4.4 zygote的分裂
- 4.4.1 ActivityManagerService發送請求
- 4.4.2 有求必應之響應請求
- 4.4.3 關于zygote分裂的總結
- 4.5 拓展思考
- 4.5.1 虛擬機heapsize的限制
- 4.5.2 開機速度優化
- 4.5.3 Watchdog分析
- 4.6 本章小結
- 第5章 深入理解常見類
- 5.1 概述
- 5.2 以“三板斧”揭秘RefBase、sp和wp
- 5.2.1 第一板斧--初識影子對象
- 5.2.2 第二板斧--由弱生強
- 5.2.3 第三板斧--破解生死魔咒
- 5.2.4 輕量級的引用計數控制類LightRefBase
- 5.2.5 題外話-三板斧的來歷
- 5.3 Thread類及常用同步類分析
- 5.3.1 一個變量引發的思考
- 5.3.2 常用同步類
- 5.4 Looper和Handler類分析
- 5.4.1 Looper類分析
- 5.4.2 Handler分析
- 5.4.3 Looper和Handler的同步關系
- 5.4.4 HandlerThread介紹
- 5.5 本章小結
- 第6章 深入理解Binder
- 6.1 概述
- 6.2 庖丁解MediaServer
- 6.2.1 MediaServer的入口函數
- 6.2.2 獨一無二的ProcessState
- 6.2.3 時空穿越魔術-defaultServiceManager
- 6.2.4 注冊MediaPlayerService
- 6.2.5 秋風掃落葉-StartThread Pool和join Thread Pool分析
- 6.2.6 你徹底明白了嗎
- 6.3 服務總管ServiceManager
- 6.3.1 ServiceManager的原理
- 6.3.2 服務的注冊
- 6.3.3 ServiceManager存在的意義
- 6.4 MediaPlayerService和它的Client
- 6.4.1 查詢ServiceManager
- 6.4.2 子承父業
- 6.5 拓展思考
- 6.5.1 Binder和線程的關系
- 6.5.2 有人情味的訃告
- 6.5.3 匿名Service
- 6.6 學以致用
- 6.6.1 純Native的Service
- 6.6.2 扶得起的“阿斗”(aidl)
- 6.7 本章小結
- 第7章 深入理解Audio系統
- 7.1 概述
- 7.2 AudioTrack的破解
- 7.2.1 用例介紹
- 7.2.2 AudioTrack(Java空間)分析
- 7.2.3 AudioTrack(Native空間)分析
- 7.2.4 關于AudioTrack的總結
- 7.3 AudioFlinger的破解
- 7.3.1 AudioFlinger的誕生
- 7.3.2 通過流程分析AudioFlinger
- 7.3.3 audio_track_cblk_t分析
- 7.3.4 關于AudioFlinger的總結
- 7.4 AudioPolicyService的破解
- 7.4.1 AudioPolicyService的創建
- 7.4.2 重回AudioTrack
- 7.4.3 聲音路由切換實例分析
- 7.4.4 關于AudioPolicy的總結
- 7.5 拓展思考
- 7.5.1 DuplicatingThread破解
- 7.5.2 題外話
- 7.6 本章小結
- 第8章 深入理解Surface系統
- 8.1 概述
- 8.2 一個Activity的顯示
- 8.2.1 Activity的創建
- 8.2.2 Activity的UI繪制
- 8.2.3 關于Activity的總結
- 8.3 初識Surface
- 8.3.1 和Surface有關的流程總結
- 8.3.2 Surface之乾坤大挪移
- 8.3.3 乾坤大挪移的JNI層分析
- 8.3.4 Surface和畫圖
- 8.3.5 初識Surface小結
- 8.4 深入分析Surface
- 8.4.1 與Surface相關的基礎知識介紹
- 8.4.2 SurfaceComposerClient分析
- 8.4.3 SurfaceControl分析
- 8.4.4 writeToParcel和Surface對象的創建
- 8.4.5 lockCanvas和unlockCanvasAndPost分析
- 8.4.6 GraphicBuffer介紹
- 8.4.7 深入分析Surface的總結
- 8.5 SurfaceFlinger分析
- 8.5.1 SurfaceFlinger的誕生
- 8.5.2 SF工作線程分析
- 8.5.3 Transaction分析
- 8.5.4 關于SurfaceFlinger的總結
- 8.6 拓展思考
- 8.6.1 Surface系統的CB對象分析
- 8.6.2 ViewRoot的你問我答
- 8.6.3 LayerBuffer分析
- 8.7 本章小結
- 第9章 深入理解Vold和Rild
- 9.1 概述
- 9.2 Vold的原理與機制分析
- 9.2.1 Netlink和Uevent介紹
- 9.2.2 初識Vold
- 9.2.3 NetlinkManager模塊分析
- 9.2.4 VolumeManager模塊分析
- 9.2.5 CommandListener模塊分析
- 9.2.6 Vold實例分析
- 9.2.7 關于Vold的總結
- 9.3 Rild的原理與機制分析
- 9.3.1 初識Rild
- 9.3.2 RIL_startEventLoop分析
- 9.3.3 RIL_Init分析
- 9.3.4 RIL_register分析
- 9.3.5 關于Rild main函數的總結
- 9.3.6 Rild實例分析
- 9.3.7 關于Rild的總結
- 9.4 拓展思考
- 9.4.1 嵌入式系統的存儲知識介紹
- 9.4.2 Rild和Phone的改進探討
- 9.5 本章小結
- 第10章 深入理解MediaScanner
- 10.1 概述
- 10.2 android.process.media分析
- 10.2.1 MSR模塊分析
- 10.2.2 MSS模塊分析
- 10.2.3 android.process.media媒體掃描工作的流程總結
- 10.3 MediaScanner分析
- 10.3.1 Java層分析
- 10.3.2 JNI層分析
- 10.3.3 PVMediaScanner分析
- 10.3.4 關于MediaScanner的總結
- 10.4 拓展思考
- 10.4.1 MediaScannerConnection介紹
- 10.4.2 我問你答
- 10.5 本章小結