[TOC]
Android 簡介
Android 操作系統是基于Linux內核, Google使用Linux內核構建了一個可移植和健壯的手機平臺,而并沒有使用傳統Linux的任何其他的東西. Android并不完全依賴于Linux內核, 但是Android底層的開發和傳統的嵌入式Linux系統開發關系非常密切, Android系統的驅動與Linux的驅動在開發上幾乎保持了完全一致, 另外,Android底層開發和移植的環境也與嵌入式Linux的環境保持了基本一致.
Android是Google于2007年宣布的基于Linux平臺的開源手機操作系統. Android是一個包括操作系統, 中間件, 用戶界面,和關鍵應用的移動設備軟件堆. 換句話說就是Android是基于Java并運行在Linux內核上的輕量級操作系統.
Android 移植
Android 移植分為兩個部分: 應用移植和系統移植.
-
應用移植:
- 為保證應用程序能在新的平臺上正常運行,需要對源代碼就行一些修改,因為硬件平臺之間以及Android SDK API之前都可能存在差異。如果無法獲取應用程序的源代碼,只能重新在新的硬件平臺上實現;
- Android應用移植不涉及驅動和HAL程序庫(注:HAL程序庫是Android新增加的硬件抽象層);
-
系統移植:
使操作系統在特定硬件平臺上運行的條件:
- 操作系統支持硬件平臺上的CPU架構。Linux內核本身支持很多常用的CPU架構,如ARM,X86,PowerPC,因此不需要做過多的改動,但不代表不做改動;
- 識別硬件平臺上的各種硬件。這些工作主要也是由Linux內核完成的,主角是Linux驅動。
HAL
- 位于第二層,也是普通的Linux程序庫(.so文件),Android SDK通過HAL之間訪問Linux驅動(一般的Linux系統都是由應用程序直接訪問驅動)。
總結:
- Android移植的主要工作:移植Linux驅動和移植HAL;
- Android驅動與Linux驅動的區別就是Android增加了HAL,一般的Android驅動會有對應的HAL,但也不是必須的, 通過NDK也可以直接訪問Linux驅動(Android O還可以嗎?).
- Android 并不能夠使用從www.kernel.org下載的Linux內核,必須使用Google提供的網址下載可以供Android使用的Linux內核源代碼;
- Android移植的基本原則是盡可能找到驅動和HAL的源代碼,在源代碼的基礎上改比從頭開始編寫容易得多,實在無法獲取源代碼,就只能從頭開始做起了;
- Android移植很大程度上是Linux內核移植,Linux內核移植主要是移植驅動程序。不同Linux版本的驅動程序不能通用,需要修改源代碼,并在新的Linux內核下重新編譯才可以運行在新的Linux內核版本下。
Android 系統架構
Android O 之前的系統劃分
Android從高層到低層分別是應用程序層、應用程序框架層、系統運行庫層和linux核心層。Linux內核和應用程序框架層之間的HAL層主要是對linux內核驅動的封裝,將硬件抽象化,屏蔽掉了底層的實現細節。HAL規定了一套應用層對硬件層讀寫和配置的統一接口,本質上就是將硬件的驅動分為用戶空間和內核空間兩個層面;Linux內核驅動程序運行于內核空間,硬件抽象層運行于用戶空間。
Android 并不是Linux
- 它沒有本地窗口系統
- 它沒有glibc支持
- 它并不包括一整套標準的Linux使用程序
- Android專有的驅動程序
Linux內核層
包括驅動程序以及管理內存、進程、電源等資源的程序,因為Android是基于Linux內核的,所以Android和其他Linux系統的核心部分的差異很小;不同版本的Android使用的Linux內核版本有差異,所以不同Android版本的驅動程序可能不通用;
c/c++代碼庫
包括使用C/C++編寫的代碼庫(Linux下的.so文件使用C/C++編寫的),以及嵌入到APK程序中的NDK代碼,也包括Dailvk虛擬機的
運行時(Runtime);
Dailvk虛擬機是Google公司設計的基于Android平臺的虛擬機,它可以支持已轉換為 .dex(即Dalvik Executable)格式的Java應用程序的運行;這些庫能被Android系統中不同的組件使用,通過Android應用程序為開發者提供服務;
一些核心庫:
- 系統C庫:一個從BSD繼承來的標準C系統函數庫(libc),是專門為基于embedded linux的設備定制的;
- 媒體庫:基于PacketVideo OpenCORE,支持多種常用的音頻、視頻格式回放和錄制,同時支持靜態圖像文件。編碼格式包括MPEG4,H.264,MP3,AAC,AMR,JPG,PNG;
- Surface Manager:對顯示子系統的管理,并且為多個應用程序提供了2D和3D圖層的無縫融合
- LibWebCore:最新的web瀏覽器引擎,支持Android瀏覽器和一個可嵌入的web視圖;
- SGL:底層的2D圖形引擎;
- 3D Libraries:基于OpenGL ES 1.0 APIs實現,該庫可以使用硬件3D加速(如果可用)或者使用高度優化的3D軟加速;
- FreeType:位圖和矢量字體顯示;
- SQLite:一個對于所有應用程序可用,功能強勁的輕型關系數據庫引擎;
Android運行庫:
Android包括了一個核心庫,該核心庫提供了Java編程語言核心庫的大多數功能。每一個Android應用程序都在自己的進程中運行,都擁有一個獨立的Dalvik虛擬機實例,Dalvik被設計成一個設備可以同時高效地運行多個虛擬系統。Dalvik虛擬機執行.dex的Dalvik可執行文件,該格式文件針對小內存使用做了優化。同時虛擬機是基于寄存器的,所有的類都經由Java編譯器編譯,然后通過SDK中的dx工具轉化成.dex格式由虛擬機執行;
Android SDK API
直接面向應用程序的Java APK. 這一層可以成為Java API層,因為Android SDk API是用Java語言編寫的。實際上這一層就是用Java編寫的各種Library,只不過這些library是基于Dalvik虛擬機格式的。
應用程序
這一層是所有的Android用戶都要接觸到的,因為這一層相當于Android的UI。所有的Android應用程序(包括拍照,電話,短信,Android的桌面,瀏覽器以及各種游戲)都屬于這一層。主要依靠第三層中的Android SDK API來完成各種功能。
Android 啟動流程
當你按下電源開關后Android設備執行了以下步驟:
Android 系統運行所需要的三大部件: bootloader,kernel , ramdisk.
- bootloader: 作為引導系統,引導linux內核的加載.如uboot
- kernel: Linux內核
- Ramdisk : 文件系統
移植前的準備
目標硬件平臺: 樹莓派 ARM-V9 四核
開發環境: ubuntu 17.04-64 位
編譯器 :
Android 源碼: Android 8.0
嵌入式交叉編譯環境的搭建
? 交叉編譯環境的搭建是Android移植的第一步,不同的體系結構,不同的操作內容甚至是不同版本的內核,都會用到不同的交叉編譯器. gcc用來生成交叉編譯器,主要生成arm-linux-gcc交叉編譯工具, 之后用此交叉編譯來編譯Linux內核. 我們通常使用網上已有的交叉編譯器而不是自己生成, 廣泛使用的是crosstool的交叉編譯工具鏈.可以在crosstool的docs中查看使用說明.
準備三大部件.
- Bootloader模塊: 燒寫U-boot到開發板
- Kernel模塊: 燒寫Linux內核鏡像到開發板
- Ramdisk模塊: 燒寫文件系統鏡像到開發板
U-Boot鏡像的下載與燒寫
1. 簡介
? bootloader是系統上電后最初加載運行的代碼。它提供了處理器上電復位后最開始需要執行的初始化代碼。通過這一小段程序,我們可以初始化硬件設備、建立內存空間的映射表,從而建立適當的系統軟硬件環境,為最終調用操作系統內核做好準備。在PC機上引導程序一般由BIOS開始執行,BIOS完成硬件檢測和資源分配之后,將位于硬盤MBR(Main Boot Record,主引導記錄)中的OS Bootloader(例如LILO或GRUB)讀到系統的RAM當中,然后將控制權交給OS Bootloader,并進一步引導操作系統的啟動,主要完成的工作是將內核印象從硬盤上讀到RAM當中, 然后跳轉到內核的入口點去執行, 即開始啟動操作系統。然而在嵌入式系統中通常沒有像BIOS那樣的固件程序,因此整個系統的加載啟動就完全由bootloader來完成。
? 比如在一個基于ARM7TDMI core的嵌入式系統中,系統在上電或復位時通常都從地址0x00000000處開始執行,而在這個地址處安排的通常就是系統的Boot Loader程序。(先想一下,通用PC和嵌入式系統為何會在此處存在如此的差異呢?)
? Bootloader進行所謂的“kernel引導”,其過程不過是從bootloader里的一句跳轉代碼,跳轉到kernel代碼處(執行kernel中的第一個函數),所謂傳遞參數也不過是bootloader和kernel約定一個內存地點存放。在這個過程中,bootloader和kernel雖然都處于同一個內存里,但是它們除了“引導”與“傳遞有限的參數”這樣的關系,并無其它關系,完全是兩個獨立的程序。之所以在kernel之前用一個bootloader來引導(為什么開機不能直接執行kernel,所有事情都交給kernel做?),其思想類似于一個板級支持包:kernel假定執行的時候已經具備了一個基本的硬件運行條件,這個環境的初始化(最底層的一些硬件初始化、硬件信息設定)需要bootloader來完成,也許這樣kernel的設計才能保持一定的一致性。
? Bootloader是基于特定硬件平臺來實現的,因此幾乎不可能為所有的嵌入式系統建立一個通用的Bootloader,不同的處理器架構都有不同的Bootloader,Bootloader不但依賴于cpu的體系結構,還依賴于嵌入式系統板級設備的配置。對于2塊不同的板子而言,即使他們使用的是相同的處理器,要想讓運行在一塊板子上的Bootloader程序也能運行在另一塊板子上,一般也需要修改Bootloader的源程序。
? U-Boot,全稱 Universal Boot Loader,是遵循GPL條款的開放源碼項目。U-Boot的作用是系統引導。U-Boot不僅僅支持嵌入式Linux系統的引導,它還支持NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS, android嵌入式操作系統。U-Boot除了支持PowerPC系列的處理器外,還能支持MIPS、 x86、ARM、NIOS、XScale等諸多常用系列的處理器。
? 一個嵌入式的存儲設備通常包括四個分區:
存放bootloader(通常是U-boot).
存放bootloader要傳遞給系統內核的參數.
系統內核
-
根文件系統
U-boot目錄結構
- board 目標板相關文件,主要包含SDRAM、FLASH驅動;
- common 獨立于處理器體系結構的通用代碼,如內存大小探測與故障檢測;
- cpu 與處理器相關的文件。如mpc8xx子目錄下含串口、網口、LCD驅動及中斷初始化等文件;
- driver 通用設備驅動,如CFI FLASH驅動(目前對INTEL FLASH支持較好)
- doc U-Boot的說明文檔;
- examples可在U-Boot下運行的示例程序;如hello_world.c,timer.c;
- include U-Boot頭文件;尤其configs子目錄下與目標板相關的配置頭文件是移植過程中經常要修改的文件;
- lib_xxx 處理器體系相關的文件,如lib_ppc, lib_arm目錄分別包含與PowerPC、ARM體系結構相關的文件;
- net 與網絡功能相關的文件目錄,如bootp,nfs,tftp;
- post 上電自檢文件目錄。尚有待于進一步完善;
- rtc RTC驅動程序;
- tools 用于創建U-Boot S-RECORD和BIN鏡像文件的工具;
工作模式
? U-boot的3種映像格式(U-Boot , U-Boot.srec , U-Boot.bin)都可以燒寫到Flash中, 但是需要看加載器是否能夠識別這些格式. 最常使用的是U-Boot.bin格式, U-Boot的工作模式有啟動加載模式和下載模式。
- 啟動加載模式(Flash 啟動方式): Bootloader的正常工作模式,嵌入式產品發布時,Bootloader必須工作在這種模式下,Bootloader將嵌入式操作系統從FLASH中加載到SDRAM中運行,整個過程是自動的。Flash有NOR Flash和NAND Flash兩種。NOR Flash可以支持隨機訪問,所以代碼可以直接在Flash上執行,Bootloader一般是存儲在Flash芯片上的。另外Flash上還存儲著參數、內核映像和文件系統。這種啟動方式與網絡啟動方式之間的不同之處就在于,在網絡啟動方式中,內核映像和文件系統首先是放在主機上的,然后經過網絡傳輸下載進目標板的,而這種啟動方式中內核映像和文件系統則直接是放在Flash中的,這兩點在我們u-boot的使用過程中都用到了。
- 下載模式(網絡啟動方式): Bootloader通過某些通信手段將內核映像或根文件系統映像等從PC機中下載到目標板的FLASH中。用戶可以利用Bootloader提供的一些命令接口來完成自己想要的操作。
啟動流程
? 大多數BootLoader都分為stage1和stage2兩大部分,U-boot也不例外。依賴于cpu體系結構的代碼(如設備初始化代碼等) 通常都放在stage1且可以用匯編語言來實現,而stage2則通常用C語言來實現,這樣可以實現復雜的功能,而且有更好的可讀性和移植性。
-
stage1(start.s代碼結構)
U-boot的stage1代碼通常放在start.s文件中,它用匯編語言寫成,其主要代碼部分如下:
定義入口
_start
。由于一個可執行的image必須有一個入口點,并且只能有一個全局入口,通常這個入口放在rom(Flash)的0x0地址,因此,必須通知編譯器以使其知道這個入口,該工作可通過修改連接器腳本來完成。-
設置異常向量(exception vector)。這樣在CPU發生異常的時候就跳轉到/cpu/xxxx/interrupts中去執行相應的中斷代碼. 在interrupts文件中大部分的異常代碼都沒有實現具體的功能,只是打印一些異常消息,其中關鍵的是reset中斷代碼,跳到reset入口地址。
reset復位入口之前有一些段的聲明。
- 在reset中,首先是將cpu設置為svc32模式下,并屏蔽所有irq和fiq。
- 在u-boot中除了定時器使用了中斷外,其他的基本上都不需要使用中斷,比如串口通信和網絡等通信等,在u-boot中只要完成一些簡單的通信就可以了,所以在這里屏蔽掉了所有的中斷響應。
- 初始化外部總線。這部分首先設置了I/O口功能,包括串口、網絡接口等的設置,其他I/O口都設置為GPIO。然后設置BCFG0~BCFG3,即外部總線控制器。這里bank0對應Flash,設置為16位寬度,總線速度設為最慢,以實現穩定的操作;Bank1對應DRAM,設置和Flash相同;Bank2對應RTL8019。
設置CPU的速度、時鐘頻率、系統重映射(告訴處理器在系統發生中斷的時候到外部存儲器中去讀取中斷向量表)及中斷控制寄存器。
初始化內存控制器 。
將rom中的程序復制到ram中。通常把stage2加載到RAM空間中來執行,因此必須為加載Boot Loader的stage2準備好一段可用的RAM空間范圍。空間大小最好是memory page大小(通常是4KB)的倍數. 一般而言,1M的RAM空間已經足夠了。flash中存儲的u-boot可執行文件中,代碼段、數據段以及BSS段都是首尾相連存儲的, 所以在計算搬移大小的時候就是利用了用BSS段的首地址減去代碼的首地址,這樣算出來的就是實際使用的空間。程序用一個循環將代碼搬移到0x81180000,即RAM底端1M空間用來存儲代碼。然后程序繼續將中斷向量表搬到RAM的頂端。由于stage2通常是C語言執行代碼,所以還要建立堆棧區。
初始化堆棧 。在堆棧區之前還要將malloc分配的空間以及全局數據所需的空間空下來,他們的大小是由宏定義給出的,可以在相應位置修改。
轉到ram中執行,該工作可使用指令ldrpc來完成。
-
stage2(C語言代碼部分)
? 這個部分是相對變化不大的部分, 我們針對不同的板子改變它調用的一些初始化函數,并且通過設置一些宏定義來改變初始化的流程,所以這些代碼在移植的過程中并不需要修改,也是錯誤相對較少出現的文件。在文件的開始先是定義了一個函數指針數組,通過這個數組,程序通過一個循環來按順序進行常規的初始化,并在其后通過一些宏定義來初始化一些特定的設備。在最后程序進入一個循環,main_loop。這個循環接收用戶輸入的命令,以設置參數或者進行啟動引導。
?lib_arm/board.c中的start armboot是C語言開始的函數,也是整個啟動代碼中C語言的主函數,同時還是整個u-boot(armboot)的主函數,該函數主要完成如下操作:
- 調用一系列的初始化函數。
- 初始化flash設備。
- 檢測系統內存映射,初始化系統內存分配函數。
- 如果目標系統擁有nand設備,則初始化nand設備。
- 如果目標系統有顯示設備,則初始化該類設備。
- 初始化相關網絡設備,填寫ip,c地址等。
- 將內核從Flash讀取到RAM中并為內核設置啟動參數.
- 進入命令循環(即整個boot的工作循環),接受用戶從串口輸入的命令,然后進行相應的工作。
U-boot上電啟動之后,可以按任意鍵退出自動啟動狀態,進入命令行模式.[圖片上傳失敗...(image-c0b0df-1517988532965)]
在命令行模式下可以執行U-Boot的命令并執行. U-Boot可以支持幾十個常用的命令,通過這些命令,可以對開發版進行調試,可以引導Linux內核, 還可以擦寫Flash完成系統部署等功能. 掌握這些命令才能順利的進行嵌入式系統的開發. 輸入help可以得到當前U-Boot的所有命令列表.
編譯Android 源碼
Android源碼目錄結構
以下的Android 源碼目錄結構相關的內容來自mr_raptor的CSND博客,Android編譯的相關內容也可以查看其博客.
在Android源碼中,按照不同功能代碼被放在不同的目錄下:
目錄 | 描述 |
---|---|
bionic | 針對Android系統定制的仿生標準C庫、鏈接器等所在目錄,Android系統并沒有使用Linux的glibc庫,bioinc C庫針對嵌入式系統做了優化,添加了一些Android特定的函數API同時大大減少庫的體積,也避免了LGPL版權的問題。 |
bootable | Android系統引導啟動代碼,用來引導系統、更新系統、恢復系統。 |
build | Android的編譯系統目錄,里面包含大量的Makefile,用來編譯目標系統、Host主機開發環境等。 |
cts | 兼容性測試工具目錄。 |
dalvik | Dalvik虛擬機,Android系統得以運行的虛擬執行環境。 |
development | 程序開發所需要的模板和工具。 |
external | Android系統使用的其它開源代碼目錄,如jpeg圖片解碼開源庫、opencore開源代碼等。 |
frameworks | 框架層代碼,frameworks/base目錄下存放目標系統的框架庫,frameworks/policies/base下存放應用程序框架代碼。 |
hardware | HAL(Hardware Abstraction Layer)硬件抽象層代碼。 |
kernel | Linux內核目錄,默認下載的Android源碼里沒有,需單獨下載。 |
packages | Android系統級應用程序源碼目錄,如攝像應用、電話應用等。 |
prebuilt | 主機編譯工具目錄,如arm-linux-gcc交叉系統工具鏈等。 |
sdk | SDK及模擬器。 |
system | init進程、藍牙、無線WIFI工具、uevent進程目錄。 |
devices | 廠商設備配置目錄,針對不同設備,由不同的子目錄來分別管理,用來裁剪實現不同設備上Android目標系統。 |
Android 編譯后的out目錄分析
Android編譯完成后,將在根目錄中生成一個out文件夾,所有生成的內容均放置在這個文件夾中。
主要的兩個目錄是host和target, 前者表示在主機(x86)生成的工具,host目錄是一些在主機上用的工具,有一些是二進制程序,有一些是JAVA的程序。后者表示目標機(模認為ARMv5)運行的內容。target中中common目錄表示通用的內容,product中則是針對產品的內容。