更多內(nèi)容請(qǐng)挪步我的博客
前言
啟動(dòng)時(shí)間包括 main 執(zhí)行之前的時(shí)間竖般,以及 main 執(zhí)行之后的時(shí)間。
main 執(zhí)行之后的時(shí)間通過(guò) Time Profiler 查看即可利花,之前寫過(guò)文章撇寞,詳情點(diǎn)擊這里。
這篇文章主要討論 main 執(zhí)行之前的時(shí)間摹迷。
main 之前 的加載過(guò)程
Load dylibs => Rebase => Binding => ObjC => Initializers
在 Xcode 中 Edit scheme -> Run -> Auguments 中設(shè)置 DYLD_PRINT_STATICS 環(huán)境變量可以輸出 main 之前執(zhí)行的時(shí)間
Load dylibs
要了解第一個(gè)階段的 dylibs见妒,先要介紹下 OS X 的可執(zhí)行文件 Mach-O
Mach-O 二進(jìn)制格式
系統(tǒng)判斷一個(gè)文件是否可以執(zhí)行孤荣,是通過(guò)將文件讀入內(nèi)存,然后尋找一個(gè)頭簽名须揣,頭簽名通常被稱為 魔數(shù) magic,通過(guò) magic 可以判斷文件的二進(jìn)制格式钱豁,如果是被支持的二進(jìn)制格式耻卡,那么就可以執(zhí)行該文件。
在很多種可執(zhí)行文件格式中牲尺, OS X 目前只支持:解釋器腳本格式卵酪、通用二進(jìn)制格式以及 Mach-O 格式。Mach-Object谤碳,簡(jiǎn)稱 Mach-O溃卡,是蘋果在 OS X 中維護(hù)的一種獨(dú)有的二進(jìn)制格式。
Mach-O 的文件頭 mach_header 中包含magic蜒简、CPU 類型和子類型等瘸羡。mach_header 其后包含了很多指令,這些指令被調(diào)用時(shí)清晰地指導(dǎo)了如何設(shè)置并加載二進(jìn)制數(shù)據(jù)搓茬,這些指令被稱為“加載指令”犹赖。加載指令包括將文件中的段映射到進(jìn)程地址空間队他、調(diào)用 dyld、開啟線程峻村、代碼簽名等麸折。
OS X 上幾乎所有的程序都是動(dòng)態(tài)鏈接的,僅有非常少量的進(jìn)程只需要內(nèi)核加載起就可以完成加載粘昨。通常情況下垢啼,使用 dyld 作為動(dòng)態(tài)連接器。
Loading Dylibs 的加載過(guò)程又分為
Parse list of dependent dylibs => Find requested mach-o file => Open and read start of file => Validate mach-o => Register code signature => Call mmap() for each segment
上面說(shuō)過(guò) mach_header 中包含很多指令张肾,其中包含了 LC_CODE_SIGNATURE 用于數(shù)字簽名膊夹,iOS 強(qiáng)制要求代碼簽名,且代碼簽名和沙盒機(jī)制是綁定在一起的捌浩,也就是說(shuō)必須放到沙盒中經(jīng)過(guò)簽名才能運(yùn)行放刨,非越獄的機(jī)器無(wú)法自行下載一個(gè)動(dòng)態(tài)庫(kù)并執(zhí)行。
mmap的作用是將一個(gè)文件或者其他對(duì)象映射進(jìn)內(nèi)存尸饺,普通文件被映射到進(jìn)程地址空間后进统,進(jìn)程可以像訪問(wèn)普通內(nèi)存一樣對(duì)文件進(jìn)行訪問(wèn)。
通常一個(gè) App 需要加載 100 - 400 個(gè) dylibs浪听,但是其中的系統(tǒng)庫(kù)加載會(huì)被優(yōu)化螟碎,能在很快的時(shí)間內(nèi)加載完成
優(yōu)化 Load dylibs 過(guò)程
減少非系統(tǒng)庫(kù)的依賴合并非系統(tǒng)庫(kù)
Rebase / Binding
由于進(jìn)程是在自己私有的虛擬地址空間中啟動(dòng),按照傳統(tǒng)方式迹栓,該地址是固定可預(yù)見的掉分,這樣黑客只要找到一段進(jìn)程的地址,就很容易推算出整個(gè)程序的地址空間布局克伊。所以大部分操作系統(tǒng)都采用了地址空間布局隨機(jī)化 (ASLR) 的技術(shù)酥郭,避免攻擊防護(hù)。
Rebase 就是系統(tǒng)為了解決動(dòng)態(tài)虛擬地址沖突愿吹,在加載動(dòng)態(tài)庫(kù)時(shí)進(jìn)行的基地址重定位操作不从,Rebase 是如何工作呢?我們先看下內(nèi)存中的段 (segment) 是什么樣的犁跪。
內(nèi)存分為以下幾個(gè)段
__PAGEZERO: 32位系統(tǒng)中椿息,這是內(nèi)存中單獨(dú)的一個(gè)頁(yè)面 (4KB)。64位系統(tǒng)中坷衍,這個(gè)段對(duì)應(yīng)了一個(gè)完整的 32 位地址空間寝优,即前 4GB。這個(gè)段有助于捕捉空指針引用枫耳,或捕捉將整數(shù)當(dāng)做指針引用乏矾。
__TEXT: 存放程序代碼,只讀可執(zhí)行,由于該段數(shù)據(jù)是只讀的妻熊,同一個(gè)程序的多個(gè)實(shí)例可以僅使用一份 __TEXT 副本夸浅,從而可以優(yōu)化內(nèi)存
__LINKEDIT: 由 dyld 使用,包含了字符串扔役、符號(hào)表以及其他數(shù)據(jù)
__DATA: 可讀可寫的數(shù)據(jù)...
LINKEDIT 段中保存了 Rebase 的相關(guān)信息帆喇,dylib 中的 LC_DYLD_INOF_ONLY 指定 rebase info 在文件中的偏移量。
使用以下命令可以輸出 rebase 信息xcrun dyldinfo -rebase -bind -lazy_bind myapp.app/myapp
Mach-O 中包含了很多外部庫(kù)和符號(hào)的引用亿胸,使用動(dòng)態(tài)鏈接器加載外部庫(kù)和符號(hào)的過(guò)程叫做符號(hào)綁定 binding坯钦。
優(yōu)化 Rebase / Binding 過(guò)程
減少 __DATA 指針
減少 OC 的 metadata,可以刪除無(wú)用的 class/selector/category減少 C++ 虛函數(shù)使用 Swift 的 struct為不需要寫的屬性添加 readonly
ObjC
大部分 ObjC 的設(shè)置工作都在 Rebease 和 Binding 時(shí)做完類的定義被注冊(cè)實(shí)例變量偏移的更新Category 被插入到方法列表Selector 的唯一性
Initializers
C++ 為靜態(tài)變量初始化執(zhí)行 +load 方法執(zhí)行 main()
優(yōu)化 Initializers 過(guò)程
減少 +load() 方法侈玄,盡量使用 +initialize() 代替
使用 dispatch_one() pthread_once() std::once() 代替 C/C++ attribute(constructor)
減少靜態(tài)構(gòu)造函數(shù)
初始化方法中不要使用 dlopen()初始化方法中不要?jiǎng)?chuàng)建線程
總結(jié)優(yōu)化的點(diǎn)
減少非系統(tǒng)的framework依賴婉刀,如果framework 在當(dāng)前 App 支持的所有 iOS 系統(tǒng)版本中都存在則設(shè)為 required,否則設(shè)置為 optional序仙,optional 會(huì)有額外檢查合并非系統(tǒng)庫(kù)
刪除無(wú)用的 class/selector/category
刪除無(wú)用的方法調(diào)用突颊、靜態(tài)變量等減少 C++ 虛函數(shù)(減少創(chuàng)建虛函數(shù)表時(shí)間)
使用 Swift 的 struct (從而減少符號(hào)數(shù)量)
為不需要寫的屬性添加 readonly減少 +load() 方法,盡量使用 +initialize() 代替使用
dispatch_one() pthread_once() std::once() 代替 C/C++ attribute(constructor)
減少靜態(tài)構(gòu)造函數(shù)
初始化方法中不要使用 dlopen()