Presentation is loading. Please wait.

Presentation is loading. Please wait.

Android系統結構.

Similar presentations


Presentation on theme: "Android系統結構."— Presentation transcript:

1 Android系統結構

2 序論 現階段Android以純熟的技術及貼近開發者的需求,迅速竄紅,且因伴隨著人們生活智慧化,各式各樣的嵌入式裝置相繼開發,由原本的智慧型手機到平板電腦,甚至連手錶皆有Android的身影,可說是達到近顛峰的狀態。 有鑑於目前市場應用程式開發人員趨近飽和,但系統移植人員卻供不應求,本章節將探討Android底層Native世界,介紹Android系統架構及開機流程,並透過解說init及Zygote運作流程,讓學員們了解Android系統從接通電源到啟動至Home介面下,過程中到底做了些什麼事情。

3 單元介紹 本單元三將介紹Android系統架構,並探討從開機接通電源到初始化所有資源進入Home桌面下之間所作的動作,其中將姐說開機的第一支程式init是如何運作,其孕做過程又將牽扯到哪些資源的初始化及啟動,其後介紹Zygote的運作方式以及他是透過什麼機制來創造Dalvik VM,而它又是如何分裂更多的Dalvik VM來執行新的Process,以下便從Android系統架構開始進行介紹。

4 基礎知識 Java、C/C++程式語言基礎能力 基本作業系統知識 Windows、Linux。
本章節所講授的內容因講解不少作業系統的運作流程概念,建議學生有作業系統基礎概念,以幫助學習理解。且因「init」及「Zygote」兩個部份除了觀念介紹外,還搭配程式來解說運作流程所以需具備一定的程式語言的能力。

5 目錄(1/2) Part I Android初探 Part II Android初始化過程 Chapter 1 Android系統架構分析
Chapter 3 Bootloader Chapter 4 kernel Chapter 5 init Chapter 6 Zygote 本章節將分為三個部份來講解,分別為Android初探、Android初始化過程及Android Dalvik。 Android初探將進行Android的介紹,並解說Android系統架構各個階層的特性及功能。 Android初始化過程首先會講解一般個人電腦所使用的作業系統及嵌入式作業系統開機的不同處,之後介紹Android作業系統的開機流程並分別介紹各個階層的功能。

6 目錄(2/2) Part III Android Dalvik Chapter 7 Dalvik概述
Android Dalvik的部分除了介紹Android Dalvik及其優化方式外,還進行Dalvik VM與Java VM比較。

7 Part I Android初探

8 Chapter 1 Android系統架構分析

9 1.1 Android來龍去脈 2005年,Google併購Android高科技公司
最初由安迪‧魯賓(Andy Rubin)所創辦,人稱Android之父 Google與多家製造商組成─開放手機聯盟(Open Handset Alliance) 手機設備大廠:宏達電、摩托羅拉、LG、Samsung 晶片設計廠商:Intel、nVIDIA、Texas 電信業者:中國移動通訊、NTT、DoCoMo、T-Mobile 2007年11月5日,正式公布Android作業系統。 手機平台是由手機硬體設備與作業系統所組成的。過去在Google還未發表Android之前,所有的手機平台均由各家開發廠商獨佔, 一般使用者不易取得相關的開發技術,甚至花錢也只能購得部分資料,直到Google Android手機平台的誕生,為封閉的手機市場開創了新的道路。 Android最初是由安迪‧魯賓所創辦,2005年Google收購了成立不到2年的Android高科技公司,並開始拉攏多家製造商組成「開放手機聯盟」開發改良Android, 在2007年Google Android作業系統正式公布。現Android今已擴充功能到平板電腦及其他領域之上。

10 1.1.1 Android發展史(1/2) 年份 年度大事 2007年
11月,Google結合33家手機相關軟硬體廠商組成開放手持裝置聯盟(OHA),並對外公開Android智慧型手機平台。 2008年 9月,T-Mobile發表世界第一台Android手機,同時Google也正式對外公開Android 1.0版。 12月,OHA聯盟宣佈新增14家廠商。 2009年 4月,Android 1.5版發表 5月,Google舉辦第二次Android程式開發挑戰賽(ADC 2)。 9月,Android 1.6版發表。 10月,Android 2.0版發表。

11 1.1.1 Android發展史(2/2) 年份 年度大事 2010年 5月,Android 2.2版發表。
2011年 2月,發表專門給平板電腦使用的Android 3.0版。 11月,Android 4.0版發表。 2012年 6月,Android 4.1版發表。 宣告不會內建Flash Player,且Adobe也聲明停止開發。 2013年 預計發表Android 5.0板。

12 1.1.2 Android 版本演進 發表日期 Android名稱 Android版本 採用Kernel版本 2009年4月30日
Cupcake 1.5 Kernel 2009年9月15日 Donut 1.6 Kernel 2009年10月26日 Eclair 2.0/2.0.1/2.1 2010年5月20日 Froyo 2.2/2.2.1 Kernel 2010年12月7日 Gingerbread 2.3 Kernel 2011年2月2日 Honeycomb 3.0.1/3.1/3.2 Kernel 2011年10月19日 Ice Cream Sandwich 4.0 Kernel 3.0.1 2012年6月28日 Jelly Bean 4.1 Kernel

13 1.2 Android系統架構 一般我們熟知的Android架構共為4層,由5個部分所組成,分別是:
第一層 LINUX KERNEL (Linux核心) 第二層LIBRARIES (函式庫)及ANDROID RUNTIME (Android 執行環境) 第三層APPLICATION FRAMEWORK (應用程式架構) 第四層 APPLICATIONS (應用程式) 但其實在第一層與第二層之間還有一層:硬體抽象層, 這層是Google特別設計出來要將Framework與Linux kernel完全隔開,使其不受GPL授權影響, 在後面將會詳細介紹各部分的功能。

14 1.2.1 Linux Kernel Android OS使用Linux Kernel,但非Linux OS的一種 不採用原生glibc
透過一些機制避開GPL授權。 不採用原生glibc 非使用全套標準Linux工具 增加及優化Linux Kernel功能 Linux這個名詞在最早的時候其實是指Kernel,它提供了系統底層與硬體間的基本核心,讓其它程式可以在上頭執行, 在Linux kernel上頭執行的程式,跟kernel本身不見得有關係。可以是自由軟體,也可以完全不是。把它加上一些自由軟體, 例如基本的函式庫、工具、圖形介面,應用程式等等,所組成的一套完整作業系統,才是一般所稱的Linux。為了避免誤解, 而且也為了正確傳達自身的貢獻,GNU(自由軟體基金會)建議大家稱呼這樣的一套作業系統為GNU/Linux。 其中的原因是,kernel提供底層機制,但系統中其他重要元件幾乎都是來自於GNU。 所以基本上Android是在Linux kernel上運作,但它並不是GNU/Linux,在一般 GNU/Linux裡面會有的東西,Android幾乎都沒有。 而Linux Kernel的版權是GNU General Public License version 2 (GPLv2), GPL授權為了確保智慧財產能夠繼續公開流傳,所以任何基於此創作的延伸創作,都自動採用了相同版權。 GPL本身還有個特色,就是「共同運作」也算是延伸的一部分,意思是說你的程式沒直接改GPL的程式碼, 但是連結了GPL的東西跟你的程式共同運作,那你的程式也必須採用GPL版權。 簡單來說,某公司將具有GPL授權的軟體進行修改並出售,其出售的同時也需要將修改的部分一併共開。

15 1.2.1.1 GPL、BSD及Apache 2.0(1/3) GPL (GNU General Public License)
LGPL(GNU Lesser General Public License) 可自行授權 可不公開原始碼 可不公開原始碼,但必須允許逆向工程 須公開原始碼 獨自開發 使用LGPL LGPL利用 使用GPL GPL GPL授權: Linux所採用的授權規範。 不修改原始碼的「使用」,不論個人或商業營運均不必支付授權金。 須隨附GPL授權(gpl-2.0.txt),不能只提供網頁連結(URL)。 修改過後的原始碼也必須使用GPL授權。 可不經開發者允許、不支付授權金,就將原始碼整合進業務軟體。 不能與其他授權方式的原始碼混用。 若使用者需要,必須無償提供原始碼,或必須直接隨附原始碼。 原始碼以「現狀」提供,也就是不提供任何保證,任何責任都必須由原始碼使用者自己負擔。 LGPL: 由於僅僅連結了GPL授權的函式庫,就必須公開原始碼,對於商業軟體來說是十分苛刻的要求, 因此誕生了LGPL授權,此授權為GPL授權的弱化版,對於函式庫方面來講, 靜態連結的部分必須採用GPL授權,而動態連結的方式則由驅動開發者自行決定授權方式。 例如:glibc、uClibc、C的函式庫。 動態連接 靜態連接 靜態、動態連接 glibc LGPL GPL函式庫 GPL Linux kernel GPL GPL授權-原文規範

16 GPL、BSD及Apache 2.0(2/3) BSD (Berkeley Software Distribution License) 自由軟體中最受廣泛使用的授權。 使用者無公開原始碼的義務。 衍生作品改用其他授權方式也沒關係,但須於文件中明確指出授權。 BSD開源​​協議是一個給於使用者很大自由的協議。基本上使用者可以”為所欲為”,可以自由的使用,修改源代碼,也可以將修改後的代碼作為開源或者專有軟件再發布。 但”為所欲為”的前提當你發布使用了BSD協議的代碼,或則以BSD協議代碼為基礎做二次開發自己的產品時,需要滿足三個條件: 1. 如果再發布的產品中包含源代碼,則在源代碼中必須帶有原來代碼中的BSD協議。 2. 如果再發布的只是二進制類庫/軟件,則需要在類庫/軟件的文檔和版權聲明中包含原來代碼中的BSD協議。 3. 不可以用開源代碼的作者/機構名字和原來產品的名字做市場推廣。 BSD 代碼鼓勵代碼共享,但需要尊重代碼作者的著作權。BSD由於允許使用者修改和重新發布代碼,也允許使用或在BSD代碼上開發商業軟件發布和銷售,因此是對商業集成很友好的協議。而很多的公司企業在選用開源產品的時候都首選BSD協議,因為可以完全控制這些第三方的代碼,在必要的時候可以修改或者二次開發 BSD授權-原文規範

17 1.2.1.1GPL、BSD及Apache 2.0(3/3) Apache 2.0 (Apache License 2.0)
Google Android HAL層所採用的授權規範。 由非營利開放原始碼組織Apache軟體基金會所公布的協議。 須滿足條件與BSD授權相似。 鼓勵分享原始碼並尊重原作者的著作權。 允許修改其源碼,再發布。 作為開放原始碼或商業軟體 Apache Licence是著名的非盈利開放原始碼組織Apache採用的協議。該協議和BSD類似,同樣鼓勵代碼共享和尊重原作者的著作權, 同樣允許代碼修改,再發佈(作為開放原始碼或商業軟體)。需要滿足的條件也和BSD類似: 需要給代碼的用戶一份Apache Licence 如果你修改了程式碼,需要再被修改的文件中說明。 在延伸的程式碼中(修改和有原始碼衍生的程式碼中)需要帶有原來程式碼中的協議,商標,專利聲明和其他原來作者規定需要包含的說明。 如果再發佈的產品中包含一個Notice文件,則在Notice文件中需要帶有Apache Licence。你可以在Notice中增加自己的許可,但不可以表現為對Apache Licence構成更改。 Apache Licence也是對商業應用友好的許可。使用者也可以在需要的時候修改程式碼來滿足需要並作為開放原始碼或商業產品發佈/銷售。 Apache授權-原文規範

