[Android的init過程(二);初始化語言(init.rc)解析](http://blog.csdn.net/nokiaguy/article/details/9109491)
本文使用的軟件版本
Android:4.2.2
Linux內(nèi)核:3.1.10
???? 本文及后續(xù)幾篇文章將對Android的初始化(init)過程進(jìn)行詳細(xì)地、剝絲抽繭式地分析,并且在其中穿插了大量的知識,希望對讀者了解Android的啟動過程又所幫助。本章主要介紹了與硬件相關(guān)初始化文件名的確定以及屬性服務(wù)的原理和實現(xiàn)。
???? Android本質(zhì)上就是一個基于Linux內(nèi)核的操作系統(tǒng)。與Ubuntu Linux、Fedora Linux類似。只是Android在應(yīng)用層專門為移動設(shè)備添加了一些特有的支持。既然Android是Linux內(nèi)核的系統(tǒng),那么基本的啟動過程也應(yīng)符合Linux的規(guī)則。如果研究過其他Linux系統(tǒng)應(yīng)該了解,一個完整的Linux系統(tǒng)首先會將一個Linux內(nèi)核裝載到內(nèi)存,也就是編譯Linux內(nèi)核源代碼生成的bzImage文件,對于為Android優(yōu)化的Linux內(nèi)核源代碼會生成zImage文件。該文件就是Linux內(nèi)核的二進(jìn)制版本。由于zImage在內(nèi)核空間運(yùn)行,而我們平常使用的軟件都是在應(yīng)用空間運(yùn)行(關(guān)于內(nèi)核空間和應(yīng)用空間的詳細(xì)描述,可以參考[《Android深度探索(卷1):HAL與驅(qū)動開發(fā)》](http://product.dangdang.com/main/product.aspx?product_id=23043311&_ddclickunion=P-263982%7Cad_type=0%7Csys_id=1#dd_refer=http%3A%2F%2Fblog.csdn.net%2Fnokiaguy)一書的內(nèi)容,在后續(xù)的各卷中將會對Android的整體體系進(jìn)行全方位的剖析)。內(nèi)核空間和應(yīng)用空間是不能直接通過內(nèi)存地址級別訪問的,所以就需要建立某種通訊機(jī)制。
???? 目前Linux有很多通訊機(jī)制可以在用戶空間和內(nèi)核空間之間交互,例如設(shè)備驅(qū)動文件(位于/dev目錄中)、內(nèi)存文件(/proc、/sys目錄等)。了解Linux的同學(xué)都應(yīng)該知道Linux的重要特征之一就是一切都是以文件的形式存在的,例如,一個設(shè)備通常與一個或多個設(shè)備文件對應(yīng)。這些與內(nèi)核空間交互的文件都在用戶空間,所以在Linux內(nèi)核裝載完,需要首先建立這些文件所在的目錄。而完成這些工作的程序就是本文要介紹的init。Init是一個命令行程序。其主要工作之一就是建立這些與內(nèi)核空間交互的文件所在的目錄。當(dāng)Linux內(nèi)核加載完后,要做的第一件事就是調(diào)用init程序,也就是說,init是用戶空間執(zhí)行的第一個程序。
在分析init的核心代碼之前,還需要初步了解init除了建立一些目錄外,還做了如下的工作
1\. 初始化屬性
2\. 處理配置文件的命令(主要是init.rc文件),包括處理各種Action。
3\. 性能分析(使用bootchart工具)。
4\. 無限循環(huán)執(zhí)行command(啟動其他的進(jìn)程)。
???? 盡管init完成的工作不算很多,不過代碼還是非常復(fù)雜的。Init程序并不是由一個源代碼文件組成的,而是由一組源代碼文件的目標(biāo)文件鏈接而成的。這些文件位于如下的目錄。
/system/core/init
???? 其中init.c是init的主文件,現(xiàn)在打開該文件,看看其中的內(nèi)容。由于init是命令行程序,所以分析init.c首先應(yīng)從main函數(shù)開始,現(xiàn)在好到main函數(shù),代碼如下:
~~~
int main(int argc, char **argv)
{
int fd_count = 0;
struct pollfd ufds[4];
char *tmpdev;
char* debuggable;
char tmp[32];
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
bool is_charger = false;
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
if (!strcmp(basename(argv[0]), "watchdogd"))
return watchdogd_main(argc, argv);
/* clear the umask */
umask(0);
// 下面的代碼開始建立各種用戶空間的目錄,如/dev、/proc、/sys等
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
/* 檢測/dev/.booting文件是否可讀寫和創(chuàng)建*/
close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));
open_devnull_stdio();
klog_init();
// 初始化屬性
property_init();
get_hardware_name(hardware, &revision);
// 處理內(nèi)核命令行
process_kernel_cmdline();
… …
is_charger = !strcmp(bootmode, "charger");
INFO("property init\n");
if (!is_charger)
property_load_boot_defaults();
INFO("reading config file\n");
// 分析/init.rc文件的內(nèi)容
init_parse_config_file("/init.rc");
… …// 執(zhí)行初始化文件中的動作
action_for_each_trigger("init", action_add_queue_tail);
// 在charger模式下略過mount文件系統(tǒng)的工作
if (!is_charger) {
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
}
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
if (is_charger) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
}
/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
// 進(jìn)入無限循環(huán),建立init的子進(jìn)程(init是所有進(jìn)程的父進(jìn)程)
for(;;) {
int nr, i, timeout = -1;
// 執(zhí)行命令(子進(jìn)程對應(yīng)的命令)
execute_one_command();
restart_processes();
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action)
timeout = 0;
// bootchart是一個性能統(tǒng)計工具,用于搜集硬件和系統(tǒng)的信息,并將其寫入磁盤,以便其
// 他程序使用
#if BOOTCHART
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || --bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif
// 等待下一個命令的提交
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();
}
}
}
return 0;
}
~~~
我們可以看到main函數(shù)是非常復(fù)雜的,不過我們也不需要每條語句都弄得非常清楚(因為這樣弄是非常困難的),通常只需要了解init的主線即可。其實從init的main函數(shù)可以看出。Init實際上就分為如下兩部分。
1.? 初始化(包括建立/dev、/proc等目錄、初始化屬性、執(zhí)行init.rc等初始化文件中的action等)。
2.? 使用for循環(huán)無限循環(huán)建立子進(jìn)程。
???? 第一項工作很好理解。而第二項工作是init中的核心。在Linux系統(tǒng)中init是一切應(yīng)用空間進(jìn)程的父進(jìn)程。所以我們平常在Linux終端執(zhí)行的命令,并建立進(jìn)程。實際上都是在這個無限的for循環(huán)中完成的。也就是說,在Linux終端執(zhí)行ps –e 命令后,看到的所有除了init外的其他進(jìn)程,都是由init負(fù)責(zé)創(chuàng)建的。而且init也會常駐內(nèi)容。當(dāng)然,如果init掛了,Linux系統(tǒng)基本上就崩潰了。
??? 由于init比較復(fù)雜,所以本文只分析其中的一部分,在后續(xù)文章中將詳細(xì)分析init的各個核心組成部分。
????? 對于main函數(shù)最開始完成的建立目錄的工作比較簡單,這部分也沒什么可以分析的。就是調(diào)用了一些普通的API(mkdir)建立一些目錄。現(xiàn)在說一些題外話,由于Android的底層源代碼(包括init)實際上是屬于Linux應(yīng)用編程領(lǐng)域,所以要想充分理解Android源代碼,除了Linux的基本結(jié)構(gòu)要了解外,Linux應(yīng)用層的API需要熟悉。為了滿足這些讀者的需要,后續(xù)我會寫一些關(guān)于Linux應(yīng)用編程的文章。Ok,現(xiàn)在言歸正傳,接下來分析一個比較重要的部分:配置文件的解析。
????? 這里的配置文件主要指init.rc。讀者可以進(jìn)到Android的shell,會看到根目錄有一個init.rc文件。該文件是只讀的,即使有了root權(quán)限,可以修改該文件也沒有。因為我們在根目錄看到的文件只是內(nèi)存文件的鏡像。也就是說,android啟動后,會將init.rc文件裝載到內(nèi)存。而修改init.rc文件的內(nèi)容實際上只是修改內(nèi)存中的init.rc文件的內(nèi)容。一旦重啟android,init.rc文件的內(nèi)容又會恢復(fù)到最初的裝載。想徹底修改init.rc文件內(nèi)容的唯一方式是修改Android的ROM中的內(nèi)核鏡像(boot.img)。其實boot.img名曰內(nèi)核鏡像,不過該文件除了包含完整的Linux內(nèi)核文件(zImage)外,還包括另外一個鏡像文件(ramdisk.img)。ramdisk.img就包含了init.rc文件和init命令。所以只有修改ramdisk.img文件中的init.rc文件,并且重新打包boot.img文件,并刷機(jī),才能徹底修改init.rc文件。如果讀者有Android源代碼,編譯后,就會看到out目錄中的相關(guān)子目錄會生成一個root目錄,該目錄實際上就是ramdisk.img解壓后的內(nèi)容。會看到有init命令和init.rc文件。在后續(xù)的文章中將會討論具體如何修改init.rc文件,如何刷機(jī)。不過這些內(nèi)容與本文關(guān)系不大,所以不做詳細(xì)的討論。
現(xiàn)在回到main函數(shù),在創(chuàng)建完目錄后,會看到執(zhí)行了如下3個函數(shù)。
??? property_init();
??? get_hardware_name(hardware, &revision);
??? process_kernel_cmdline();
???? 其中property_init主要是為屬性分配一些存儲空間,該函數(shù)并不是核心。不過當(dāng)我們查看init.rc文件時會發(fā)現(xiàn)該文件開始部分用一些import語句導(dǎo)入了其他的配置文件,例如,/init.usb.rc。大多數(shù)配置文件都直接使用了確定的文件名,只有如下的代碼使用了一個變量(${ro.hardware})執(zhí)行了配置文件名的一部分。那么這個變量值是從哪獲得的呢?
import /init.${ro.hardware}.rc
???? 首先要了解init.${ro.hardware}.rc配置文件的內(nèi)容通常與當(dāng)前的硬件有關(guān)。現(xiàn)在我們先來關(guān)注get_hardware_name函數(shù),代碼如下:
~~~
void get_hardware_name(char *hardware, unsigned int *revision)
{
char data[1024];
int fd, n;
char *x, *hw, *rev;
/* 如果hardware已經(jīng)有值了,說明hardware通過內(nèi)核命令行提供,直接返回 */
if (hardware[0])
return;
// 打開/proc/cpuinfo文件
fd = open("/proc/cpuinfo", O_RDONLY);
if (fd < 0) return;
// 讀取/proc/cpuinfo文件的內(nèi)容
n = read(fd, data, 1023);
close(fd);
if (n < 0) return;
data[n] = 0;
// 從/proc/cpuinfo文件中獲取Hardware字段的值
hw = strstr(data, "\nHardware");
rev = strstr(data, "\nRevision");
// 成功獲取Hardware字段的值
if (hw) {
x = strstr(hw, ": ");
if (x) {
x += 2;
n = 0;
while (*x && *x != '\n') {
if (!isspace(*x))
// 將Hardware字段的值都轉(zhuǎn)換為小寫,并更新hardware參數(shù)的值
// hardware也就是在init.c文件中定義的hardware數(shù)組
hardware[n++] = tolower(*x);
x++;
if (n == 31) break;
}
hardware[n] = 0;
}
}
if (rev) {
x = strstr(rev, ": ");
if (x) {
*revision = strtoul(x + 2, 0, 16);
}
}
}
~~~
????? 從get_hardware_name方法的代碼可以得知,該方法主要用于確定hardware和revision的變量的值。Revision這里先不討論,只要研究hardware。獲取hardware的來源是從Linux內(nèi)核命令行或/proc/cpuinfo文件中的內(nèi)容。Linux內(nèi)核命令行暫且先不討論(因為很少傳遞該值),先看看/proc/cpuinfo,該文件是虛擬文件(內(nèi)存文件),執(zhí)行cat /proc/cpuinfo命令會看到該文件中的內(nèi)容,如圖1所示。在白框中就是Hardware字段的值。由于該設(shè)備是Nexus 7,所以值為grouper。如果程序就到此位置,那么與硬件有關(guān)的配置文件名是init.grouper.rc。有Nexus 7的讀者會看到在根目錄下確實有一個init.grouper.rc文件。說明Nexus 7的原生ROM并沒有在其他的地方設(shè)置配置文件名,所以配置文件名就是從/proc/cpuinfo文件的Hardware字段中取的值。

