Linux進(jìn)程間通信
基礎(chǔ)概念
內(nèi)核態(tài)/用戶(hù)態(tài)

如上圖所示,從宏觀上來(lái)看,Linux操作系統(tǒng)的體系架構(gòu)分為用戶(hù)態(tài)和內(nèi)核態(tài)(或者用戶(hù)空間和內(nèi)核空間)。操作系統(tǒng)的資源是有限的,如果訪(fǎng)問(wèn)資源的操作過(guò)多,必然會(huì)消耗過(guò)多的資源,而且如果不對(duì)這些操作加以區(qū)分,很可能造成資源訪(fǎng)問(wèn)的沖突。所以,為了減少有限資源的訪(fǎng)問(wèn)和使用沖突,Unix/Linux的設(shè)計(jì)哲學(xué)之一就是:對(duì)不同的操作賦予不同的執(zhí)行等級(jí),就是所謂特權(quán)的概念。簡(jiǎn)單說(shuō)就是有多大能力做多大的事,與系統(tǒng)相關(guān)的一些特別關(guān)鍵的操作必須由最高特權(quán)的程序來(lái)完成。Intel的X86架構(gòu)的CPU提供了0到3四個(gè)特權(quán)級(jí),數(shù)字越小,特權(quán)越高,Linux操作系統(tǒng)中主要采用了0和3兩個(gè)特權(quán)級(jí),分別對(duì)應(yīng)的就是內(nèi)核態(tài)和用戶(hù)態(tài)。當(dāng)一個(gè)任務(wù)(進(jìn)程)執(zhí)行系統(tǒng)調(diào)用而陷入內(nèi)核代碼中執(zhí)行時(shí),稱(chēng)進(jìn)程處于內(nèi)核運(yùn)行態(tài)(內(nèi)核態(tài)),此時(shí)處理器處于特權(quán)級(jí)最高的(0級(jí))內(nèi)核代碼中執(zhí)行。進(jìn)程在執(zhí)行用戶(hù)自己的代碼的時(shí)候,我們稱(chēng)其處于用戶(hù)運(yùn)行態(tài)(用戶(hù)態(tài)),此時(shí)處理器在特權(quán)級(jí)最低的(3級(jí))用戶(hù)代碼中運(yùn)行。
進(jìn)程空間
[圖片上傳失敗...(image-a8d6b8-1649494164150)]
如上是32為操作系統(tǒng)的進(jìn)程空間劃分,它的尋址空間(虛擬存儲(chǔ)空間)就是 2 的 32 次方,也就是 4GB。操作系統(tǒng)的核心是內(nèi)核,獨(dú)立于普通的應(yīng)用程序,可以訪(fǎng)問(wèn)受保護(hù)的內(nèi)存空間,也可以訪(fǎng)問(wèn)底層硬件設(shè)備的權(quán)限。為了保護(hù)用戶(hù)進(jìn)程不能直接操作內(nèi)核,保證內(nèi)核的安全,操作系統(tǒng)從邏輯上將虛擬空間劃分為用戶(hù)空間(User Space)和內(nèi)核空間(Kernel Space)。針對(duì) Linux 操作系統(tǒng)而言,將最高的 1GB 字節(jié)供內(nèi)核使用,稱(chēng)為內(nèi)核空間;較低的 3GB 字節(jié)供各進(jìn)程使用,稱(chēng)為用戶(hù)空間。
系統(tǒng)調(diào)用
雖然從邏輯上進(jìn)行了用戶(hù)空間和內(nèi)核空間的劃分,但不可避免的用戶(hù)空間需要訪(fǎng)問(wèn)內(nèi)核資源,比如文件操作、訪(fǎng)問(wèn)網(wǎng)絡(luò)等等。為了突破隔離限制,就需要借助系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)。系統(tǒng)調(diào)用是用戶(hù)空間訪(fǎng)問(wèn)內(nèi)核空間的唯一方式,保證了所有的資源訪(fǎng)問(wèn)都是在內(nèi)核的控制下進(jìn)行的,避免了用戶(hù)程序?qū)ο到y(tǒng)資源的越權(quán)訪(fǎng)問(wèn),提升了系統(tǒng)安全性和穩(wěn)定性。
進(jìn)程隔離
簡(jiǎn)單的說(shuō)就是操作系統(tǒng)中,進(jìn)程與進(jìn)程間內(nèi)存是不共享的。兩個(gè)進(jìn)程就像兩個(gè)平行的世界,A 進(jìn)程沒(méi)法直接訪(fǎng)問(wèn) B 進(jìn)程的數(shù)據(jù),這就是進(jìn)程隔離的通俗解釋。A 進(jìn)程和 B 進(jìn)程之間要進(jìn)行數(shù)據(jù)交互就得采用特殊的通信機(jī)制:進(jìn)程間通信(IPC)。
進(jìn)程間通信
基本原理
通常的做法是消息發(fā)送方將要發(fā)送的數(shù)據(jù)存放在內(nèi)存緩存區(qū)中,通過(guò)系統(tǒng)調(diào)用進(jìn)入內(nèi)核態(tài)。然后內(nèi)核程序在內(nèi)核空間分配內(nèi)存,開(kāi)辟一塊內(nèi)核緩存區(qū),調(diào)用 copy_from_user() 函數(shù)將數(shù)據(jù)從用戶(hù)空間的內(nèi)存緩存區(qū)拷貝到內(nèi)核空間的內(nèi)核緩存區(qū)中。同樣的,接收方進(jìn)程在接收數(shù)據(jù)時(shí)在自己的用戶(hù)空間開(kāi)辟一塊內(nèi)存緩存區(qū),然后內(nèi)核程序調(diào)用 copy_to_user() 函數(shù)將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到接收進(jìn)程的內(nèi)存緩存區(qū)。這樣數(shù)據(jù)發(fā)送方進(jìn)程和數(shù)據(jù)接收方進(jìn)程就完成了一次數(shù)據(jù)傳輸,我們稱(chēng)完成了一次進(jìn)程間通信。如下圖:

分類(lèi)
管道
管道分為有名管道和無(wú)名管道。
無(wú)名管道是一種半雙工的通信方式,數(shù)據(jù)只能單向流動(dòng),而且只能在具有親緣關(guān)系的進(jìn)程間使用.進(jìn)程的親緣關(guān)系一般指的是父子關(guān)系。無(wú)明管道一般用于兩個(gè)不同進(jìn)程之間的通信。當(dāng)一個(gè)進(jìn)程創(chuàng)建了一個(gè)管道,并調(diào)用fork創(chuàng)建自己的一個(gè)子進(jìn)程后,父進(jìn)程關(guān)閉讀管道端,子進(jìn)程關(guān)閉寫(xiě)管道端,這樣提供了兩個(gè)進(jìn)程之間數(shù)據(jù)流動(dòng)的一種方式。
有名管道也是一種半雙工的通信方式,但是它允許無(wú)親緣關(guān)系進(jìn)程間的通信。
信號(hào)
信號(hào)是一種比較復(fù)雜的通信方式,用于通知接收進(jìn)程某個(gè)事件已經(jīng)發(fā)生。
信號(hào)量
信號(hào)量是一個(gè)計(jì)數(shù)器,可以用來(lái)控制多個(gè)線(xiàn)程對(duì)共享資源的訪(fǎng)問(wèn),它不是用于交換大批數(shù)據(jù),而用于多線(xiàn)程之間的同步。它常作為一種鎖機(jī)制,防止某進(jìn)程在訪(fǎng)問(wèn)資源時(shí)其它進(jìn)程也訪(fǎng)問(wèn)該資源。因此,主要作為進(jìn)程間以及同一個(gè)進(jìn)程內(nèi)不同線(xiàn)程之間的同步手段。
消息隊(duì)列
消息隊(duì)列是消息的鏈表,存放在內(nèi)核中并由消息隊(duì)列標(biāo)識(shí)符標(biāo)識(shí).消息隊(duì)列克服了信號(hào)傳遞信息少,管道只能承載無(wú)格式字節(jié)流以及緩沖區(qū)大小受限等特點(diǎn).消息隊(duì)列是UNIX下不同進(jìn)程之間可實(shí)現(xiàn)共享資源的一種機(jī)制,UNIX允許不同進(jìn)程將格式化的數(shù)據(jù)流以消息隊(duì)列形式發(fā)送給任意進(jìn)程.對(duì)消息隊(duì)列具有操作權(quán)限的進(jìn)程都可以使用msget完成對(duì)消息隊(duì)列的操作控制.通過(guò)使用消息類(lèi)型,進(jìn)程可以按任何順序讀信息,或?yàn)橄才艃?yōu)先級(jí)順序。
共享內(nèi)存
共享內(nèi)存就是映射一段能被其他進(jìn)程所訪(fǎng)問(wèn)的內(nèi)存,這段共享內(nèi)存由一個(gè)進(jìn)程創(chuàng)建,但多個(gè)進(jìn)程都可以訪(fǎng)問(wèn).共享內(nèi)存是最快的IPC(進(jìn)程間通信)方式,它是針對(duì)其它進(jìn)程間通信方式運(yùn)行效率低而專(zhuān)門(mén)設(shè)計(jì)的.它往往與其他通信機(jī)制,如信號(hào)量,配合使用,來(lái)實(shí)現(xiàn)進(jìn)程間的同步與通信。
套接字
socket,即套接字是一種通信機(jī)制,憑借這種機(jī)制,客戶(hù)/服務(wù)器(即要進(jìn)行通信的進(jìn)程)系統(tǒng)的開(kāi)發(fā)工作既可以在本地單機(jī)上進(jìn)行,也可以跨網(wǎng)絡(luò)進(jìn)行。也就是說(shuō)它可以讓不在同一臺(tái)計(jì)算機(jī)但通過(guò)網(wǎng)絡(luò)連接計(jì)算機(jī)上的進(jìn)程進(jìn)行通信。也因?yàn)檫@樣,套接字明確地將客戶(hù)端和服務(wù)器區(qū)分開(kāi)來(lái)。
Binder
理解了 Linux IPC 相關(guān)概念和通信原理,接下來(lái)我們來(lái)了解下Binder IPC的原理。
Binder IPC 原理
Binder 驅(qū)動(dòng)
跨進(jìn)程通信是需要內(nèi)核空間做支持的。傳統(tǒng)的 IPC 機(jī)制如管道、Socket 都是內(nèi)核的一部分,因此通過(guò)內(nèi)核支持來(lái)實(shí)現(xiàn)進(jìn)程間通信自然是沒(méi)問(wèn)題的。但是 Binder 并不是 Linux 系統(tǒng)內(nèi)核的一部分,那怎么辦呢?這就得益于 Linux 的 動(dòng)態(tài)內(nèi)核可加載模塊(Loadable Kernel Module,LKM)的機(jī)制;模塊是具有獨(dú)立功能的程序,它可以被單獨(dú)編譯,但是不能獨(dú)立運(yùn)行。它在運(yùn)行時(shí)被鏈接到內(nèi)核作為內(nèi)核的一部分運(yùn)行。這樣,Android 系統(tǒng)就可以通過(guò)動(dòng)態(tài)添加一個(gè)內(nèi)核模塊運(yùn)行在內(nèi)核空間,用戶(hù)進(jìn)程之間通過(guò)這個(gè)內(nèi)核模塊作為橋梁來(lái)實(shí)現(xiàn)通信。在 Android 系統(tǒng)中,這個(gè)運(yùn)行在內(nèi)核空間,負(fù)責(zé)各個(gè)用戶(hù)進(jìn)程通過(guò) Binder 實(shí)現(xiàn)通信的內(nèi)核模塊就叫 Binder 驅(qū)動(dòng)(Binder Dirver)。
內(nèi)存映射
Binder IPC 機(jī)制中涉及到的內(nèi)存映射通過(guò) mmap() 來(lái)實(shí)現(xiàn),mmap() 是操作系統(tǒng)中一種內(nèi)存映射的方法。內(nèi)存映射簡(jiǎn)單的講就是將用戶(hù)空間的一塊內(nèi)存區(qū)域映射到內(nèi)核空間。映射關(guān)系建立后,用戶(hù)對(duì)這塊內(nèi)存區(qū)域的修改可以直接反應(yīng)到內(nèi)核空間;反之內(nèi)核空間對(duì)這段區(qū)域的修改也能直接反應(yīng)到用戶(hù)空間。
內(nèi)存映射能減少數(shù)據(jù)拷貝次數(shù),實(shí)現(xiàn)用戶(hù)空間和內(nèi)核空間的高效互動(dòng)。兩個(gè)空間各自的修改能直接反映在映射的內(nèi)存區(qū)域,從而被對(duì)方空間及時(shí)感知。也正因?yàn)槿绱?,?nèi)存映射能夠提供對(duì)進(jìn)程間通信的支持。
實(shí)現(xiàn)原理
一次完整的 Binder IPC 通信過(guò)程通常是這樣:
1、首先 Binder 驅(qū)動(dòng)在內(nèi)核空間創(chuàng)建一個(gè) 數(shù)據(jù)接收緩存區(qū) ;
2、接著在內(nèi)核空間開(kāi)辟一塊內(nèi)核緩存區(qū),建立 內(nèi)核緩存區(qū) 和 內(nèi)核中數(shù)據(jù)接收緩存區(qū) 之間的映射關(guān)系,以及 內(nèi)核中數(shù)據(jù)接收緩存區(qū) 和 接收進(jìn)程用戶(hù)空間地址 的映射關(guān)系;
3、發(fā)送方進(jìn)程通過(guò)系統(tǒng)調(diào)用 copyfromuser() 將數(shù)據(jù) copy 到內(nèi)核中的內(nèi)核緩存區(qū),由于內(nèi)核緩存區(qū)和接收進(jìn)程的用戶(hù)空間存在內(nèi)存映射,因此也就相當(dāng)于把數(shù)據(jù)發(fā)送到了接收進(jìn)程的用戶(hù)空間,這樣便完成了一次進(jìn)程間的通信。

