iOS隨筆之系統(tǒng)架構(gòu)與App啟動過程

最近的狀態(tài)嘚吧嘚

最近工作有些忙碌,不停追趕著迭代,修復(fù)問題填坑…已經(jīng)不記得上次自己寫文章是什么時候了,導(dǎo)致之前的賬號都忘了,好在一直有做筆記的習(xí)慣。知識在于積累和反復(fù),無論多么成熟的開發(fā)人員都有遺漏或者淡忘一些時間久不用或者不在意的東西。


回顧下iOS系統(tǒng)架構(gòu)

iOS 系統(tǒng)是基于 ARM 架構(gòu)的,大致可以分為四層:?

1.最上層是用戶體驗層,主要是提供用戶界面。這一層包含了 SpringBoard用戶GUI管理界面、Spotlight、Accessibility輔助功能。

2.第二層是應(yīng)用框架層,是開發(fā)者會用到的,這一層包含了開發(fā)框架 Cocoa Touch。

3.第三層是核心框架層,是系統(tǒng)核心功能的框架層。這一層包含了各種圖形和媒體核心框架、Metal 等。

4.第四層是 Darwin 層,是操作系統(tǒng)的核心,屬于操作系統(tǒng)的內(nèi)核態(tài)。這一層包含了系統(tǒng)內(nèi)核 XNU、驅(qū)動等。

iOS 系統(tǒng)架構(gòu)極

用戶體驗層、應(yīng)用框架層和核心框架層,屬于用戶態(tài),是上層 App 的活動空間。Darwin 是用戶態(tài)的下層支撐,是 iOS 系統(tǒng)的核心。

Darwin 的內(nèi)核是 XNU,而 XNU 是在 UNIX 的基礎(chǔ)上做了很多改進(jìn)以及創(chuàng)新。了解 XNU 的內(nèi)部是怎么樣的,將有助于我們解決系統(tǒng)層面的問題。

Darwin 歷史

Darwin: (部分開源)基于喬布斯的:OpenStep

OpenStep:及其前身NextStep則是衍生與加州大學(xué)伯克利分校所發(fā)布的Berkeley Software Distribution(BSD).

Darwin內(nèi)核XNU: 結(jié)合了BSD 與 Mach,以及蘋果自己的一些科技研發(fā)出來的。

早期的 BSD 是 UNIX 衍生出的操作系統(tǒng),現(xiàn)在 BSD 是類 UNIX 操作系統(tǒng)的統(tǒng)稱。XNU 的 BSD 來源于 FreeBSD 內(nèi)核,經(jīng)過深度定制而成。IEEE 為了保證軟件可以在各個 UNIX 系統(tǒng)上運行而制定了 POSIX 標(biāo)準(zhǔn),iOS 也是通過 BSD 對 POSIX 的兼容而成為了類 UNIX 系統(tǒng)。

iOS 6 后,為了增強(qiáng)系統(tǒng)安全,BSD 實行了 ASLR(Address Space Layout Randomization,地址空間布局隨機(jī)化)。隨著 iPhone 硬件升級,為了更好地利用多核,BSD 加入了工作隊列,以支持多核多線程處理,這也是 GCD 能更高效工作的基礎(chǔ)。 BSD 還從 TrustdBSD 引入了 MAC 框架以增強(qiáng)權(quán)限 entitlement 機(jī)制的安全。

Darwin主要組件:

BSD

Mach : 最底層為(BSD和I/O kit提供服務(wù)

I/O Kit : 面向?qū)ο蟮脑O(shè)備驅(qū)動框架

Platform Expert

libkern

libs

XNU內(nèi)核是一個混合內(nèi)核,它的核心是一個叫Mach的微內(nèi)核,Mach中也是消息傳遞機(jī)制,但是它使用的是指針形式傳遞,因為大部分服務(wù)都在XNU內(nèi)核中,所以Mach沒有昂貴的復(fù)制操作,只用指針就可以完成消息傳遞。

