本文參考《Mac OS X and iOS Internals: To the Apple’s Core》 by Jonathan Levin
文章內(nèi)容主要是閱讀這本書的讀書筆記,建議讀者掌握《操作系統(tǒng)》,了解現(xiàn)代操作系統(tǒng)的技術(shù)特點(diǎn),再閱讀本文可以事半功倍。
雖然iOS系統(tǒng)內(nèi)核使用極簡(jiǎn)的微內(nèi)核架構(gòu),但內(nèi)容依然十分龐大,所以會(huì)分
系統(tǒng)架構(gòu)、進(jìn)程調(diào)度、內(nèi)存管理和文件系統(tǒng)四個(gè)部分進(jìn)行闡述。
1 進(jìn)程
進(jìn)程是獨(dú)立運(yùn)行、獨(dú)立分配資源和獨(dú)立接受調(diào)度的基本單位。進(jìn)程有三個(gè)基本狀態(tài)。
就緒狀態(tài)
當(dāng)進(jìn)已分配到除CPU外的所有必要資源后,只要再獲得CPU,便可立即執(zhí)行,進(jìn)程這時(shí)的狀態(tài)稱為就緒狀態(tài)。在系統(tǒng)中處于就緒狀態(tài)的進(jìn)程往往會(huì)有多個(gè),通常將這些進(jìn)程存入一個(gè)隊(duì)列中,稱為就緒隊(duì)列。執(zhí)行狀態(tài)
進(jìn)程已獲得CPU,其程序正在執(zhí)行。
- 睡眠狀態(tài)(阻塞狀態(tài))
正在執(zhí)行的進(jìn)程由于某些事件暫時(shí)無法繼續(xù)執(zhí)行,便放棄CPU占用轉(zhuǎn)入暫停。阻塞狀態(tài)的進(jìn)程也會(huì)排入隊(duì)列中,現(xiàn)代操作系統(tǒng)會(huì)根據(jù)阻塞原因的不同將處于阻塞狀態(tài)的進(jìn)程排入多個(gè)隊(duì)列。導(dǎo)致阻塞的事件有:請(qǐng)求I/O,申請(qǐng)緩沖空間。
在iOS中進(jìn)程通過Progress ID(進(jìn)程ID,即PID)來唯一辨識(shí)。進(jìn)程還會(huì)將其和父進(jìn)程的親屬關(guān)系保存在父進(jìn)程ID(Parent Progress ID, PPID)中。父進(jìn)程可以通過fork(或通過posix_spawn)創(chuàng)建子進(jìn)程,并且預(yù)期子進(jìn)程會(huì)消亡。子進(jìn)程返回的整數(shù)由其父進(jìn)程收集。
1.1 iOS進(jìn)程生命周期
上文提到Darwin是雙內(nèi)核系統(tǒng),由Mach 和 BSD兩個(gè)部分組成,所以iOS的進(jìn)程就需要理解兩個(gè)概念,BSD 進(jìn)程和Mach 任務(wù)。
1.2 BSD 進(jìn)程 Process
BSD 的進(jìn)程可以唯一地映射到Mach 任務(wù),但是包含的信息比Mach任務(wù)提供的基本調(diào)度和統(tǒng)計(jì)信息要豐富。BSD 進(jìn)程包含了文件描述符和信號(hào)處理程序的數(shù)據(jù)。進(jìn)程還支持復(fù)雜的譜系,將進(jìn)程和其父進(jìn)程、兄弟進(jìn)程和子進(jìn)程連接起來。BSD 在struct proc 中維護(hù)了進(jìn)程的很多特性,struct porc擁有許多字段,因此需要多個(gè)鎖來保護(hù)不同的字段,以及字段參與的列表。進(jìn)程鎖保護(hù)整個(gè)數(shù)據(jù)結(jié)構(gòu),還有一個(gè)線程自旋鎖、一個(gè)文件描述符鎖以及其他保護(hù)進(jìn)程組合兄弟進(jìn)程的鎖。
1.3 Mach 任務(wù) task
Mach 并不關(guān)心進(jìn)程,而是使用了比進(jìn)程更輕量級(jí)的概念:任務(wù)(task)。經(jīng)典的UNIX采用了自上而下的方式:最基本的對(duì)象是進(jìn)程,然后進(jìn)一步劃分一個(gè)或多個(gè)線程。而Mach 采用自底向上的方式,最基本的單元是線程,一個(gè)或多個(gè)線程包含在一個(gè)任務(wù)中。
任務(wù)是一種容器對(duì)象,虛擬內(nèi)存空間和其他資源都是通過這個(gè)容器對(duì)象管理的,這些資源包括設(shè)備和其他句柄。資源進(jìn)一步被抽象為端口。因而資源的共享實(shí)際上相當(dāng)于允許對(duì)對(duì)應(yīng)端口的訪問。
嚴(yán)格地說,Mach 的任務(wù)并不是其他操作系統(tǒng)中所謂的進(jìn)程,因?yàn)镸ach 作為一個(gè)微內(nèi)核的操作系統(tǒng),并沒有提供“進(jìn)程”的邏輯,而只是提供了最基本的實(shí)現(xiàn)。任務(wù)是沒有生命的。任務(wù)存在的目的就是稱為一個(gè)或多個(gè)線程的容器。任務(wù)中的線程都在threads成員中維護(hù),這是一個(gè)包含thread_count個(gè)線程的隊(duì)列。此外,大部分對(duì)任務(wù)的操作實(shí)際上就是遍歷給定任務(wù)中的所有線程,并對(duì)這些線程進(jìn)行對(duì)應(yīng)的線程操作。
1.4 BSD 進(jìn)程和Mach 任務(wù)的關(guān)系
這兩個(gè)概念是一對(duì)一的映射關(guān)系,每一個(gè)BSD 進(jìn)程都在底層關(guān)聯(lián)了一個(gè)Mach 任務(wù)對(duì)象。實(shí)現(xiàn)這種映射的方法是指定一個(gè)透明的指針bsd_info,Mach 對(duì)bsd_info 完全無知。Mach 將內(nèi)核也用任務(wù)表示。
2 線程 Thread
線程是利用CPU的基本單位,進(jìn)程是占有資源的基本單位。為了最大化利用進(jìn)程時(shí)間片的方法,引入線程的概念。通過使用多個(gè)線程,程序的指向可以分割表面上看上去并發(fā)執(zhí)行的子任務(wù)。線程之間切換的開銷比較小,只要保存和恢復(fù)寄存器即可。多核處理器更是特別和適合線程,因?yàn)槎鄠€(gè)處理器核心共享同樣的cache和ARM,為線程間的共享虛擬內(nèi)存提供了基礎(chǔ)。一般一個(gè)進(jìn)程會(huì)包括多個(gè)線程
線程是Mach中的最小的執(zhí)行單元。線程表示的是底層的機(jī)器寄存器狀態(tài)以及各種調(diào)度統(tǒng)計(jì)數(shù)據(jù)。線程從設(shè)計(jì)上提供了所需要的大量信息,同時(shí)又盡可能地維持最小開銷。
在Mach中線程的數(shù)據(jù)結(jié)構(gòu)非常巨大,因此大部分的線程創(chuàng)建時(shí)都是從一個(gè)通用的模板復(fù)制而來的,這個(gè)模板使用默認(rèn)值填充這個(gè)數(shù)據(jù)結(jié)構(gòu),這個(gè)模板名為thread_template,內(nèi)核引導(dǎo)過程中被調(diào)用的thread_bootstrap( )負(fù)責(zé)填充這個(gè)模板。thread_create_internal( )函數(shù)分配新的線程數(shù)據(jù)結(jié)構(gòu),然后將換這個(gè)模板的內(nèi)容負(fù)責(zé)到新的線程數(shù)據(jù)結(jié)構(gòu)中。
Mach API thread_create( ) 就是通過thread_create_internal( )實(shí)現(xiàn)的。
3 調(diào)度 Scheduling
多進(jìn)程程序系統(tǒng)中,作業(yè)被提交后,必須經(jīng)過處理機(jī)調(diào)度,才能被處理機(jī)執(zhí)行。進(jìn)程調(diào)度主要有兩種方式,非搶占式和搶占式。現(xiàn)在面向用戶的操作系統(tǒng)基本上都采用搶占式調(diào)度方式,包括iOS。主要的搶占原則有:
- 優(yōu)先權(quán)原則
- 短作業(yè)優(yōu)先原則
- 時(shí)間片原則
由于Mach具有處理器集的抽象,所以 Mach 比Linux 和 Windows 更擅長管理多核處理器:Mach 可以將同一個(gè)CPU 的多個(gè)核心放在同一個(gè)pset管理,并且通過不同的pset管理不同的CPU。
3.1 Mach 調(diào)度器特性
3.1.1 控制權(quán)轉(zhuǎn)交
允許一個(gè)線程主動(dòng)放棄CPU,但不是將CPU放棄給任何其他線程,而是將CPU轉(zhuǎn)交給自己選擇的某個(gè)特定的線程。由于Mach 是一個(gè)基于消息傳遞的內(nèi)核,線程之間通過消息傳遞通訊,所以這項(xiàng)特性在Mach 中特別有用。通過這個(gè)特性,消息的處理延遲可以達(dá)到最小,而不需要投機(jī)地等待消息處理線程(發(fā)送者或接收者)下一次得到調(diào)度。這個(gè)特性是Mach特有的。
3.1.2 使用續(xù)體
可以使線程不用管理自己的棧,線程可以丟棄自己的棧,系統(tǒng)恢復(fù)線程執(zhí)行時(shí)不需要恢復(fù)線程的棧。續(xù)體是緩解上下文切換開銷的簡(jiǎn)單有效的機(jī)制。這個(gè)特性是Mach特有的。
3.1.3 異步軟件陷阱 Asynchronous Software Trap,AST
是軟件對(duì)底層硬件陷阱機(jī)制的補(bǔ)充完善,通過使用AST,內(nèi)核可以響應(yīng)需要得到關(guān)注的out-off-band事件,例如調(diào)度事件。
AST是人工引發(fā)的非硬件觸發(fā)的陷阱。AST是內(nèi)核操作的關(guān)鍵部分,而且是調(diào)度時(shí)間的底層機(jī)制,也是BSD信號(hào)的實(shí)現(xiàn)基礎(chǔ)。AST實(shí)現(xiàn)為線程控制塊中一個(gè)包含各種標(biāo)志位的字段,這些標(biāo)志位可以通過thread_ast_set( )分別設(shè)置。這個(gè)特性是Mach特有的。
3.1.4 上下文切換(content switch)
上下文切換是暫停某個(gè)線程的執(zhí)行,并且將其寄存器狀態(tài)記錄在某個(gè)預(yù)定義的內(nèi)存位置中。寄存器狀態(tài)是和及其相關(guān)的。當(dāng)一個(gè)線程被搶占時(shí),CPU 寄存器中會(huì)價(jià)值另一個(gè)線程保存的線程狀態(tài),從而恢復(fù)到那個(gè)線程的執(zhí)行。
一個(gè)線程在CPU上可以執(zhí)行任意長的時(shí)間。執(zhí)行(execute)指的是這樣的一個(gè)事實(shí):CPU 寄存器中填滿了線程的狀態(tài),因此CPU(通過EIP/RIP指令指針或PC程序計(jì)數(shù)器)執(zhí)行該線程函數(shù)的代碼。這個(gè)執(zhí)行會(huì)一直持續(xù),直到發(fā)生下面某種情況:
- 線程終止
- 線程自愿放棄
- 外部中斷打斷了線程的執(zhí)行,外部中斷要求CPU 保存線程狀態(tài)并且立即執(zhí)行中斷處理代碼
3.1.5 優(yōu)先級(jí)
每一個(gè)Mach線程都包含優(yōu)先級(jí)信息,優(yōu)先級(jí)直接影響線程被調(diào)度的頻率。Mach 有128個(gè)優(yōu)先級(jí)。
內(nèi)核線程的最低優(yōu)先級(jí)為80,比用戶態(tài)線程的優(yōu)先級(jí)要高。可以保證內(nèi)核以及用戶維護(hù)管理的線程能夠搶占用戶態(tài)的線程。
3.1.6 優(yōu)先級(jí)偏移
給線程分配優(yōu)先級(jí)只是一個(gè)開頭,這些優(yōu)先級(jí)在運(yùn)行時(shí)常常需要調(diào)整。Mach 會(huì)針對(duì)每一個(gè)線程的CPU 利用率和整體系統(tǒng)負(fù)載動(dòng)態(tài)調(diào)整每一個(gè)線程的優(yōu)先級(jí)。
3.1.7 運(yùn)行隊(duì)列
線程是通過運(yùn)行隊(duì)列管理的。 運(yùn)行隊(duì)列是一個(gè)多層列表,即一個(gè)列表的數(shù)組,針對(duì)128個(gè)優(yōu)先級(jí)中的每一個(gè)優(yōu)先級(jí)都要一個(gè)隊(duì)列。Mach 實(shí)際采用的方法是檢查位圖,這樣就可以同時(shí)檢查32個(gè)隊(duì)列,這樣時(shí)間復(fù)雜度為O(4)。
3.1.8 等待隊(duì)列
當(dāng)進(jìn)程或者線程阻塞,就沒有必要考慮調(diào)度這個(gè)線程,因?yàn)橹挥挟?dāng)線程等待的對(duì)象或I/O 操作完成或時(shí)間發(fā)生時(shí)才能繼續(xù)執(zhí)行。所以可以將線程放在等待隊(duì)列中。當(dāng)?shù)却臈l件滿足之后,一個(gè)或多個(gè)等待的線程可以被解除阻塞并且再次分發(fā)執(zhí)行。
3.1.9 CPU 親緣性
在使用多核、SMP 或 超線程的現(xiàn)代架構(gòu)中,還可以設(shè)置某個(gè)線程和一個(gè)或多個(gè)指定CPU 的親緣性(affinity)。這種親緣性對(duì)于線程和系統(tǒng)來說都是有好處的,因?yàn)楫?dāng)線程回到同一個(gè)CPU上執(zhí)行時(shí),線程的數(shù)據(jù)可能還留在CPU的緩存中,從而提升性能。
在Mach中,線程對(duì)CPU 的親緣性的意思就是綁定。thread_bind( )的目的就是綁定線程,這個(gè)函數(shù)僅僅是更新thread_t的bound_processor字段。如果這個(gè)字段被設(shè)置為PROCESSOR_NULL之外的任何值,那么未來的調(diào)度策略就會(huì)將這個(gè)線程分發(fā)到對(duì)應(yīng)處理器的運(yùn)行隊(duì)列。
3.2 Mach調(diào)度搶占模式
顯式搶占
即線程放棄CPU的控制權(quán)或進(jìn)入阻塞的操作,顯式搶占是事先可以預(yù)知的,所以顯式搶占是同步的隱式搶占
這種搶占是由中斷引起的,由于中斷不可預(yù)測(cè)的本身,所有隱式搶占是異步的
3.3 Mach 中斷模式
搶占式操作系統(tǒng)必須具備中斷能力。中斷一般由 CPU 中的一個(gè)特殊組件產(chǎn)生的。這個(gè)組件稱為可編程中斷控制器(Programmable Interrupt Controller,PIC),在更加高級(jí)的CPU中,稱之為高級(jí)可編程中斷控制器(Advanced PIC,APIC)。PIC 接收來自系統(tǒng)總線上的設(shè)備的消息,然后將消息分揀到某一條中斷請(qǐng)求(Interrupt Request,IRQ)線上去。當(dāng)產(chǎn)生中斷的時(shí)候,PIC將相應(yīng)的中斷標(biāo)記為活躍。在這個(gè)中斷被一個(gè)函數(shù)(稱為中斷處理程序或中斷服務(wù)程序)處理或服務(wù)完成之前,這條中斷線一直保持活躍狀態(tài)。處理這個(gè)中斷的函數(shù)要負(fù)責(zé)重置這條線的狀態(tài)。
Mach的調(diào)度是由中斷驅(qū)動(dòng)的,對(duì)于搶占式多任務(wù)系統(tǒng)來說,必須有某種機(jī)制允許調(diào)度器能夠首先得到CPU的控制權(quán),從而搶占當(dāng)前正在執(zhí)行的線程,然后才能執(zhí)行調(diào)度算法,并且通過調(diào)度算法決定當(dāng)前的線程可以繼續(xù)恢復(fù)執(zhí)行還是要搶奪其 CPU 給更重要的線程使用。為了能夠從當(dāng)前運(yùn)行的線程搶奪CPU,現(xiàn)在的操作系統(tǒng)利用了現(xiàn)有的硬件中斷機(jī)制。由于中斷的特點(diǎn)是強(qiáng)迫CPU在發(fā)生中斷時(shí)“放下手中所有的任務(wù)”,并longjmp 跳轉(zhuǎn)到中斷處理程序(也稱為中斷服務(wù)例程(interrupt service routinr,ISR))執(zhí)行,因此可以通過中斷機(jī)制在發(fā)生中斷時(shí)運(yùn)行調(diào)度器。
3.4 Mach 調(diào)度算法
調(diào)度算法是模塊化的,系統(tǒng)引導(dǎo)時(shí)可以動(dòng)態(tài)設(shè)置調(diào)度器(使用sched引導(dǎo)參數(shù))。不過實(shí)際中只用了一個(gè)調(diào)度器
Mach 的線程調(diào)度算法高度可擴(kuò)展,而且運(yùn)行更換用于線程調(diào)度的算法。通常情況下,只啟用了一個(gè)調(diào)度器。但是Mach的架構(gòu)運(yùn)行定義額外的調(diào)度器,并且在編譯時(shí)根據(jù)CONFIG_SCHED_的定義設(shè)置調(diào)度器。每一個(gè)調(diào)度器對(duì)象都維護(hù)一個(gè)sched_dispatch_table 數(shù)據(jù)結(jié)構(gòu),其中以函數(shù)指針的方式保存了各種操作。一個(gè)全局表sched_current_dispatch保存了當(dāng)前活動(dòng)的調(diào)度算法,并且允許運(yùn)行時(shí)切換調(diào)度器。所有的調(diào)度器都必須實(shí)現(xiàn)相同的字段,通用的調(diào)度邏輯可以通過SCHED宏訪問這些字段。
4 IPC
Inter-Process Communication 是指進(jìn)程間的信息交換,所交換的信息量,少者是一個(gè)狀態(tài)或數(shù)值,多者則是成千上萬字節(jié)。IPC的方式主要有共享存儲(chǔ)器、消息系統(tǒng)、管道通信。
iOS的IPC核心機(jī)制是消息,在Mach中一切以消息為媒介。
4.1 Mach 內(nèi)核設(shè)計(jì)原則
Mach 采用的是極簡(jiǎn)主義:具有一個(gè)簡(jiǎn)單最小的核心,支持面向?qū)ο蟮哪P停沟锚?dú)立的具有良好定義的組件,實(shí)際上就是子系統(tǒng)。可以通過消息的方式互相通訊。在Mach 中,所有的東西都是通過自己的對(duì)象實(shí)現(xiàn)的。進(jìn)程(在Mach 中稱為任務(wù))、線程、虛擬內(nèi)存都是對(duì)象,所有對(duì)象都有自己的屬性。
Mach 對(duì)象就是C語言結(jié)構(gòu)體加上函數(shù)指針。Mach 的獨(dú)特之處在于選擇了通過消息傳遞的方式實(shí)現(xiàn)對(duì)象和對(duì)象之間的通信。XNU 的“官方”API 是BSD 的POSIX API,蘋果保持Mach絕對(duì)的極簡(jiǎn)。由于外層具有非常豐富的Cocoa API,所以很多開發(fā)者都根本意識(shí)不到Mach的存在。不過,Mach調(diào)用仍然是整個(gè)架構(gòu)中最基礎(chǔ)的部分。
4.2 Mach 消息
Mac 中最基本的概念就是消息,消息在端口(port)之間傳遞。消息是Mach IPC 的核心構(gòu)建塊。Mach 消息的設(shè)計(jì)考慮了參數(shù)串行話、對(duì)齊、填充和字節(jié)順序的問題。
Mach 消息的發(fā)送和接收都是通過同一個(gè)API函數(shù) mach_msg( )進(jìn)行的。
這個(gè)函數(shù)在用戶態(tài)和內(nèi)核中都有實(shí)現(xiàn)的。
Mach 消息原本是為真正的微內(nèi)核架構(gòu)而設(shè)計(jì)的。也就是說,mach_msg( )函數(shù)必須在發(fā)送者和接收者之間復(fù)制消息所在的內(nèi)存。盡管這種實(shí)現(xiàn)忠實(shí)于微內(nèi)核的范式,但是事實(shí)證明頻繁內(nèi)存復(fù)制操作帶來的性能損耗是不能忍受的,因此,XNU 將所有的內(nèi)核組件都共享一個(gè)地址空間,因此消息傳遞只需要傳遞消息的指針就可以了,從而省去了昂貴的內(nèi)存復(fù)制操作。
為了實(shí)現(xiàn)消息的發(fā)送和接收,mach_msg( ) 函數(shù)調(diào)用了一個(gè)Mach 陷阱(trap)。Mach 陷阱就是和系統(tǒng)調(diào)用的概念,在用戶態(tài)調(diào)用mach_msg_trap( ) 會(huì)引發(fā)陷阱機(jī)制,切換到內(nèi)核態(tài),在內(nèi)核態(tài)中,內(nèi)核實(shí)現(xiàn)的mach_msg( ) 會(huì)完成實(shí)際的工作。
消息在口之間傳遞,端口是32位整型標(biāo)識(shí)符。所有的mach 原生對(duì)象都是通過對(duì)于的端口訪問的。也就是說,查找一個(gè)對(duì)象的句柄時(shí),實(shí)際上請(qǐng)求的是這個(gè)對(duì)象端口的句柄。
4.3 Mach消息傳遞機(jī)制
用戶態(tài)的Mach消息傳遞使用mach_msg( )函數(shù)。
這個(gè)函數(shù)通過內(nèi)核的Mach 陷阱機(jī)制調(diào)用內(nèi)核函數(shù)mach_msg_trap( ) 。然后mach_msg_trap( )調(diào)用 mach_msg_overwrite_trap( ),mach_msg_overwrite_trap( )
通過測(cè)試MACH_SEND_MSG和MACH_REV_MSG標(biāo)志位來判斷發(fā)送操作還是接收操作
4.4 消息發(fā)送實(shí)現(xiàn)
Mach 消息發(fā)送的邏輯在內(nèi)核中的兩處實(shí)現(xiàn):Mach_msg_overwrite_trap( ) 和 mach_msg_send( )。后者只用于內(nèi)核態(tài)的消息傳遞,在用戶態(tài)不可見。
兩種情形的邏輯都差不多,遵循以下的流程:
- 調(diào)用current_space( ) 獲得當(dāng)前的IPC空間
- 調(diào)用current_map( ) 獲得的當(dāng)前的VM空間(vm_map)
- 對(duì)消息的大小進(jìn)行正確性檢查
- 計(jì)算要分配的消息大小:從send_size參數(shù)獲得大小,然后加上硬編碼的MAX_REAILER_SIZE
- 通過ipc_kmsg_alloc 分配消息
- 復(fù)制消息(復(fù)制消息send_size字節(jié)的部分),然后在消息頭設(shè)置msgh_size
- 復(fù)制消息關(guān)聯(lián)的端口權(quán)限,然后通過ipc_kmsg_copyin 將所有out-of-line 數(shù)據(jù)內(nèi)存復(fù)制到當(dāng)前的vm_map。ipc_kmsg_copyin 函數(shù)調(diào)用了ipc_kmsg_copyin_header 和 ipc_kmsg_copyin_body
- 調(diào)用ipc_kmsg_send( )發(fā)送消息:
- 首先,獲得msgh_remote_port 引用,并鎖定端口
- 如果端口是一個(gè)內(nèi)核端口(即端口的ip_receiver是內(nèi)核IPC空間),那么通過ipc_kobject_server( ) 函數(shù)處理消息。這個(gè)函數(shù)會(huì)在內(nèi)核中找到相應(yīng)的函數(shù)來執(zhí)行消息(或者調(diào)用ipc_kobject_notify( )來執(zhí)行),而且一個(gè)會(huì)生成消息的應(yīng)答。
- 不論是哪種端口:也就是說如果端口不在內(nèi)核空間中,或者從ipc_kobjct_server( ) 返回了應(yīng)答,這個(gè)函數(shù)會(huì)貫穿到傳遞消息(或應(yīng)答消息)的部分,調(diào)用ipc_mqueue_send( ),這個(gè)函數(shù)將消息直接復(fù)制到端口的ip_messgaes 隊(duì)列中并喚醒任何正在等待的線程
4.5 消息接收實(shí)現(xiàn)
和消息發(fā)送的情形類似,Mach 消息接收的邏輯也是現(xiàn)在內(nèi)核中的兩個(gè)地方,和發(fā)送一樣,mach_msg_overwrite_trap( ) 從用戶態(tài)接收請(qǐng)求,而內(nèi)核態(tài)通過mach_msg_receive( ) 接收消息
- 調(diào)用current_space( ) 獲得當(dāng)前的IPC空間
- 調(diào)用current_map( ) 獲得當(dāng)前的VM控件(vm_map)
- 不對(duì)消息的大小進(jìn)行檢查。這種檢查沒有必要,因?yàn)橄⒃诎l(fā)送時(shí)已經(jīng)驗(yàn)證過了
- 通過調(diào)用ipc_mqueue_copyin( ) 獲得IPC隊(duì)列
- 持有當(dāng)前線程的一個(gè)引用。使用當(dāng)前線程的引用可使它適應(yīng)使用Mach 的續(xù)體(continuation)模型,續(xù)體模型可以避免維護(hù)完整線程棧的必要性
- 調(diào)用ipc_mqueue_receive( )從隊(duì)列中取出消息
- 最后,調(diào)用mach_msg_receive_results( ) 函數(shù)。這個(gè)函數(shù)也可以從續(xù)體中調(diào)用
5 同步機(jī)制 synchronization
為使系統(tǒng)的多線程和進(jìn)程能有條不紊地運(yùn)行,在系統(tǒng)中必須提供用于實(shí)現(xiàn)線程間或者進(jìn)程間同步的機(jī)制。
在Mach系統(tǒng)中主要有以下幾種同步機(jī)制
對(duì)象 | 所有者 | 空可見性 | 等待 |
---|---|---|---|
互斥鎖(lck_mtx_t) | 1個(gè) | 內(nèi)核態(tài) | 阻塞 |
信號(hào)量(semaphore_t) | 多個(gè) | 用戶態(tài) | 阻塞 |
自旋鎖(hw_lock_t等) | 1個(gè) | 內(nèi)核態(tài) | 忙等 |
鎖集(lock_set_t) | 1個(gè) | 用戶態(tài) | 阻塞 |
大部分Mach 同步對(duì)象都不是自己獨(dú)立存在的,而是屬于一個(gè) lck_grp_t 對(duì)象。lck_grp_t 就是一個(gè)鏈表中的一個(gè)元素,帶有一個(gè)給定的名字,以及最多3種鎖的類型:自旋鎖、互斥鎖和讀寫鎖。鎖組還帶有統(tǒng)計(jì)信息(lck_grp_stat_t 數(shù)據(jù)結(jié)構(gòu)),用于調(diào)試和同步相關(guān)的問題。在Mach 和 BSD 中幾乎每一個(gè)子系統(tǒng)在初始化時(shí)都會(huì)創(chuàng)建一個(gè)自己使用的鎖組。
互斥鎖
互斥鎖是最常用的鎖對(duì)象。互斥體定義為lck_mtx_t,互斥鎖必須屬于一個(gè)鎖組。讀寫鎖
互斥鎖有一個(gè)最大的缺點(diǎn),就是一次只能有一個(gè)線程持有鎖。在很多情況下,多個(gè)線程可能對(duì)資源請(qǐng)求只讀的訪問,這些情況下,使用互斥鎖會(huì)阻止并發(fā)訪問。讀寫鎖(read-write lock)就是問題的解決方案。讀寫鎖是個(gè)“更智能”的互斥體,能夠區(qū)分讀訪問和寫訪問。多個(gè)讀者可以同時(shí)持有鎖,而一次只能有一個(gè)寫者可以獲得鎖。自旋鎖
互斥體和信號(hào)量都是阻塞等待的對(duì)象。阻塞等待的意思是說:如果鎖對(duì)象被其他線程持有,那么請(qǐng)求訪問的線程就被加入到等待隊(duì)列中,因而被阻塞。阻塞一個(gè)線程就意味著放棄線程的時(shí)間片,把處理器讓給調(diào)度器認(rèn)為下一個(gè)要執(zhí)行的線程。當(dāng)鎖可用時(shí),調(diào)度器會(huì)得到通知,然后根據(jù)自己的判斷將線程從等待隊(duì)列中取出并重新調(diào)度。然而這個(gè)方式可能會(huì)嚴(yán)重地影響性能,由于在很多情況下,鎖對(duì)象只需要持有短短幾個(gè)周期的時(shí)間,因而造成了兩次或更多次的上下文切換帶來的開銷則要大好幾個(gè)數(shù)量級(jí)。這種情況下,如果線程不是放棄處理器,而是重復(fù)地嘗試訪問鎖對(duì)象可能是更明智的選擇,這種方式稱之為“忙等(busy-wait)”,如果當(dāng)前鎖的持有者確實(shí)在幾個(gè)周期后就放棄鎖了,那么這樣就可以節(jié)省至少兩次上下文切換。當(dāng)然這個(gè)鎖要慎用,否則很可能進(jìn)入一個(gè)非常可怕的死鎖場(chǎng)景,導(dǎo)致整個(gè)系統(tǒng)陷入停滯狀態(tài)。信號(hào)量
Mach 提供了信號(hào)量(semaphore),信號(hào)量是泛化的互斥體。互斥體的值只能是0和1,而信號(hào)量的值這樣的一種互斥體。取值可以達(dá)到某個(gè)正數(shù),即允許并發(fā)持有信號(hào)量的持有者的個(gè)數(shù),換句話說,互斥體可以看成是二值信號(hào)量的特殊情況。信號(hào)量可以在用戶態(tài)使用,而互斥體只能在內(nèi)核態(tài)使用。信號(hào)量本身是一個(gè)不可鎖的對(duì)象。信號(hào)量對(duì)象是一個(gè)很小的結(jié)構(gòu)體,包含指向所有者和端口的引用。此外,還保護(hù)桿一個(gè)wait_queue_t,這是一個(gè)保存正在等待這個(gè)信號(hào)量的線程的鏈表。wait_queue_t會(huì)通過硬件所的方式鎖定。信號(hào)量還有一個(gè)有意思的屬性:信號(hào)量可以轉(zhuǎn)換為端口,也可以由端口轉(zhuǎn)換而來。鎖集
任務(wù)可以在用戶態(tài)使用鎖集。鎖集就是鎖(實(shí)際上就是互斥體)的數(shù)組。通過給定的鎖ID 可以訪問鎖。鎖也可以傳遞給其他線程。交出一個(gè)鎖會(huì)阻塞交出鎖的線程,并喚醒接受鎖的線程。鎖集實(shí)際上是對(duì)內(nèi)核互斥體lck_mtx_t的封裝
6 XPC
XPC是iOS 5引入的輕量級(jí)進(jìn)程間通訊機(jī)制,XPC 和 GCD 結(jié)合的非常緊密,XPC 允許開發(fā)者將應(yīng)用程序分解為獨(dú)立的組件。這樣可以同時(shí)增強(qiáng)應(yīng)用程序的穩(wěn)定性和安全性,因?yàn)椴环€(wěn)定的功能可以包含在一個(gè)XPC服務(wù)中,而XPC服務(wù)可以在外部進(jìn)行管理。
XPC 服務(wù)程序和客戶程序都鏈接了libxpc.dylib,libxpc.dylib 提供了各種各樣的C語言層次的XPC機(jī)制,NSXPCConnection。XPC 還依賴于兩個(gè)私有框架:XPCService 和 XPCObjects。前者負(fù)責(zé)處理XPC服務(wù)運(yùn)行時(shí)相關(guān)的事務(wù),后者為XPC 對(duì)象提供編碼和解碼服務(wù)。iOS 還有一個(gè)包含私有框架 XPCKit。
6.1 XPC 對(duì)象類型
XPC對(duì)各種數(shù)據(jù)進(jìn)行包裝盒序列化,這種方式類似于CoreFoundation框架。任何類型的XPC對(duì)象都可以處理為不透明的類型 xpc_object_t, 并且通過 xpc_object(3)文檔中描述的函數(shù)進(jìn)行操作。這些函數(shù)包括xpc_retain/release、xpc_get_type、xpc_hash(提供對(duì)象的散列值,可以用于數(shù)組索引)、xpc_equal(用于比較對(duì)象)和xpc_copy。
6.2 XPC 消息
對(duì)象可以通過消息來發(fā)送和接收。默認(rèn)情況下消息是異步發(fā)送的,并且通過分發(fā)隊(duì)列(GCD)處理。通過使用屏障(barrier),開發(fā)人員可以指定一個(gè)代碼塊在某個(gè)連接上的所有消息都發(fā)生完成之后執(zhí)行。發(fā)出器的消息可以有應(yīng)答,應(yīng)答也是異步的,通過_reply_sync 函數(shù)可以阻塞直到收到應(yīng)答消息。XPC消息是通過Mach 消息機(jī)制實(shí)現(xiàn)的,并且使用了Mach Interface Genetator(MIG)實(shí)施。后者提供了xpc_domain子系統(tǒng)。xpc_domain 子系統(tǒng)包含用于登記、加載或添加服務(wù)以及獲得服務(wù)名稱的消息。
6.3 xpc_connection_send_message 流程:
6.4 XPC 服務(wù)
XPC 服務(wù)可以通過Objective-C 或C或C++創(chuàng)建。不管通過哪種語言創(chuàng)建,都需要調(diào)用 libxpc.dylib 庫的 xpc_main 函數(shù)開始服務(wù)。C/C++ 的 服務(wù) mian 函數(shù)只不過是一個(gè)簡(jiǎn)單的包裝函數(shù),它調(diào)用xpc_main,并傳入時(shí)間處理函數(shù)(xpc_connection_handler_t)。Objective-C服務(wù)也調(diào)用xpc_main,不過是通過NSXPCConnection_t的resume方法間接調(diào)用的。
事件處理函數(shù)接受單獨(dú)一個(gè)參數(shù):xpc_connection_t(Objective-C 將這個(gè)對(duì)象封裝為Foundation.framework 框架中的 NSXPCConnection)。XPC 連接是一個(gè)不透明的對(duì)象,需要通過xpc_connection_*函數(shù)進(jìn)行操作。
XPC服務(wù)程序的一般架構(gòu)包括:調(diào)用dispatch_queue_create 創(chuàng)建一個(gè)隊(duì)列用于接收來接收自客戶程序的消息,然后通過xpc_connectiona_set_target_queue 將這個(gè)隊(duì)列分配給連接。服務(wù)程序還有設(shè)置連接的時(shí)間處理程序:調(diào)用 xpc_connection_set_event_handle 并提供一個(gè)表示處理程序的代碼執(zhí)行(代碼本身也可以包裝其他函數(shù))。每當(dāng)服務(wù)程序收到一條消息的時(shí)候都會(huì)調(diào)用這個(gè)處理程序。服務(wù)程序可以創(chuàng)建一個(gè)應(yīng)答(通過調(diào)用 xpc_dictionary_create_reply)并將應(yīng)答消息發(fā)送出去。
6.5 XPC實(shí)現(xiàn)代碼
- 發(fā)送代碼
- (void)sendXPC
{
const char *connectionName = "com.moft.XPCService.XPCService";
connection = xpc_connection_create(connectionName, NULL);
xpc_connection_set_event_handler(connection, ^(xpc_object_t object){
double result = xpc_dictionary_get_double(object, "result");
NSLog(@"%f",result);
});
xpc_connection_resume(connection);
xpc_object_t dictionary = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_double(dictionary, "value1", 1.0);
xpc_dictionary_set_double(dictionary, "value2", 2.0);
xpc_connection_send_message(connection, dictionary);
}
- 接收代碼
static void XPCService_event_handler(xpc_connection_t peer)
{
xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
xpc_type_t type = xpc_get_type(event);
//處理業(yè)務(wù)
double value1 = xpc_dictionary_get_double(event, "value1");
double value2 = xpc_dictionary_get_double(event, "value2");
xpc_object_t dictionary = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_double(dictionary, "result", value1+value2);
xpc_connection_send_message(peer, dictionary);
});
xpc_connection_resume(peer);
}
void receiveXPC
{
xpc_main(XPCService_event_handler);
}
總結(jié)
Mach是核心的核心,在iOS系統(tǒng)中進(jìn)程和線程的調(diào)度都是由Mach負(fù)責(zé)。port是Mach 中最重要的概念,是幾乎所有Mach 對(duì)象實(shí)現(xiàn)的基礎(chǔ)。消息在端口間傳遞,并且允許消息進(jìn)行各種操作。
Mach 內(nèi)核的IPC就是在消息的基礎(chǔ)上實(shí)現(xiàn)的。Mach中沒有進(jìn)程,任務(wù)就是Mach的進(jìn)程,在Mach中所有的組件都是對(duì)象,線程、虛擬內(nèi)存都是對(duì)象,所有對(duì)象都有自己的屬性。
Mach 對(duì)象就是C語言結(jié)構(gòu)體加上函數(shù)指針。