WWDC關(guān)于APP啟動的建議

一些概念

Mach-O是運行時產(chǎn)生的可執(zhí)行文件的文件類型
  • Image:

    • Executable:程序的主二進制文件
    • Dylib:動態(tài)庫
    • Bundle:是一種特殊的Dylib,是不能進行鏈接的,只能在運行時用dlopen()函數(shù)打開。
  • Framework:Dylib+儲存該Dylib需要的資源、頭文件的目錄結(jié)構(gòu)

Mach-O Image File被分割成幾個段
引用自WWDC2016
  • _TEXT:頭文件,代碼,只讀常量
  • _DATA:所有可讀寫內(nèi)容(全局變量、靜態(tài)變量等等)
  • _LINKEDIT:儲存關(guān)于如何加載程序的“元數(shù)據(jù)”

每個段都是page size的倍數(shù),圖中_TEXT占有三個page,arm64一個page size是16KB,其它的是4KB


虛擬內(nèi)存把每一個進程地址映射到物理內(nèi)存RAM中:
  • page錯誤
  • 多個進程中出現(xiàn)的相同的RAM page
  • 文件回溯page:mmap()、延遲讀取(lazy reading)
  • Copy-On-Write(COW)
  • 臟page和干凈page
  • Permissions:rwx


安全:

ASLR(Address space layout randomization)是一種針對緩沖區(qū)溢出的安全保護技術(shù),通過對堆、棧、共享庫映射等線性區(qū)布局的隨機化,通過增加攻擊者預(yù)測目的地址的難度,防止攻擊者直接定位攻擊代碼位置,達(dá)到阻止溢出攻擊的目的。據(jù)研究表明ASLR可以有效的降低緩沖區(qū)溢出攻擊的成功率,如今Linux、FreeBSD、Windows等主流操作系統(tǒng)都已采用了該技術(shù)。

ASLR:
  • 地址空間隨機布局
  • 鏡像(Images)加載在隨機的地址

代碼簽名

  • 每一個page擁有的內(nèi)容
  • page內(nèi)的哈希驗證

exec()到main(),內(nèi)核讓你的App在隨機的地址開始運行


什么是Dyld?
  • Dyld是動態(tài)加載器,內(nèi)核加載的輔助程序
  • 程序從Dyld開始執(zhí)行
  • Dyld運行在進程中
  • Dyld負(fù)責(zé)加載動態(tài)依賴庫
  • Dyld擁有與App相同的權(quán)限



Main()函數(shù)之前的五個階段

Dyld主導(dǎo)的五個階段:
引用自WWDC2016
一、Load dylibs:

動態(tài)映射所有的動態(tài)依賴庫。

  1. 解析動態(tài)依賴庫(dylib)的列表
  2. 找到所有需要的mach-o(dylib)文件
  3. 打開并讀取每一個找到的文件
  4. 驗證這些文件是不是mach-o文件
  5. 找到它的編碼簽名,在內(nèi)核里對它進行注冊
  6. 給每一個分段調(diào)用映射

現(xiàn)在,所有App指向的動態(tài)依賴庫都被遞歸加載,App通常需要加載100-400個動態(tài)庫,大多數(shù)是OS(操作系統(tǒng))的動態(tài)庫

二、Rebase

遍歷所有內(nèi)部數(shù)據(jù)指針為他們添加一個滑動值。這些指針的位置都被編碼在__LINKEDIT段里。

  1. 調(diào)整所有鏡像內(nèi)的指針,添加一個slide偏差值。
Slide = actual_address - preferred_address
  1. 出于安全考慮,引入了 ASLR,全稱 Address Space Layout Randomization

大概意思就是鏡像(dylib)會加載在隨機的地址上,和actual_address會有一個偏差(slide),dyld需要修正這個偏差,來指向正確的地址。

  1. Rebase+Bind+ObjC大多數(shù)時間在做修復(fù):
    • 代碼簽名意味著命令不能被修改
    • 代碼不能被加載到任何地址上,而且永遠(yuǎn)不能被修改
    • 所有的修復(fù)都發(fā)生在__DATA數(shù)據(jù)段
三、Bind

