iOS啓動優化之從exec()到main()

上一篇文章咱們講到了啓動優化須要的一些理論知識,這篇文章咱們講一下從exec()到main()系統幫咱們作了哪些操做。緩存

什麼是exec()?

exec()函數是一個系統調用,當啓動一個應用程序的時候,系統內核把應用映射到新的地址空間,且每次起始位置都是隨機的(由於使用了ASLR),並將起始位置到0x000000這段範圍的進程權限都標記爲不可讀寫不可執行。安全

  • 若是是32位進程,這個範圍至少是4KB。
  • 若是是64位進程,至少是4GB。
  • 能夠捕捉任何空指針引用。
  • 捕捉任何指針截斷。

關於Dylibs

首先,內核加載動態連接庫的幫助程序Dyld,讓Dyld來啓動應用的進程。Dyld的工做是:bash

  • 加載全部依賴的Dylib。
  • 它擁有和應用同樣的權限。

Dyld的加載步驟

加載dylibs

  • 從主執行文件的header獲取須要加載的依賴庫列表。
  • 找到動態庫的Mach-O文件。
  • 打開和開始運行每個文件。
  • 確保它是mach-o文件。
  • 找到代碼簽名並將其註冊到內核。
  • 而後在該dylib的每一個segment上調用mmap()。

應用所依賴的dylib文件可能會依賴其它的dylib,因此dyld加載dylib的過程是一個遞歸的調用過程。ide

  • 加載應用程序全部的直接依賴項
  • 加載每一個dylib所依賴的dylib
  • 清洗和重複
  • 應用通常會加載100-400個dylib文件。
    • 大部分都是系統的dylib。
    • 系統的dylib會預加載和緩存那些dyld要作的工做,加載速度很快。

修復(Fix-ups)

在加載完全部依賴的dylib後,它們是彼此獨立的,咱們須要將它們綁在到一塊兒,這個過程就是修復。由於代碼簽名的存在,咱們沒法修復指令,那麼就不能讓一個dylib調用另外一個dylib,這時須要加載更多的中間層。函數

現代的code-gen被稱爲動態PIC(位置無關代碼),能夠加載到該地址上,而且是動態的,也就是說地址被間接的分配了。當調用發生時,code_gen會在__DATA段中建立一個指向被調用者的指針,而後加載該指針並跳轉過去。優化

因此dyld作的事情就是修正(fix-up)指針和數據,Fix-up有兩種類型,rebasing(重設地址)和binding(綁定)。spa

Rebasing指的是在鏡像內調整指針,Binding指的是在鏡像外調整指針。指針

能夠在任何二進制文件上使用dyldinfo指令來查看全部的修復:code

Rebasing

在過去,dyld會把dylib加載到指定的地址,全部指針和數據對於代碼來講都是對的,dyld無需作任何fix-up。現在使用了ASLR會將dylib加載到新的隨機地址,這個隨機地址跟代碼和數據指向的舊地址會有誤差,dyld須要修正這個誤差(slide),Rebasing就是將dylib內部的指針地址都加上這個偏移量,計算方法以下:cdn

Slide = actual_address - preferred_address
複製代碼

當咱們重設地址時,實際上在全部的Data頁面上都產生了錯誤,而後對頁面進行修改,就會產生COW(寫入時複製),因此重設地址有時會很是昂貴。這可能會產生I/O瓶頸,但由於rebase的順序是按地址排列的,因此從內核的角度來看這是個有次序的任務,它會進行預讀,減小I/O消耗。

Binding

Binding是處理那些指向dylib外部的指針,這些指針經過名稱進行綁定,實際上就是個字符串。運行時,dyld須要找到該符號指針的位置,這須要計算,遍歷查找符號表,一旦找到,就將該值存儲到數據指針裏。計算複雜度比Rebasing要高的多,可是I/O不多,由於Rebasing完成了大部分I/O操做。

ObjC

  • 大多數ObjC的設置都是經過rebasing和binding來完成的,好比Class中指向超類的指針和指向方法的指針。
  • ObjC是個動態語言,能夠用類的名字來實例化一個類的對象。這意味着ObjC運行時須要維護一張映射類名與類的全局表。當加載dylib時,其定義的全部類都須要註冊到這個全局表中。
  • C++中有個問題叫作脆弱的基類問題。ObjC就沒有這個問題,由於會在加載時經過fix-up動態類中改變實例變量的偏移量。
  • 能夠定義類別,改變另外一個類中的方法。
  • 選擇器是惟一的。

Initializers

  • C++編譯器生成初始化器來完成那些抽象DATA的初始化。
  • ObjC中經過+load方法來完成該操做,但不建議使用+load方法。若是有+load方法,此時開始運行。
  • 全部的dylib都須要運行初始化器。
  • 從下往上開始運行初始化器,這樣能夠保證很安全的調用所依賴的內容。
  • 全部的初始化器調用完成以後,Dyld就會調用可執行文件的main()函數。

下期預告:iOS啓動優化實踐篇

關注公衆號,獲取更多文章內容

相關文章
相關標籤/搜索