深入淺出iOS系統(tǒng)內(nèi)核(2)— 進(jìn)程調(diào)度

本文參考《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)程生命周期

進(jìn)程狀態(tài)切換 —— 圖片引用自《深入解析Mac OS X & iOS 操作系統(tǒng)》

上文提到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 流程:

XPC流程 —— 圖片引用自《深入解析Mac OS X & iOS 操作系統(tǒng)》

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ù)指針。

參考資料

https://www.amazon.com/Mac-OS-iOS-Internals-Apples/dp/1118057651/ref=sr_1_2?ie=UTF8&qid=1331298923&sr=8-2

https://developer.apple.com/library/content/documentation/Darwin/Conceptual/KernelProgramming/About/About.html

https://github.com/apple/darwin-xnu

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評(píng)論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,855評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,833評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,016評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,273評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評(píng)論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,730評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,006評(píng)論 2 374

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