天天看點

(7)Android之路====Android開機流程

開機是建立并運作系統的過程,在日常的開發中也常常會接觸到,本次以開機流程為切入點,繼續深入了解Android系統.

在Android系統上,我們可以把開機分為3個層次,分别是:Bootloader-->kernel-->Android,其中bootloader比較常用的有U-boot和little-kernel, qcom用的是little-kernel,很多國産平台用的是U-boot,這裡也以使用量比較多的U-boot為例進行說明.

U-boot啟動階段

通過檢視開機時的序列槽log,我們可以清楚地看到uboot啟動的流程,量産階段的uboot啟動列印資訊較少,我們通過修改 uboot/include/common.h的#ifdef DEBUG前定義一個#define DEBUG,之後,重編uboot,刷機,就可以看到uboot的資訊量增加了很多.

下面,我們來簡單介紹一下uboot的啟動流程,分為兩個部分,第一部分是BL1,對應uboot-spl.bin,第二部分是BL2,對應uboot.bin,這樣分是因為記憶體不足問題而做的改進,SoC的内部一般都有一小塊存儲空間,分為RAM和ROM兩部分,ROM中固化了BL0階段的代碼,主要是初始化外部存儲器,并加載BL1階段的代碼到内部RAM上,而這塊RAM空間很小,比如隻有16K,這不足以使用全功能的uboot,是以,這個階段,通常是初始化外部器件(如外部RAM),并加載BL2階段的代碼到外部RAM,uboot全功能代碼由BL2階段完成.當然,uboot也為我們開發了省去BL2這個步驟的流程,不過,這種操作不常用.

下面,我們簡單介紹一下這個流程,更詳細流程請參考這位大神的uboot系列文章:https://blog.csdn.net/ooonebook/article/details/

(7)Android之路====Android開機流程

uboot BL1階段流程.

(7)Android之路====Android開機流程

uboot BL2階段流程.

Linux啟動階段

同樣,我們可以通過開機log看出其啟動的流程,為了能夠看到開機的全部log,我們需要修改loglevel,這個值可以在dts中修改,也可以在menuconfig中修改,或者是uboot的bootargs傳參中添加loglevel = 8(或者7,都可以),之後,就會在Linux啟動起來之後,列印全部log.

這裡簡單的說一下啊Linux啟動的流程,詳細流程可以參考着位大神的Linux系列文章:https://blog.csdn.net/ooonebook/article/details/

(7)Android之路====Android開機流程

Android啟動階段

以Android 8.1為例子進行講解,大緻流程如下:

(7)Android之路====Android開機流程

OS-Level前邊已經貼圖進行了簡要的說明,下面從init入口,重點說一下Native-Level和Java-Level這兩部分.

init是第一個使用者空間程序,在Android上,它會建構後續Android的系統,它的主要工作可分為以下幾項:

a).建立檔案系統目錄并挂載相關的檔案系統

b).初始化log系統

c).完成SELinux相關工作

d).初始化/設定/啟動屬性相關的資源

e).解析init.rc

f).atcion/service管理

a).建立檔案系統目錄并挂載相關的檔案系統

在Android 8.0之前,挂載是通過觸發do_mount_all來做的,從8.0開始,以前由do_mount_all來做的事情現在分成兩部分,新增FirstStageMount,将system/vendor/odm分區挂載放在FirtstStageMount階段來做,而其它分區的挂載仍然在do_mount_all階段, 系統啟動時,在kernel挂載ramdisk.img,接着在init裡面會分别把system.img和userdata.img挂載到ramdisk下的目錄.

main() @ system/core/init/init.cpp為rootfs建立必要的檔案夾并挂載适當的分區至/dev, /dev/pts, /proc, /sys.建立/dev/null和/dev/kmsg節點.而rc檔案會建立其餘的檔案夾.

b).初始化log系統

Log是Android提供的用來輸出日志的工具類。當使用Log.V()、Log.D()、Log.e()來列印的日志的時候,可以通過logcat來檢視輸出的日志。另外有種情況是我們經常能夠遇到的,當應用發生異常的時候,logcat螢幕就會顯示相關的堆棧資訊,讓我們得以知道是哪裡發生了錯誤。

log的列印時通過調用logger實作的,而logger在初始化階段被指派為kernelLogger,是以init log會從kernel log輸出.是以這種方法我們還可以用于以後的調試工作,比如遇到一個需要使用序列槽抓取上層log的問題,而恰恰工程配置中沒有配置console服務,那麼,序列槽logcat是用不了的,我們可以通過重定向的方式,讓上層log走kernel log,這樣就不需要走logcat了.我們可以通過gMinimumLogSeverity的指派,來改變log輸出等級,它的位置在system/core/base/logging.cpp裡.