18 1.2.3 Android HAL 採用Apache Licence 2.0授權,規避Linux kernel的GPL授權。
把Android framework與Linux kernel完整「隔開」。 實作存取硬體的方法供給上層使用。 Android HAL(硬體抽象層)最主要的目的是為了把Android framework與Linux kernel完整「隔開」來。它主要是為了讓一些硬件廠商不用根據 Linux kernel的GPL協議放出全部的 Linux驅動原始碼。 有了HAL層,硬體廠商就可以只在Kernel驅動中實現基本的操作而把硬體操作的邏輯移到HAL層來實現,HAL層是apache 2.0協議的,所以廠商可以只提供二進制函式庫。 前面所提到的,由於Linux kernel是採用GPL授權,所以Google為了規避GPL授權,則在Android架構中加入了HAL層, 透過HAL層來實作存取硬體的方法,然後上層的Android framework再透過JNI的方式來使用HAL提供的方法, 如此一來雖然因為GPL授權的延伸規範(若使用到含有GPL授權的軟體需要將自身的源碼公開),HAL必須公開其源碼,但因為HAL本身是Apache Licence 2.0授權, 所以上層透過JNI來叫用HAL所實作的方法並不用公開源碼。 且HAL只負責進行傳進傳出的簡單動作,最重要的控制指令都寫在Android函式庫中,如此一來對外只要公開通道(HAL),其中最重要的控制方式便不需要公開。

19 1.2.4 Android Libraries Android系統包含了一些C/C++函式庫 提供原生函式庫及自行新增的函式
自行開發Bionic Libc代替glibc 一般嵌入式使用的glibc及uclibc均為LGPL授權 提供原生函式庫及自行新增的函式 這邊所講的libraries是在系統裡頭的函式庫,採用的語言不是Java,他們提供了許多基礎建設。 其中Bionic是Android自行開發的的 libc。libc 是GNU/Linux及其他類似UNIX系統上最基礎的函式庫, 一般最常用的是 glibc 或是 uclibc ,但因為這兩種均屬於LGPL授權(GPL的弱化版), 所以基於版權問題,且須考慮到輕量及快速,才作了自己的 libc。

20 1.2.5 Android Runtime 採用自身開發的Android Runtime來執行系統
以Dalvik VM代替Java VM 分為Core Libraries及Dalvik Virtual Machine Core Libraries含有大多數Java所需要呼叫的函式。 Dalvik Virtual Machine是一種基於暫存器的VM。 Dalvik VM運行的檔案格式為「*.dex」檔 「*.class」轉換成「*.dex」。 Android Runtime分成二個重要的元件來執行系統,雖然Android是用Java來開發、撰寫應用程式, 但卻不使用Java Runtime來執行Java程式,而是自行研發Android Runtime來執行程式。 這二個重要元件分別是Core Libraries(核心函式庫),另一個是Dalvik Virtual Machine(Dalvik 虛擬機器)。 Core Libraries(核心函式庫) Core Libraries(核心函式庫)裡頭已經包含了絕大多數Java所需要呼用的函式, 接著每一個Android應用程式都會以自屬的process(程 序)。而且Android不是用一個Dalvik虛擬機器來同時執行多個Android應用程式, 而是每個Android應用程式都用一個自屬的 Dalvik虛擬機器來執行。 Dalvik Virtual Machine(Dalvik 虛擬機器) Dalvik Virtual Machine(Dalvik虛擬機器)是一種暫存器型態的虛擬機器。在撰寫開發時就已經設想用最少的記憶體資源來執行, 以及前述的「同時可執行多個VM 個體」。這樣的設計,讓系統在執行程式可以最佳化的方式來執行,節省更多的資源。

21 1.2.6 Android Framework 提供開發人員大量API 具大量基礎軟體元件 Android官方API查詢
Activity Manager、Package Manager、Window Manager、Resource Manager、Content Manager、View System等。 Android官方API查詢 Application Framework定義了能讓開發者可以完整使用與核心應用程式相同的應用程式標準介面(API), 應用程式架構是為了要簡化元件的重新利用而設 計的,應用程式可以發佈功能並為其它應用程式所使用, 但是,要受限於應用程式架構的安全限制,使用者也用同樣的機制用來新增、置換元件。 其中主要的有應用程式元件分為四大類,分別是Activities、Services、Broadcast receivers及Content providers,這些應用元件在後續會詳細說明。

22 1.2.7 Android Applications 開發Android Applications僅需下載Google開發的SDK
內建模擬器,可直接檢驗應用程式。 採用Java程式語言搭配XML檔,進行應用程式開發 已提供核心應用程式 Home、Contact、Phone、Browser等 一般開發人員也屬於這層 Android 的APP是採用Java來撰寫,要開發Android APP不需要下載整個平台的程式碼。 只需要安裝Google發行的SDK即可,且因SDK已內建模擬器,所以可直接檢驗開發的應用程式,不需要擁有實體手機設備。 建議以Eclipse為開發環境,此軟體為開放原始碼,不必付費便可使用。 且Google在提供了Eclipse的一個延伸套件,與模擬器、除錯等等都有整合,並提供完整文件,進入門檻降得相當低。

23 練習 Android系統架構是由哪6個部分組成(不包含硬體)?
Linux kernel是採用何種授權規範?而Android又是以何種方式將其Framework與Kernel區隔開來? Android Runtime分為哪兩個部份?其功能為何? 1. Linux kernel、Hardeare Abstract Layer、Libraries、Android runtime、Android Framework、Android Application 2. Linux kernel採用的是GPL授權方式,Android是透過HAL將其隔開以免受到GPL授權的延伸導致需開放原始碼 3. Android Runtime分成二個重要的元件來執行系統,這二個重要元件分別是Core Libraries(核心函式庫),另一個是Dalvik Virtual Machine(Dalvik 虛擬機器)。Libraries(核心函式庫)裡頭已經包含了絕大多數Java所需要呼用的函式,接著每一個Android應用程式都會以自屬的process(程 序)。而且Android不是用一個Dalvik虛擬機器來同時執行多個Android應用程式,而是每個Android應用程式都用一個自屬的 Dalvik虛擬機器來執行。Dalvik Virtual Machine(Dalvik虛擬機器)是一種暫存器型態的虛擬機器。在撰寫開發時就已經設想用最少的記憶體資源來執行,以及前述的「同時可執行多個VM 個體」。這樣的設計,讓系統在執行程式可以最佳化的方式來執行,節省更多的資源。

24 Part II Android初始化過程

25 Chapter 2 Android開機流程 2.1 個人電腦與嵌入式系統開機流程比較 2.2 Android開機流程

26 2.1 個人電腦與嵌入式系統開機流程 我們一般的個人電腦開機的流程主要分為四個階段,分別是BIOS、Bootloader、Kernel及 root檔案系統,當我們按下Power on後,一般電腦會立即執行BIOS,BIOS會開始進行一些裝置診斷及低階初始化(例如:磁碟裝置及一些輸入輸出埠等),之後便載入Bootloader;Bootloader是作業系統開始運行前的一個小程式,他會先進行初始化硬體裝置,之後建立記憶體空間映射圖,然後設定Kernel啟動參數並載入Kernel;Kernel被掛載到RAM後便會開始進行Kernel的初始化,並且將Root檔案系統掛載近來及管理一些軟硬體資源,在root檔案系統掛載好後,就會開始一連串的初始化動作,root檔案系統的初始化主要是依照「init」(開機初始化命令稿)檔案來設定環境變數並啟動系統服務,直到整個系統啟動完畢。 而嵌入式系統因硬體需求的關係,相較一般個人電腦的開機流程便少了BIOS這部分。但並非所有的嵌入式系統都沒有BIOS這塊,依照硬體設計的不同可以分為兩類,一類是以X86為處理器,設計的機板,這類的嵌入式系統因為X86機板通大都是採用PC架構,所以會擁有BIOS;而處理器若是用非X86的,譬如ARM系列的,這類的機板則不具有BIOS,而他們所採用的方式則是將Bootloader存放於Flash記憶體或是ROM中,直接由Bootloader進行開機。

27 2.2 Android系統開機流程(1/8) u-boot Kernel Image init init.rc Boot Loader
U-boot是嵌入式開發人員為了擁有一個功能強大且支援大量平台的Boot Loader而開發出來的,Android系統在啟動時,也就是設備通電的那一刻,最先運行的程式就是u-boot,系統會透過它來初始化硬體,由其是記憶體控制器,並找出系統核心的位置,將核心載入記憶體中解壓縮並執行,之後便將工作交與核心處理。 Boot Loader Linux Kernel Android Booting

28 2.2 Android系統開機流程(2/8) u-boot Kernel Image init init.rc Boot Loader
Kernel Image載到記憶體中並解壓縮後便會開始檢查各個週邊設備,例如:儲存裝置、網路卡、音效卡等,雖然U-boot會先偵測硬體資訊,但kernel不一定會直接使用,通常會再自行偵測一次。等到系統內部元件初始化完成後,Kernel編會尋找Android的根檔案系統來掛載,而尋找的方式可能會由u-boot所告知的位置或者由內部程式碼來尋找跟檔案系統的位置。 Boot Loader Linux Kernel Android Booting

29 2.2 Android系統開機流程(3/8) Kernel Image init init.rc Android Booting
當Kernel將Android的根檔案系統掛載完畢後,便會自動搜尋並執行 init(開機初始化命令稿)檔案,init是以常駐程序執行,在它開始運行後,會建立檔案系統目錄,並解析 init.rc檔, init.rc檔是Android系統啟動的腳本,它運行後會進行環境變數的設定、掛載檔案系統、並開始啟動系統服務。 init.rc

30 2.2 Android系統開機流程(4/8) Android Booting Kernel Image init init.rc

31 2.2 Android系統開機流程(5/8) usbd adbd debuggerd rild daemons Zygote init.rc
Init.rc會先啟動daemons(背景處理行程)及Zygote,daemons運行後主要會啟動四項背景行程,分別是:USB背景處理行程(usbd),用來管理UBS連線、Android Debug Bridge背景處理行程(adbd),來管理ADB連線、Debug背景處理行程(debuggerd),來管理偵測處理行程的要求(包括記憶體轉換等)、無線介面背景處理行程(rild),來管理無線通訊。 Zygote是Android 中JAVA Framework的基礎,它會進行VM的初始化,且會載入Socket要求所需的類別並監聽。所有的Android application 皆由此行程透過Linux fork()行程,並運行自身擁有的VM上。 daemons Zygote init.rc

32 2.2 Android系統開機流程(6/8) Audio Flinger Surface Flinger Service Manager
System Server 接著系統會初始化runtime處理行程,當runtime運行後首先會初始化「Service Manager」,並註冊Service Manager,以它作為預設Binder服務的Context管理員,在完成初始化後,runtime會傳送一個訊息給Zygote,要求開始啟動系統服務,此時Zygote將會為系統服務處理行程建立一個Androud虛擬機器-Dalvik VM的實體,並啟動System server。 System server將會啟動員生系統服務,主要包括了「Surface Flinger」及「Audio Flinger」並將系統服務註冊到Service Manager作為IPC服務的目標。 daemons runtime Zygote Dalvik VM init.rc

33 2.2 Android系統開機流程(7/8) Zygote daemons Service Manager System Server
Dalvik VM runtime init.rc Content Manager Window Manager Telephony Manager Activity Bluetooth Manager Package Connectivity Service Power Location ‧‧‧ Surface Flinger 此圖為啟動Android的各種管理服務並將其註冊到Service Manager。