????????????????????????????????????????????????????????????????????????????????????????????????????? 圖1
現(xiàn)在來看在get_hardware_name函數(shù)后面調(diào)用的process_kernel_cmdline函數(shù),代碼如下:
~~~
static void process_kernel_cmdline(void)
{
/* don't expose the raw commandline to nonpriv processes */
chmod("/proc/cmdline", 0440);
// 導(dǎo)入內(nèi)核命令行參數(shù)
import_kernel_cmdline(0, import_kernel_nv);
if (qemu[0])
import_kernel_cmdline(1, import_kernel_nv);
// 用屬性值設(shè)置內(nèi)核變量
export_kernel_boot_props();
}
~~~
????? 在process_kernel_cmdline函數(shù)中除了使用import_kernel_cmdline函數(shù)導(dǎo)入內(nèi)核變量外,主要的功能就是調(diào)用export_kernel_boot_props函數(shù)通過屬性設(shè)置內(nèi)核變量,例如,通過ro.boot.hardware屬性設(shè)置hardware變量,也就是說可以通過ro.boot.hardware屬性值可以修改get_hardware_name函數(shù)中從/proc/cpuinfo文件中得到的hardware字段值。下面看一下export_kernel_boot_props函數(shù)的代碼。
~~~
static void export_kernel_boot_props(void)
{
char tmp[PROP_VALUE_MAX];
const char *pval;
unsigned i;
struct {
const char *src_prop;
const char *dest_prop;
const char *def_val;
} prop_map[] = {
{ "ro.boot.serialno", "ro.serialno", "", },
{ "ro.boot.mode", "ro.bootmode", "unknown", },
{ "ro.boot.baseband", "ro.baseband", "unknown", },
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
};
// 通過內(nèi)核的屬性設(shè)置應(yīng)用層配置文件的屬性
for (i = 0; i < ARRAY_SIZE(prop_map); i++) {
pval = property_get(prop_map[i].src_prop);
property_set(prop_map[i].dest_prop, pval ?: prop_map[i].def_val);
}
// 根據(jù)ro.boot.console屬性的值設(shè)置console變量
pval = property_get("ro.boot.console");
if (pval)
strlcpy(console, pval, sizeof(console));
/* save a copy for init's usage during boot */
strlcpy(bootmode, property_get("ro.bootmode"), sizeof(bootmode));
/* if this was given on kernel command line, override what we read
* before (e.g. from /proc/cpuinfo), if anything */
// 獲取ro.boot.hardware屬性的值
pval = property_get("ro.boot.hardware");
if (pval)
// 這里通過ro.boot.hardware屬性再次改變hardware變量的值
strlcpy(hardware, pval, sizeof(hardware));
// 利用hardware變量的值設(shè)置設(shè)置ro.hardware屬性
// 這個屬性就是前面提到的設(shè)置初始化文件名的屬性,實際上是通過hardware變量設(shè)置的
property_set("ro.hardware", hardware);
snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
property_set("ro.revision", tmp);
/* TODO: these are obsolete. We should delete them */
if (!strcmp(bootmode,"factory"))
property_set("ro.factorytest", "1");
else if (!strcmp(bootmode,"factory2"))
property_set("ro.factorytest", "2");
else
property_set("ro.factorytest", "0");
}
~~~
????? 從export_kernel_boot_props函數(shù)的代碼可以看出,該函數(shù)實際上就是來回設(shè)置一些屬性值,并且利用某些屬性值修改console、hardware等變量。其中hardware變量(就是一個長度為32的字符數(shù)組)在get_hardware_name函數(shù)中已經(jīng)從/proc/cpuinfo文件中獲得過一次值了,在export_kernel_boot_props函數(shù)中又通過ro.boot.hardware屬性設(shè)置了一次值,不過在Nexus 7中并沒有設(shè)置該屬性,所以hardware的值仍為grouper。最后用hardware變量設(shè)置ro.hardware屬性,所以最后的初始化文件名為init.grouper.rc。
????? 這里還有一個問題,前面多次提到屬性或?qū)傩晕募?,那么這些屬性文件指的是什么呢?是init.rc?當(dāng)然不是。實際上這些屬性文件是一些列位于不同目錄,系統(tǒng)依次讀取的配置文件。
**屬性服務(wù)(Property Service)**
在研究這些配置文件之前應(yīng)先了解init是如何處理這些屬性的。編寫過Windows本地應(yīng)用的讀者都應(yīng)了解,在windows中有一個注冊表機(jī)制,在注冊表中提供了大量的屬性。在Linux中也有類似的機(jī)制,這就是屬性服務(wù)。init在啟動的過程中會啟動屬性服務(wù)(Socket服務(wù)),并且在內(nèi)存中建立一塊存儲區(qū)域,用來存儲這些屬性。當(dāng)讀取這些屬性時,直接從這一內(nèi)存區(qū)域讀取,如果修改屬性值,需要通過Socket連接屬性服務(wù)完成。在init.c文件中的一個action函數(shù)中調(diào)用了start_property_service函數(shù)來啟動屬性服務(wù),action是init.rc及其類似文件中的一種執(zhí)行機(jī)制,由于內(nèi)容比較多,所以關(guān)于init.rc文件中的執(zhí)行機(jī)制將在下一篇文章中詳細(xì)討論。
???? 現(xiàn)在順藤摸瓜,找到start_property_service函數(shù),該函數(shù)在Property_service.c文件中,該文件與init.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();
// 創(chuàng)建socket服務(wù)(屬性服務(wù))
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);
// 開始服務(wù)監(jiān)聽
listen(fd, 8);
property_set_fd = fd;
}
~~~
????? 現(xiàn)在我們已經(jīng)知道屬性服務(wù)的啟動方式了,那么在start_property_service函數(shù)中還涉及到如下兩個宏。
PROP_PATH_SYSTEM_BUILD
PROP_PATH_SYSTEM_DEFAULT
????? 這兩個宏都是系統(tǒng)預(yù)定義的屬性文件名的路徑。為了獲取這些宏的定義,我們先進(jìn)行另外一個函數(shù)的分析。
????? 在前面讀取屬性值時使用過一個property_get函數(shù),該函數(shù)在Property_service.c中實現(xiàn),代碼如下:
~~~
const char* property_get(const char *name)
{
prop_info *pi;
if(strlen(name) >= PROP_NAME_MAX) return 0;
pi = (prop_info*) __system_property_find(name);
if(pi != 0) {
return pi->value;
} else {
return 0;
}
}
~~~
????? 可以看到,在property_get函數(shù)中調(diào)用了一個核心函數(shù)__system_property_find,該函數(shù)真正實現(xiàn)了獲取屬性值的功能。該函數(shù)屬于bionic的一個library,在system_properties.c文件中實現(xiàn),讀者可以在如下的目錄找到該文件。
/bionic/libc/bionic
__system_property_find函數(shù)的代碼如下:
~~~
const prop_info *__system_property_find(const char *name)
{
// 獲取屬性存儲內(nèi)存區(qū)域的首地址
prop_area *pa = __system_property_area__;
unsigned count = pa->count;
unsigned *toc = pa->toc;
unsigned len = strlen(name);
prop_info *pi;
while(count--) {
unsigned entry = *toc++;
if(TOC_NAME_LEN(entry) != len) continue;
pi = TOC_TO_INFO(pa, entry);
if(memcmp(name, pi->name, len)) continue;
return pi;
}
return 0;
}
~~~
????? 從__system_property_find函數(shù)的代碼很容易看出,第一行使用了一個__system_property_area__變量,該變量是全局的。在前面分析main函數(shù)時涉及到一個property_init函數(shù),該函數(shù)調(diào)用了init_property_area函數(shù),該函數(shù)用于初始化屬性內(nèi)存區(qū)域,也就是__system_property_area__變量。
~~~
static int init_property_area(void)
{
prop_area *pa;
if(pa_info_array)
return -1;
if(init_workspace(&pa_workspace, PA_SIZE))
return -1;
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
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;
/* 初始化屬性內(nèi)存區(qū)域,屬性服務(wù)會使用該區(qū)域 */
__system_property_area__ = pa;
property_area_inited = 1;
return 0;
}
~~~
? 在前面涉及到的system_properties.c文件對應(yīng)的頭文件system_properties.h中定義了前面提到的兩個表示屬性文件路徑的宏,其實還有另外兩個表示路徑的宏,一共4個屬性文件。system_properties.h文件可以在/bionic/libc/include/sys目錄中找到。這4個宏定義如下:
~~~
#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"
~~~
????? 現(xiàn)在讀者可以進(jìn)入Android設(shè)備的相應(yīng)目錄,通??梢哉业缴鲜?個文件,如一般會在根目錄,會發(fā)現(xiàn)一個default.prop文件,cat default.prop會看到該文件的內(nèi)容。而屬性服務(wù)就是裝載所有這4個屬性文件中的所有屬性以及使用property_set設(shè)置的屬性。在Android設(shè)備的終端可以直接使用getprop命令從屬性服務(wù)獲取所有的屬性值。如圖2所示。getprop命令還可以直接根屬性名還獲取具體的屬性值,例如,getprop ro.build.product。

