iOS 啟動優(yōu)化原理

1平斩、虛擬內(nèi)存 & ASLR

在早期計(jì)算機(jī)中數(shù)據(jù)是直接通過物理地址訪問的,這就造成了下面兩個(gè)問題

  • 1家卖、內(nèi)存不夠用
  • 2前普、數(shù)據(jù)安全問題

內(nèi)存不夠 --- > 虛擬內(nèi)存

虛擬內(nèi)存就是通過創(chuàng)建一張物理地址和虛擬地址的映射表來管理內(nèi)存,提高了CPU利用率酥艳,使多個(gè)進(jìn)程可以同時(shí)/按需加載

  • 在iOS中摊溶,每個(gè)進(jìn)程都有獨(dú)立的虛擬內(nèi)存,存放物理內(nèi)存中充石,其地址是從0開始的莫换,大小固定4G,每個(gè)虛擬內(nèi)存又會按頁劃分骤铃,每頁16K拉岁,以頁為單位加載,每個(gè)進(jìn)程是相互獨(dú)立的惰爬,保證進(jìn)程間的數(shù)據(jù)安全
  • 當(dāng)一個(gè)進(jìn)程只有部分功能使用時(shí)喊暖,系統(tǒng)會自動將使用部分加載到物理內(nèi)存中
  • CPU在進(jìn)行數(shù)據(jù)訪問時(shí),會先訪問虛擬內(nèi)存撕瞧,通過虛擬內(nèi)存和物理內(nèi)存的映射關(guān)系陵叽,去對應(yīng)的物理內(nèi)存中查找狞尔,在CPU上有專門處理映射的硬件
  • 當(dāng)CPU訪問數(shù)據(jù)時(shí),如果虛擬內(nèi)存中的數(shù)據(jù)沒有物理內(nèi)存綁定巩掺,會發(fā)生缺頁異常(pagefault)偏序,會阻塞當(dāng)前進(jìn)程,直到數(shù)據(jù)加載到物理內(nèi)存中胖替,虛擬內(nèi)存和物理內(nèi)存綁定
  • 當(dāng)內(nèi)存條中的內(nèi)存全部用完后研儒,系統(tǒng)會將新的數(shù)據(jù)覆蓋到很久沒使用的內(nèi)存上
    虛擬內(nèi)存和物理內(nèi)存

數(shù)據(jù)安全 --- > ASLR

因?yàn)樘摂M內(nèi)存的起始地址(0x000000)和大小(4G)是固定的,這就意味著我們的數(shù)據(jù)地址也是固定的独令,所以在iOS4.3引進(jìn)了ASLR
ASLR:英文全稱Address Space Layout Randomization端朵,也叫地址空間配置隨機(jī)加載,是一種針對緩沖區(qū)溢出安全保護(hù)技術(shù)燃箭。通過對堆冲呢、棧、共享庫映射等線性區(qū)布局的隨機(jī)化遍膜,增強(qiáng)了攻擊者找到目標(biāo)物理內(nèi)存的難度碗硬,防止攻擊者直接定位攻擊代碼位置,達(dá)到阻止溢出攻擊的目的的一種技術(shù)瓢颅。
由于ASLR的存在恩尾,導(dǎo)致可執(zhí)行文件和動態(tài)鏈接庫在虛擬內(nèi)存中的地址每次都是不固定的,所以需要在編譯時(shí)來修復(fù)鏡像中的資源指針挽懦,來指向正確的地址翰意。即正確的內(nèi)存地址 = ASLR地址 + 偏移值

2、Mach-O

Mach-O文件是Mach Object文件格式的縮寫信柿,它是用于可執(zhí)行文件冀偶、動態(tài)庫、目標(biāo)代碼的文件格式渔嚷。作為a.out格式的替代进鸠,Mach-O格式提供了更強(qiáng)的擴(kuò)展性,以及更快的符號表信息訪問速度
我們可以通過工具MachOView來查看Mach-O文件具體信息