34 2.2 Android系統開機流程(8/8) DAEMON PRCESSES RUNTIME ZYGOTE SYSTEM SERVER
HOME CONTACTS Activity Manager Home Contacts Package Manager Dalvik VM Dalvik VM ‧‧‧ Dalvik VM 在所有Android 管理服務都載入完成後,系統會處於等待狀態,等待應用程式的執行。但每個應用程式都將會啟動一個單獨的處理行程。如此圖中Zygote分別給「Home」及「Contacts」一個虛擬的Dalvik VM來運行處理行程,至於行程行程之間的溝通方式便須透過 IPC (Inter-Process Communication)機制來處理。 Surface Flinger daemons daemons Audio Finger daemons rumtime Zygote libc libc libc libc libc libc

35 練習 一般個人電腦開機可分為哪幾個階段?而嵌入式系統又是哪幾個階段?
Android開機流程中的Bootloader是採用哪套Bootloader? init.rc最主要會啟動哪三個行程來初始化並註冊一些Android開機時所需的資源及功能? 1. 一般的個人電腦開機的流程主要分為四個階段,分別是BIOS、Bootloader、Kernel及 root檔案系統,嵌入式系統則是,Bootloader、Kernel及 root檔案系統。 2. U-boot 3. Daemons、Zygote、Runtime

36 Chapter 3 Bootloader 3.1 何謂Bootloader 3.2 Bootloader功能
3.4 Uboot介紹 3.5 Uboot優點 3.6 Uboot運作方式 3.7 Uboot運作流程

37 3.1 何謂Bootloader BootLoader為開機載入程式 相依於CPU體系結構及開發板設備 嵌入式系統中不可或缺的一項元件
也稱為Boot code或Boot Monitor 類似Windows中的BIOS 相依於CPU體系結構及開發板設備 可同時支援不同體系結構的CPU 例:U-Boot。 嵌入式系統中不可或缺的一項元件 Bootlosder是開機載入程式,負責載入各種作業系統,因為嵌入式系統所使用的硬體設備不像一般桌上型電腦般如此龐大,所以在嵌入式系統中就採用Bootlosder為開機的初始化程式,他的功用就類似一般Windows作業系統中的BIOS一樣需要進行一些初始化及載入Kernel的動作,而體系結構不同的CPU都有不同的Bootloader,而有些Bootloader可支援不同型態體系結構的CPU,如U-Boot便是一種,但除了相依CPU體系結構問題外,Bootloader還需針對特定的嵌入式開發版進行修改,也就是說就算採用同一種體細節夠的CPU,但其嵌入式開發版的硬體結構不同仍需進行修改才能適用。

38 3.2 BootLoader功能 主要功能 其他功能 初始化硬體(如:記憶體控制器)。 提供Kernel啟動參數,並啟動Kernel。
讀寫儲存器。 通過串接埠/網路埠下載文件到RAM。 從RAM複製內容到Flash中。 Bootloader的主要功能大致分為兩項,分別是一開始進行裝置初始化及提供Kernel啟動參數來啟動Kernel,而進行這兩項主要功能時另外還做了讀寫儲存器、下載文件並放置於RAM中及把RAM內容付至到Flash內。

39 3.3 常見Boot-loader介紹 Boot-loader Monitor 說明 X86 ARM PowerPC LILO No
Linux系統主要開機載入程式 X GRUB LILO的GUN版後繼者 ROLO 無須BIOS,可直接從ROM載入Linux Etherboot 透過網路卡開啟系統的 ROMable loader BLOB Yes 專為Strong ARM架構下的LART設計的Boot-loader U-Boot 以PPCBoot及ARMBoot為基礎的Boot-loader Redboot 以eCos為基礎的Boot-loader 常見的BootLoader大致可分為表格內的這七項,其中在嵌入式系統內最常用到的就是U-Boot這個BootLoader,Android系統便是採用此項,後面將針對U-Boot進行討論。

40 3.4 U-Boot介紹 U-Boot(Universal Boot Loader) 德國DENX小組開發,以PPC-Boot為基礎改進。
遵循GPL授權的開放原始碼 採模組化編譯方式,支援多種嵌入式系統Kernel及CPU。 目錄結構及編譯方式與Linux kernel極為相似 U-Boot全名為「Universal Boot Loader」,它是由德國的DENX軟體工程中心的Wolfgang Denk(人名),基於8xxROM架構一路更改並擴充處理器的支援而來,其中還經歷了PPCBoot及ARMBoot兩個版本的BootLoader。一路的過程即是先由8xxROM原程式碼來創建PPCBoot,然後PPCBoot被移植到ARM平台上後,便產生了ARMBoot版本,最後基於PPCBoot和ARMBoot兩項開發了U-Boot。 U-Boot是採用GPL規範的開放原始碼,且目前已經可以支援PowerPC、ARM、X86、MIPS體系結構的CPU,估計已有上百種開發平台。由於如此相當受到嵌入式系統的寵愛,而Android系統便是採用U-Boot作為BootLoader。

41 3.5 U-Boot優點 開放原始碼 支援多種嵌入式作業系統 支援多種處理器 穩定性及可靠性高 功能設定靈活度高 大量裝置驅動程式
Linux、NetBSD、VxWorks、QNX、ARTOS、RTEMS、LynxOS等。 支援多種處理器 PowerPC、ARM、x86、MIPS、Xscale等。 穩定性及可靠性高 功能設定靈活度高 大量裝置驅動程式 完善的開發偵錯文件及網路技術支援 支援SD/TF卡啟動 U-Boot的優點大致可分為這幾項。

42 3.6 U-Boot運作方式 U-Boot運作共為兩階段 第一階段介紹: 以組合語言(assembly language)撰寫。
硬體初始化(CPU部分)。 設置堆疊、初始化data segment。 第二階段介紹: 以C語言撰寫。 實現複雜功能。 具有更好的可讀性及移植性。 U-Boot運作方式可分為兩個階段,第一階段為依賴CPU架構的程式碼,採用組合語言(assembly language)撰寫,第二階段則是使用C語言。在第一階段主要進行硬體初始化及設置堆疊和初始化data segment。而第二階段則進行記憶體的初始化、設置Kernel啟動參數並將Kernel及root file system載入記憶體內進行啟動。

43 3.7 U-Boot運作流程 這是詳細的U-Boot兩個階段的運作流程,它的硬體設備初始化可以分為兩個階段,第一階段的初始化是跟CPU有關的,第二階段的初始化是與其他硬體設備相關,整個運作流程就如同圖面上的敘述。

44 練習 Bootloader的功用為何? U-Boot的演化過程是經歷了哪3個階段? U-Boot支援哪幾種作業系統? (請舉3個)
U-Boot支援哪幾種CPU類型? (請舉3個) 1. Bootlosder是開機載入程式,類似一般個人電腦中的BIOS,負責載入各種作業系統。 2. 最早是 8xxROM再來是PPCBoot然後是ARMBoot,之後就是U-Boot了。 3. Linux、NetBSD、VxWorks、QNX、ARTOS、RTEMS、LynxOS等 4. PowerPC、ARM、x86、MIPS、Xscale等

45 Chapter 4 kernel 4.1 何謂Kernel 4.2 Kernel功能選單 4.3 Kernel到init的流程

46 4.1 何謂Kernel 作業系統的核心程式 系統底層與硬體間的基本平台 不同的Kernel版本提供不同的軟硬體功能 核心分類
提供硬體抽像方法 記憶體及行程管理 基於權限限制的安全模型 經安全驗證驅動程式模型 共享函式庫 不同的Kernel版本提供不同的軟硬體功能 核心分類 單核心(Monolithic kernel) 微核心(Micro kernel) 混合核心(Hybrid Kernel) 外核心(Exo Kernel) Kernel就大家所知他是一個作業系統的核心,他為系統處理了軟硬體之間的溝通,且不同版本的Kernel所提供的軟硬體功能皆不同,其中所提供最重要的功能為這五項,硬體抽象方法、記憶體及行程管理、基於權限限制的安全模型、經安全驗證驅動程式模型和共享函式庫。 而且核心其實可分為四種類型,分別為單核心、微核心、混合核心及外核心,以下將針對這四種核心進行說明。

47 4.1.1 單核心 單核心(Monolithic kernel) 定義一抽象介面以提供硬體大量的抽象控制
以一個或多個模組搭配System call來實現系統功能 如:行程管理、記憶體管理、File System等 所有模組均運行於Kernel Space中 若模組出現小bug,便會造成系統崩潰 透過動態載入/卸載模組,使Kernel輕量化 代表性作業系統: UNIX、Linux、MS-DOS等 單核心全名為Monolithic kernel,它的架構很簡單,整個核心程式都是以Kernel Space的身份及Supervisor Mode來執行,他定義了一個抽象介面,提供大量的抽象方法並搭配了一些System call來控制硬體。因為採用了模組化,讓眾多服務模組可以各自運作,但因為其結構非常緊密,難以進行修改,即無法修改成別種類型的作業系統架構,而模組化的服務均使用同一個定址空間,這導致如果其中一個模組損壞了,將造成整個系統崩壞,但相對的若系統在開發時,設計完善且經過可靠的測試後,其具有高度緊密性的各個模組,在系統低階運行上將特別有效率。其中最具代表性的作業系統便是UNIX、Linux及MS-DOS。

48 單核心架構圖 由這個單核心架構可知,系統所有的服務模組均運行於Kernel mode中就如同剛才說到的所有模組,例如:System call、File System、Device Drivers等,皆使用同一個定址空間,只要其中一個模組損壞便會造成整個系統的崩壞,相對的若設計得宜則會大幅提升運行效率。

49 4.1.2 微核心 微核心(Micro kernel) 目標將系統實作及系統基本操作規則區分開來 最輕量化且提供完整作業系統功能
執行緒管理、記憶體管理、IPC等功能。 由一簡單硬體抽象層及一組System call組成。 為多執行緒結構 將許多系統服務隔離到分離的行程中運行 分離的系統服務若出錯,不會導致系統癱瘓。 如:File System、Device Driver等。 代表性作業系統: GNU HURD、Mac OS X、QNX。 微核心全名為Micro kernel,顧名思義是眾多Kernel類型中最輕量化的,雖然屬最輕量化但他也提供了完整的作業系統功能,它的結構是由一個簡單的硬體抽象程及一組叫關鍵的Native code或System call所組成,而Native code的部分則是系統的重點功能,例如:執行緒管理、記憶體管理、IPC等。 微核心的主要目標是將系統服務的實作與基本操作功能區分開來,例如說:行程的輸入/輸出服務可以由一個執行在微核心外的服務原件來提供,透過模組化的設計架構,將多數服務與核心服務區隔開來並且運行在User mode,如果某個服務原件崩壞了,那麼核心只要將這個元件重新啟動及可,並不會像單核心那樣服務原件的崩壞會直接影響到核心的運行。代表性的作業系統如:GNU HURD、Mac OS X、QNX等。

50 微核心架構圖 如同上一頁所提到的,微核心的架構將許多服務模組化並且運行於User mode下,而一些較為重要的服務,如記憶體管理、IPC等等則運行於Kernel mode。如此模組化後,若在User mode下的服務元件崩壞了,核心便會將那些服務重新啟動,完全不影響Kernel mode中的元件運行,更不會使Kernel跟著崩潰。而且採取這種方式的架構,使用者可以依照需求隨時增加或減少服務模組,也就是說如果今天的硬體設備所提供的資源很少,那麼在設計Kernel價購時可以將多數用不到的服務拿掉,已合乎硬體平台所提供的資源使用。

