




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、Android本質上就是一個基于Linux核的操作系統。與Ubuntu Linux、Fedora Linux類似。只是Android在應用層專門為移動設備添加了一些特有的支持。既然Android是Linux核的系統,那么基本的啟動過程也應符合Linux的規則。如果研究過其他Linux系統應該了解,一個完整的Linux系統首先會將一個Linux核裝載到存,也就是編譯Linux核源代碼生成的bzImage文件,對于為Android優化的Linux核源代碼會生成zImage文件。該文件就是Linux核的二進制版本。由于zImage在核空間運行,而我們平常使用的軟件都是在應用空間運行(關于核空間和應用
2、空間的詳細描述,可以參考Android深度探索(卷1):HAL與驅動開發一書的容,在后續的各卷中將會對Android的整體體系進行全方位的剖析)。核空間和應用空間是不能直接通過存地址級別訪問的,所以就需要建立某種通訊機制。 目前Linux有很多通訊機制可以在用戶空間和核空間之間交互,例如設備驅動文件(位于/dev目錄中)、存文件(/proc、/sys目錄等)。了解Linux的同學都應該知道Linux的重要特征之一就是一切都是以文件的形式存在的,例如,一個設備通常與一個或多個設備文件對應。這些與核空間交互的文件都在用戶空間,所以在Linux核裝載完
3、,需要首先建立這些文件所在的目錄。而完成這些工作的程序就是本文要介紹的init。Init是一個命令行程序。其主要工作之一就是建立這些與核空間交互的文件所在的目錄。當Linux核加載完后,要做的第一件事就是調用init程序,也就是說,init是用戶空間執行的第一個程序。在分析init的核心代碼之前,還需要初步了解init除了建立一些目錄外,還做了如下的工作1. 初始化屬性2. 處理配置文件的命令(主要是init.rc文件),包括處理各種Action。3. 性能分析(使用bootchart工具)。4. 無限循環執行command(啟動其他的進程)。
4、盡管init完成的工作不算很多,不過代碼還是非常復雜的。Init程序并不是由一個源代碼文件組成的,而是由一組源代碼文件的目標文件而成的。這些文件位于如下的目錄。<Android源代碼本目錄>/system/core/init 其中init.c是init的主文件,現在打開該文件,看看其中的容。由于init是命令行程序,所以分析init.c首先應從main函數開始,現在好到main函數,代碼如下:int main(int argc, char *argv) int fd_count = 0; struct pollfd ufds4; ch
5、ar *tmpdev; char* debuggable; char tmp32; int property_set_fd_init = 0; int signal_fd_init = 0; int keychord_fd_init = 0; bool is_charger = false; if (!strcmp(basename(argv0), "ueventd") return ueventd_main(argc, argv); if (!strcmp(basename(argv0), "watchdogd") return watchdogd_m
6、ain(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&
7、quot;, 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文件是否可讀寫和創
8、建*/ close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000); open_devnull_stdio(); klog_init(); / 初始化屬性 property_init(); get_hardware_name(hardware, &revision); / 處理核命令行 process_kernel_cmdline(); is_charger = !strcmp(bootmode, "charger"); INFO("property initn"); if (!
9、is_charger) property_load_boot_defaults(); INFO("reading config filen"); / 分析/init.rc文件的容 init_parse_config_file("/init.rc"); / 執行初始化文件中的動作 action_for_each_trigger("init", action_add_queue_tail); / 在charger模式下略過mount文件系統的工作 if (!is_charger) action_for_each_trigger("
10、;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, &
11、quot;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-b
12、oot", 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(b
13、ootchart_init_action, "bootchart_init");#endif / 進入無限循環,建立init的子進程(init是所有進程的父進程) for(;) int nr, i, timeout = -1; / 執行命令(子進程對應的命令) execute_one_command(); restart_processes(); if (!property_set_fd_init && get_property_set_fd() > 0) ufdsfd_count.fd = get_property_set_fd(); ufdsfd_
14、count.events = POLLIN; ufdsfd_count.revents = 0; fd_count+; property_set_fd_init = 1; if (!signal_fd_init && get_signal_fd() > 0) ufdsfd_count.fd = get_signal_fd(); ufdsfd_count.events = POLLIN; ufdsfd_count.revents = 0; fd_count+; signal_fd_init = 1; if (!keychord_fd_init && get_
15、keychord_fd() > 0) ufdsfd_count.fd = get_keychord_fd(); ufdsfd_count.events = POLLIN; ufdsfd_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)
16、 timeout = 0;/ bootchart是一個性能統計工具,用于搜集硬件和系統的信息,并將其寫入磁盤,以便其/ 他程序使用#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 / 等待下
17、一個命令的提交 nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i+) if (ufdsi.revents = POLLIN) if (ufdsi.fd = get_property_set_fd() handle_property_set_fd(); else if (ufdsi.fd = get_keychord_fd() handle_keychord(); else if (ufdsi.fd = get_signal_fd() handle_signal
18、(); return 0; 我們可以看到main函數是非常復雜的,不過我們也不需要每條語句都弄得非常清楚(因為這樣弄是非常困難的),通常只需要了解init的主線即可。其實從init的main函數可以看出。Init實際上就分為如下兩部分。1. 初始化(包括建立/dev、/proc等目錄、初始化屬性、執行init.rc等初始化文件中的action等)。2. 使用for循環無限循環建立子進程。 第一項工作很好理解。而第二項工作是init中的核心。在Linux系統中init是一切應用
19、空間進程的父進程。所以我們平常在Linux終端執行的命令,并建立進程。實際上都是在這個無限的for循環中完成的。也就是說,在Linux終端執行ps e 命令后,看到的所有除了init外的其他進程,都是由init負責創建的。而且init也會常駐容。當然,如果init掛了,Linux系統基本上就崩潰了。 由于init比較復雜,所以本文只分析其中的一部分,在后續文章中將詳細分析init的各個核心組成部分。 對于main函數最開始完成的建立目錄的工作比較簡單,這部分也沒什么可以分析的。就是調用了一些普通的A
20、PI(mkdir)建立一些目錄。現在說一些題外話,由于Android的底層源代碼(包括init)實際上是屬于Linux應用編程領域,所以要想充分理解Android源代碼,除了Linux的基本結構要了解外,Linux應用層的API需要熟悉。為了滿足這些讀者的需要,后續我會寫一些關于Linux應用編程的文章。Ok,現在言歸正傳,接下來分析一個比較重要的部分:配置文件的解析。 這里的配置文件主要指init.rc。讀者可以進到Android的shell,會看到根目錄有一個init.rc文件。該文件是只讀的,即使有了root權限,可以修改該文件
21、也沒有。因為我們在根目錄看到的文件只是存文件的鏡像。也就是說,android啟動后,會將init.rc文件裝載到存。而修改init.rc文件的容實際上只是修改存中的init.rc文件的容。一旦重啟android,init.rc文件的容又會恢復到最初的裝載。想徹底修改init.rc文件容的唯一方式是修改Android的ROM中的核鏡像(boot.img)。其實boot.img名曰核鏡像,不過該文件除了包含完整的Linux核文件(zImage)外,還包括另外一個鏡像文件(ramdisk.img)。ramdisk.img就包含了init.rc文件和init命令。所以只有修改ramdisk.img文件
22、中的init.rc文件,并且重新打包boot.img文件,并刷機,才能徹底修改init.rc文件。如果讀者有Android源代碼,編譯后,就會看到out目錄中的相關子目錄會生成一個root目錄,該目錄實際上就是ramdisk.img解壓后的容。會看到有init命令和init.rc文件。在后續的文章中將會討論具體如何修改init.rc文件,如何刷機。不過這些容與本文關系不大,所以不做詳細的討論。現在回到main函數,在創建完目錄后,會看到執行了如下3個函數。 property_init(); get_hardware_nam
23、e(hardware, &revision); process_kernel_cmdline(); 其中property_init主要是為屬性分配一些存儲空間,該函數并不是核心。不過當我們查看init.rc文件時會發現該文件開始部分用一些import語句導入了其他的配置文件,例如,/init.usb.rc。大多數配置文件都直接使用了確定的文件名,只有如下的代碼使用了一個變量($ro.hardware)執行了配置文件名的一部分。那么這個變量值是從哪獲得的呢?import /init.$ro.hardwar
24、e.rc 首先要了解init.$ro.hardware.rc配置文件的容通常與當前的硬件有關。現在我們先來關注get_hardware_name函數,代碼如下:void get_hardware_name(char *hardware, unsigned int *revision) char data1024; int fd, n; char *x, *hw, *rev; /* 如果hardware已經有值了,說明hardware通過核命令行提供,直接返回 */ if (hardware0) return; / 打開/proc/cpuinfo文
25、件 fd = open("/proc/cpuinfo", O_RDONLY); if (fd < 0) return; / 讀取/proc/cpuinfo文件的容 n = read(fd, data, 1023); close(fd); if (n < 0) return; datan = 0; / 從/proc/cpuinfo文件中獲取Hardware字段的值 hw = strstr(data, "nHardware"); rev = strstr(data, "nRevision"); / 成功獲取Hardware字段
26、的值 if (hw) x = strstr(hw, ": "); if (x) x += 2; n = 0; while (*x && *x != 'n') if (!isspace(*x) / 將Hardware字段的值都轉換為小寫,并更新hardware參數的值 / hardware也就是在init.c文件中定義的hardware數組 hardwaren+ = tolower(*x); x+; if (n = 31) break; hardwaren = 0; if (rev) x = strstr(rev, ": "
27、); if (x) *revision = strtoul(x + 2, 0, 16); 從get_hardware_name方法的代碼可以得知,該方法主要用于確定hardware和revision的變量的值。Revision這里先不討論,只要研究hardware。獲取hardware的來源是從Linux核命令行或/proc/cpuinfo文件中的容。Linux核命令行暫且先不討論(因為很少傳遞該值),先看看/proc/cpuinfo,該文件是虛擬文件(存文件),執行cat /proc/cpuinfo命令會看
28、到該文件中的容,如圖1所示。在白框中就是Hardware字段的值。由于該設備是Nexus 7,所以值為grouper。如果程序就到此位置,那么與硬件有關的配置文件名是init.grouper.rc。有Nexus 7的讀者會看到在根目錄下確實有一個init.grouper.rc文件。說明Nexus 7的原生ROM并沒有在其他的地方設置配置文件名,所以配置文件名就是從/proc/cpuinfo文件的Hardware字段中取的值。
29、160; 圖1現在來看在get_hardware_name函數后面調用的process_kernel_cmdline函數,代碼如下:static void process_kernel_cmd
30、line(void) /* don't expose the raw commandline to nonpriv processes */ chmod("/proc/cmdline", 0440); / 導入核命令行參數 import_kernel_cmdline(0, import_kernel_nv); if (qemu0) import_kernel_cmdline(1, import_kernel_nv); / 用屬性值設置核變量 export_kernel_boot_props();
31、 在process_kernel_cmdline函數中除了使用import_kernel_cmdline函數導入核變量外,主要的功能就是調用export_kernel_boot_props函數通過屬性設置核變量,例如,通過ro.boot.hardware屬性設置hardware變量,也就是說可以通過ro.boot.hardware屬性值可以修改get_hardware_name函數中從/proc/cpuinfo文件中得到的hardware字段值。下面看一下export_kernel_boot_props函數的代碼。static void export_kernel_boot_props(voi
32、d) char tmpPROP_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.
33、baseband", "ro.baseband", "unknown", , "ro.boot.bootloader", "ro.bootloader", "unknown", , ; / 通過核的屬性設置應用層配置文件的屬性 for (i = 0; i < ARRAY_SIZE(prop_map); i+) pval = property_get(prop_mapi.src_prop); property_set(prop_mapi.dest_prop, pval ?: pro
34、p_mapi.def_val); / 根據ro.boot.console屬性的值設置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 o
35、n 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變量的值設置設置ro.hardware屬性 /
36、 這個屬性就是前面提到的設置初始化文件名的屬性,實際上是通過hardware變量設置的 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_s
37、et("ro.factorytest", "1"); else if (!strcmp(bootmode,"factory2") property_set("ro.factorytest", "2"); else property_set("ro.factorytest", "0"); 從export_kernel_boot_props函數的代碼可以看出,該函數實際上就是來回設置一些屬性值,并且利
38、用某些屬性值修改console、hardware等變量。其中hardware變量(就是一個長度為32的字符數組)在get_hardware_name函數中已經從/proc/cpuinfo文件中獲得過一次值了,在export_kernel_boot_props函數中又通過ro.boot.hardware屬性設置了一次值,不過在Nexus 7中并沒有設置該屬性,所以hardware的值仍為grouper。最后用hardware變量設置ro.hardware屬性,所以最后的初始化文件名為init.grouper.rc。 這里還有一個問題,前
39、面多次提到屬性或屬性文件,那么這些屬性文件指的是什么呢?是init.rc?當然不是。實際上這些屬性文件是一些列位于不同目錄,系統依次讀取的配置文件。屬性服務(Property Service) 在研究這些配置文件之前應先了解init是如何處理這些屬性的。編寫過Windows本地應用的讀者都應了解,在windows中有一個注冊表機制,在注冊表中提供了大量的屬性。在Linux中也有類似的機制,這就是屬性服務。init在啟動的過程中會啟動屬性服務(Socket服務),并且在存中建立一塊存儲區域,用來存儲這些屬性。當讀取這些屬性時,直接從這一
40、存區域讀取,如果修改屬性值,需要通過Socket連接屬性服務完成。在init.c文件中的一個action函數中調用了start_property_service函數來啟動屬性服務,action是init.rc及其類似文件中的一種執行機制,由于容比較多,所以關于init.rc文件中的執行機制將在下一篇文章中詳細討論。 現在順藤摸瓜,找到start_property_service函數,該函數在Property_service.c文件中,該文件與init.c文件中同一個目錄。void start_property_service(void) int
41、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(); / 創建socket服務(屬性服務) fd = create_socket(PROP_SER
42、VICE_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; 現在我們已經知道屬性服務的啟動方式了,那么在start_property_service函數中還涉及到如下兩個宏。PROP_PATH_SYSTEM_BUILDPROP_PATH_SYSTEM_DEFAU
43、LT 這兩個宏都是系統預定義的屬性文件名的路徑。為了獲取這些宏的定義,我們先進行另外一個函數的分析。 在前面讀取屬性值時使用過一個property_get函數,該函數在Property_service.c中實現,代碼如下:const char* property_get(const char *name) prop_info *pi; if(strlen(name) >= PROP_NAME_MAX) return 0; pi = (prop_info*) _syste
44、m_property_find(name); if(pi != 0) return pi->value; else return 0; 可以看到,在property_get函數中調用了一個核心函數_system_property_find,該函數真正實現了獲取屬性值的功能。該函數屬于bionic的一個library,在system_properties.c文件中實現,讀者可以在如下的目錄找到該文件。<Android源代碼根目錄>/bionic/libc/bionic_system_property_find函數的代
45、碼如下:const prop_info *_system_property_find(const char *name) / 獲取屬性存儲存區域的首地址 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; p
46、i = TOC_TO_INFO(pa, entry); if(memcmp(name, pi->name, len) continue; return pi; return 0; 從_system_property_find函數的代碼很容易看出,第一行使用了一個_system_property_area_變量,該變量是全局的。在前面分析main函數時涉及到一個property_init函數,該函數調用了init_property_area函數,該函數用于初始化屬性存區域,也就是_system_property_area_變量。s
47、tatic 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
48、->magic = PROP_AREA_MAGIC; pa->version = PROP_AREA_VERSION; /* 初始化屬性存區域,屬性服務會使用該區域 */ _system_property_area_ = pa; property_area_inited = 1; return 0; 在前面涉及到的system_properties.c文件對應的頭文件system_properties.h中定義了前面提到的兩個表示屬性文件路徑的宏,其實還有另外兩個表示路徑的宏,一共4個屬性文件。system_propert
49、ies.h文件可以在<Android源代碼根目錄>/bionic/libc/include/sys目錄中找到。這4個宏定義如下:#define PROP_PATH_RAMDISK_DEFAULT "/p"#define PROP_PATH_SYSTEM_BUILD "/system/p"#define PROP_PATH_SYSTEM_DEFAULT "/system/p"#define PROP_PATH_LOCAL_OVERRIDE "/data/
50、p" 現在讀者可以進入Android設備的相應目錄,通常可以找到上述4個文件,如一般會在根目錄,會發現一個p文件,cat p會看到該文件的容。而屬性服務就是裝載所有這4個屬性文件中的所有屬性以及使用property_set設置的屬性。在Android設備的終端可以直接使用getprop命令從屬性服務獲取所有的屬性值。如圖2所示。getprop命令還可以直接根屬性名還獲取具體的屬性值,例如,getprop duct。 &
51、#160; &
52、#160; 圖2 如果讀者感興趣,可以看一下getprop是如何通過屬性服務讀寫屬性的。getprop命令的源代碼文件是getprop.c。讀者可以在<Android源代碼根目錄>/system/core/toolbox目錄中找到該文件。實際上,getprop獲取屬性值也是通過property_get函數完成的。在前面分析過該函數,實際上調用了_system_property_find函數從_system_property_area_變量指定的存區域獲取相應的屬性值。 此外在system_properties.c文件中還有如下兩個函數用于通過屬性服務修改或添加某個屬性的值。static int send_prop_msg(prop_msg *msg) struct pollfd pollfds1; struct sockaddr_un addr; socklen_t alen; size_t namelen; int s; int r; int result = -1;
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年消費者行為變化與風險試題及答案
- 網絡安全監測與分析試題與答案
- 2025年VB考試內容全面分析與試題
- 行政法學完整試題與答案分享
- 戰略執行中的合規性風險管理對策試題及答案
- 信息處理行業標準化的必要性試題及答案
- 【成都】2025年上半年成都市住房和城鄉建設局所屬6家事業單位招聘工作人員13人筆試歷年典型考題及考點剖析附帶答案詳解
- 2025商業辦公裝修合同范本匯編
- 高考數學中低檔題型解析試題及答案
- 編程技術的發展和未來展望試題及答案
- 2025世界高血壓日控住血壓穩住幸福高血壓健康講座
- 安徽卓越縣中聯盟2024-2025學年高三下學期5月份檢測政治試卷+答案
- 廣東省珠海市2024-2025學年下學期期中八年級數學質量監測試卷(含答案)
- 焊接工程師職業技能考核試題及答案
- 《神經網絡模型》課件
- 中小學教師資格筆試2024年考試真題解析
- 工抵房轉讓購買合同協議
- 四川省成都外國語2025年高三聯考數學試題科試題含解析
- 嘉興市申嘉有軌電車運營管理有限公司招聘筆試題庫2025
- 國網四川省電力公司電網工程設備材料補充信息參考價2025
- 委托清收服務合同協議
評論
0/150
提交評論