本節涉及源碼有:
根目錄爲system:
Init.c (core\init)
Parser.c (core\init)
Builtins.c (core\init)
Keywords.h (core\init)
Init.rc (core\rootdir)
Property_service.c (core\init)
Properties.c (core\libcutils)
和
Libc_init_common.c (f:\db\src\bionic\libc\bionic)
Libc_init_dynamic.c (f:\db\src\bionic\libc\bionic)
概述:
init是一個進程,確切地說,它是linux系統中用戶空間的第一個進程。由於Android是基於linux內核的,所以init也是Android系統中用戶空間的第一個進程,它的進程號是1。它具有很重要的職責:
1.如何創建zygote。zygote是Java世界的開創者
2.init的屬性服務是如何工作的。
init分析:
ini進程的入口函數是main,它的代碼如下:
1.解析兩個配置文件,我們將分析其中對init.rc文件的解析。
2.執行各個階段的動作。early-init,init,early-boot,boot,創建zygote的工作就是在其中的某個階段完成的。
3.調用property_init初始化屬性相關的資源,並通過property_start_service啓動屬性服務
4.init進入一個無線循環,並且等待一些事情的發生。重點關注init如何處理來自socket和來自屬性服務器的相關事情。
一、解析配置文件:
init會解析兩個配置文件,一個是系統配置文件init.rc,一個是硬件平臺相關的配置,以HTC G7爲例,配置文件爲init.bravo.rc其中brava是硬件平臺的名稱。對這兩個配置文件進行分析,調用的是同一個parse_config_file函數。下面就來看這個函數,在分析過程中以init.rc爲主。
看下如何進行解析的:
讀取完文件的內容後,將調用parse_config進行解析,這個函數的代碼如下所示:
從整體來看parse_config首先會找到配置文件的一個section,然後針對不同的section使用不同的解析函數來解析。那麼,什麼是section呢?這和init.rc文件的組織結構有關。先不必急着去看init.rc,還是先到代碼中去尋找答案。
1.關鍵字的定義:
keywords.h中定義了init中使用的關鍵字:
終於領略到了keyword.h的神奇之粗,原來它幹了兩件事情:
1. 第一次包含keyword.h的時候,它聲明瞭一些諸如do_class_start的函數,另外還定義了一個枚舉,枚舉值爲K_class,K_mkdir等關鍵字。
2. 第二次包含keywords.h後,得到了一個keyword_info結構體數組,這個keyword_info結構體數組以前定義的枚舉值爲索引,存儲對於的關鍵字信息,這些信息包括了關鍵字名稱,處理函數,處理函數的參數個數,以及屬性。
目前,關鍵字信息中最重要的就是symol和flags了,什麼樣的關鍵字被認爲是section呢?
根據keywords.h的定義,當symobl爲on或service的時候就表示section:
KEYWORD(on, SECTION, 0, 0)
KEYWORD(service, SECTION, 0, 0)
有了上面的知識,在來看配置文件init.rc的內容就比較容易了。
解析init.rc
截取內容如下:
總結:
從上面的init.rc的分析可知:
1.一個section的內容從這個表示section的關鍵字開始,到下一個標識的section的地方結束
2.init.rc中出現了名爲early-init,boot和init的section,這裏的boot和init就是前面介紹的4個動作執行階段中的boot和init。也就是說,在boot階段執行的動作都是由boot這個section定義的。
解析Services
以在zygote爲例:
解析section的入口函數是parse_new_section,代碼如下:
在解析service時,用到了parse_service和parse_line_service這兩個函數,在分別介紹它們之前,先看init是如何組織Services的。
Service結構體:
init中使用了一個叫service的結構體來保存與service section相關的信息。不妨來看看這個結構體,代碼如下所示:
現在已經瞭解的service結構體,相對來說還算是清晰易懂的。而zygote中的那三個onrestart該怎麼表示呢?請看service結構體中使用的action結構體:
瞭解上面的知識後,你是否能猜到parse_service和parse_line_service的作用了呢?馬上就來看它們。
可以看到parse_service函數只是搭建了一個service的架子。具體的內容尚需由後面的解析函數來填充。來看service的另外一個雞西函數parse_line_service.
瞭解parse_line_service
可以看到parse_line_service是將配置文件的內容填充到service結構體中。
下面是zygote解析完成後的結果:
從上圖中可知:
1. service_list鏈表將解析後的service全部連接到了一起,並且是一個雙向鏈表,前向節點用prev表示,後項節點用next表示。
2. socketinfo也是一個雙向鏈表,因爲zygote只有一個socket,所以畫了一個虛框作爲鏈表的示範
3. onrestart通過commands指向一個commands鏈表,zygote有三個commands.
zygote這個service解析完了,現在就是“萬事具備,只欠東風”了,接下來了解init是如何控制servies的?
1.啓動zygote
在init.rc有這樣一句話
class_start標識一個COMMAND,對應的處理函數爲do_class_start,它位於boot section的範圍內。爲什麼收它很重要呢?還記得init進程中的四個執行階段嗎?當init進程執行到下面幾句話時,do_class_start就會被執行
下面看看do_class_start函數:
參數爲default的話最終調用service_start_if_not_disabled
看看該函數的實現:
service_start的實現:
原來,zygote是通過fork和execv公共創建的!但是service結構中的那個onrestart好像沒有派上用場,原因何在?
重啓zygote
應該根據名字就可以猜到onrestart是在zygote重啓時用的,下面先看 在zygote死後,它的父進程init會有什麼動作:
signal_fd就是在init中通過socketpair創建的兩個socket中的一個,既然會往這個signal_fd中發送數據,那麼另外一個socket就一定能接收到,這樣就會導致init從poll函數中返回,代碼如下:
通過上面的代碼可以知道onrestart的作用了,但是zygote本身又在哪裏重啓的呢?答案在下面的代碼中:
這樣,zygote又回來了!
屬性服務
Key/Value鍵值對,一般而言,系統或某些應用程序會把自己的一些屬性存儲在註冊表中,即使系統重啓或應用程序重啓,它還能夠根據之前在註冊表中設置的屬性,進行相應的初始化工作。Android中的這種機制就稱爲屬性服務。
可以使用:
adb shell
getprop 查看當前系統有那些屬性
屬性服務是如何實現的?
在init.c中與屬性服務有關的代碼有:
1. 屬性服務初始化:
在property_init函數中先調用init_property_area函數,創建一塊用於存儲屬性的存儲區域,然後加載default.prop文件中的內容。先看看init_property_area 是如何工作的,它的代碼如下所示:
上面的內容比較簡單,不過最後的賦值語句可是大有來頭。__system_property_area__是bionic libc庫中輸出的一個變量,爲什麼這裏要給他賦值?
原來,雖然屬性區域是由init進程創建的,但Android系統希望其他進程也能讀取這塊內存裏的東西。爲了做到這一點,Android便做了以下的兩項工作:
1. 把屬性區域創建到共享內存上,共享內存是可以跨進程的。。這一點,已經在上面的代碼中見到了,init_workspace函數內部將創建這個共享內存。
2.如何讓其他進程直到這個共享內存:Android利用了gcc的constructor屬性,這個屬性指明瞭一個__libc_prenit函數,當bionic libc庫被加載時,將自動調用這個__libc_prenit,這個函數內部就將完成共享內存到本地進程的映射工作.
客戶端進程獲取存儲空間
__libc_init_common函數爲:
上面代碼中有很多地方與共享內存有關。在以後的章節會對共享內存有關的問題進行介紹。
總之,通過這種方式,客戶端進程就直接直接讀取屬性空間了,但是沒有權限設置屬性。客戶端進程又是如何設置屬性的呢?
屬性服務器的分析:
1. 啓動屬性服務器
init進程會啓動一個屬性服務器,而客戶端只能通過與屬性服務器交互來設置屬性。先來看屬性服務器的內容,它由start_property_service函數啓動,代碼如下所示:
屬性服務創建了一個用來接收請求的socket,可這個請求在哪裏被處理呢?事實上,在init中的for循環處已經進行相關處理了。
處理設置屬性請求
接收請求的地方在init進程中,代碼如下所示:
當屬性服務器收到客戶端請求時,init會調用handle_property_set_fd進行處理。這個函數的代碼如下所示:
當客戶端的權限滿足要求時,init就調用property_set進行相關處理。這個函數比較簡單,代碼如下所示:
好,屬性服務端的工作已經瞭解了,下面看客戶端是如何設置屬性的。
客戶端發送請求
客戶端通過property_set發送請求,property_set由libcutuls庫提供,代碼如下所示:
至此,屬性服務器就介紹完了。