51 4.1.3 混合核心 混合核心(Hybrid Kernel) 基於單核心及微核心架構所設計 與單核心非常相似 核心的運行效率更高
代表性作業系統: 基於NT技術的Windows作業系統、Mac OS X。 混合核心全名為Hybrid Kernel,它是基於單核心與微核心架構所設計而成的,可以說是綜合兩者的優點。為什麼說取兩者的優點,原因在於先前提到的,單核心雖然只要設計得宜就可以大幅提升系統運作效率,可是只要一個不小心有服務元件崩壞及會使系統一同崩壞,而微核心因為採用模組化且將大部分的服務與核心服務區隔開來,使得一般服務元件若是崩壞也不會影響到核心,但相對的來說大部分的服務元件都位於User Mode使的運行效率降低,而混合核心就是基於這兩點,將原本User mode的部分服務元件移到Kernel mode層去執行,藉以提昇Kernel運行效率。代表性的作業系統為:基於NT技術的Windows作業系統、Mac OS X。

52 混合核心架構圖 將混合核心的架構圖與微核心的架構圖來比較可以清楚的知,混合核心將部分運行於User Mode的服務元件移到Kernel mode層去執行,藉以解決單核心與微核心的問題來提升核心運行效率。

53 4.1.4 外核心 外核心(Exo Kernel) 也稱為「縱向結構」作業系統 麻省理工學院-平行與分散式系統工作小組所開發
減少軟體抽象部分,開發者可專注於硬體抽象層 應用程式可直接存取一物理空間或特定磁區。 核心極小,只負責系統保護及系統資源復用相關服務。 現階段尚為研究階段,尚未有商業系統問世 開發中的系統為劍橋大學的Nemesis 外核心全名為Exo Kernel,它的核心架構屬於一種極端的設計方式。之所以說是一種極端的設計方式是因為他的設計理念是要讓使用者設計的程式來決定硬體介面的設計,而Kernel本身非常的小,主要只處理系統資源及系統保護的相關服務。簡單來講,傳統的設計方式就是將硬體資源及裝置的驅動程式藏匿在硬體抽象層之下,上層的應用程式開發者並不知到底層硬體資源的實際位置,無法直接存取這些資源,一切都需要透過硬體抽象層來協助處理。而外核心的目的就是讓程式開發者所設計的程式可以直接存取硬體資源(例如:磁碟區塊),而系統要做的則是確定這個區塊當前是空閒的就會允許應用程式直接存取。但換句話說外核心只提供了低階的硬體操作,而不提供如其它系統般的硬體抽象層的話,則需要提供更多的library如此才能提供使用者完整的功能。但目前這種核心架構還在開發階段,並未有商業系統發售,例如:劍橋大學的Nemesis,格拉斯哥大學的Citrix系統和瑞士電腦科學院的一套系統。麻省理工學院也在進行著這類研究。

54 外核心架構圖 由架構圖上所示,外核心是一個很小的核心他只負責處理系統資源及系統保護相關的服務,且提供了大量的Library給予上層的應用程式開發人員利用來取的一些資源,或者是直接透過核心提供的低階硬體存取方式值接存取硬體資源。

55 4.2 Kernel功能選單(1/3) 設定Kernel所提供的功能及相關設定 編譯Kernel前,需先進入選單進行設定
透過指令「make menuconfig」進入選單畫面 「*」表選用、「 」表不選用、「M」表編譯成模組 可載入或匯出「.config」檔 前述提到了一些Kernel相關的架構觀念、而我們實際在製做一個Kernel則需要進行一些相關的設定去選定一些功能,在Linux作業系統中,我們可以透過終端機到存放Kernel原始檔的資料夾內下「make menuconfig」的指令進入到Kernel的選單畫面,他的選項的選用方式是以空白鍵搭配方向件來選擇,而離開則是利用Esc鍵。在選擇的過程中,米字號表示在編Kernel時會選用一起編進去,若是留空則不會在編譯時一同編進去,標示成M則是編成模組,可以在需要使用時才動態載入,可以節省Kernel的大小。整個功能選單在後續會有詳細介紹。

56 4.2 Kernel功能選單(2/3) 項目 名稱 Code maturity level options
選擇是否使用開發中的程式、驅動程式 General setup 一般設定 Loadable module support 支援可載入之模組 Process debugging support 可監控User的行程 Block layer 支援區塊處理模式 Processor type and features 選擇處理器類型 Power management options 電源管理控制 Bus options(PCI,PCMCIA,EISAmMCA,ISA) 匯流排控制 Executable file formats 可執行之檔案格式 以下兩頁所列的Kernel功能選單是依照實際選單列表的排列,每個項目均是Kernel將各個功能模組進行分類的選項,各位學員在進行編譯Kernel時可以對照這兩個表來查看自己所要選用或者是移除的功能屬於哪個選項之中,要注意的是,每個版本的Kernel所分類的選項內容會略有不同,簡單來講某個功能在前一個版本的Kernel中是放在A項目內,但到了下一個版本後,就被移到B項目之中,亦或者會出現某個設備裝置的驅動,一般直覺會認為就是放在「Device Drivers」選項中,但或許這個驅動是與網路相關的,則這個功能就可能是放在「Networking」的選項中的問題,當然也會有可能所要的功能並不在選項清單之內,這時便要自己手動增加新的功能並修改選單列表來進行選擇編譯,這些進一步的動作會在前兩章節的實作課已有說明,在此就不重複講述。

57 4.2 Kernel功能選單(3/3) 項目 名稱 Networking 支援的網路協定 Device Drivers 裝置驅動程式
File systems 檔案系統格式 Instrumentation Support 支援的測試方法 Kernel hacking Kernel除錯 Security options 安全性選項 Cryptographic options 加密方式 Library routines 函式庫規範 Load an Alternate Configuration File 載入現有的config檔 Save Configuration to an Alternate File 將新的設定值存入config檔

58 4.3 Kernel到init的流程(1/2) 一般個人電腦
在一般個人電腦也就是大家常用的windows作業系統中,最初的動作便是由從放於Flash中的BIOS進行啟動,它將存放在硬碟中的MBR載入到記憶體中去啟動,然後由Bootloader來設定一些開機相關的參數,例如Kernel的啟動參數,當Bootloader設定準備完畢後,便會從硬碟中將Kernel的映像檔載入到記憶體之中並進行解壓縮的動作,解壓縮完畢並執行完相關的啟動工作後(一些硬體資源的啟動配置等),便會由系統中的第一支程式init來進行相關的軟體機制初始化、資源分配、參數設定等動作,最後將root檔案系統掛載近來,之後才會到我們一般使用電腦的桌面,也就是HOME。

59 4.3 Kernel到init的流程(2/2) 嵌入式系統
在前面提到的嵌入式系統不像一般個人電腦那樣具有龐大的硬體資源可以使用,相對的開機時很多動作流程都被精簡化但必要的部分還是會保留。因為缺少了硬碟這個龐大的儲存裝置,嵌入式系統直接將Bootloader、Kernel映像檔、root檔案系統均放置於Flash記憶體中,而整個開機流程類似一般個人電腦那樣,一開始是透過Bootloader來進行一些相關設定,在設置Kernel參數並啟動Kernel後就將工作交給Kernel繼續進行,Kernel在完成所需要做的工作後就會透過init這支程式來進行更進一步的系統初始化,之後才載入並啟動 root檔案系統,進入HOME完成開機動作。

60 練習 核心的分類有哪四種? 核心所提供的功能中有哪五項最為重要? 1. 單核心、微核心、混合核心及外核心
2. 提供硬體抽像方法、記憶體及行程管理、基於權限限制的安全模型、經安全驗證驅動程式模型、共享函式庫

61 Chapter 5 init 5.1 init概述 5.2 init工作流程 5.3 解析init 5.4 init.rc概述

62 5.1 init概述 init (initialization),Linux系統啟動的第一個行程 負責開機啟動的初始化,行程代號為1。
基於Linux Kernel的Android系統也是由此進行系統啟動 位置:「system/core/init/init.c」 負責兩個主要工作 新增系統重要的行程 例如:Zygote。 提供property service管理屬性系統 init全名為initialization,是系統啟動的第一支程式也就是第一個行程,他所做的開機初始化過程中最主要的兩個工作便是新增並運行一些系統重要的行程,例如:Zygote這支行程,後續將會針對這支行程有進一步的介紹,除此之外還提供 property service管理屬性系統。而這些動作都會在init.rc這支開機命令稿內進行參數設定及初始化運作。

63 5.2 init工作流程(1/2) Init主要工作流程可分為下列幾項: 清空umask 創建並掛載基本目錄
設備目錄「/dev」、系統訊息目錄「/proc」、系統儲存「/sys」、遠端控制設備「/dev/pts」、 socket 「/dev/socket」等 初始化NULL設備,重新定義標準輸入/輸出 初始化kmsg系統、並解析「init.rc」文件 取得Kernel命令參數,並解析特定的「init.*.rc」文件 除了剛剛所提到的兩個重要工作外,其實init還做了很多重要的工作,他的工作流程最初是將umask清空,這個umask是設置一些預設權限,我們系統所有的檔案或目錄最初的使用權限都會受到它的設定值所影響,在清空它的設定值之後,init便開始創建即掛載一些基本目錄,例如:設備目錄dev、系統訊息目錄proc、系統儲存sys等,在設置完成後便開始初始化NULL設備並重新定義標準輸入/輸出,以供後續硬體資源的使用,這些都完成後就會解析初始化腳本 init.rc並依照其內容進行相關的資源配置、參數設定、新增行程等工作。

64 5.2 init工作流程(2/2) Init主要工作流程可分為下列幾項: 解析文件 系統配置文件「init.rc」、硬體設備配置文件。
解析文件的程式位於「system/core/init/parser.c」。 依「init.rc」文檔,執行各個階段動作 如:新增Zygote。 進入無線循環,等帶事件發生 Init所解析的文件除了初始化腳本init.rc外還有硬體配置文件,而要解析這兩個文件的程式則是放在「system/core/init」目錄中的parser.c檔,前面所講到的重要行程Zygote便是由init.rc這個初始化腳本來給予相關的啟動參數並新增執行,在完成所有工作之後init便會進入無限循環等待,等待並監聽事件發生。

65 5.2.1 init進入無線循環後的工作 Init進入無線循環後主要進行下列五項工作 初始化及啟動屬性服務(porpety service)
「property_init」,初始化屬性相關資源。 「property_start_service」,啟動屬性服務。 動態生成設備節點 扮演Linux系統中udev的角色,監聽uevent事件。 監聽keychord 殺死殭屍行程 不做事且佔據空間的行程。 管理native服務 例: servicemanager、vold。 在init進入無限循環後其實也做了不少工作,主要可分為五項。 初始化及啟動屬性服務,這個部分就是前述提到的提供 property service管理屬性系統,它主要由兩支程式來進行,分別是property_init和property_start_service,property_init是設置屬性初始化的相關資源,property_start_service則是如字面上所示,啟動屬性服務;而動態生成設備節點,這個部分就是扮演如同Linux系統中的udev的腳色,來監聽uevent事件,並進行相對應的處裡剩下的還有監聽keychord、殺死殭屍行程與管理Native服務等。

66 5.3 解析init(1/5) init.c 片段程式 創建並掛載一些基本目錄 重新定義標準輸入/輸出
前面所提到的init.c這支程式是存放於「system/core/init/」目錄中,從main類別開始執行時會先創建並掛載一些基本目錄,我們可以看到它是透過Linux的創建目錄指令「mkdir」來進行目錄新增並給予權限設定,之後透過函式來重新定義標準輸入/輸出。