c).完成SELinux相關工作

SELinux是從Android 4.4開始導入的, Android 5.0開始全面啟用的安全相關子產品,它同樣是在init中開始初始化的,SELinux初始化入口是main() @ system/core/init/init.cpp.

在第一階段,Load SeLinux Policy檔案,從Android 8.0開始,sepolicy相關的檔案已經拆分到system和vendor分區了,是以這裡加載policy檔案的過程會不一樣,最終是通過selinux_load_split_policy來分别導入system/etc/selinux和vendor/etc/selinux下的policy檔案.

在第二階段,通過調用selinux_init_all_handles來裝載檔案和屬性的安全上下文.

在日常調試時,我們經常會遇到權限的問題,這時,我們可以關閉SELinux的方式防止不确定性權限問題的發生,等都調試通了,再來解決權限問題,關閉SELinux的方式有很多種,比如通過adb shell setenforce的方式,也可以通過uboot的bootargs傳參androidboot.selinux = permissive,還可以修改SELinux的代碼,讓其直接傳回false等方式,SELinux是一個很複雜的系統,而Android O(8.x)通過Treble架構,将SELinux做了split,使得它的規則更加繁瑣.

d).初始化/設定/啟動屬性相關的資源

在完成init log/SELinux系統的初始化後,緊接着開始初始化/修改/加載propery. Android property系統其實可以了解為鍵值對(屬性名字和屬性值),大部分property是記錄在某些檔案中的,init程序啟動的時候,會加載這些檔案,完成property系統初始化.

e).解析init.rc

init.rc的解析,我們可以分文如下幾個步驟說:

e.1: 解析init.rc,将所有服務和操作資訊加傳入連結表,從/proc/cmdline中提取核心啟動參數,并儲存到全局變量.擷取硬體資訊和版本号,根據硬體号解析init.${hardware}.rc檔案,将服務和操作資訊加傳入連結表;

e.2: 執行連結清單中觸發辨別為"early-init"的指令;

e.3: 處理wait_for_coldboot_done, property_init, keychord_init, console_init和set_init_properties;

e.4: 解析init.${hardware}.3rdparty.rc檔案,将服務和操作資訊加傳入連結表;

e.5: 執行連結清單中觸發辨別為"init", "early-fs", "fs", "post-fs"的指令;

e.6: 處理property_service_init, signal_init和check_startup;

e.7: 執行觸發辨別為early-boot和boot的action;

e.8: 基于目前property狀态,處理queue_property_triggers,注冊輪詢時間值ufds數組: device_fd, property_set_fd, signal_recv_fd, keychord_fd.

進入for無限循環,取保這個init程序永駐不退出.

Processes after init

1,ServiceManager是Binder的服務管理守護程序,也是Binder機制的核心,所有Binder服務都會通過它注冊,用戶端再通過它擷取服務接口;

2,zygote是Android java部分的孵化器,也就是Android java架構的基礎.zygote首先孵化出SystemServer,這是Android大多數系統服務的守護程序,之後SystemServer與zygote一起完成其它所有Android的應用程式程序的啟動;

3,MediaServer是多媒體服務的守護程序,負責多媒體播放, 照相機, 音頻三項服務;

4,SurfaceFlinger負責将layer重新整理到螢幕,維持layer的z序等功能;

5,SystemServer則通過zygote孵化出來;

這幾個程序是Android系統運作的基礎.

Surfaceflinger and bootAnim

首先介紹使用者可以看到的開機動畫,開機動畫從surfaceflinger程序啟動後開始執行到launcher啟動完成後退出。

Surfaceflinger service屬于class core,在init執行boot action階段會被啟動。而且surfaceflinger如果因為異常啟動失敗會到導緻zygote重新開機。在android啟動階段首先要關注surfaceflinger是否啟動正常。

service surfaceflinger /system/bin/surfaceflinger  
	class core animation  
	user system  
	group graphics drmrpc readproc  
	onrestart restart zygote  
	writepid /dev/stune/foreground/tasks
           
service bootanim /system/bin/bootanimation
	class core animation
	user graphics
	group graphics audio
	disabled
	oneshot
	writepid /dev/stune/top-app/tasks
           

bootanim雖然屬于class core,但是它注明了disable,在init執行boot action階段不會被啟動,其啟動入口是在surfaceflinger::init方法中.