微內(nèi)核可以提高系統(tǒng)的模塊化程度,提供內(nèi)存保護(hù)的消息傳遞機(jī)制;

宏內(nèi)核也可以叫單內(nèi)核,在出現(xiàn)高負(fù)荷狀態(tài)時依然能夠讓系統(tǒng)保持高效運作。

Mach 是對內(nèi)核運作方式的一次探索創(chuàng)新。Mach 提出了“微內(nèi)核”的概念——將系統(tǒng)內(nèi)核的部分任務(wù)交給用戶層進(jìn)程處理。(Mach 可以認(rèn)為是微內(nèi)核的 BSD 系統(tǒng))

XNU(“X is Not UNIX”)

Mach負(fù)責(zé) XNU 比較底層的任務(wù)。如:

-搶占式多任務(wù),包括內(nèi)核線程(Mac OS X用內(nèi)核線程實現(xiàn)POSIX線程)

-內(nèi)存保護(hù)

-虛擬內(nèi)存管理

-進(jìn)程間通信

-中斷管理

-實時支持

-內(nèi)核調(diào)試支持

-控制臺I/O

下面詳細(xì)聊下 XNU 的架構(gòu),看看它的內(nèi)部到底都包含了了哪些

Darwin

其中,Mach是作為 UNIX 內(nèi)核的替代,主要解決 UNIX 一切皆文件導(dǎo)致抽象機(jī)制不足的問題,為現(xiàn)代操作系統(tǒng)做了進(jìn)一步的抽象工作。 Mach 負(fù)責(zé)操作系統(tǒng)最基本的工作,包括進(jìn)程和線程抽象、處理器調(diào)度、進(jìn)程間通信、消息機(jī)制、虛擬內(nèi)存管理、內(nèi)存保護(hù)等。

進(jìn)程對應(yīng)到 Mach 是 Mach Task,Mach Task 可以看做是線程執(zhí)行環(huán)境的抽象,包含虛擬地址空間、IPC 空間、處理器資源、調(diào)度控制、線程容器。

進(jìn)程在 BSD 里是由 BSD Process 處理,BSD Process 擴(kuò)展了 Mach Task,增加了進(jìn)程 ID、信號信息等,BSD Process 里面包含了擴(kuò)展 Mach Thread 結(jié)構(gòu)的 Uthread。

Mach 的模塊包括進(jìn)程和線程都是對象,對象之間不能直接調(diào)用,只能通過 Mach Msg 進(jìn)行通信,也就是 mach_msg() 函數(shù)。在用戶態(tài)的那三層中,也就是在用戶體驗層、應(yīng)用框架層和核心框架層中,你可以通過 mach_msg_trap() 函數(shù)觸發(fā)陷阱,從而切至 Mach,由 Mach 里的 mach_msg() 函數(shù)完成實際通信,具體實現(xiàn)可以參看 NSHipster 的這篇文章“Inter-Process Communication”。

每個 Mach Thread 表示一個線程,是 Mach 里的最小執(zhí)行單位。Mach Thread 有自己的狀態(tài),包括機(jī)器狀態(tài)、線程棧、調(diào)度優(yōu)先級(有 128 個,數(shù)字越大表示優(yōu)先級越高)、調(diào)度策略、內(nèi)核 Port、異常 Port。

Mach Thread 既可以由 Mach Task 處理,也可以擴(kuò)展為 Uthread,通過 BSD Process 處理。這是因為 XNU 采用的是微內(nèi)核 Mach 和 宏內(nèi)核 BSD 的混合內(nèi)核,具備微內(nèi)核和宏內(nèi)核的優(yōu)點。

Mach 是微內(nèi)核,可以將操作系統(tǒng)的核心獨立在進(jìn)程上運行,不過,內(nèi)核層和用戶態(tài)各層之間切換上下文和進(jìn)程間消息傳遞都會降低性能。為了提高性能,蘋果深度定制了 BSD 宏內(nèi)核,使其和 Mach 混合使用。