67 5.3 解析init(2/5) init.c片段程式 解析兩份配置文件 取得Kernel命令參數 初始化及啟動屬性服務
之後便解析兩份配置文件,依照這兩份文件來進行相關的初始化及啟動服務動作。這兩個配置文件就是init.rc及硬體設備配置檔。 整個流程便是先解析init.rc檔,來進行一些相關初始化,之後便透過函式來取得kernel的啟動參數,取得之後才開始解析硬體設備配置文件來初始周邊硬體設備,再來才是呼叫property_set函式來定義一些屬性項目的名稱及內含值。

68 5.3.1 解析配置文件的函式 parser.c片段程式 init.c程式呼叫用來解析文件的函式
Init要解析init.rc及硬體設備配置檔均是使用parser.c這支程式來進行解析,我們看到的是這支程式內的片段程式,這段程式是透過函式去讀取init.rc配置檔,並呼叫parse_config這個方法來解析init.rc。

69 5.3.2 解析init.rc配置文件 Init.rc片段程式 創建Zygote
在inti.rc這個配置檔中有一個很重要的動作,便是設置Zygote的參數並進行啟動,Zygote這個行程是Android系統中最重要的行程之一,它攸關Android世界的運行,少了這支行程,Android便無法正常運作。我們可以從片段程式截圖中看到,Zygote在inir.cr中是對應到section,而section是採用service來標示,這邊只是一個部分的service的標示,其實還有很多,但由於篇幅關係只舉Zygote這個片段來講解,這段程式是init.rc中一個片段的actions的撰寫方式,一開頭的on boot表示開機時(boot)便觸發此區的命令動作,我們看到的這段程式式進行Zygote的參數配置並啟動,其中Zygote被標示成service,在init.rc中所有標示的service的動作均會透過「service_start」這個類別來執行,而有關於Zygote的介紹後續將會有詳細的說明。

70 5.3.3 init創建並啟動service(1/2) init片段程式 service_start函式片段程式
Init程式中的service_start這個函式將會去判斷各個service的狀態是否是運行中,如果不是則會進行啟動。由於service也是運行於process中,所以在啟動運行之前需要先去判斷每個service相對應的文檔是否存在,若存在才可以進行新增啟動。而Zygote的對應文檔則是放置於「/system/bin/」中的app_process這個檔名也就是Zygote最初的名稱。

71 5.3.3 init創建並啟動service(2/2) init片段程式 service_start函式片段程式
在判斷要啟動的service的文檔是否存在後,便會透過「fork」這個系統呼叫(system call)來進行新增的動作,這個指令是Linux中的系統呼叫之一,這邊將採用啟動Zygote的例子來講述它的執行過程,一開始會先判斷該service是否運行於子行程內,若是沒錯便透過函式來取得並設置屬性的資訊到環境變數中,然後再到service相對應的文檔位置去存取執行,其實從這邊可以看到Android Zygote要新增出來除了使用「fork」這個系統呼叫外,還使用了「execve」這個系統呼叫,一同來進行創建並啟動Zygote,一般大都只提到fork這個系統呼叫,而比較少會講道execve這個系統呼叫。

72 5.3 解析init(3/5) init片段程式 啟動屬性服務porpety service
Init在進行到啟動屬性服務時,會透過函式去執行先前設定好的「early-boot」及「boot」階段的動作。

73 5.3.1 property service屬性表 屬性前綴 描述 示例 ro. 只讀屬性
setprop ro.media.capture.maxres 5m persist. 另外儲存到「/data/property」目錄下 Setprop persist.sys.country CN net. 網路相關,如gprs、藍牙 setprop net.bt.name CAPF 註:最後一次net.change=net.gprs.local-ip ctrl.start 啟動init.rc中標註為service的服務 Setprop ctl.start bootanim 屬性返回:init.svc.bootanim=running ctrl.stop 停止init.rc中標註為service的服務 setprop ctl.stop bootanim 屬性返回:init.svc.bootanim=stoped Porperty的屬性如表所示,依照每段屬性設置的前綴詞可分為「ro.」、「persist.」、「net.」、「ctrl.start」及「ctrl.stop」這五種,從這些屬性前綴就可以知道該段屬性是做什麼動作,例如:第一個「ro.」是進行讀取的動作,這個例子是讀取攝影頭的最大像素(5m);第二個「persist.」是將文件另外儲存到「/data/property」目錄之下,這是因為每個屬性都儲存為一個獨立文件;第三個「net.」,就如同名稱一樣是進行網路相關的設定,而這邊的例子是採用藍牙網路為例,藍牙網路名稱為CAPF,net.change的值最後一次更改net.*屬性的屬性名稱;而第四第五個分別啟動/停止init.rc中標示為service的服務,後述的例子分別為:啟動boot動態圖像,當一個服務設置後,其結果會以此屬性方式回返、及停止boot動態圖像,當一個服務設置後,其結果會以此屬性方式回返。

74 5.3 解析init(4/5) init片段程式 監聽事件
Init進行到監聽的步驟時,總共會去監聽四個方面的事件,分別是Uevent、porperty server、socket及keychord這四種。

75 5.3 解析init(5/5) init片段程式 進入無線循環後便會開始進行一些事件監聽處理
例:重新啟動已死去的行程、處理Uevent事件、處理屬性服務事件等。 當init所要做的初始化及新增的動作都完成後,便會進入無限迴圈且會不斷執行上篇所提到的四個方面的事件監聽動作,並且會隨時監控行程,並將一些死掉的行程重新處理等。

76 5.4 init.rc概述 Android 開機初始化腳本(initial script)
位置:「system/core/rootdir/init.rc」 採用Android init language 語法共分四大類 行為(Actions) 命令(Commands) 服務(Services) 選項(Options) Init.rc是Android init的開機初始化腳本,它是採用Android init language來撰寫,而非傳統嵌入式系統的shell script方法,要注意的是,Android init language的初始化語言是以行為單位,並由空格這個間格符號所組成的。註解時是使用「#」號為起始,且「#」號前是允許有空格,Actions與Services兩者所命名出來的名稱屬於獨一無二的,所以若在之後採用了相同的命名將會被當作是錯誤並忽略掉。而其語法共分為四種類型,分別是:行為(Actions)、命令(Commands)、服務(Services)、選項(Options)。

77 5.4.1 Android init language─Actions
由一個觸發器(trigger)與許多命令(command)組成 setprop語法格式 setprop <name> <value> 例:setprop ro.product.device dma6410xp trigger commands Android init language中的Actions是一組多個命令動作的名稱,他由一個觸發器(trigger)與許多個命令(commands)所組成,而且每一個command都是屬於獨立一行程式。當一個action在符合觸發條件被執行時,如果它還沒被加入到待執行隊列中的話,則加入到隊列最後。隊列中的action依次執行,action中的命令也依次執行。Init在執行命令的中間處理其他活動(設備創建/銷毀,property設定,進程重啟)。 下面這是從init.rc中擷取出來的片段程式,其中「on boot」就是觸發器,它代表的意思是以下的這些命令需要再開機時(boot)執行,而「setprop」表示此段程式將設定為property命令,再對照先前的property表可以知道這邊的「ro」表示的是這段property屬於「只讀」。「setprop」他是在Action區段命令中最常出現的命令,這個命令是用來設定property的值。這個property就如同環境變數(environment variable)其格式是起頭為setprop後面再接name及value,我們看到這下面這個例子,此段語法所表示「ro.product.device」=「dma6410xp」。 設定property的值

78 5.4.1.1 Triggers 名稱 描述 boot 當init開始後執行的第一個觸發器(當/init.conf時被加載)
<name>=<value> 當property <name>被設為指定的值<value>時觸發。 device-added-<path> 當設備節點被添加或移除時觸發。 device-removed-<path> service-exited-<name> 當指定的服務存在時觸發

79 5.4.1.2 Commands(1/3) 名稱 描述 exec <path> [ <argument> ]*
Fork並執行一個程序(<path>). export <name> <value> 設定全域環境變數<name>的值<value>,當這個命令執行後所有的行程都可以取得。 ifup <interface> 使網路接口<interface>連線。 import <filename> 解析一個init配置文件,擴展當前配置文件。 hostname <name>  設定主機名 chmod <octal-mode> <path> 改變檔案使用權限 chown <owner> <group> <path> 改變文件所屬和組 PS: 並沒有 chgrp 指令 class_start <serviceclass> 當指定類別的服務沒有運行,啟動該類別所有的服務。 class_stop <serviceclass>  當指定類別的服務正在運行,停止該類別所有的服務。 Exec這個命令要注意的是,由於它將被block直到程序執行完畢。最好避免執行例如內建命令以外的程序,它可能會導致init被阻塞不動。

80 5.4.1.2 Commands(2/3) 名稱 描述 domainname <name> 設定域名。
 設定域名。 insmod <path>  加載該路徑<path>的模塊 mkdir <path> [mode] [owner] [group] 在<path>創建一個目錄,可選選項:mod,owner,group.如果沒有指定,目錄以755權限,owner為root、group 為 root 建立。 mount <type> <device> <dir> [ <mountoption> ]*  嘗試mount <device>到目錄<dir>. block device。<mountoption>包含"ro","rw","remount","noatime"。 setkey  暫時沒有 setprop <name> <value>  設定系統property <name>的值<value>。 setrlimit <resource> <cur> <max> 設定resource的rlimit。

81 5.4.1.2 Commands(3/3) 名稱 描述 start <service> 啟動一個沒有運行的服務。
stop <service> 停止一個正在運行的服務。 symlink <target> <path> 創建一個<path>的symbol link 到<target> sysclktz <mins_west_of_gmt> 設定系統時區(GMT為0) trigger <event> 觸發一個事件。用於呼叫其他action。 write <path> <string> [ <string> ]* 打開<path>的文件並寫入一個或多個字串。

82 5.4.1.3 Properties 名稱 描述 init.action 當前正在執行的action,如果沒有則為""
init.command 被執行的命令,如果沒有則為"" init.svc.<name> 命名為<name>的服務的狀態("stopped", "running", "restarting") Init會更新一些系統property以提供查看它正在幹嘛。

83 5.4.2 Android init language─Services
Services由init啟動,且在退出時可進行重啟(可選) 組成方式: 『service <name> <pathname> [<argument>]* <option> <option> 』 Options 是Services的修飾,它影響init何時、如何運行Services

84 5.4.2.1 Options(1/2) 名稱 描述 critical
這是一個設備關鍵服務(device-critical service) .如果它在4分鐘內退出超過4次,設備將重啟並進入恢復模式。 disabled 這個服務的級別將不會自動啟動,它必須被依照服務名指定啟動才可以啟動。 setenv <name> <value> 設定已啟動的進程的環境變數<name>的值<value>。 socket <name> <type> <perm> [ <user> [ <group> ] ] 創建一個名為/dev/socket/<name>的unix domin socket,並傳送它的fd到已啟動的進程。<type>必須為"dgram"或"stream"。用戶和組預設為0。 user <username> 在執行服務前改變用戶名。當前預設為root.如果你的進程需要linux能力,你不能使用這個命令。你必須在還是root時請求能力,並下降到你需要的uid。

85 group <groupname> [ <groupname> ]*
Options(2/2) 名稱 描述 group <groupname> [ <groupname> ]* 在執行服務前改變組。在第一個組後的組將設為進程附加組(通過setgroups()) 。當前預設為root。 oneshot 在服務退出後不重新啟動。 class <name> 為service指定一個類別名。同樣類名的所有的服務可以一起啟動或停止。如果沒有指定類別的服務預設為"default"類。 onrestart 當服務重啟時執行一個命令。