對于OS X 和iOS來說形病,Mach-O是其可執(zhí)行文件的格式客年,主要包括以下幾種文件類型

  • Executable:可執(zhí)行文件
  • Dylib:動態(tài)鏈接庫
  • Bundle:無法被鏈接的動態(tài)庫,只能在運(yùn)行時(shí)使用dlopen加載
  • Image:指的是Executable漠吻、Dylib和Bundle的一種
  • Framework:包含Dylib量瓜、資源文件和頭文件的集合

Mach-O文件格式

Mach-O鏡像文件

一個(gè)完整的Mach-O文件主要分為三大部分:

  • Header:Mach-O的cpu架構(gòu),文件類型以及加載命令等信息
  • Load Comands:文件中數(shù)據(jù)的具體組織結(jié)構(gòu)途乃,不同數(shù)據(jù)使用不同的加載命令
  • Data:數(shù)據(jù)中的每個(gè)段(Segment)的數(shù)據(jù)保存在這里绍傲,每個(gè)段有一個(gè)或多個(gè)部分,存放具體的數(shù)據(jù)耍共、代碼烫饼、符號表猎塞、動態(tài)符號表等
Header

Header中包含了整個(gè)Mach-O文件中的關(guān)鍵信息,可以使CPU快速知道整個(gè)Mach-O的基本信息枫弟,決定了一些基礎(chǔ)架構(gòu)邢享、系統(tǒng)類型鹏往、指令條數(shù)等信息淡诗。針對32位和64位架構(gòu)的cpu,分別使用了mach_headermach_header_64結(jié)構(gòu)體來描述Mach-O頭部伊履,mach_header_64結(jié)構(gòu)體相比于mach_header只是多了一個(gè)reserved保留字段

/*
 - magic:0xfeedface(32位) 0xfeedfacf(64位)韩容,系統(tǒng)內(nèi)核用來判斷是否是mach-o格式
 - cputype:CPU類型,比如ARM
 - cpusubtype:CPU的具體類型唐瀑,例如arm64群凶、armv7
 - filetype:由于可執(zhí)行文件、目標(biāo)文件哄辣、靜態(tài)庫和動態(tài)庫等都是mach-o格式请梢,所以需要filetype來說明mach-o文件是屬于哪種文件
 - ncmds:sizeofcmds:LoadCommands加載命令的條數(shù)(加載命令緊跟header之后)
 - sizeofcmds:LoadCommands加載命令的大小
 - flags:標(biāo)志位標(biāo)識二進(jìn)制文件支持的功能,主要是和系統(tǒng)加載力穗、鏈接有關(guān)
 - reserved:保留字段
 */
struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};

filetype主要記錄Mach-O的文件類型毅弧,常用的有以下幾種

#define MH_OBJECT   0x1     /* 目標(biāo)文件*/
#define MH_EXECUTE  0x2     /* 可執(zhí)行文件*/
#define MH_DYLIB    0x6     /* 動態(tài)庫*/
#define MH_DYLINKER 0x7     /* 動態(tài)鏈接器*/
#define MH_DSYM     0xa     /* 存儲二進(jìn)制文件符號信息,用于debug分析*/
MachOView中的Header
Load Commands

Load Commands主要用于加載指令当窗,例如動態(tài)鏈接器的位置够坐、程序的入口、依賴庫的信息崖面、代碼的位置元咙、符號表的位置等等。其大小和數(shù)目在Header中已確定巫员,在Mach.h中的定義如下

/*
 load_command用于加載指令
 - cmd 加載命令的類型
 - cmdsize 加載命令的大小
 */
struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};
Mach-O文件中Load Commands

其中LC_SEGMENT_64的類型segment_command_64定義如下

