歡迎轉載,轉載請註明:http://blog.csdn.net/zhgxhuaa
Android中的屬性主要用來保存一些全局性的信息,這裏可以理解爲Android中的“註冊表”。Android中的屬性服務只針對系統開發者使用,並不對應用開發者開發,這通過SystemProperties是hide的可以看出。下面讓我們一起來剖析屬性服務。
初始化屬性空間
在init進程啓動一文中我們講到,init的其中一個作用就是啓動系統屬性服務。在init進程的main()函數中有這樣一句:
@system/core/init/init.c
property_init();//屬性服務初始化
property_init()的實現如下:
@system/core/init/property_service.c
void property_init(void)
{
init_property_area();
}
從字面意思來看init_property_area是用來初始化屬性存儲區域,讓我們來看一下:
@system/core/init/property_service.c
static int init_property_area(void)
{
if (property_area_inited)//屬性區域已初始化則直接返回,不再初始化
return -1;
if(__system_property_area_init())//通過mmap創建共享內存
return -1;
if(init_workspace(&pa_workspace, 0))//初始化pa_workspace
return -1;
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
property_area_inited = 1;
return 0;
}
通過上面的代碼我們可以知道,屬性服務是創建在共享內存上的,通過共享內存實現跨進程的訪問。那共享內存是如何創建的呢?來看一下__system_property_area_init的實現:
@/bionic/libc/bionic/system_properties.c
int __system_property_area_init()
{
return map_prop_area_rw();
}
static int map_prop_area_rw()
{
prop_area *pa;
int fd;
int ret;
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC |
O_EXCL, 0444);//打開property_filename文件,即:/dev/__properties__
if (fd < 0) {
if (errno == EACCES) {
/* for consistency with the case where the process has already
* mapped the page in and segfaults when trying to write to it
*/
abort();
}
return -1;
}
ret = fcntl(fd, F_SETFD, FD_CLOEXEC);//這裏設置爲FD_CLOEXEC表示當程序執行exec函數時本fd將被系統自動關閉,表示不傳遞給exec創建的新進程
if (ret < 0)
goto out;
if (ftruncate(fd, PA_SIZE) < 0)//更改fd指向文件的大小爲PA_SIZE(128 * 1024)大小
goto out;
pa_size = PA_SIZE;//設置屬性空間的大小爲PA_SIZE(128 * 1024)
pa_data_size = pa_size - sizeof(prop_area);//屬性空間中可以用來保存屬性的區域的大小,prop_area用來保存屬性空間自身的一些信息
compat_mode = false;
pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//映射屬性文件到進程
if(pa == MAP_FAILED)
goto out;
memset(pa, 0, pa_size);//初始化屬性區域
pa->magic = PROP_AREA_MAGIC;
pa->version = PROP_AREA_VERSION;
/* reserve root node */
pa->bytes_used = sizeof(prop_bt);
/* plug into the lib property services */
__system_property_area__ = pa;
close(fd);
return 0;
out:
close(fd);
return -1;
}
上面的內容比較簡單,不過最後的賦值語句可是大有來頭。_system_property_area_是bionic libc庫中輸出的一個變量,爲什麼這裏要給她賦值呢?
原來,雖然屬性區域是由init進程創建的,但是Android系統希望其他進程也能夠讀取這塊內存的東西。爲了做到這一點,它便做了一下兩項工作:
- 把屬性區域創建在共享內存上,而共享內存是可以跨進程的。
- 如何讓其他進程知道這個共享內存呢?Android利用gcc的constructor屬性,這個屬性指明瞭一個_libc_prenit函數,當bionic libc庫被加載時,將自動調用這個_libc_prenit,這個函數內部完成共享內存到本地進程的映射工作。(這一段說明轉自:《深入理解Android:卷1》)
關於上面的內容來看下面的代碼:
@/bionic/libc/bionic/libc_init_dynamic.cpp// We flag the __libc_preinit function as a constructor to ensure
// that its address is listed in libc.so's .init_array section.
// This ensures that the function is called by the dynamic linker
// as soon as the shared library is loaded.
__attribute__((constructor)) static void __libc_preinit() {
// Read the kernel argument block pointer from TLS.
void* tls = const_cast<void*>(__get_tls());
KernelArgumentBlock** args_slot = &reinterpret_cast<KernelArgumentBlock**>(tls)[TLS_SLOT_BIONIC_PREINIT];
KernelArgumentBlock* args = *args_slot;
// Clear the slot so no other initializer sees its value.
// __libc_init_common() will change the TLS area so the old one won't be accessible anyway.
*args_slot = NULL;
__libc_init_common(*args);
// Hooks for the debug malloc and pthread libraries to let them know that we're starting up.
pthread_debug_init();
malloc_debug_init();
}
@/bionic/libc/bionic/libc_init_common.cpp
void __libc_init_common(KernelArgumentBlock& args) {
// Initialize various globals.
environ = args.envp;
errno = 0;
__libc_auxv = args.auxv;
__progname = args.argv[0] ? args.argv[0] : "<unknown>";
__abort_message_ptr = args.abort_message_ptr;
// AT_RANDOM is a pointer to 16 bytes of randomness on the stack.
__stack_chk_guard = *reinterpret_cast<uintptr_t*>(getauxval(AT_RANDOM));
// Get the main thread from TLS and add it to the thread list.
pthread_internal_t* main_thread = __get_thread();
main_thread->allocated_on_heap = false;
_pthread_internal_add(main_thread);
__system_properties_init(); // Requires 'environ'.
}
@/bionic/libc/bionic/system_properties.c
int __system_properties_init()
{
return map_prop_area();
}
static int map_prop_area()
{
bool fromFile = 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) && (errno == ENOENT)) {
/*
* For backwards compatibility, if the file doesn't
* exist, we use the environment to get the file descriptor.
* For security reasons, we only use this backup if the kernel
* returns ENOENT. We don't want to use the backup if the kernel
* returns other errors such as ENOMEM or ENFILE, since it
* might be possible for an external program to trigger this
* condition.
*/
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);
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;
}
對比上面map_prop_area_rw()和map_prop_area()這兩個方法,我們可以發現他們在打開文件和進行映射時的不同。在map_prop_area_rw()中:static int map_prop_area_rw()
{
......
fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC |
O_EXCL, 0444);
......
pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//映射屬性文件到進程
......
}
在map_prop_area()中:
static int map_prop_area()
{
......
fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
......
prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);
......
}
map_prop_area_rw()是init進程初始化屬性服務時調用到的;map_prop_area()則是其他進程通過bionic初始化時調用的。通過對比,也說明只有init進程才擁有屬性的寫權限,其他進程只能讀。好了,到這裏又引出來另外一個問題,其他進程要想設置屬性,應該怎麼做呢?屬性服務
啓動屬性服務
在init進程的main()中,我們可以看見如下語句:
@system/core/init/init.c
queue_builtin_action(property_service_init_action, "property_service_init");
這句話的意思是啓動action鏈表中的property服務:
static int property_service_init_action(int nargs, char **args)
{
/* read any property files on system or data and
* fire up the property service. This must happen
* after the ro.foo properties are set above so
* that /data/local.prop cannot interfere with them.
*/
start_property_service();
return 0;
}
start_property_service()的實現如下:
@system/core/init/property_service.c
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;
}
關於這段代碼做一些說明:
在Android中定義了5個存儲屬性的文件,它們分別是:
@/bionic/libc/includes/sys/_system_properties.h
#define PROP_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"
#define PROP_PATH_FACTORY "/factory/factory.prop"
load_persistent_properties()用來加載persist開頭的屬性文件,這些屬性文件是需要保存到永久介質上的,這些屬性文件存儲在/data/property目錄下,並且文件名必須以“psesist."開頭,下面是我的手機上的persist屬性文件:
在start_property_service()的最後創建了一個名爲"property_service"的socket,啓動監聽,並將socket的句柄保存在property_set_fd中。
處理設置屬性請求
接收屬性設置請求的地方在init進程中:
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}
可以看到,在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;
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/* Check socket options here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
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 (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
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);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}
從上面的代碼可以看出在進行一系列檢查和判斷後,最後悔調用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.* properties may NEVER be modified once set */
if(!strncmp(name, "ro.", 3)) return -1;
__system_property_update(pi, value, valuelen);
} else {
ret = __system_property_add(name, namelen, value, valuelen);
if (ret < 0) {
ERROR("Failed to set '%s'='%s'\n", name, value);
return ret;
}
}
/* If name starts with "net." treat as a DNS property. */
if (strncmp("net.", name, strlen("net.")) == 0) {
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
property_set("net.change", name);
} else if (persistent_properties_loaded &&
strncmp("persist.", name, strlen("persist.")) == 0) {
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);
} else if (strcmp("selinux.reload_policy", name) == 0 &&
strcmp("1", value) == 0) {
selinux_reload_policy();
}
property_changed(name, value);
return 0;
}
從上面的代碼可以看出:首先會判斷所設置屬性的屬性名和屬性值是否超過限制,這個限制定義如下:
@bionic/libc/includes/sys/system _properties.h
#define PROP_NAME_MAX 32
#define PROP_VALUE_MAX 92
可以看出屬性名最長不能超過32個字符;屬性值最長不能超過92個字符,到這裏我們也可以根據之前map_prop_area_rw()中的信息推斷出,系統總共可以支持多少個屬性:
pa_size = PA_SIZE;//設置屬性空間的大小爲PA_SIZE(128 * 1024)
pa_data_size = pa_size - sizeof(prop_area);//屬性空間中可以用來保存屬性的區域的大小,prop_area用來保存屬性空間自身的一些信息
compat_mode = false;
按照這裏的推斷,系統支持1000多個屬性的樣子。這裏與之前比較老的Android版本有所不同,在老的Android版本中系統最多支持的屬性個數爲247個。在判斷文新添加屬性的合法性以後接下來會判斷該屬性是否以存在,如果已存在則更新屬性的值即可,如果不存在則增加新的屬性。
然後還有剩下的一些其他判斷,這裏就不再贅述。到時最後一句property_changed()引起了我的注意:
property_changed(name, value);
看到這裏,你有沒有想起些什麼?是的,init.rc中類似於下面這樣的句子就是在這裏通過property_changed()觸發的。
on property:vold.decrypt=trigger_restart_min_framework
class_start main
on property:vold.decrypt=trigger_restart_framework
class_start main
class_start late_start
OK,到這裏屬性服務相關的東東基本介紹完了。接下來簡單介紹一下如何設置系統屬性。
設置系統屬性
C代碼中屬性的設置是由libcutils庫提供的,代碼如下:@system/core/libutils/properties.c
int property_set(const char *key, const char *value)
{
return __system_property_set(key, value);
}
@/bionic/libc/bionic/system_property
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;
}
注意這裏的msg.cmd = PROP_MSG_SETPROP,send_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));
if(r == sizeof(prop_msg)) {
// We successfully wrote to the property server but now we
// wait for the property server to finish its work. It
// acknowledges its completion by closing the socket so we
// poll here (on nothing), waiting for the socket to close.
// If you 'adb shell setprop foo bar' you'll see the POLLHUP
// once the socket closes. Out of paranoia we cap our poll
// at 250 ms.
pollfds[0].fd = s;
pollfds[0].events = 0;
r = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) {
result = 0;
} else {
// Ignore the timeout and treat it like a success anyway.
// The init process is single-threaded and its property
// service is sometimes slow to respond (perhaps it's off
// starting a child process or something) and thus this
// times out and the caller thinks it failed, even though
// it's still getting around to it. So we fake it here,
// mostly for ctl.* properties, but we do try and wait 250
// ms so callers who do read-after-write can reliably see
// what they've written. Most of the time.
// TODO: fix the system properties design.
result = 0;
}
}
close(s);
return result;
}
設置屬性的請求在這裏被通過socket發送出去。
Java層設置屬性的方法位於SystemProperties類中,在該類中實現了一系列set/get方法,Java代碼中就是通過這些set/get方法設置和讀取系統屬性的。
@/framework/base/core/ java/android/os/SystemProperties.java
/**
* Set the value for the given key.
* @throws IllegalArgumentException if the key exceeds 32 characters
* @throws IllegalArgumentException if the value exceeds 92 characters
*/
public static void set(String key, String val) {
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
if (val != null && val.length() > PROP_VALUE_MAX) {
throw new IllegalArgumentException("val.length > " +
PROP_VALUE_MAX);
}
native_set(key, val);
}
注意到這裏最終會調用到native的set方法。private static native void native_set(String key, String def);
看到這裏,接下來會調到那裏去,相信大家應該都清楚了。。。