86 練習 init的作用為何? init.rc的語法分為哪四大類?
1. init全名為initialization,是系統啟動的第一支程式也就是第一個行程,他所做的開機初始化過程中最主要的兩個工作便是新增並運行一些系統重要的行程 2. 行為(Actions)、命令(Commands)、服務(Services)、選項(Options)

87 Chapter 6 Zygote 6.1 Zygote概述 6.2 Zygote分析 6.3 Zygote運行週期

88 6.1 Zygote概述 C/C++ Android同時存在兩種世界 Native世界 採用Native語言所開發的程式 Java世界
Android SDK 基於Dalvik VM的Java程式 一般上層應用程式開發者都知道Android是採用Java語言來開發應用程式,很直覺得可以知道Android有著Java的世界,但若是以系統來看Android,那麼在底層為了上層應用程式解決硬體控制,資源存取等服務功能的部分則是交由Native語言來處理,而這個Native語言即是C/C++這兩種。所以Android其實是同時有著兩個不同的世界分別是Native世界與Java世界,而且其實當Android在電源啟動之後是先運行Native世界之後將所需佈署的功能及初始化的資源都處理完畢後才啟動JAVA世界。再來就讓我們以Zygote來告訴大家Android是如何在Native層處理完一切事項後進入到Java層。 C/C++ Java

89 6.1 Zygote概述 Zytoge Android monitor process
由init透過Linux System Call指令進行新增 Fork及Execve 主要工作 創建Dalvik VM運行Android app 啟動system server Zygote,就字面上來看它是受精卵,為什麼要用這個詞呢?,其實是跟它的功能及在Android中扮演的角色有著很大的關係,甚至說它是Android孕育Java世界的亞當與夏娃也不為過,沒錯Android的Java世界就是由Zygote而起,接著就讓我們來了解到底這個Zygote是何物而他又做了哪些事情。 Zygote它是Android的monitoe process,主要是由init透過Linux system call中的fork與execve這兩個命令來創建,他最主要的工作有兩點,第一是創建Dalvik VM,第二式啟動了system server,在這之中只要Zygote和System server這兩個其中一個崩壞就會導致Java世界毀滅。以下就讓我們來探討Zygote的啟動過程。

90 6.2 Zygote啟動過程 Zygote整個運作流程可分下列幾項 啟動並更名 app_process更名為Zygote。
新增Dalvik VM 創建Dalvik VM並給予各種所需參數。 為Java函式註冊JNI函式。 Native與JAVA世界溝通 註冊socket。 載入一些類別與資源。 啟動system_server。 等待請求 叫用「runSelectLoopMode」等待請求。 Zygote最初的名稱其實並不是Zygote,而是app_process。所以在Zygote的啟動過程中的第一步驟就是更名,將原本的「app_process」更名為「Zygote」,在更名之後便創建了Dalvik VM並給予設定相關啟動參數,且為Java註冊了JNI函式,以便透過JNI讓Native世界與Java世界進行溝通,而JNI是何物就留在後續單元五再做詳細介紹,現在只要知道它是在兩個不同的語言間搭建橋梁讓兩種語言可以進行溝通即可。除了註冊JNI之外,為了讓Native世界與Java世界進行溝通,Zygote還註冊了socket、載入一些類別與資源急啟動system server來為Java世界服務。完成上述工作後Zygote便會呼叫「runSelectLoopMode」然後開始等待事件發生。

91 6.2.1 Zygote啟動序列圖 這個序列圖是依照Zygote程式檔案名稱及其中的類別名稱來說明Zygote啟動流程。由這個圖可知道Zygote最初的app_process所對應到的程式是「app_main.cpp」這支程式,而改名的動作也是從這邊開始,再來由AndroidRuntime來進行一些類別的宣告及實作,到了ZygoteInit這支程式時,進行了ZygoteSocket註冊、啟動Systemserver、處理SystemServerProcess,之後將上層Java世界的部分交由Systemserver來處理,處理的內容不外乎就是依照請求來fork新的子行程運行並檢查子行程的狀態是否需要kill掉之類的,最後Zygote會回到ZygoteInit這邊進行等待監聽事件處理。後面將以程式碼的方式來解說整個啟動流程做的事情。

92 6.3 解析Zygote(1/7) app_main.cpp片段程式
位置:「framework/base/cmds/app_process/app_main.cpp」 啟動並更名 Zygote是由 init 透過 fork這個system call來新增Zygote process,並由init.rc來設置Zygote啟動參數,畫面上看到的第一個片段程式便是init.rc設置Zygote啟動參數的片段程式,Zygote最初的進入程式是「app_main.cpp」這支檔案,Zygote中比較重要的功能都是由AppRuntime這個類別來運行,一開始便進行了Zygote名稱的設定,就是前面提到的將最初的「app_process」這個名稱改為「Zygote」。 我們可以看到程式片段中的「AppRuntime runtime;」這段簡短的程式碼中的AppRuntime類別它主要是為Zygote完成一些重要的功能。

93 6.3.1 AppRuntime類別簡述 AppRuntime類別 宣告及實現均在「app_main.cpp」中
繼承了「AndroidRuntime.cpp」的函式 重載(overload): onSarted、onZygoteInit、onExit函式 AppRuntime這個類別的宣告及實作均在「app_main.cpp」這支檔案內,他繼承了「AndroidRuntime.cpp」檔案內的函式,分別是onSarted、onZygoteInit、onExit這幾個函式,

94 6.3 解析Zygote (2/7) AndroidRuntime.cpp片段程式
位置:「framework/base/core/jni/AndroidRuntime.cpp」 新增Dalvik VM、註冊JNI函式。 剛才提到了AppRuntime類別繼承了「AndroidRuntime.cpp」的一些函式,而這支程式裡到底做了些什麼重要的事就讓我們來看看。在AndroidRuntime中start這個函式裡,他先是設定一些必要的環境變數,之後新增了JavaVM,這個JavaVM就是DalvikVM,新增完畢後便註冊了JNI預備讓Native世界與Java世界溝通使用。

95 6.3.2 設置JNI check AndroidRuntime.cpp Native層呼叫JNI函式時,系統要做的檢查工作
這段程式碼是用來設定JNI check選項,JNI check是指在Native層呼叫JNI函數時,系統所做的一些檢查工作,例如:呼叫NewUTFString函式時,系統會檢查傳入的字串是不是符合UTF-8的格式要求。除此之外JNI check還能檢查資源是否有被正常釋放掉。但這個也是有缺點,例如說檢查工作耗時,會影響系統的運行效率、而且有些檢查工作過於嚴格,一旦出問題系統會直接將行程abort。所以通常JNI check只會在eng板設置,若是在發佈的User版則不會設置該選項。

96 6.3.3 設置Dalvik VM的heapsize AndroidRuntime.cpp 設置Dalvik VM的heapsize
修改Android source code,重新生成Android Image。 修改config.ini,加入「vm.heapSize=32」,在重新啟動。 「/data」下產生「local.prop」,加入「dalvik.vm.heapsize=32m」 1. cd /data 2. echo dalvik.vm.heapsize=32m > local.prop 此段程式是在設置Dalvik VM的heapsize,什麼是heapsize呢,由於Android的Dalvik VM是基於暫存器設計的架構,與sun的JVM相比,雖然所需要的指令條數較少但也比較複雜,不過到了Android2.2以後加入了JIT(Just in time)效能就又更快了,(關於Dalvik VM在這個部份後在後面章節做詳細的介紹),而我們所調整的heap size是最小配額,怎麼說最小配額呢?因為當每個DVM被生成出來後系統會配給一定額度的heap,但上層的應用程式因大小不同所需用到的heap也不同,有的可以需要大量有的甚至不用,而我們這邊所配置的是基本給予的量,若是上層應用程式需要比我們設置的還要更多,那麼系統會動態的加大heap的大小,但並非配給之後就不收回,系統會依照使用的比例%來決定是否回收heap,但回收的底線就是到我們所設定的大小為止。通常在Android source code裡預設值是16MB,但絕大多數的開發廠商都會將其修改為32MB。 以下提供三種修改的方式,第一種方式就是修改Android source code的方式,就是我們上面所貼的片段程式碼,在修改完畢後只要重新編譯生成新的Android Image擋在燒入到開發板後就可以了,第二種是針對透過模擬器開發應用程式的上層開發員使用,只要去修改模擬器的config.ini檔案,在裡面加入「vm.heapSize=32」,載重新啟動模擬器便可以修改了,第三種是直接透過終端機連到設備裡頭進行修改,此方法需要使用shell的方式來修改,修改方式首先要進入到/dtat的目錄中,在目錄下修改local.prop檔案並加入dalvik.vm.heapsize=32m 即可修改。

97 6.3.4 Zygote Fork子行程 由前面init創造Zygote可以知道,init是透過system call中的fork及execve兩個來進行建立。而當Zygote要建立Dalvik VM時也是透過Fork來進行創建。在Android中特別的一個功能就是可同時執行多個Process,而這就歸功於Zygote,當系統需要Dalvik VM來運行新的應用程式時,Zygote便會透過Fork來複製自己的資源並創一個新的子行程來運行,就如圖這張圖所看到的,複製出來的子行程都是將父行程的獨立儲存空間複製一份出來用然後在加上自己本身的東西。

98 6.3 解析Zygote(3/7) AndroidRuntime.cpp片段程式 為使用JNI函式,預先設定兩個字串。
Zygote中為了使用JNI的函式,首先會先創建一個擁有兩個元數位置的字串陣列,並指定好兩個內容值,這些內容值主要是存放要透過JNI讓兩個不同與法撰寫的程式相互溝通的方法,其中在「.so」以前的是絕對路徑,而「ZygoteInit」這個是我們要呼叫啟動的Java程式,設置這個字串最主要就是為了讓Zygote從Native世界進入Java世界的準備動作。

99 6.3 解析Zygote(4/7) AndroidRuntime.cpp 更改字串中的符號,以符合JNI規範。
由於JNI的語法有特殊格式,所以需要將剛才儲存好的字串做修改,將「com.android.internal.os.ZygoteInit」字串中的所有「.」更換成「/」。

100 6.3 解析Zygote(5/7) AndroidRuntime.cpp片段程式 透過JNI呼叫函式,從Native層進入Java層運行。
更改完字串後,Zygote便可以透過該字串命令來呼叫ZygoteInit這個檔案內的main運作,如此一來便可讓Native程式透過JNI去呼叫Java程式來運作,啟動玩ZygoteInit後,Zygote可以可以進行關閉退出,不過通常是不需要。

101 6.3 解析Zygote(6/7) ZygoteInit.java片段程式 位置:
「framework/base/core/java/com/android/internal/os/ZygoteInit.java」。 註冊Zygote的socket並載入一些類別及資源。 接下來讓我們來看ZygoteInit這支java程式做了哪些事情,首先它會使用「registerZygoteSocket」這個方法來註冊Zygote會需要用到的socket並透過「preloadClassses」及「preloadResources」方法來載入一些類別和資源(關於註冊socket及載入類別及資源的方法等等會有更進一步的說明),之後會先執行一次資源回收機制,將不必要或者使用完畢的資源進行回收動作。

102 6.3.4 註冊socket的函式 ZygoteInit.java片段程式
registerZygoteSocket()函式註冊socket。 剛才講到的為Zygote註冊socket的方法,他的做法是先以system call中的「execv」指令從環境變數中取得socket的file desc,所為的file desc是一個文件描述符,他是一個抽象的指標,用來呼叫文件檔案用。之後new出一個socket的伺服器,透過這個socket來監聽來自client端的訊息。