??????????????????????????????????????????????????????????????????????????????????????? 圖2
??????? 如果讀者感興趣,可以看一下getprop是如何通過屬性服務(wù)讀寫屬性的。getprop命令的源代碼文件是getprop.c。讀者可以在/system/core/toolbox目錄中找到該文件。實際上,getprop獲取屬性值也是通過property_get函數(shù)完成的。在前面分析過該函數(shù),實際上調(diào)用了__system_property_find函數(shù)從__system_property_area__變量指定的內(nèi)存區(qū)域獲取相應(yīng)的屬性值。
????? 此外在system_properties.c文件中還有如下兩個函數(shù)用于通過屬性服務(wù)修改或添加某個屬性的值。
~~~
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;
// 創(chuàng)建用于連接屬性服務(wù)的socket
s = socket(AF_LOCAL, SOCK_STREAM, 0);
if(s < 0) {
return result;
}
memset(&addr, 0, sizeof(addr));
// property_service_socket是Socket設(shè)備文件名稱
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)) {
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 {
result = 0;
}
}
close(s);
return result;
}
// 用戶可以直接調(diào)用該函數(shù)設(shè)置屬性值
int __system_property_set(const char *key, const char *value)
{
int err;
int tries = 0;
int update_seen = 0;
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);
// 設(shè)置屬性值
err = send_prop_msg(&msg);
if(err < 0) {
return err;
}
return 0;
}
~~~
在send_prop_msg函數(shù)中涉及到一個property_service_socket變量,定義如下:
~~~
static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME;
~~~
? 實際上,send_prop_msg通過這個設(shè)備文件與屬性服務(wù)通訊的。讀者可以在Android設(shè)備的終端進(jìn)入/dev/socket目錄,通常會看到一個property_service文件,該文件就是屬性服務(wù)映射的設(shè)備文件。
???? 現(xiàn)在已經(jīng)分析完了init如何確定與硬件相關(guān)的初始化文件名(init.grouper.rc),并且討論了4個屬性文件及其裝載過程,以及屬性服務(wù)實現(xiàn)的基本原理。在下一篇文章中將討論更深入的內(nèi)容,例如,init.rc文件中提供了很多action,那么什么是aciton呢,init有是如何解析init.rc文件呢?這些內(nèi)容都將在下一篇文章中揭曉。
- 前言
- Android深度探索(卷1):安裝C/C++交叉編譯環(huán)境
- 開發(fā)可統(tǒng)計單詞個數(shù)的Android驅(qū)動程序(1)
- 《Android深度探索(卷1):HAL與驅(qū)動開發(fā)》新書發(fā)布
- 開發(fā)可統(tǒng)計單詞個數(shù)的Android驅(qū)動程序(2)
- 開發(fā)可統(tǒng)計單詞個數(shù)的Android驅(qū)動程序(3)
- Android驅(qū)動程序開發(fā)和調(diào)試環(huán)境配置
- 在Android模擬器和Ubuntu上測試Linux驅(qū)動
- 使用Android NDK和Java測試Linux驅(qū)動
- Android的init過程詳解(一)
- Android雙機(jī)(網(wǎng)絡(luò)和USB)調(diào)試及其完美ROOT
- Android的init過程(二):初始化語言(init.rc)解析
