Android init啓動理解

本節涉及源碼有:

根目錄爲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,它的代碼如下:









總結一下init的工作流程:

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中使用的關鍵字:




Keywords.h好像沒有什麼奇特之處,它不過是個簡單的頭文件,爲什麼說它的用法很有意思呢?下面看下代碼中是如何使用它的:



終於領略到了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_serviceparse_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庫提供,代碼如下所示:



至此,屬性服務器就介紹完了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章