103 6.3.5 載入類別函式簡介 preloadClasses();
透過coolfind工具載入「framework/base/preloaded-classes」文件 PreloadClasses這個方法會透過coolfind工具來載入位於「ramework/base/」目錄中的「preloaded-classes」文件,這個文件裡描述了眾多需要載入的類別。

104 6.3.6 載入資源函式簡介 preloadResources();
載入「system/framework/framework-res.apk」中的資源。 「.apk」檔將其附檔名改為「.rar」即可開啟。 preloadResources這個方法與PreloadClasses很類似,他所要做的是將「system/framework」目錄內的「framework-res.apk」載入,由於該檔案是已經封裝起來的.apk檔,若是要看到裡面有什麼資源檔案,只要將附檔名從「.apk」修改成「.rar」便可以解壓縮觀看,裡面主要會放置一些xml檔、圖檔等資源。

105 6.3 解析Zygote(7/7) ZygoteInit.java片段程式 啟動system server。
進行完註冊socket及載入一些重要的類別及資源後,接下來ZygoteInit會啟動system server來為Zygote管理service。再判斷完是否符合啟動system server的條件後,便呼叫StartSystemServer方法來設定System server的一些參數並進行創建,之後會透過system call命令fork來創建Zygote mode,最後呼叫runSelectLoopMode方法進入等待。

106 6.3.7 設置System Server參數並啟動(1/2)
ZygoteInit.java片段程式 設置system server參數及行程的名稱。 再呼叫startSystemServer方法後會先進行system server的參數設置並且設置行程的名稱為「system_server」及要啟動該類別的名稱。

107 6.3.7 設置System Server參數並啟動(2/2)
ZygoteInit.java片段程式 透過fork指令新增system server行程。 參數及命名都設置完畢後就後透過system call命令中的fork指令來新增system server行程。

108 6.3.8 進入等待事件函式簡介 runSelectLoopMode(); 連接Client及處理Client的請求
ZygoteConnection物件表示Client。 runOnce函式處理Client請求。 當Zygote再創建完syetem server後便將管理Java世界的service工作交由其管理,之後Zygote便會呼叫runSelectLoopMode進入等但事件,這個方法首先會為Zygote socket中的server 與 client連接上,而在Zygote中的socket client便是用「ZygoteConnection」來表示,連接上後便會透過rumOnce函式來處理一些相關的事件回應。

109 練習 Zygote這個行程是如何產生的? Zygote主要工作是哪兩個? 請簡單敘述Zygote的啟動流程。
1. 是由init透過Linux system call中的fork與execve這兩個命令來創建 2. 創建Dalvik VM運行Android app和啟動system server 3. Zygote最初的app_process所對應到的程式是「app_main.cpp」這支程式,而改名的動作也是從這邊開始,再來由AndroidRuntime來進行一些類別的宣告及實作,到了ZygoteInit這支程式時,進行了ZygoteSocket註冊、啟動Systemserver、處理SystemServerProcess,之後將上層Java世界的部分交由Systemserver來處理,處理的內容不外乎就是依照請求來fork新的子行程運行並檢查子行程的狀態是否需要kill掉之類的,最後Zygote會回到ZygoteInit這邊進行等待監聽事件處理。

110 Part III Android Dalvik

111 Chapter 7 Dalvik概述 7.1 什麼是Dalvik VM? 7.2 Dalvik VM的功能 7.3 Dalvik VM特點
7.4 Dalvik VM V.S. Java VM 7.5 Dalvik VM與Java VM行程管理比較

112 7.1 什麼是Dalvik VM? Dalvik virtual machine(DVM) 2007年11月與Android系統一同發佈
由Dan Bornstein所設計。 Android平台所開發的Java虛擬機器 提供Android的Java程式運行環境 支援執行Android工具所轉換的「.dex」檔 適合資源有限的嵌入式系統 大家都知道Android在上層要開發應用程式,所使用的語言是Java,但是編譯出來的檔案卻不是sun java所編譯出來的檔案格式「.jar」而是「.apk」,這是因為在Android系統中所用來運行應用程式的虛擬機器並非Java virtual machine,而是Dalvik virtual machine,他在2007年11月與Android系統一同發佈,是google為了Android量身打造的Java虛擬機器,它提供了Android要運行Java程式的執行環境,且透過Android的轉檔工具,將原本Java的class檔轉換成Dalvik VM所運行的dex檔。這個dex檔案格式是專為Dalvik設計的壓縮格式,且經過了Google的各種最佳化,Dalvik 成了一個非常適合記憶體和處理速度有限的虛擬機器,也就說是何用於嵌入式系統。

113 7.2 Dalvik VM的功能 具多種管理功能 充分使用Linux行程管理功能
物件生命週期管理、Stack管理、執行緒管理、安全及例外管理、資源回收處理(Garbage collection, GC)等。 充分使用Linux行程管理功能 採物件導向設計、可同時執行多個處理程序。 以功能來講,Dalvik VM需要處理物件的生命週期管理、stack管理,執行緒管理、安全及例外管理,資源回收處理等功能,因為他是採用物件導向的方式設計,有別於SUN 的Java VM,可同時運行多個Process,簡單來說在手機平台上舊式的Java VM一次只能運行一個Process,若要運行不同的Process時則需要將上一個關閉,而在Android系統中,會有Zygote是需求而不斷產生新的Dalvik VM來同時運行不同的Process。而且基於Linux的關係,Dalvik VM很多操作都直接和系統核心有關,或者直接叫用核心介面來處理,如此作法大大提昇了Dalvik VM的效能,但也增加了嵌入式系統人員在移植系統時的難度。

114 7.3 Dalvik VM特點 執行效率高、程式碼密度小、節省資源 常數池只使用32位元的索引 記憶體限制
預設Stack大小為12KB(3分頁,每頁4KB) Stack支援啟動大小為1MB~1024MB 預設Heap啟動大小為2MB Stack及Heap參數可透過「-Xms」與「-Xmx」更改 就特點而言DalvikVM有以下優點。

115 7.4 Dalvik VM V.S. Java VM(1/2) DVM JVM Google Android系統的程序虛擬機。
CPU速率及RAM要求低,且OS交換空間要求少甚至不需要。 應用程式運行時轉化成「.dex」格式。 Process可平行執行且共享loaded class。 Android 2.2版本加入JIT(just in time compiler) 。 JVM Write Once,Run Anywhere。 JER重要組成部分。 執行對象主要為「.class」及「.jar」文件。 透過JIT(just in time compiler)加快運行速度。 先前介紹中有提到Dalvik VM雖然也是運行Java語言但他有別於Java VM,以下就讓我們來比較一下兩者的差異。

116 7.4 Dalvik VM V.S. Java VM(2/2) DVM JVM 架構 基於 register 基於 heap 開發語言
SDK Android SDK/NDK JDK 文件格式 .dex .jar 平台 Android 技術 JIT (Android 2.2以後新增) JIT (Hot Spot) AOT編譯器 子令集 所需數量少、但長度大 所需數量多,但長度小 constant pool 1個 多個 同時運行Process數量 以下是Dalvik VM與Java VM的比較表,由此表可以清楚看出兩者之間的差異。

117 7.5 Dalvik VM與Java VM行程管理比較
大家都知道Android系統是基於Linux kernel的,而其中在Android系統中的每個應用程式更是運行於自己的 Dalvik VM之上,在手機中以作業系統的角度來看,Dalvik VM與Java VM在行程上有個很大的不同,因Dalvik VM是由Android系統中的Zygote依照需求來不斷新增,也就是說對作業系統而言,每個Porcess都運行在不同的VM之上,所以在Android系統中,Process是可以同時平行運行的,但在Java VM上就不同了,在手機上的Java VM一次只可以運行一個Process,若是當前A Process正運行於Java VM上,那麼使用者若要啟動B Process時,系統便會將A Process停止之後才運行B Process。

118 練習 Dalvik VM有哪些管理功能?(請舉3個) Dalvik VM 與Java VM有何差別?(請舉3個)
1. 物件生命週期管理、Stack管理、執行緒管理、安全及例外管理、資源回收處理(Garbage collection, GC)等 2. DVM是基於register架構,而JVM是基於heap的架構、DVM採用的檔案格式為「.dex」,而JVM是採用「.jar」、DVM的平台是Android,而JVM是java、DVM在指令集的需求度上所需數量較少但長度較大,而JVM則是所需數量較多但長度較短,DVM有1個constant pool,而JVM則有多個constant pool、DVM可同時運行多個行程,而JVM一次只能運行1個行程。 3. DVM可同時運行多個行程,每個行程都運行在自己的DVM之上,而JVM一次只能執行1個行程,當要執行後者行程前需要先把前者的行程停止。

119 Chapter 8 Dalvik架構與實作 8.1 Dalvik架構 8.2 Android檔案格式轉換
8.3 Android dx與dexdump工具 8.4 Dalvik檔案格式解析 8.5 「.jar」檔轉換「.dex」檔結構分析 8.6 Dalvik直譯器 8.7 Dalvik JIT

120 8.1 Dalvik架構 Dalvik VM的架構如此圖所示,一個Java應用程式首先必須經過Android獨有的 dx工具將「.class」檔案轉換成「.dex」格式的檔案,然後再由類別載入器,分別將原生類別與Java類別載入後,才由Dalvik VM的直譯器根據指令集來對Dalvik 位元組碼進行執譯及執行,最後在目錄「Dalvik/vm」中的DVM.mk檔中,根據dvm_arch來選擇要編譯成哪種設備架構可執行的執行檔。譬如說:如果今天是要編譯成在ARM架構上運行的執行檔,那麼編譯時就會使用ARM的組合語言語相關的C語言,如果是要在X86架構上運行的話,則是採用X86的組合語言與相關的C語言來編譯,而這些設備架構的程式碼都是位於「Dalvik/vm」目錄下的「DVM.mk」檔之中。

121 8.2 Android檔案格式轉換 Java檔案在Android系統中的檔案格式轉換過程如此圖所示,由最初開發人員所撰寫的「.java」檔經過「javac」編譯後生成「.class」檔,之後透過Android獨特的 DX轉檔工具,將「.class」格式的檔案轉換成Dalvik VM所運行的「.dex」檔,之後透過封裝工具「aatp」來進行檔案封裝,封裝完畢後及會生成「.apk」檔,這就是我們在Android系統上可以運行的檔案,除了這些步驟外,Google還加入了一個優化方式,在「.apk」檔運行時透過Android獨有的工具「deodexing」將「.apk」檔優化成「.odex」檔,提昇Dalvik VM運行的效率。通常「.dex」檔在進行優化成「.odex」之後,其檔案大小會有所增加,通常是原先的1~4倍大。

122 8.2.1 內建應用程式最佳化流程 Android應用程式的最佳化分為內建的和非內建的方式,此圖便是Android內建的應用程式在Android Dalvik VM在進行內部優化的過程,一般內建的應用程式在系統編譯時便會進行優化產生最佳化的「.odex」檔,這樣在發佈時除了「.apk」檔外還會一個相對應的「.odex」檔,要注意的是這邊所發佈的檔案是不包含「.dex」檔的。它整個流程是由一個可執行的「.jar」檔會先透過Sun JDK編譯成「.class」格式的檔案,之後透過Android DX工具將「.class」檔轉換成「.dex」檔,之後載透過Android deodexing工具進行檔案轉換,生成「.odex」檔。

