Mach 原語(yǔ):一切以消息為媒介
XNU 的核心是Mach 微內(nèi)核靴姿。 Mach 是 OS X 和 iOS 的核心中的核心葵萎。盡管Mach 核心被 BSD 層包裝起來(lái)了,而且主要的內(nèi)核接口是標(biāo)準(zhǔn)的POSIX 系統(tǒng)調(diào)用扇住,但是這個(gè)Mach 核心具有一組獨(dú)特的API和原語(yǔ)缝其。
Mach 設(shè)計(jì)原則
Mach 采用的是極簡(jiǎn)主義的概念:具有一個(gè)簡(jiǎn)單最小的核心,支持面向?qū)ο蟮哪P图烧ぃ沟锚?dú)立的具有良好定義的組件(實(shí)際上就是子系統(tǒng))可以通過(guò)消息的方式互相通訊车酣。在Mach 中,所有的東西都是通過(guò)自己的對(duì)象實(shí)現(xiàn)的索绪。進(jìn)程(在Mach 中稱為任務(wù))湖员、線程、虛擬內(nèi)存都是對(duì)象者春,所有對(duì)象都有自己的屬性。其實(shí)對(duì)象就是C語(yǔ)言結(jié)構(gòu)體加上函數(shù)指針清女。Mach 的獨(dú)特之處在于選擇了通過(guò)消息傳遞的方式實(shí)現(xiàn)對(duì)象和對(duì)象之間的通信钱烟。XNU 的“官方”API 是BSD 的POSIX API,蘋果保持Mach絕對(duì)的極簡(jiǎn)嫡丙。由于外層具有非常豐富的Cocoa API拴袭,所以很多開(kāi)發(fā)者都根本意識(shí)不到Mach的存在。不過(guò)曙博,Mach調(diào)用仍然是整個(gè)架構(gòu)中最基礎(chǔ)的部分拥刻。
Mach 設(shè)計(jì)目標(biāo)
Mach的手機(jī)文檔列出了一些設(shè)計(jì)目標(biāo),其中首要目標(biāo)就是將所有功能移出內(nèi)核父泳,并放在用戶態(tài)中般哼,將內(nèi)核保持在極簡(jiǎn)的狀態(tài):
- “控制點(diǎn)”或執(zhí)行單元(線程)管理
- 線程或線程組(任務(wù))的資源分配
- 虛擬內(nèi)存分配和管理
- 底層物理資源:即CPU吴汪、內(nèi)存和任何其他物理設(shè)備的分配
Mach 消息
Mac 中最基本的概念就是消息了,消息在兩個(gè)端點(diǎn)(endpoint)或端口(port)之間傳遞蒸眠。消息是Mach IPC 的核心構(gòu)建塊漾橙。Mach 消息的設(shè)計(jì)考慮了參數(shù)串行話、對(duì)齊楞卡、填充(padding霜运,為了對(duì)齊)和字節(jié)順序的問(wèn)題。
發(fā)送消息
Mach 消息的發(fā)送和接收都是通過(guò)同一個(gè)API函數(shù) mach_msg( )進(jìn)行的蒋腮。這個(gè)函數(shù)在用戶態(tài)和內(nèi)核中都有實(shí)現(xiàn)的淘捡。Mach 消息原本是為真正的微內(nèi)核架構(gòu)而設(shè)計(jì)的。也就是說(shuō)池摧,mach_msg( )函數(shù)必須在發(fā)送者和接收者之間復(fù)制消息所在的內(nèi)存焦除。盡管這種實(shí)現(xiàn)忠實(shí)于微內(nèi)核的范式,但是事實(shí)證明頻繁內(nèi)存復(fù)制操作帶來(lái)的性能損耗是不能忍受的险绘,因此踢京,XNU 算是通過(guò)單一內(nèi)核的方式“作弊”:所有的內(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í)際的工作。
端口
消息在端點(diǎn)(也稱為端口)之間傳遞思瘟,端口只不過(guò)是32為整型的標(biāo)識(shí)符荸百。所有的mach 原生對(duì)象都是通過(guò)對(duì)于的端口訪問(wèn)的。也就是說(shuō)滨攻,查找一個(gè)對(duì)象的句柄(handle)時(shí)够话,實(shí)際上請(qǐng)求的是這個(gè)對(duì)象端口的句柄。
深入IPC
IPC 所需要的基本原語(yǔ):消息光绕、發(fā)送和接收消息的端口女嘲,以及確保安全并發(fā)的信號(hào)量和鎖。每一個(gè)Mach 任務(wù)(進(jìn)程的高級(jí)抽象)包含一個(gè)指針指向自己的IPC 名稱空間诞帐,在名稱空間中保存了自己的端口欣尼。此外,任務(wù)也可以獲得系統(tǒng)范圍內(nèi)的端口停蕉,例如主機(jī)端口愕鼓、特權(quán)端口和其他端口钙态。導(dǎo)出給用戶空間的端口對(duì)象實(shí)際上是對(duì)“真正”端口對(duì)象的一個(gè)句柄。
消息傳遞的實(shí)現(xiàn)
用戶態(tài)的Mach消息傳遞使用mach_msg( )函數(shù)拒啰。這個(gè)函數(shù)通過(guò)內(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( ) 通過(guò)測(cè)試MACH_SEND_MSG和MACH_REV_MSG標(biāo)志位來(lái)判斷發(fā)送操作還是接收操作
發(fā)送消息
Mach 消息發(fā)送的邏輯在內(nèi)核中的兩處實(shí)現(xiàn):Mach_msg_overwrite_trap( ) 和 mach_msg_send( )谋旦。后者只用于內(nèi)核態(tài)的消息傳遞剩失,在用戶態(tài)不可見(jiàn)。
兩種情形的邏輯都差不多册着,遵循以下的流程:
- 調(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
- 通過(guò)ipc_kmsg_alloc 分配消息
- 復(fù)制消息(復(fù)制消息send_size字節(jié)的部分)甲捏,然后在消息頭設(shè)置msgh_size
- 復(fù)制消息關(guān)聯(lián)的端口權(quán)限演熟,然后通過(guò)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空間),那么通過(guò)ipc_kobject_server( ) 函數(shù)處理消息大溜。這個(gè)函數(shù)會(huì)在內(nèi)核中找到相應(yīng)的函數(shù)來(lái)執(zhí)行消息(或者調(diào)用ipc_kobject_notify( )來(lái)執(zhí)行)化漆,而且一個(gè)會(huì)生成消息的應(yīng)答。
- 不論是哪種端口:也就是說(shuō)如果端口不在內(nèi)核空間中钦奋,或者從ipc_kobjct_server( ) 返回了應(yīng)答座云,這個(gè)函數(shù)會(huì)貫穿到傳遞消息(或應(yīng)答消息)的部分,調(diào)用ipc_mqueue_send( )付材,這個(gè)函數(shù)將消息直接復(fù)制到端口的ip_messgaes 隊(duì)列中并喚醒任何正在等待的線程
** 接收消息**
和消息發(fā)送的情形類似朦拖,Mach 消息接收的邏輯也是現(xiàn)在內(nèi)核中的兩個(gè)地方,和發(fā)送一樣厌衔,mach_msg_overwrite_trap( ) 從用戶態(tài)接收請(qǐng)求璧帝,而內(nèi)核態(tài)通過(guò)mach_msg_receive( ) 接收消息
- 調(diào)用current_space( ) 獲得當(dāng)前的IPC空間
- 調(diào)用current_map( ) 獲得當(dāng)前的VM控件(vm_map)
- 不對(duì)消息的大小進(jìn)行檢查。這種檢查沒(méi)有必要富寿,因?yàn)橄⒃诎l(fā)送時(shí)已經(jīng)驗(yàn)證過(guò)了
- 通過(guò)調(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)用
同步原語(yǔ)
消息傳遞機(jī)制只是Mach IPC架構(gòu)中的一個(gè)組件晕城。另一個(gè)組件是同步機(jī)制(synchronization)泞坦,同步機(jī)制用于判定兩個(gè)或多個(gè)并發(fā)的操作如何訪問(wèn)共享資源。Mach 的同步原語(yǔ)如下表
對(duì)象 | 所有者 | 空可見(jiàn)性 | 等待 |
---|---|---|---|
互斥體(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) | 一個(gè) | 用戶態(tài) | 阻塞 |
Mach 的鎖也是由兩個(gè)層次組合而成的:
- 硬件相關(guān)層:依賴于硬件的特殊性質(zhì)砖顷,并且通過(guò)特定的匯編指令實(shí)現(xiàn)原子性和互斥性
- ** 硬件無(wú)關(guān)層**:通過(guò)統(tǒng)一的API包裝硬件特定的調(diào)用贰锁。這些API使得Mach 之上的層(或用戶 API)完全不用關(guān)心實(shí)現(xiàn)的細(xì)節(jié)赃梧,這通常是通過(guò)一組簡(jiǎn)單的宏實(shí)現(xiàn)的
鎖組對(duì)象
大部分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)的問(wèn)題芯肤。在Mach 和 BSD 中幾乎每一個(gè)子系統(tǒng)在初始化時(shí)都會(huì)創(chuàng)建一個(gè)自己使用的鎖組巷折。
互斥體對(duì)象
互斥體是最常用的鎖對(duì)象⊙伦桑互斥體定義為lck_mtx_t锻拘,互斥體必須屬于一個(gè)鎖組。
讀寫鎖對(duì)象
互斥體有一個(gè)最大的缺點(diǎn)击蹲,就是一次只能有一個(gè)線程持有鎖署拟。在很多情況下,多個(gè)線程可能對(duì)資源請(qǐng)求只讀的訪問(wèn)歌豺,這些情況下推穷,使用互斥鎖會(huì)阻止并發(fā)訪問(wèn)。讀寫鎖(read-write lock)就是問(wèn)題的解決方案世曾。讀寫鎖是個(gè)“更智能”的互斥體缨恒,能夠區(qū)分讀訪問(wèn)和寫訪問(wèn)。多個(gè)讀者可以同時(shí)持有鎖轮听,而一次只能有一個(gè)寫者可以獲得鎖骗露。
自旋鎖對(duì)象
互斥體和信號(hào)量都是阻塞等待的對(duì)象。阻塞等待的意思是說(shuō):如果鎖對(duì)象被其他線程持有血巍,那么請(qǐng)求訪問(wèn)的線程就被加入到等待隊(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í)間波附,因而造成了兩次或更多次的上下文切換帶來(lái)的開(kāi)銷則要大好幾個(gè)數(shù)量級(jí)。這種情況下,如果線程不是放棄處理器掸屡,而是重復(fù)地嘗試訪問(wèn)鎖對(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)量對(duì)象
Mach 提供了信號(hào)量(semaphore)碎罚,信號(hào)量是泛化的互斥體宁改。互斥體的值只能是0和1魂莫,而信號(hào)量的值這樣的一種互斥體还蹲。取值可以達(dá)到某個(gè)正數(shù),即允許并發(fā)持有信號(hào)量的持有者的個(gè)數(shù)耙考,換句話說(shuō)谜喊,互斥體可以看成是二值信號(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ì)通過(guò)硬件所的方式鎖定。信號(hào)量還有一個(gè)有意思的屬性:信號(hào)量可以轉(zhuǎn)換為端口肮雨,也可以由端口轉(zhuǎn)換而來(lái)遵堵。
鎖集對(duì)象
任務(wù)可以在用戶態(tài)使用鎖集。鎖集就是鎖(實(shí)際上就是互斥體)的數(shù)組怨规。通過(guò)給定的鎖ID 可以訪問(wèn)鎖陌宿。鎖也可以傳遞給其他線程。交出一個(gè)鎖會(huì)阻塞交出鎖的線程波丰,并喚醒接受鎖的線程壳坪。鎖集實(shí)際上是對(duì)內(nèi)核互斥體lck_mtx_t的封裝,如下圖所示:
鎖集的有趣之處在于允許鎖的傳遞掰烟。鎖的傳遞指是將鎖從一個(gè)任務(wù)傳遞給另一個(gè)任務(wù)的過(guò)程爽蝴。Mach 在調(diào)度中也使用了傳遞的概念扩灯,允許一個(gè)線程放棄處理器但是指定哪一個(gè)線程接替允許。
機(jī)器原語(yǔ)
Mach 通過(guò)一些所謂的“機(jī)器原語(yǔ)”對(duì)運(yùn)行的機(jī)器進(jìn)行抽象霜瘪,機(jī)器原語(yǔ)處理的對(duì)象包括主機(jī)、時(shí)鐘惧磺、處理器以及處理器集颖对。
主機(jī)對(duì)象
Mach 最基礎(chǔ)的對(duì)象是“主機(jī)(host)”,也就是表示機(jī)器本身的對(duì)象磨隘。主機(jī)對(duì)象是一個(gè)簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)缤底。主機(jī)只不過(guò)是一組“特殊端口”的集合(用于向主機(jī)發(fā)送各種消息),以及一組異常處理程序的集合番捂。主機(jī)定于了一個(gè)鎖組用于保護(hù)異常處理的并發(fā)訪問(wèn)个唧。
主機(jī)的數(shù)據(jù)結(jié)構(gòu)主要有三個(gè)基本功能:
- 提供機(jī)器信息:Mach 提供了一組異常豐富的API調(diào)用用于查詢機(jī)器信息,所有這些調(diào)用都要求獲得主機(jī)端口才能工作设预。
- 提供子系統(tǒng)的訪問(wèn):通過(guò)主機(jī)抽象徙歼,應(yīng)用程序可以請(qǐng)求訪問(wèn)子系統(tǒng)使用的任何“特殊”端口。此外鳖枕,還可以獲得所有其他機(jī)器抽象(例如:processor 和 processor_set)的訪問(wèn)權(quán)魄梯。
- 提供默認(rèn)的異常處理:異常從線程基本提升到進(jìn)程(任務(wù))基本,如果沒(méi)有被處理的話宾符。則進(jìn)一步提升到主機(jī)級(jí)別做通用的處理酿秸。
時(shí)鐘對(duì)象
Mach 內(nèi)核提供了一個(gè)簡(jiǎn)單的“時(shí)鐘(clock)”對(duì)象抽象。這個(gè)對(duì)象用于計(jì)時(shí)和鬧鐘魏烫。時(shí)鐘是一個(gè)帶有兩個(gè)端口的對(duì)象:一個(gè)用于“服務(wù)類”的函數(shù)(例如報(bào)時(shí)或鬧鈴)辣苏,另一個(gè)用于“控制類”的函數(shù),例如設(shè)置一天中的時(shí)間哄褒。
處理器對(duì)象
處理器(processor)對(duì)象表示機(jī)器上的一個(gè)邏輯CPU 或 CPU 核心稀蟋。如今多核架構(gòu)已是默認(rèn)架構(gòu),多核架構(gòu)中的每一個(gè)核心都可以看出一個(gè)CPU呐赡,處理器被分配給處理器集糊治,處理器集是一個(gè)或多個(gè)處理器的邏輯分組。處理器是CPU的簡(jiǎn)單抽象罚舱,被Mach 用于一些基本的操作井辜,例如啟動(dòng)和關(guān)閉一個(gè)CPU,以及向CPU分發(fā)要執(zhí)行的線程管闷。
處理器集對(duì)象
一個(gè)或多個(gè)processor_t 對(duì)象可以分組為處理器集(processor set)粥脚,或稱為pset(這是processor對(duì)象中的processor_set 成員),處理器集是將處理器綁定在一起的邏輯分組包个,Mach 可以以處理器集作為相關(guān)處理器的容器刷允,從而能夠高效地?cái)U(kuò)展到SMP架構(gòu)。
pset 中的處理器通過(guò)兩個(gè)隊(duì)列進(jìn)行維護(hù):一個(gè)是active_queue烘豹,保存當(dāng)前正在執(zhí)行的處理器憔鬼,另一個(gè)是idle_queue侮叮,用于保存當(dāng)前空閑的處理器(即正在執(zhí)行idle_thread的處理器)。處理器集還有一個(gè)全局的run_queue(pset_runq)映企,這個(gè)隊(duì)列保存了在這個(gè)集合中的處理器上執(zhí)行的線程得问。和其他所有對(duì)象一樣,處理器集也暴露一些端口:pset_self(用于對(duì)處理器集進(jìn)行操作) 和 pset_name_self(用于獲得處理器集的消息)