與傳統(tǒng)IPC的比較
性能
Binder相比傳統(tǒng)IPC,通過(guò)內(nèi)存映射減少了數(shù)據(jù)拷貝次數(shù),從而提供了傳輸?shù)男阅堋?/strong>
| IPC方式 | 拷貝次數(shù) |
|---|---|
| 共享內(nèi)存 | 0 |
| Binder | 1 |
| 管道/消息隊(duì)列/信號(hào)量/Socket | 2 |
穩(wěn)定性
Binder 基于 C/S 架構(gòu),客戶(hù)端(Client)有什么需求就丟給服務(wù)端(Server)去完成,架構(gòu)清晰、職責(zé)明確又相互獨(dú)立,自然穩(wěn)定性更好。共享內(nèi)存雖然無(wú)需拷貝,但是控制負(fù)責(zé),難以使用。從穩(wěn)定性的角度講,Binder 機(jī)制是優(yōu)于內(nèi)存共享的。
安全性
Binder通過(guò)UID/PID來(lái)保證通信雙方的合法性,進(jìn)而達(dá)到IPC間消息傳輸?shù)陌踩浴?/strong>Binder對(duì)每個(gè)進(jìn)程都分配了UID/PID,當(dāng)通過(guò)Binder進(jìn)行IPC時(shí),系統(tǒng)會(huì)去核對(duì)UID/PID是否合法,只要系統(tǒng)認(rèn)為合法的PID,才能完成Binder的IPC調(diào)用。如此,在IPC上增加一道關(guān)卡,保障IPC通信安全,所以相對(duì)其他IPC方式更具安全性。
Binder通信模型