/*
 segment_command 段加載命令
 - cmd:表示加載命令類型庶香,
 - cmdsize:表示加載命令大小(還包括了緊跟其后的nsects個(gè)section的大屑蚴丁)
 - segname:16個(gè)字節(jié)的段名字
 - vmaddr:段的虛擬內(nèi)存起始地址
 - vmsize:段的虛擬內(nèi)存大小
 - fileoff:段在文件中的偏移量
 - filesize:段在文件中的大小
 - maxprot:段頁面所需要的最高內(nèi)存保護(hù)(4 = r赶掖,2 = w,1 = x)
 - initprot:段頁面初始的內(nèi)存保護(hù)
 - nsects:段中section數(shù)量
 - flags:其他雜項(xiàng)標(biāo)志位
 
 - 從fileoff(偏移)處财异,取filesize字節(jié)的二進(jìn)制數(shù)據(jù)倘零,放到內(nèi)存的vmaddr處的vmsize字節(jié)。(fileoff處到filesize字節(jié)的二進(jìn)制數(shù)據(jù)戳寸,就是“段”)
 - 每一個(gè)段的權(quán)限相同(或者說呈驶,編譯時(shí)候,編譯器把相同權(quán)限的數(shù)據(jù)放在一起疫鹊,成為段)袖瞻,其權(quán)限根據(jù)initprot初始化司致。initprot指定了如何通過讀/寫/執(zhí)行位初始化頁面的保護(hù)級別
 - 段的保護(hù)設(shè)置可以動態(tài)改變,但是不能超過maxprot中指定的值(在iOS中聋迎,+x和+w是互斥的)
 */
struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};
Data

Data主要用于存儲具體的只讀脂矫、可讀寫代碼,例如方法霉晕、符號表庭再、字符表、代碼數(shù)據(jù)牺堰、重定向/符號綁定等拄轻。其中大多數(shù)的Mach-O文件均包含以下三個(gè)段:

  • __TEXT代碼段:只讀,包括函數(shù)和只讀字符串
  • __DATA數(shù)據(jù)段:讀寫伟葫,可讀寫的全局變量等
  • __LINKEDIT:包含方法和變量的元數(shù)據(jù)(位置恨搓、偏移量),代碼簽名等信息

Data區(qū)中筏养,section占很大比例斧抱,Section在Mach.h中是以結(jié)構(gòu)體section_64(在arm64架構(gòu)下)表示,其定義如下

/*
 Section節(jié)在MachO中集中體現(xiàn)在TEXT和DATA兩段里.
 - sectname:當(dāng)前section的名稱
 - segname:section所在的segment名稱
 - addr:內(nèi)存中起始位置
 - size:section大小
 - offset:section的文件偏移
 - align:字節(jié)大小對齊
 - reloff:重定位入口的文件偏移
 - nreloc:重定位入口數(shù)量
 - flags:標(biāo)志渐溶,section的類型和屬性
 - reserved1:保留(用于偏移量或索引)
 - reserved2:保留(用于count或sizeof)
 - reserved3:保留
 */

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};
MachOView中TEXT和DATA
常見section

3辉浦、優(yōu)化建議

啟動的過程一般是指點(diǎn)擊App圖標(biāo)開始到AppDelegate 的didFinishLaunching

  • 冷啟動:內(nèi)存中不包含App相關(guān)數(shù)據(jù),一般可以通過重啟手機(jī)來實(shí)現(xiàn)冷啟動
  • 熱啟動:殺掉App進(jìn)程后掌猛,數(shù)據(jù)依然存在內(nèi)存中時(shí)啟動

啟動優(yōu)化一般指在冷啟動情況下主要分為兩部分:

  • main函數(shù)開始之前 (pre-main): 操作系統(tǒng)加載App可執(zhí)行文件到內(nèi)存盏浙,執(zhí)行一系列的加載和鏈接操作,也就是dyld加載過程
  • main函數(shù)開始:從main函數(shù)開始到AppDelegate 的didFinishLaunching方法執(zhí)行為止荔茬,加載渲染第一個(gè)界面

main函數(shù)之前

在之前的文章已經(jīng)了解過dyld加載流程
我們可以通過Edit Scheme -> Run -> Arguments ->Environment Variables->點(diǎn)擊+添加環(huán)境變量DYLD_PRINT_STATISTICS == 1

檢測啟動時(shí)間