123 8.2.2 非內建應用程式最佳化流程 而對非內建的應用程式,就是由外步開發人員所開發的應用程式來說,包含在「.apk」檔案中的「.dex」檔,會在執行時才被優化,而最佳化後的「.odex」檔會被存在緩衝區中,Android Dalvik VM會直接從緩衝區中執行該檔案。如果應用程式沒有被更改,那麼「.odex」檔便不會在重新生成。

124 8.2.3 封裝及反組譯 Android 檔案的封裝及反組譯如此圖所示。
首先會使用「baksmaili.jar」將odex文件分解成smail文件,成功的話就會在「AndroidDecompile」目錄下產生一個名為「out」的目錄,這裡面會存放一些解析後的「.smali」文件;之後再使用「smali.jar」將「out」目錄下的smali文件轉換成「classes.dex」檔,這個格式的檔案就是Android中DVM編譯後的檔案格式正常APK檔案內都會有;來到這邊支後就分為兩個方向,一個是進行反組譯另一個是進行封裝,首先先講進行反組譯的部份,在得到「classes.dex」檔後,可透過 dex2kar工具將「classes.dex」檔進行反組譯成為jar檔,之後我們可以透過「Jd-gui」這個工具來查看反組譯後的jar檔案的內容,它可以適時的將class檔案反組譯成java檔案,已供開發者觀看;若是進行封裝,首先將反編譯後的classes.dex和原先的app.apk(不含classes.dex)重新壓縮成一個完整的app.apk(apk文件可用壓縮工具打開),也就是說將classes.dex放進app .apk中。將下載的AutoSign文件解壓,可以看到有signapk.jar(還有個Sign.bat)文件,執行以下命令給app.apk文件簽名,就可以生成一個可以運行的apk文件了。網路上還有個工具是apktool,可以對apk進行解析,反編譯資源文件,並將類文件解析成smali文件;同時還可以將解析後的文件重新打包成apk。 參考出處

125 8.3 Andrid dx與dexdump工具 「dx」,Android用來編譯的工具 「dexdump」,Android用來反編譯的工具
路徑:「dalvik/dx」。 將Java位於組碼(.class)轉換成Dalvik可執行的檔案格式(.dex)。 「dexdump」,Android用來反編譯的工具 路徑:「dalvik/dexdump」。 將Android檔案(.dex)反編譯。 可檢視Java封裝後的檔案(.jar)資訊。

126 8.4 Dalvik檔案格式解析 每個「.dex」檔都會按照結構大小、索引、位移來解析 struct DexFile定義如下
解析結果均放置於「dalvik/libdex」目錄中的「DexFile.h (.c)」 struct DexFile定義如下 DEX檔定義的資料類型

127 8.4.1 ODEX檔案結構圖

128 8.4.2 DEX檔資料類型解析(1/2) 類型名稱 格式 說明 ODexHeader OptimizedDex檔頭 DexHeader
header_item Dex檔頭(後續有更詳細說明) string_ids string_id_item[] 在dex檔案中所用到的UTF-16LE字串識別ID(StringIds)列表,包括內部的命名(e.g.,type descriptors) 或由程式碼所參考的字串常數物件. type_ids type_id_item[] 在dex檔案中所用的形態名稱識別ID(TypeIds)列表,包括檔案中所有參考到classes,arrays, or primitive types,不管是否有在這檔案中定義,只要有參考到的就會納入到此. 型態名稱也會對應到StringID Index. proto_ids proto_id_item[] ClassMethod對應的PrototypeIDs,在這會包括所有這個Dex檔案中參考的PrototypeIDs.

129 8.4.2 DEX檔資料類型解析(2/2) 類型名稱 格式 說明 field_ids field_id_item[]
ClassField (變數Type)所對應的IDs,在這會包括所有這個Dex檔案中參考的FieldIDs. method_ids method_id_item[] ClassMethod所對應的IDs,在這會包括所有這個Dex檔案中參考的MethodIDs. 在檔案中的排序方式會以TypeId為主,之後參考StringID,之後參考ProtoID. class_defs class_def_item[] ClassDefinitions List可用來查詢每個ClassField/Method,Class Index,Access Flag相關訊息.如果今天要去Dump所有Class/Method/Field的訊息時,ClassDefinitions List會是基本必要元素之一. link_data ubyte[] 用來支援StaticllyLinked的資料.(mmm,不過筆者手中沒有看到有支援這Section的檔案.)

130 ubyte[8]= DEX_FILE_MAGIC
8.4.3 DexHeader對應欄位說明(1/5) 類型名稱 格式 說明 magic ubyte[8]= DEX_FILE_MAGIC 屬於DEX檔頭的8bytesMagic Code "dex/n035/0" checksum uint 使用zlib的adler32所計算的32-bitsCheckSum.計算的範圍為DEX檔案的長度(Header->fileSize)減去8bytes Magic Code與4bytes CheckSum的範圍.用來確保DEX檔案內容沒有損毀. signature ubyte[20] SHA-1signature (hash) 用來識別原本的DEX檔案(被最佳化以前的DEX).SHA-1計算的範圍為DEX檔案的長度(Header->fileSize)減去8bytes Magic Code,4 bytes CheckSum與20bytesSHA-1的範圍.用來識別最原本的DEX檔案的唯一性.(所以被最佳化過後,這個SHA-1儘能用來識別原本的DEX檔案,而無法透過ODEX檔案計算回最原本的DEX檔案SHA-1值了).

131 uint= ENDIAN_CONSTANT
8.4.3 DexHeader對應欄位說明(2/5) 類型名稱 格式 說明 file_size uint 包含DEXHeader與到DEX檔案的檔案長度(inbytes). header_size uint= 0x70 用來記錄目前DEXHeader的大小(現有版本為0x70bytes),可用來做為後續Header如果有改版時,最基本的檔頭欄位向前,向後相容的依據. endian_tag uint= ENDIAN_CONSTANT 預設值為Little-Endian,在這欄位會顯示32bits值"0x ".(ㄟ....應該在Big-Endian處理器上,會轉為“0x ”,才能表彰出這個值的意義) link_size LinkSection的大小,如果為0表示該DEX檔案不是靜態連結. link_off 用來表示LinkSection距離Dex檔頭的Offset.如果LinkSize為0,此值也會為0.資料格式可以參考structDexLink.

132 8.4.3 DexHeader對應欄位說明(3/5) 類型名稱 格式 說明 map_off uint
用來表示MapItem距離Dex檔頭的Offset.如果為0,表示這DEX檔案沒有MapItem. 資料格式可以參考structmap_list. string_ids_size 用來表示StringIDs List的總數. string_ids_off 用來表示StringIds List距離Dex檔頭的Offset.如果StringIDs Size為0,此值也會為0.資料格式可以參考structDexStringId. type_ids_size 用來表示TypeIDs List的總數. type_ids_off 用來表示TypeIDs List距離Dex檔頭的Offset.如果type_ids_size為0這個值亦為0.資料格式可以參考structDexTypeId. proto_ids_size 用來表示PrototypeIDs List的總數.

133 8.4.3 DexHeader對應欄位說明(4/5) 類型名稱 格式 說明 proto_ids_off uint
用來表示PrototypeIDs List距離Dex檔頭的Offset.如果proto_ids_size為0這個值亦為0.資料格式可以參考structDexProtoId. field_ids_size 用來表示FieldIDs List的總數. field_ids_off 用來表示FieldIDs List距離Dex檔頭的Offset.如果field_ids_size為0這個值亦為0.資料格式可以參考structDexFieldId. method_ids_size 用來表示MethodIDs List的總數. method_ids_off 用來表示MethodIDs List距離Dex檔頭的Offset.如果method_ids_size為0這個值亦為0.資料格式可以參考structDexMethodId. class_defs_size 用來表示ClassDefinitions List的總數.

134 8.4.3 DexHeader對應欄位說明(5/5) 類型名稱 格式 說明 class_defs_off uint
用來表示ClassDefinitionList距離Dex檔頭的Offset.如果class_defs_size為0這個值亦為0.資料格式可以參考structDexClassDef. data_size 用來表示DataSection的大小.並需為sizeof(uint)的偶數倍.(所以就是0,8,16...etc) data_off 用來表示DataSection距離Dex檔頭的Offset.

135 8.5 「.jar」檔轉換「.dex」檔結構分析

136 8.6 Dalvik直譯器 直譯器,所有虛擬機器的核心 路徑:「dalvik/vm/interp」。 除C語言上有其他各種組合語言版本。
路徑:「 dalvik/vm/mterp」。 透過「python」工具,產生目標程式碼。 路徑:「 dalvik/vm/mterp」目錄中的「gen-mterp.py」檔。 各種CUP編譯的組態檔均放置於「 dalvik/vm/mterp 」目錄內 編譯後產生的檔案均放置於「out」目錄內。 Android2.2之後才導入JIT機制。

137 8.7 Dalvik JIT Just In Time(JIT),Java VM最佳化的一種技術
Android 2.2之後才加入Dalvik VM 使Dalvik VM運行速度提升5倍(官方表示) 對多次執行的程式進行編譯 編譯後的機械碼放置於記憶體中,以便重復讀取。 具兩種編譯方式 method:以函式或方法為單位。 trace:以路徑為單位,且包含method方法。 通常,程序有兩種運行方式:靜態編譯與動態直譯。靜態編譯的程序在執行前全部被翻譯為機器碼,而直譯執行的則是一句一句邊運行邊翻譯。 即時編譯器則混合了這二者,一句一句編譯原始碼,但是會將翻譯過的代碼緩存起來以降低性能損耗。相對於靜態編譯代碼,即時編譯的代碼可以處理延遲綁定並增強安全性。 即時編譯器有兩種類型,一是位元組碼翻譯,二是動態編譯翻譯。

138 8.7.1 Dalvik JIT優化重點 指令集優化 (Instruction set optimizations)
資源再利用 (Resource Reuse) 運行分析及優化 (Runtime Analysis & Optimizations) 高階記憶體管理 (Superior Memory Management) Dalvik VM透過 JIT 技術來進行優化,透過JIT技術,把Android經常運行的一些程式先編譯成機械碼並存至記憶體中,可直接從記憶體去取出預先編譯好的機械碼來執行,以便多次執行時不用一直重復編譯,在程式編譯執行時,JIT會去收集、統計編譯和執行時的資料,來對最佳化作重新排列及重新編譯(指令集的部分),且提供的多種資源再利用的方法,就如同Java在new一個字串時,如果先前已經有先new過了,那系統便會去資源池裡面尋找有無要new的相同資源,如果有便重復使用,以便節省系統資源的損耗,但因為apk檔案在Dalvik VM上編譯執行的過程中會進行優化的動作,而優化後的檔案大小會比原先的檔案還大,如此一來便會佔據更多記憶體空間,導致系統執行效率變差,但經由JIT技術優化了記憶體管理,讓系統效能不至於大幅下降。

139 練習 請簡述Android檔案格式的轉換。 Andrid dx與dexdump工具作用為何? Dalvik JIT優化重點為哪四項?
1. 「.java」檔經過「javac」編譯後生成「.class」檔,之後透過Android獨特的 DX轉檔工具,將「.class」格式的檔案轉換成Dalvik VM所運行的「.dex」檔,之後透過封裝工具「aatp」來進行檔案封裝,封裝完畢後及會生成「.apk」檔,之後在透過Android獨有的工具「deodexing」將「.apk」檔優化成「.odex」檔。 2. 「dx」是Android用來編譯的工具,而「dexdump」則是用來反編譯的工具。 3. 指令集優化、資源再利用、運行分析及優化 、高階記憶體管理


Download ppt "Android系統結構."

Similar presentations


Ads by Google