宏內(nèi)核 BSD 是對 Mach 封裝,提供進(jìn)程管理、安全、網(wǎng)絡(luò)、驅(qū)動、內(nèi)存、文件系統(tǒng)(HFS+)、網(wǎng)絡(luò)文件系統(tǒng)(NFS)、虛擬文件系統(tǒng)(VFS)、POSIX(Portable Operating System Interface of UNIX,可移植操作系統(tǒng)接口)兼容。

BSD 提供了更現(xiàn)代、更易用的內(nèi)核接口,以及 POSIX 的兼容,比如通過擴(kuò)展 Mach Task 進(jìn)程結(jié)構(gòu)為 BSD Process。對于 Mach 使用 mach_msg_trap() 函數(shù)觸發(fā)陷阱來處理異常消息,BSD 則在異常消息機(jī)制的基礎(chǔ)上建立了信號處理機(jī)制,用戶態(tài)產(chǎn)生的信號會先被 Mach 轉(zhuǎn)換成異常,BSD 將異常再轉(zhuǎn)換成信號。對于進(jìn)程和線程,BSD 會構(gòu)建 UNIX 進(jìn)程模型,創(chuàng)建 POSIX 兼容的線程模型 pthread。

除了微內(nèi)核 Mach 和宏內(nèi)核 BSD 外,XNU 還有 IOKit。IOKit 是硬件驅(qū)動程序的運行環(huán)境,包含電源、內(nèi)存、CPU 等信息。IOKit 底層 libkern 使用 C++ 子集 Embedded C++ 編寫了驅(qū)動程序基類,比如 OSObject、OSArray、OSString 等,新驅(qū)動可以繼承這些基類來寫。

了解了 XNU 后,接下來,我再跟你聊聊 XNU 怎么加載 App 的?

XNU 怎么加載 App?

iOS 的可執(zhí)行文件和動態(tài)庫都是 Mach-O 格式,所以加載 APP 實際上就是加載 Mach-O 文件。

Mach-O header 信息結(jié)構(gòu)代碼如下:

struct mach_header_64 {

? ?uint32_t? ? ? ? magic;? ? ? // 64位還是 32 位

? ? cpu_type_t? ? ? cputype;? ? // CPU類型,比如 arm 或 X86

? ? cpu_subtype_t? cpusubtype; // CPU子類型,比如 armv8

? ? uint32_t? ? ? ? filetype;? //文件類型

? ? uint32_t? ? ? ? ncmds;? ? ? // load commands的數(shù)量

? ? uint32_t? ? ? ? sizeofcmds; // load commands大小

? ? uint32_t? ? ? ? flags;? ? ? //標(biāo)簽

? ? uint32_t? ? ? ? reserved;? //保留字段

};

如上面代碼所示,包含了表示是 64 位還是 32 位的 magic、CPU 類型 cputype、CPU 子類型 cpusubtype、文件類型 filetype、描述文件在虛擬內(nèi)存中邏輯結(jié)構(gòu)和布局的 load commands 數(shù)量和大小等文件信息。

其中,文件類型 filetype 表示了當(dāng)前 Mach-O 屬于哪種類型。Mach-O 包括以下幾種類型。

OBJECT,指的是 .o 文件或者 .a 文件;

EXECUTE,指的是 IPA 拆包后的文件;

DYLIB,指的是 .dylib 或 .framework 文件;

DSYM,指的是保存有符號信息用于分析閃退信息的文件(平時診斷crash信息會用到)。

整個 fork 進(jìn)程,加載解析 Mach-O 文件的過程可以在 XNU 的源代碼中查看,代碼路徑是 darwin-xnu/bsd/kern/kern_exec.c,kern_exec.c,相關(guān)代碼在 __mac_execve 函數(shù)里DYLINKER,指的是動態(tài)鏈接器;