pre-main啟動時(shí)間
上圖中pre-main階段總共用時(shí)1.7s
  • dylib loading:動態(tài)庫加載耗時(shí)

  • rebase/binding:偏移修正和符號綁定

    • rebase:每個(gè)app的二進(jìn)制文件內(nèi)部的方法废膘、函數(shù)調(diào)用,都有一個(gè)偏移地址,每次app啟動慕蔚,系統(tǒng)會分配一個(gè)ALSR新啼,例如韩脑,二進(jìn)制文件中有一個(gè) test方法,偏移值是0x0001,而隨機(jī)分配的ASLR是0x1f00虐沥,如果想訪問test方法灵份,其內(nèi)存地址(即真實(shí)地址)變?yōu)?ASLR+偏移值 = 運(yùn)行時(shí)確定的內(nèi)存地址(即0x1f00+0x0001 = 0x1f01)
    • binding綁定就是給符號賦值的過程扬跋。方法脆栋、函數(shù)在編譯時(shí)期會創(chuàng)建一個(gè)對應(yīng)的符號指向一個(gè)隨機(jī)地址,在運(yùn)行時(shí)(磁盤加載到內(nèi)存中鸠匀,是一個(gè)鏡像文件)蕉斜,會將真正的內(nèi)存地址和符號進(jìn)行綁定(即dyld將內(nèi)存地址和符號綁定,也稱動態(tài)庫符號綁定)
  • Objc setup:oc類注冊的耗時(shí),類越多宅此,耗時(shí)越長机错,swift沒有這個(gè)

  • initializer:執(zhí)行l(wèi)oad和構(gòu)造函數(shù)的耗時(shí)

優(yōu)化
  • 少用外部動態(tài)庫,蘋果官方建議自定義動態(tài)庫數(shù)量不要超過6個(gè)父腕,如果超過需要進(jìn)行動態(tài)庫合并

  • 減少OC類使用

  • 將不必須在+load方法中做的事情延遲到+initialize中弱匪,盡量不要用C++虛函數(shù)

  • 如果是swift,盡量使用struct

main函數(shù)開始后

  • 減少啟動初始化的流程:能懶加載的懶加載璧亮,能延遲的延遲萧诫,能放后臺初始化的放后臺,盡量不要占用主線程的啟動時(shí)間

  • 優(yōu)化代碼邏輯杜顺,去除非必要的代碼邏輯

  • 啟動階段能使用多線程來初始化的财搁,就使用多線程

  • 主UI框架盡量使用純代碼構(gòu)建

  • 刪除廢棄的類和方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蘸炸,一起剝皮案震驚了整個(gè)濱河市躬络,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搭儒,老刑警劉巖穷当,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異淹禾,居然都是意外死亡馁菜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門铃岔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汪疮,“玉大人,你說我怎么就攤上這事毁习≈侨拢” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵纺且,是天一觀的道長盏道。 經(jīng)常有香客問我,道長载碌,這世上最難降的妖魔是什么猜嘱? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮嫁艇,結(jié)果婚禮上朗伶,老公的妹妹穿的比我還像新娘。我一直安慰自己步咪,他們只是感情好论皆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般纯丸。 火紅的嫁衣襯著肌膚如雪偏形。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天觉鼻,我揣著相機(jī)與錄音俊扭,去河邊找鬼。 笑死坠陈,一個(gè)胖子當(dāng)著我的面吹牛萨惑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仇矾,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼庸蔼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贮匕?” 一聲冷哼從身側(cè)響起姐仅,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刻盐,沒想到半個(gè)月后掏膏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡敦锌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年馒疹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乙墙。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颖变,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出听想,到底是詐尸還是另有隱情腥刹,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布哗魂,位于F島的核電站肛走,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏录别。R本人自食惡果不足惜朽色,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望组题。 院中可真熱鬧葫男,春花似錦、人聲如沸崔列。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盈咳,卻和暖如春耿眉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鱼响。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工鸣剪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丈积。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓筐骇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親江滨。 傳聞我的和親對象是個(gè)殘疾皇子铛纬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容