遍歷查詢符號表,設(shè)置指向鏡像外部的指針。

  1. 所有在其它動態(tài)庫引用的東西都會符號化
  2. Dyld需要找到所有符號名
  3. 會比Rebasing進行更多的計算
四、ObjC

通知 runtime 準(zhǔn)備鏡像、OC類的注冊、偏移ivar的地址、加載Category

  1. runtime要維護一張表,包含所有類名(Class Name)及其映射的類(Class)
  2. 完成所有OC類的定義注冊
  3. 運行時改變所有ivar的偏移量
  4. 接下來在ObjC階段可以定義分類(Category)
  5. 最后讓Selector都是唯一的
五、Initializers
  1. dyld開始調(diào)用C++靜態(tài)構(gòu)造函數(shù),初始化器用來初始化那些抽象DATA

  2. 調(diào)用所有類的 +load 方法,順序:父類->子類->Category

  3. 每個Initializers按照從下向上的順序執(zhí)行。

    為什么從下向上?

    因為當(dāng)Initializers運行時,可能會調(diào)用一些dylib,我們需要確保那些dylib已經(jīng)準(zhǔn)備好被調(diào)用,所以從下開始運行Initializers,一直往上到應(yīng)用類,可以很安全的調(diào)用依賴的內(nèi)容。

  4. 最后Dyld調(diào)用main()函數(shù)

小結(jié):Dyld是一個輔助程序
  1. 加載所有的依賴庫
  2. 修復(fù)所有DATA page中的指針
  3. 運行所有的初始化器(Initializers)
  4. 跳轉(zhuǎn)到主函數(shù)

優(yōu)化啟動時間的實踐部分

  • 啟動時間如果在400ms(0.4s)以內(nèi)會讓用戶覺得啟動快
  • 啟動時間千萬不要超過20s,否則OS將會認(rèn)為你的APP進入死循環(huán),殺死APP
App啟動都做了什么?

在main函數(shù)之前的五個階段之后,還要調(diào)用:

  1. main()

  2. UIApplicationMain() : 加載framework的初始化器,加載nibs

  3. applicationWillFinishLaunching

以上8個步驟都算在這400ms內(nèi)。


熱啟動和冷啟動
  • 熱啟動:App及其數(shù)據(jù)已經(jīng)在內(nèi)存(磁盤)中
  • 冷啟動:App不在內(nèi)核的緩存中


優(yōu)化方案:

一、Load Dylib階段
  1. 合并已有的動態(tài)庫(包括framework),限制動態(tài)庫(包括framework)個數(shù)效果非常明顯
  2. 使用靜態(tài)庫代替

  3. 可以使用延遲加載,也就是使用dlopen()函數(shù),但是dlopen()會帶來細(xì)微的性能和正確性的問題。


    優(yōu)化前的鏈接庫個數(shù)

    優(yōu)化前的啟動時間

原有26個framework合并成2個后,由240ms變?yōu)?1ms。

優(yōu)化后的鏈接庫個數(shù)

優(yōu)化后的啟動時間
二、Rebase和Bind階段
  1. 減少OC元數(shù)據(jù)
    • 減少OC類的數(shù)量(不鼓勵使用很多很小的類,只有一兩個方法的那種)
    • 減少selector的數(shù)量
    • 減少Category的數(shù)量
  2. 減少C++虛擬函數(shù),虛擬函數(shù)創(chuàng)建被稱作 V表格,和OC元數(shù)據(jù)相同

  3. 避免讓機器生成過多的代碼,機器生成的指針非常耗內(nèi)存
    • 使用偏移量代替指針
    • 標(biāo)記為只讀
三、ObjC階段

這個階段的優(yōu)化工作已經(jīng)在Rebase和Bind階段做完了

四、Initializers階段

有兩種Initializers:顯式和隱式

顯式:
  1. +load
方案:使用 +initiailize 代替


  1. C/C++的 __attribute__((constructor))

方案:使用site initializers

  • 使用dispatch_once()
  • 使用pthread_once()
  • 使用std::once()

隱式:大部分是C++的全局變量帶來的非默認(rèn)初始化器
  1. 使用site initializers
  2. 只設(shè)置簡單的值
  3. 不要在初始化器中調(diào)用dlopen()
  4. 不要在初始化器中創(chuàng)建線程


參考

WWDC2016-406

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容