加載 Mach-O 文件,內(nèi)核會 fork 進(jìn)程,并對進(jìn)程進(jìn)行一些基本設(shè)置,比如為進(jìn)程分配虛擬內(nèi)存、為進(jìn)程創(chuàng)建主線程、代碼簽名等。用戶態(tài) dyld 會對 Mach-O 文件做庫加載和符號解析。

int __mac_execve(proc_t p, struct __mac_execve_args *uap, int32_t *retval)

{

? ? // 字段設(shè)置

? ? ...

? ? int is_64 = IS_64BIT_PROCESS(p);

? ? struct vfs_context context;

? ? struct uthread? *uthread; // 線程

? ? task_t new_task = NULL;? // Mach Task

? ? ...

? ? context.vc_thread = current_thread();

? ? context.vc_ucred = kauth_cred_proc_ref(p);

? ? // 分配大塊內(nèi)存,不用堆棧是因為 Mach-O 結(jié)構(gòu)很大。

? ? MALLOC(bufp, char *, (sizeof(*imgp) + sizeof(*vap) + sizeof(*origvap)), M_TEMP, M_WAITOK | M_ZERO);

? ? imgp = (struct image_params *) bufp;

? ? // 初始化 imgp 結(jié)構(gòu)里的公共數(shù)據(jù)

? ? ...

? ? uthread = get_bsdthread_info(current_thread());

? ? if (uthread->uu_flag & UT_VFORK) {

? ? ? ? imgp->ip_flags |= IMGPF_VFORK_EXEC;

? ? ? ? in_vfexec = TRUE;

? ? } else {

? ? ? ? // 程序如果是啟動態(tài),就需要 fork 新進(jìn)程

? ? ? ? imgp->ip_flags |= IMGPF_EXEC;

? ? ? ? // fork 進(jìn)程

? ? ? ? imgp->ip_new_thread = fork_create_child(current_task(),

? ? ? ? ? ? ? ? ? ? NULL, p, FALSE, p->p_flag & P_LP64, TRUE);

? ? ? ? // 異常處理

? ? ? ? ...

? ? ? ? new_task = get_threadtask(imgp->ip_new_thread);

? ? ? ? context.vc_thread = imgp->ip_new_thread;

? ? }

? // 加載解析 Mach-O

? ? error = exec_activate_image(imgp);

? ?if (imgp->ip_new_thread != NULL) {

? ? ? ? new_task = get_threadtask(imgp->ip_new_thread);

? ?}

? ? if (!error && !in_vfexec) {

? ? ? ? p = proc_exec_switch_task(p, current_task(), new_task, imgp->ip_new_thread);

? ? ? ? should_release_proc_ref = TRUE;

? ? }

? ? kauth_cred_unref(&context.vc_ucred);

? ? if (!error) {

? ? ? ? task_bank_init(get_threadtask(imgp->ip_new_thread));

? ? ? ? proc_transend(p, 0);

? ? ? ? thread_affinity_exec(current_thread());

? ? ? ? // 繼承進(jìn)程處理

? ? ? ? if (!in_vfexec) {

? ? ? ? ? ? proc_inherit_task_role(get_threadtask(imgp->ip_new_thread), current_task());

? ? ? ? }

? ? ? ? // 設(shè)置進(jìn)程的主線程

? ? ? ? thread_t main_thread = imgp->ip_new_thread;

? ? ? ? task_set_main_thread_qos(new_task, main_thread);

? ? }

? ? ...

}

小結(jié)

總體來說,XNU 加載就是為 Mach-O 創(chuàng)建一個新進(jìn)程,建立虛擬內(nèi)存空間,解析 Mach-O 文件,最后映射到內(nèi)存空間。流程可以概括為:

fork 新進(jìn)程;

為 Mach-O 分配內(nèi)存;

解析 Mach-O;

讀取 Mach-O 頭信息;

遍歷 load command 信息,將 Mach-O 映射到內(nèi)存;

啟動 dyld。

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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