本文參考《Mac OS X and iOS Internals: To the Apple’s Core》 by Jonathan Levin
文章內(nèi)容主要是閱讀這本書的讀書筆記,建議讀者掌握《操作系統(tǒng)》,了解現(xiàn)代操作系統(tǒng)的技術特點,再閱讀本文可以事半功倍互广。
雖然iOS系統(tǒng)內(nèi)核使用極簡的微內(nèi)核架構踪栋,但內(nèi)容依然十分龐大焙格,所以會分
系統(tǒng)架構、進程調(diào)度夷都、內(nèi)存管理和文件系統(tǒng)四個部分進行闡述眷唉。
1 進程
進程是獨立運行、獨立分配資源和獨立接受調(diào)度的基本單位囤官。進程有三個基本狀態(tài)冬阳。
就緒狀態(tài)
當進已分配到除CPU外的所有必要資源后,只要再獲得CPU治拿,便可立即執(zhí)行摩泪,進程這時的狀態(tài)稱為就緒狀態(tài)笆焰。在系統(tǒng)中處于就緒狀態(tài)的進程往往會有多個劫谅,通常將這些進程存入一個隊列中,稱為就緒隊列嚷掠。執(zhí)行狀態(tài)
進程已獲得CPU捏检,其程序正在執(zhí)行。
- 睡眠狀態(tài)(阻塞狀態(tài))
正在執(zhí)行的進程由于某些事件暫時無法繼續(xù)執(zhí)行不皆,便放棄CPU占用轉(zhuǎn)入暫停贯城。阻塞狀態(tài)的進程也會排入隊列中,現(xiàn)代操作系統(tǒng)會根據(jù)阻塞原因的不同將處于阻塞狀態(tài)的進程排入多個隊列霹娄。導致阻塞的事件有:請求I/O能犯,申請緩沖空間。
在iOS中進程通過Progress ID(進程ID犬耻,即PID)來唯一辨識踩晶。進程還會將其和父進程的親屬關系保存在父進程ID(Parent Progress ID, PPID)中。父進程可以通過fork(或通過posix_spawn)創(chuàng)建子進程枕磁,并且預期子進程會消亡渡蜻。子進程返回的整數(shù)由其父進程收集。
1.1 iOS進程生命周期
上文提到Darwin是雙內(nèi)核系統(tǒng)计济,由Mach 和 BSD兩個部分組成茸苇,所以iOS的進程就需要理解兩個概念,BSD 進程和Mach 任務沦寂。
1.2 BSD 進程 Process
BSD 的進程可以唯一地映射到Mach 任務学密,但是包含的信息比Mach任務提供的基本調(diào)度和統(tǒng)計信息要豐富。BSD 進程包含了文件描述符和信號處理程序的數(shù)據(jù)传藏。進程還支持復雜的譜系则果,將進程和其父進程幔翰、兄弟進程和子進程連接起來。BSD 在struct proc 中維護了進程的很多特性西壮,struct porc擁有許多字段遗增,因此需要多個鎖來保護不同的字段,以及字段參與的列表款青。進程鎖保護整個數(shù)據(jù)結構做修,還有一個線程自旋鎖、一個文件描述符鎖以及其他保護進程組合兄弟進程的鎖抡草。
1.3 Mach 任務 task
Mach 并不關心進程饰及,而是使用了比進程更輕量級的概念:任務(task)。經(jīng)典的UNIX采用了自上而下的方式:最基本的對象是進程康震,然后進一步劃分一個或多個線程燎含。而Mach 采用自底向上的方式,最基本的單元是線程腿短,一個或多個線程包含在一個任務中屏箍。
任務是一種容器對象,虛擬內(nèi)存空間和其他資源都是通過這個容器對象管理的橘忱,這些資源包括設備和其他句柄赴魁。資源進一步被抽象為端口。因而資源的共享實際上相當于允許對對應端口的訪問钝诚。
嚴格地說颖御,Mach 的任務并不是其他操作系統(tǒng)中所謂的進程,因為Mach 作為一個微內(nèi)核的操作系統(tǒng)凝颇,并沒有提供“進程”的邏輯潘拱,而只是提供了最基本的實現(xiàn)。任務是沒有生命的拧略。任務存在的目的就是稱為一個或多個線程的容器芦岂。任務中的線程都在threads成員中維護,這是一個包含thread_count個線程的隊列辑鲤。此外盔腔,大部分對任務的操作實際上就是遍歷給定任務中的所有線程,并對這些線程進行對應的線程操作月褥。
1.4 BSD 進程和Mach 任務的關系
這兩個概念是一對一的映射關系弛随,每一個BSD 進程都在底層關聯(lián)了一個Mach 任務對象。實現(xiàn)這種映射的方法是指定一個透明的指針bsd_info宁赤,Mach 對bsd_info 完全無知舀透。Mach 將內(nèi)核也用任務表示。
2 線程 Thread
線程是利用CPU的基本單位决左,進程是占有資源的基本單位愕够。為了最大化利用進程時間片的方法走贪,引入線程的概念。通過使用多個線程惑芭,程序的指向可以分割表面上看上去并發(fā)執(zhí)行的子任務坠狡。線程之間切換的開銷比較小,只要保存和恢復寄存器即可遂跟。多核處理器更是特別和適合線程逃沿,因為多個處理器核心共享同樣的cache和ARM,為線程間的共享虛擬內(nèi)存提供了基礎幻锁。一般一個進程會包括多個線程
線程是Mach中的最小的執(zhí)行單元凯亮。線程表示的是底層的機器寄存器狀態(tài)以及各種調(diào)度統(tǒng)計數(shù)據(jù)。線程從設計上提供了所需要的大量信息哄尔,同時又盡可能地維持最小開銷假消。
在Mach中線程的數(shù)據(jù)結構非常巨大,因此大部分的線程創(chuàng)建時都是從一個通用的模板復制而來的岭接,這個模板使用默認值填充這個數(shù)據(jù)結構富拗,這個模板名為thread_template,內(nèi)核引導過程中被調(diào)用的thread_bootstrap( )負責填充這個模板亿傅。thread_create_internal( )函數(shù)分配新的線程數(shù)據(jù)結構媒峡,然后將換這個模板的內(nèi)容負責到新的線程數(shù)據(jù)結構中瘟栖。
Mach API thread_create( ) 就是通過thread_create_internal( )實現(xiàn)的葵擎。
3 調(diào)度 Scheduling
多進程程序系統(tǒng)中,作業(yè)被提交后半哟,必須經(jīng)過處理機調(diào)度酬滤,才能被處理機執(zhí)行。進程調(diào)度主要有兩種方式寓涨,非搶占式和搶占式《⒋現(xiàn)在面向用戶的操作系統(tǒng)基本上都采用搶占式調(diào)度方式,包括iOS戒良。主要的搶占原則有:
- 優(yōu)先權原則
- 短作業(yè)優(yōu)先原則
- 時間片原則
由于Mach具有處理器集的抽象体捏,所以 Mach 比Linux 和 Windows 更擅長管理多核處理器:Mach 可以將同一個CPU 的多個核心放在同一個pset管理,并且通過不同的pset管理不同的CPU糯崎。
3.1 Mach 調(diào)度器特性
3.1.1 控制權轉(zhuǎn)交
允許一個線程主動放棄CPU几缭,但不是將CPU放棄給任何其他線程,而是將CPU轉(zhuǎn)交給自己選擇的某個特定的線程沃呢。由于Mach 是一個基于消息傳遞的內(nèi)核年栓,線程之間通過消息傳遞通訊,所以這項特性在Mach 中特別有用薄霜。通過這個特性某抓,消息的處理延遲可以達到最小纸兔,而不需要投機地等待消息處理線程(發(fā)送者或接收者)下一次得到調(diào)度。這個特性是Mach特有的否副。
3.1.2 使用續(xù)體
可以使線程不用管理自己的棧汉矿,線程可以丟棄自己的棧,系統(tǒng)恢復線程執(zhí)行時不需要恢復線程的棧备禀。續(xù)體是緩解上下文切換開銷的簡單有效的機制负甸。這個特性是Mach特有的。
3.1.3 異步軟件陷阱 Asynchronous Software Trap痹届,AST
是軟件對底層硬件陷阱機制的補充完善呻待,通過使用AST,內(nèi)核可以響應需要得到關注的out-off-band事件队腐,例如調(diào)度事件蚕捉。
AST是人工引發(fā)的非硬件觸發(fā)的陷阱。AST是內(nèi)核操作的關鍵部分柴淘,而且是調(diào)度時間的底層機制迫淹,也是BSD信號的實現(xiàn)基礎。AST實現(xiàn)為線程控制塊中一個包含各種標志位的字段为严,這些標志位可以通過thread_ast_set( )分別設置敛熬。這個特性是Mach特有的。
3.1.4 上下文切換(content switch)
上下文切換是暫停某個線程的執(zhí)行第股,并且將其寄存器狀態(tài)記錄在某個預定義的內(nèi)存位置中应民。寄存器狀態(tài)是和及其相關的。當一個線程被搶占時夕吻,CPU 寄存器中會價值另一個線程保存的線程狀態(tài)诲锹,從而恢復到那個線程的執(zhí)行。
一個線程在CPU上可以執(zhí)行任意長的時間涉馅。執(zhí)行(execute)指的是這樣的一個事實:CPU 寄存器中填滿了線程的狀態(tài)归园,因此CPU(通過EIP/RIP指令指針或PC程序計數(shù)器)執(zhí)行該線程函數(shù)的代碼。這個執(zhí)行會一直持續(xù)稚矿,直到發(fā)生下面某種情況:
- 線程終止
- 線程自愿放棄
- 外部中斷打斷了線程的執(zhí)行庸诱,外部中斷要求CPU 保存線程狀態(tài)并且立即執(zhí)行中斷處理代碼
3.1.5 優(yōu)先級
每一個Mach線程都包含優(yōu)先級信息,優(yōu)先級直接影響線程被調(diào)度的頻率晤揣。Mach 有128個優(yōu)先級桥爽。
內(nèi)核線程的最低優(yōu)先級為80,比用戶態(tài)線程的優(yōu)先級要高碉渡【鬯可以保證內(nèi)核以及用戶維護管理的線程能夠搶占用戶態(tài)的線程。
3.1.6 優(yōu)先級偏移
給線程分配優(yōu)先級只是一個開頭滞诺,這些優(yōu)先級在運行時常常需要調(diào)整形导。Mach 會針對每一個線程的CPU 利用率和整體系統(tǒng)負載動態(tài)調(diào)整每一個線程的優(yōu)先級环疼。
3.1.7 運行隊列
線程是通過運行隊列管理的。 運行隊列是一個多層列表朵耕,即一個列表的數(shù)組炫隶,針對128個優(yōu)先級中的每一個優(yōu)先級都要一個隊列。Mach 實際采用的方法是檢查位圖阎曹,這樣就可以同時檢查32個隊列伪阶,這樣時間復雜度為O(4)。
3.1.8 等待隊列
當進程或者線程阻塞处嫌,就沒有必要考慮調(diào)度這個線程栅贴,因為只有當線程等待的對象或I/O 操作完成或時間發(fā)生時才能繼續(xù)執(zhí)行。所以可以將線程放在等待隊列中熏迹。當?shù)却臈l件滿足之后檐薯,一個或多個等待的線程可以被解除阻塞并且再次分發(fā)執(zhí)行。
3.1.9 CPU 親緣性
在使用多核注暗、SMP 或 超線程的現(xiàn)代架構中坛缕,還可以設置某個線程和一個或多個指定CPU 的親緣性(affinity)。這種親緣性對于線程和系統(tǒng)來說都是有好處的捆昏,因為當線程回到同一個CPU上執(zhí)行時赚楚,線程的數(shù)據(jù)可能還留在CPU的緩存中,從而提升性能骗卜。
在Mach中宠页,線程對CPU 的親緣性的意思就是綁定。thread_bind( )的目的就是綁定線程膨俐,這個函數(shù)僅僅是更新thread_t的bound_processor字段勇皇。如果這個字段被設置為PROCESSOR_NULL之外的任何值罩句,那么未來的調(diào)度策略就會將這個線程分發(fā)到對應處理器的運行隊列焚刺。
3.2 Mach調(diào)度搶占模式
顯式搶占
即線程放棄CPU的控制權或進入阻塞的操作,顯式搶占是事先可以預知的门烂,所以顯式搶占是同步的隱式搶占
這種搶占是由中斷引起的乳愉,由于中斷不可預測的本身,所有隱式搶占是異步的
3.3 Mach 中斷模式
搶占式操作系統(tǒng)必須具備中斷能力屯远。中斷一般由 CPU 中的一個特殊組件產(chǎn)生的蔓姚。這個組件稱為可編程中斷控制器(Programmable Interrupt Controller,PIC)慨丐,在更加高級的CPU中坡脐,稱之為高級可編程中斷控制器(Advanced PIC,APIC)房揭。PIC 接收來自系統(tǒng)總線上的設備的消息备闲,然后將消息分揀到某一條中斷請求(Interrupt Request晌端,IRQ)線上去。當產(chǎn)生中斷的時候恬砂,PIC將相應的中斷標記為活躍咧纠。在這個中斷被一個函數(shù)(稱為中斷處理程序或中斷服務程序)處理或服務完成之前,這條中斷線一直保持活躍狀態(tài)泻骤。處理這個中斷的函數(shù)要負責重置這條線的狀態(tài)漆羔。
Mach的調(diào)度是由中斷驅(qū)動的,對于搶占式多任務系統(tǒng)來說狱掂,必須有某種機制允許調(diào)度器能夠首先得到CPU的控制權演痒,從而搶占當前正在執(zhí)行的線程,然后才能執(zhí)行調(diào)度算法趋惨,并且通過調(diào)度算法決定當前的線程可以繼續(xù)恢復執(zhí)行還是要搶奪其 CPU 給更重要的線程使用嫡霞。為了能夠從當前運行的線程搶奪CPU,現(xiàn)在的操作系統(tǒng)利用了現(xiàn)有的硬件中斷機制希柿。由于中斷的特點是強迫CPU在發(fā)生中斷時“放下手中所有的任務”诊沪,并longjmp 跳轉(zhuǎn)到中斷處理程序(也稱為中斷服務例程(interrupt service routinr,ISR))執(zhí)行曾撤,因此可以通過中斷機制在發(fā)生中斷時運行調(diào)度器端姚。
3.4 Mach 調(diào)度算法
調(diào)度算法是模塊化的,系統(tǒng)引導時可以動態(tài)設置調(diào)度器(使用sched引導參數(shù))挤悉。不過實際中只用了一個調(diào)度器
Mach 的線程調(diào)度算法高度可擴展渐裸,而且運行更換用于線程調(diào)度的算法。通常情況下装悲,只啟用了一個調(diào)度器昏鹃。但是Mach的架構運行定義額外的調(diào)度器,并且在編譯時根據(jù)CONFIG_SCHED_的定義設置調(diào)度器诀诊。每一個調(diào)度器對象都維護一個sched_dispatch_table 數(shù)據(jù)結構洞渤,其中以函數(shù)指針的方式保存了各種操作。一個全局表sched_current_dispatch保存了當前活動的調(diào)度算法属瓣,并且允許運行時切換調(diào)度器载迄。所有的調(diào)度器都必須實現(xiàn)相同的字段,通用的調(diào)度邏輯可以通過SCHED宏訪問這些字段抡蛙。
4 IPC
Inter-Process Communication 是指進程間的信息交換护昧,所交換的信息量,少者是一個狀態(tài)或數(shù)值粗截,多者則是成千上萬字節(jié)惋耙。IPC的方式主要有共享存儲器、消息系統(tǒng)、管道通信绽榛。
iOS的IPC核心機制是消息遥金,在Mach中一切以消息為媒介。
4.1 Mach 內(nèi)核設計原則
Mach 采用的是極簡主義:具有一個簡單最小的核心蒜田,支持面向?qū)ο蟮哪P透逍担沟锚毩⒌木哂辛己枚x的組件,實際上就是子系統(tǒng)冲粤∶滥可以通過消息的方式互相通訊。在Mach 中梯捕,所有的東西都是通過自己的對象實現(xiàn)的厢呵。進程(在Mach 中稱為任務)、線程傀顾、虛擬內(nèi)存都是對象襟铭,所有對象都有自己的屬性。
Mach 對象就是C語言結構體加上函數(shù)指針短曾。Mach 的獨特之處在于選擇了通過消息傳遞的方式實現(xiàn)對象和對象之間的通信寒砖。XNU 的“官方”API 是BSD 的POSIX API,蘋果保持Mach絕對的極簡嫉拐。由于外層具有非常豐富的Cocoa API哩都,所以很多開發(fā)者都根本意識不到Mach的存在。不過婉徘,Mach調(diào)用仍然是整個架構中最基礎的部分漠嵌。
4.2 Mach 消息
Mac 中最基本的概念就是消息,消息在端口(port)之間傳遞盖呼。消息是Mach IPC 的核心構建塊儒鹿。Mach 消息的設計考慮了參數(shù)串行話、對齊几晤、填充和字節(jié)順序的問題约炎。
Mach 消息的發(fā)送和接收都是通過同一個API函數(shù) mach_msg( )進行的。
這個函數(shù)在用戶態(tài)和內(nèi)核中都有實現(xiàn)的锌仅。
Mach 消息原本是為真正的微內(nèi)核架構而設計的章钾。也就是說,mach_msg( )函數(shù)必須在發(fā)送者和接收者之間復制消息所在的內(nèi)存热芹。盡管這種實現(xiàn)忠實于微內(nèi)核的范式,但是事實證明頻繁內(nèi)存復制操作帶來的性能損耗是不能忍受的惨撇,因此伊脓,XNU 將所有的內(nèi)核組件都共享一個地址空間,因此消息傳遞只需要傳遞消息的指針就可以了,從而省去了昂貴的內(nèi)存復制操作报腔。
為了實現(xiàn)消息的發(fā)送和接收株搔,mach_msg( ) 函數(shù)調(diào)用了一個Mach 陷阱(trap)。Mach 陷阱就是和系統(tǒng)調(diào)用的概念纯蛾,在用戶態(tài)調(diào)用mach_msg_trap( ) 會引發(fā)陷阱機制纤房,切換到內(nèi)核態(tài),在內(nèi)核態(tài)中翻诉,內(nèi)核實現(xiàn)的mach_msg( ) 會完成實際的工作炮姨。
消息在口之間傳遞,端口是32位整型標識符碰煌。所有的mach 原生對象都是通過對于的端口訪問的舒岸。也就是說,查找一個對象的句柄時芦圾,實際上請求的是這個對象端口的句柄蛾派。
4.3 Mach消息傳遞機制
用戶態(tài)的Mach消息傳遞使用mach_msg( )函數(shù)。
這個函數(shù)通過內(nèi)核的Mach 陷阱機制調(diào)用內(nèi)核函數(shù)mach_msg_trap( ) 个少。然后mach_msg_trap( )調(diào)用 mach_msg_overwrite_trap( )洪乍,mach_msg_overwrite_trap( )
通過測試MACH_SEND_MSG和MACH_REV_MSG標志位來判斷發(fā)送操作還是接收操作
4.4 消息發(fā)送實現(xiàn)
Mach 消息發(fā)送的邏輯在內(nèi)核中的兩處實現(xiàn):Mach_msg_overwrite_trap( ) 和 mach_msg_send( )。后者只用于內(nèi)核態(tài)的消息傳遞夜焦,在用戶態(tài)不可見拙徽。
兩種情形的邏輯都差不多媚赖,遵循以下的流程:
- 調(diào)用current_space( ) 獲得當前的IPC空間
- 調(diào)用current_map( ) 獲得的當前的VM空間(vm_map)
- 對消息的大小進行正確性檢查
- 計算要分配的消息大小:從send_size參數(shù)獲得大小,然后加上硬編碼的MAX_REAILER_SIZE
- 通過ipc_kmsg_alloc 分配消息
- 復制消息(復制消息send_size字節(jié)的部分)圆凰,然后在消息頭設置msgh_size
- 復制消息關聯(lián)的端口權限,然后通過ipc_kmsg_copyin 將所有out-of-line 數(shù)據(jù)內(nèi)存復制到當前的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 引用,并鎖定端口
- 如果端口是一個內(nèi)核端口(即端口的ip_receiver是內(nèi)核IPC空間)瞪慧,那么通過ipc_kobject_server( ) 函數(shù)處理消息髓考。這個函數(shù)會在內(nèi)核中找到相應的函數(shù)來執(zhí)行消息(或者調(diào)用ipc_kobject_notify( )來執(zhí)行),而且一個會生成消息的應答弃酌。
- 不論是哪種端口:也就是說如果端口不在內(nèi)核空間中氨菇,或者從ipc_kobjct_server( ) 返回了應答,這個函數(shù)會貫穿到傳遞消息(或應答消息)的部分妓湘,調(diào)用ipc_mqueue_send( )查蓉,這個函數(shù)將消息直接復制到端口的ip_messgaes 隊列中并喚醒任何正在等待的線程
4.5 消息接收實現(xiàn)
和消息發(fā)送的情形類似,Mach 消息接收的邏輯也是現(xiàn)在內(nèi)核中的兩個地方榜贴,和發(fā)送一樣豌研,mach_msg_overwrite_trap( ) 從用戶態(tài)接收請求,而內(nèi)核態(tài)通過mach_msg_receive( ) 接收消息
- 調(diào)用current_space( ) 獲得當前的IPC空間
- 調(diào)用current_map( ) 獲得當前的VM控件(vm_map)
- 不對消息的大小進行檢查。這種檢查沒有必要鹃共,因為消息在發(fā)送時已經(jīng)驗證過了
- 通過調(diào)用ipc_mqueue_copyin( ) 獲得IPC隊列
- 持有當前線程的一個引用鬼佣。使用當前線程的引用可使它適應使用Mach 的續(xù)體(continuation)模型,續(xù)體模型可以避免維護完整線程棧的必要性
- 調(diào)用ipc_mqueue_receive( )從隊列中取出消息
- 最后霜浴,調(diào)用mach_msg_receive_results( ) 函數(shù)晶衷。這個函數(shù)也可以從續(xù)體中調(diào)用
5 同步機制 synchronization
為使系統(tǒng)的多線程和進程能有條不紊地運行,在系統(tǒng)中必須提供用于實現(xiàn)線程間或者進程間同步的機制阴孟。
在Mach系統(tǒng)中主要有以下幾種同步機制
對象 | 所有者 | 空可見性 | 等待 |
---|---|---|---|
互斥鎖(lck_mtx_t) | 1個 | 內(nèi)核態(tài) | 阻塞 |
信號量(semaphore_t) | 多個 | 用戶態(tài) | 阻塞 |
自旋鎖(hw_lock_t等) | 1個 | 內(nèi)核態(tài) | 忙等 |
鎖集(lock_set_t) | 1個 | 用戶態(tài) | 阻塞 |
大部分Mach 同步對象都不是自己獨立存在的晌纫,而是屬于一個 lck_grp_t 對象。lck_grp_t 就是一個鏈表中的一個元素温眉,帶有一個給定的名字缸匪,以及最多3種鎖的類型:自旋鎖、互斥鎖和讀寫鎖类溢。鎖組還帶有統(tǒng)計信息(lck_grp_stat_t 數(shù)據(jù)結構)凌蔬,用于調(diào)試和同步相關的問題。在Mach 和 BSD 中幾乎每一個子系統(tǒng)在初始化時都會創(chuàng)建一個自己使用的鎖組闯冷。
互斥鎖
互斥鎖是最常用的鎖對象砂心。互斥體定義為lck_mtx_t蛇耀,互斥鎖必須屬于一個鎖組辩诞。讀寫鎖
互斥鎖有一個最大的缺點,就是一次只能有一個線程持有鎖纺涤。在很多情況下译暂,多個線程可能對資源請求只讀的訪問,這些情況下撩炊,使用互斥鎖會阻止并發(fā)訪問外永。讀寫鎖(read-write lock)就是問題的解決方案。讀寫鎖是個“更智能”的互斥體拧咳,能夠區(qū)分讀訪問和寫訪問伯顶。多個讀者可以同時持有鎖,而一次只能有一個寫者可以獲得鎖骆膝。自旋鎖
互斥體和信號量都是阻塞等待的對象祭衩。阻塞等待的意思是說:如果鎖對象被其他線程持有,那么請求訪問的線程就被加入到等待隊列中阅签,因而被阻塞掐暮。阻塞一個線程就意味著放棄線程的時間片,把處理器讓給調(diào)度器認為下一個要執(zhí)行的線程愉择。當鎖可用時劫乱,調(diào)度器會得到通知织中,然后根據(jù)自己的判斷將線程從等待隊列中取出并重新調(diào)度锥涕。然而這個方式可能會嚴重地影響性能衷戈,由于在很多情況下,鎖對象只需要持有短短幾個周期的時間层坠,因而造成了兩次或更多次的上下文切換帶來的開銷則要大好幾個數(shù)量級殖妇。這種情況下,如果線程不是放棄處理器破花,而是重復地嘗試訪問鎖對象可能是更明智的選擇谦趣,這種方式稱之為“忙等(busy-wait)”,如果當前鎖的持有者確實在幾個周期后就放棄鎖了座每,那么這樣就可以節(jié)省至少兩次上下文切換前鹅。當然這個鎖要慎用,否則很可能進入一個非城褪幔可怕的死鎖場景舰绘,導致整個系統(tǒng)陷入停滯狀態(tài)。信號量
Mach 提供了信號量(semaphore)葱椭,信號量是泛化的互斥體捂寿。互斥體的值只能是0和1孵运,而信號量的值這樣的一種互斥體秦陋。取值可以達到某個正數(shù),即允許并發(fā)持有信號量的持有者的個數(shù)治笨,換句話說驳概,互斥體可以看成是二值信號量的特殊情況。信號量可以在用戶態(tài)使用旷赖,而互斥體只能在內(nèi)核態(tài)使用顺又。信號量本身是一個不可鎖的對象。信號量對象是一個很小的結構體杠愧,包含指向所有者和端口的引用待榔。此外,還保護桿一個wait_queue_t流济,這是一個保存正在等待這個信號量的線程的鏈表锐锣。wait_queue_t會通過硬件所的方式鎖定。信號量還有一個有意思的屬性:信號量可以轉(zhuǎn)換為端口绳瘟,也可以由端口轉(zhuǎn)換而來雕憔。鎖集
任務可以在用戶態(tài)使用鎖集。鎖集就是鎖(實際上就是互斥體)的數(shù)組糖声。通過給定的鎖ID 可以訪問鎖斤彼。鎖也可以傳遞給其他線程分瘦。交出一個鎖會阻塞交出鎖的線程,并喚醒接受鎖的線程琉苇。鎖集實際上是對內(nèi)核互斥體lck_mtx_t的封裝
6 XPC
XPC是iOS 5引入的輕量級進程間通訊機制嘲玫,XPC 和 GCD 結合的非常緊密,XPC 允許開發(fā)者將應用程序分解為獨立的組件并扇。這樣可以同時增強應用程序的穩(wěn)定性和安全性去团,因為不穩(wěn)定的功能可以包含在一個XPC服務中,而XPC服務可以在外部進行管理穷蛹。
XPC 服務程序和客戶程序都鏈接了libxpc.dylib土陪,libxpc.dylib 提供了各種各樣的C語言層次的XPC機制,NSXPCConnection肴熏。XPC 還依賴于兩個私有框架:XPCService 和 XPCObjects鬼雀。前者負責處理XPC服務運行時相關的事務,后者為XPC 對象提供編碼和解碼服務蛙吏。iOS 還有一個包含私有框架 XPCKit源哩。
6.1 XPC 對象類型
XPC對各種數(shù)據(jù)進行包裝盒序列化,這種方式類似于CoreFoundation框架出刷。任何類型的XPC對象都可以處理為不透明的類型 xpc_object_t, 并且通過 xpc_object(3)文檔中描述的函數(shù)進行操作璧疗。這些函數(shù)包括xpc_retain/release、xpc_get_type馁龟、xpc_hash(提供對象的散列值崩侠,可以用于數(shù)組索引)、xpc_equal(用于比較對象)和xpc_copy坷檩。
6.2 XPC 消息
對象可以通過消息來發(fā)送和接收却音。默認情況下消息是異步發(fā)送的,并且通過分發(fā)隊列(GCD)處理矢炼。通過使用屏障(barrier)系瓢,開發(fā)人員可以指定一個代碼塊在某個連接上的所有消息都發(fā)生完成之后執(zhí)行。發(fā)出器的消息可以有應答句灌,應答也是異步的夷陋,通過_reply_sync 函數(shù)可以阻塞直到收到應答消息。XPC消息是通過Mach 消息機制實現(xiàn)的胰锌,并且使用了Mach Interface Genetator(MIG)實施骗绕。后者提供了xpc_domain子系統(tǒng)。xpc_domain 子系統(tǒng)包含用于登記资昧、加載或添加服務以及獲得服務名稱的消息酬土。
6.3 xpc_connection_send_message 流程:
6.4 XPC 服務
XPC 服務可以通過Objective-C 或C或C++創(chuàng)建。不管通過哪種語言創(chuàng)建格带,都需要調(diào)用 libxpc.dylib 庫的 xpc_main 函數(shù)開始服務撤缴。C/C++ 的 服務 mian 函數(shù)只不過是一個簡單的包裝函數(shù)刹枉,它調(diào)用xpc_main,并傳入時間處理函數(shù)(xpc_connection_handler_t)屈呕。Objective-C服務也調(diào)用xpc_main微宝,不過是通過NSXPCConnection_t的resume方法間接調(diào)用的。
事件處理函數(shù)接受單獨一個參數(shù):xpc_connection_t(Objective-C 將這個對象封裝為Foundation.framework 框架中的 NSXPCConnection)凉袱。XPC 連接是一個不透明的對象芥吟,需要通過xpc_connection_*函數(shù)進行操作侦铜。
XPC服務程序的一般架構包括:調(diào)用dispatch_queue_create 創(chuàng)建一個隊列用于接收來接收自客戶程序的消息专甩,然后通過xpc_connectiona_set_target_queue 將這個隊列分配給連接。服務程序還有設置連接的時間處理程序:調(diào)用 xpc_connection_set_event_handle 并提供一個表示處理程序的代碼執(zhí)行(代碼本身也可以包裝其他函數(shù))钉稍。每當服務程序收到一條消息的時候都會調(diào)用這個處理程序涤躲。服務程序可以創(chuàng)建一個應答(通過調(diào)用 xpc_dictionary_create_reply)并將應答消息發(fā)送出去。
6.5 XPC實現(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è)務
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);
}
總結
Mach是核心的核心贡未,在iOS系統(tǒng)中進程和線程的調(diào)度都是由Mach負責种樱。port是Mach 中最重要的概念,是幾乎所有Mach 對象實現(xiàn)的基礎俊卤。消息在端口間傳遞嫩挤,并且允許消息進行各種操作。
Mach 內(nèi)核的IPC就是在消息的基礎上實現(xiàn)的消恍。Mach中沒有進程岂昭,任務就是Mach的進程,在Mach中所有的組件都是對象狠怨,線程约啊、虛擬內(nèi)存都是對象,所有對象都有自己的屬性佣赖。
Mach 對象就是C語言結構體加上函數(shù)指針恰矩。