void SurfaceFlinger::init() {  
	……  
	if (getHwComposer().hasCapability(//建立propertysetthread  
		HWC2::Capability::PresentFenceIsNotReliable)) {  
		mStartPropertySetThread = new StartPropertySetThread(false);  
	} else {  
		mStartPropertySetThread = new StartPropertySetThread(true);  
	}  
	//運作StartPropertySetThread  
	if (mStartPropertySetThread->Start() != NO_ERROR) {  
		ALOGE("Run StartPropertySetThread failed!");  
	}  
	……  
}  
           

繼續看一下StartPropertySetThread做了什麼:

status_t StartPropertySetThread::Start() {  
	return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);  
} 
 
bool StartPropertySetThread::threadLoop() {  
	// Set property service.sf.present_timestamp, consumer need check its readiness  
	property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");  
	// Clear BootAnimation exit flag  
	property_set("service.bootanim.exit", "0");  
	// Start BootAnimation if not started  
	property_set("ctl.start", "bootanim");//開始運作bootanim  
	// Exit immediately  
	return false;  
} 
           

這部分實作在AndroidN和AndroidO上有些差異,但總體過程不變,仍然是通過property service ctrl msg啟動bootanim servce,并設定service.bootanim.exit 為0,該屬性值決定開機動畫是否退出。

在android func中會重複播放android預設動畫,并在每次播放後都執行checkExit來讀取service.bootanim.exit屬性值,當service.bootanim.exit屬性值為1時會執行requestExit将mExitPending值設定為ture,退出循環播放。Bootanim執行結束退出。當android系統桌面應用被啟動後并且進入Idle狀态時會通知surfaceflinger将service.bootanim.exit設定為1.

void SurfaceFlinger::bootFinished()  
{  
	……  
	if (mVrFlinger) {  
		mVrFlinger->OnBootFinished();  
	}  
  
	// stop boot animation  
	// formerly we would just kill the process, but we now ask it to exit so it  
	// can choose where to stop the animation.  
	property_set("service.bootanim.exit", "1"); //關閉bootanim  
  
	const int LOGTAG_SF_STOP_BOOTANIM = 60110;  
	LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM,  
				   ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));  
  
	sp<LambdaMessage> readProperties = new LambdaMessage([&]() {  
		readPersistentProperties();  
	});  
	postMessageAsync(readProperties);  
}  
           

上面的代碼中可以看到surfaceflinger::bootfinished将service.bootanim.exit設定為1。bootanim程序在checkExit時檢測到service.bootanim.exit=1時退出循環播放。

ServiceManager

frameworks/native/cmds/servicemanager/service_manager.c

ServiceManager service屬于class core,在init執行boot action階段會被啟動。ServiceManager啟動異常會導緻zygote、surfaceflinger、media主要系統服務重新開機。而且被辨別為critical服務,當在4分鐘内重新開機超過4次系統會進入recovery模式。ServiceManager是Binder機制的守護程序,用來管理android各種服務,并向client提供查詢server遠端接口的方法。

service servicemanager /system/bin/servicemanager  
	class core animation  
	user system  
	group system readproc  
	critical  
	onrestart restart healthd  
	onrestart restart zygote  
	onrestart restart audioserver  
	onrestart restart media  
	onrestart restart surfaceflinger  
	onrestart restart inputflinger  
	onrestart restart drm  
	onrestart restart cameraserver  
    writepid /dev/cpuset/system-background/tasks  
	shutdown critical 
           

ServiceManager在啟動中主要做3件事:

1).打開 /dev/binder裝置

bs = binder_open(driver, 128*1024);
           

2).通知binder裝置,将ServiceManager設定為context_manager

if (binder_become_context_manager(bs)) {
	ALOGE("cannot become context manager (%s)\n", strerror(errno));
	return -1;
}
           

3).循環讀取binder裝置,檢測是否有service binder請求,當檢測到請求後,調用svcmgr_handler處理.

binder_loop(bs, svcmgr_handler);
           

Servicemanager是Android Binder機制的一個組成部分,負責管理系統中的所有服務,當用戶端要與服務端進行通信時,首先通過Service Manager來查詢和取得所要互動的服務。而每個服務也要向servicemanager注冊自己能提供的服務。而注冊服務實際上是通過在本地建立sm代理BpServiceManager并調用其addService()函數實作的.

Zygote

startVM——該函數中主要是初始化VM的參數。需要重點關注其中dalvik heapsize的初始化,如果沒有配置正确的參數或者使用預設的參數很有可能導緻手機無法進入Launcher。因為目前APP占用的heapsize都比較大,使用預設參數很容易出現OOM導緻應用不斷重新開機。