Client, Server, ServiceManager 都是通過(guò)系統(tǒng)調(diào)用 open, mmap,和ioctl 來(lái)訪(fǎng)問(wèn)設(shè)備文件 /dev/binder, 從而實(shí)現(xiàn)與 Binder 驅(qū)動(dòng)的交互來(lái)間接的實(shí)現(xiàn)跨進(jìn)程通信.
Binder 驅(qū)動(dòng)
Binder 驅(qū)動(dòng)就如同路由器一樣,是整個(gè)通信的核心;驅(qū)動(dòng)負(fù)責(zé)進(jìn)程之間 Binder 通信的建立,Binder 在進(jìn)程之間的傳遞,Binder 引用計(jì)數(shù)管理,數(shù)據(jù)包在進(jìn)程之間的傳遞和交互等一系列底層支持。
ServiceManager
ServiceManager 和 DNS 類(lèi)似,作用是將字符形式的 Binder 名字轉(zhuǎn)化成 Client 中對(duì)該 Binder 的引用,使得 Client 能夠通過(guò) Binder 的名字獲得對(duì) Binder 實(shí)體的引用。注冊(cè)了名字的 Binder 叫實(shí)名 Binder,就像網(wǎng)站一樣除了除了有 IP 地址意外還有自己的網(wǎng)址。Server 創(chuàng)建了 Binder,并為它起一個(gè)字符形式,可讀易記得名字,將這個(gè) Binder 實(shí)體連同名字一起以數(shù)據(jù)包的形式通過(guò) Binder 驅(qū)動(dòng)發(fā)送給 ServiceManager ,通知 ServiceManager 注冊(cè)一個(gè)名為“張三”的 Binder,它位于某個(gè) Server 中。驅(qū)動(dòng)為這個(gè)穿越進(jìn)程邊界的 Binder 創(chuàng)建位于內(nèi)核中的實(shí)體節(jié)點(diǎn)以及 ServiceManager 對(duì)實(shí)體的引用,將名字以及新建的引用打包傳給 ServiceManager。ServiceManger 收到數(shù)據(jù)后從中取出名字和引用填入查找表。
ServierManager 是一個(gè)進(jìn)程,Server 是另一個(gè)進(jìn)程,Server 向 ServiceManager 中注冊(cè) Binder 必然涉及到進(jìn)程間通信。ServiceManager 和其他進(jìn)程同樣采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 實(shí)體,其他進(jìn)程都是 Client,需要通過(guò)這個(gè) Binder 的引用來(lái)實(shí)現(xiàn) Binder 的注冊(cè),查詢(xún)和獲取。ServiceManager 提供的 Binder 比較特殊,它沒(méi)有名字也不需要注冊(cè)。當(dāng)一個(gè)進(jìn)程使用 BINDER_SET_CONTEXT_MGR 命令將自己注冊(cè)成 ServiceManager 時(shí) Binder 驅(qū)動(dòng)會(huì)自動(dòng)為它創(chuàng)建 Binder 實(shí)體。其次這個(gè) Binder 實(shí)體的引用在所有 Client 中都固定為 0 而無(wú)需通過(guò)其它手段獲得。也就是說(shuō),一個(gè) Server 想要向 ServiceManager 注冊(cè)自己的 Binder 就必須通過(guò)這個(gè) 0 號(hào)引用和 ServiceManager 的 Binder 通信。類(lèi)比互聯(lián)網(wǎng),0 號(hào)引用就好比是域名服務(wù)器的地址,你必須預(yù)先動(dòng)態(tài)或者手工配置好。要注意的是,這里說(shuō)的 Client 是相對(duì)于 ServiceManager 而言的,一個(gè)進(jìn)程或者應(yīng)用程序可能是提供服務(wù)的Server,但對(duì)于 ServiceManager 來(lái)說(shuō)它仍然是個(gè) Client。
Client Server 向 ServiceManager 中注冊(cè)了 Binder 以后, Client 就能通過(guò)名字獲得 Binder 的引用了。Client 也利用保留的 0 號(hào)引用向 ServiceManager 請(qǐng)求訪(fǎng)問(wèn)某個(gè) Binder: 我申請(qǐng)?jiān)L問(wèn)名字叫張三的 Binder 引用。ServiceManager 收到這個(gè)請(qǐng)求后從請(qǐng)求數(shù)據(jù)包中取出 Binder 名稱(chēng),在查找表里找到對(duì)應(yīng)的條目,取出對(duì)應(yīng)的 Binder 引用作為回復(fù)發(fā)送給發(fā)起請(qǐng)求的 Client。從面向?qū)ο蟮慕嵌瓤矗琒erver 中的 Binder 實(shí)體現(xiàn)在有兩個(gè)引用:一個(gè)位于 ServiceManager 中,一個(gè)位于發(fā)起請(qǐng)求的 Client 中。如果接下來(lái)有更多的 Client 請(qǐng)求該 Binder,系統(tǒng)中就會(huì)有更多的引用指向該 Binder ,就像 Java 中一個(gè)對(duì)象有多個(gè)引用一樣。
Binder通信過(guò)程

Binder應(yīng)用
binder在Android的應(yīng)用非常多,比如Activity的啟動(dòng)、Broadcast的發(fā)送、Service的綁定等,而且常用的Messager、AIDL、ContentProvider都是使用Binder來(lái)實(shí)現(xiàn)的。
參考
https://www.cnblogs.com/bakari/p/5520860.html
https://blog.csdn.net/ypbsyy/article/details/79915117
https://www.linuxprobe.com/linux-process-method.html
http://m.itdecent.cn/p/38c75edb4301
http://m.itdecent.cn/p/41d3bbecff2c