startReg——注冊JNI函數,周遊gRegJNI數組中JNI register方法注冊JNI method。注冊JNI方法後,會通過JNI調用java class(zygoteInit)的main函數進入java世界。

registerServerSocket——通過擷取環境變量值得到zygote檔案描述符,根據該檔案描述符建立socket,用來和ActivityManagerService通信。AMS通過Process.start來建立新的程序,而Process.start會先通過socket連接配接到zygote程序,并最終由zygote完成程序建立。

Preload——預加載類和資源,zygote通過預加載類和資源可以加快子程序的執行速度和優化記憶體。因為預加載的類和資源較多,在開機優化過程中也需要重點關注preload的耗時。

Class預加載檔案路徑:/system/etc/preloaded-classes

resource預加載檔案路徑:frameworks/base/core/res/res/values/arrays.xml

forkSystemServer——啟動system_server程序,android java 系統服務都将駐留在該程序中。是android framework核心。設定systemserver程序uid和gid,process name,class name。

SystemServer

上面介紹了SystemServer的實際程序名是system_server。System_server作為zygote fork的第一個程序,不言而喻具很重要。如果system_server退出也會導緻zygote程序退出,具體的處理是在Zygote Sigchldhandler中。具體看下com.android.server.systemserver的入口函數:

public static void main(String[] args) {
	new SystemServer().run();
}
           

Run方法中主要是啟動android系統服務

// Start services.
try {
	traceBeginAndSlog("StartServices");
	startBootstrapServices();
	startCoreServices();
	startOtherServices();
	SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
	Slog.e("System", "******************************************");
	Slog.e("System", "************ Failure starting system services", ex);
	throw ex;
} finally {
	traceEnd();
}
           

将service分為3個等級: 開機相關服務, 核心服務, 其它服務.

  1).Bootstrap Services

  ActivityManagerService

  Installer

  PowerManagerservice

  LightService

  DisplayManagerService

  PackageManagerservice

  SensorService(native)

  2).Core Services

  BatteryService

  UsageStatsService

  webViewUpdateService

  3).Other Service

  TelecomLoaderService

  CameraService(java)

  AccountManagerService

  ContentService

  AlarmManagerService

  InputManagerService

  VRManagerService(VR系統服務)

  WindowManagerService

  BluetoothService

StartOtherService()最後調用ActivityManagerService.SystemReady() 啟動Home 程序.

APP啟動

ActivityManagerService.systemReady()啟動所有常駐應用,需要注意的是,這裡啟動的常駐應用不是全部必須的,對于第三方的apk,若是常駐,會使得程序在不做處理時仍然占用記憶體空間,在某種程度上是浪費記憶體。特别是記憶體不足時,因為kernel需要周遊所有的page來進行reclaim,以釋放記憶體。這個過程是極為耗費資源的,是以我們經常會看到由此引起的卡頓,實際上就是CPU搶占不到,處理不能流程進行。是以我們需要嚴格控制常駐程序數量。

繼續啟動HOME

啟動到這裡,若是檢查ActivityStack沒有ActivityRecord,會将Launcher啟動啟動,同時通過SurfaceFlinger将開機動畫停掉。

啟動其他應用

在ActivityManagerService檢測到空閑的時候,會發送ACTION_BOOT_COMPLETED INTENT,導緻MMS, AlarmClock, Calendar等應用啟動。

app的啟動

在這一階段,有許多的apk會通過接受到BOOT_COMPLETED或者啟動廣播來啟動因為這樣就會占用大量的記憶體,特别是第三方的apk,比如微網誌,遊戲等等,在啟動的時候需要禁止這類應用的啟動,除非使用者點選啟動,否則就讓它們不要啟動來減少對資源的浪費。

對于程序,通常預設在fork的時候,預設程序的排程方式是SCHED_OTHER,這是最低的排程方式,其它由低到高是:SCHED_FIFO,SCHED_RR。FIFO(先進先出)和RR(實時的排程)是較高的,我們可以通過設定這個排程方式來擷取更到CPU資源,或者說更高的程序優先級,使得它的資源不太容易被搶占。對于IO操作,在init程序fork這個程序時,可以設定一些選項來提高目前程序對于io操作的iopriority的等級,使得目前程序的priority提高。通過對程序priority的調整可以減少對不太重要的程序的資源控制,進而改進程序的啟動速度。

到這裡,android就完成了初步的啟動。