一文深入理解Linux進(jìn)程間通信
一、進(jìn)程間通信的本質(zhì)
什么是進(jìn)程間通信?為什么要有進(jìn)程間通信?
為什么能進(jìn)程間通信?
1.1 為什么要通信
我們先拿人來做個(gè)類比,人與人之間為什么要通信,有兩個(gè)原因。首先是因?yàn)槟阌泻蛯Ψ綔贤ǖ男枨?,如果你都不想搭理對方,那就肯定不用通信了。其次是因?yàn)橛锌臻g隔離,如果你倆在一起,對方就站在你面前,你有話直說就行了,不需要通信。此時(shí)你非要給對方打個(gè)電話或者發(fā)個(gè)微信,是不是顯得非常奇怪、莫名其妙。如果你倆不在一塊,還有事需要溝通,此時(shí)就需要通信了。通信的方式有點(diǎn)烽火、送信鴿、寫信、發(fā)電報(bào)、打電話、發(fā)微信等。采取什么樣的通信方式跟你的需求、通信量的大小、以及客觀上能否實(shí)現(xiàn)有關(guān)。
同樣的,軟件體系中為什么會(huì)有進(jìn)程間通信呢?首先是因?yàn)檐浖杏羞@個(gè)需求,比如有些任務(wù)是由多個(gè)進(jìn)程一起協(xié)同來完成的,或者一個(gè)進(jìn)程對另一個(gè)進(jìn)程有服務(wù)請求,或者有消息要向另一方提供。其次是因?yàn)檫M(jìn)程間有隔離,每個(gè)進(jìn)程都有自己獨(dú)立的用戶空間,互相看不到對方,所以才需要通信。
1.2 為什么能通信
為什么能通信呢?那是因?yàn)閮?nèi)核空間是共享的,雖然N個(gè)進(jìn)程都有N個(gè)用戶空間,但是內(nèi)核空間只有一個(gè),雖然用戶空間之間是完全隔離的,但是用戶空間與內(nèi)核空間并不是完全隔離的,他們之間有系統(tǒng)調(diào)用這個(gè)通道可以溝通。所以兩個(gè)用戶空間就可以通過內(nèi)核空間這個(gè)橋梁進(jìn)行溝通了。
我們再借助一副圖來講解一下。

雖然這個(gè)圖是講進(jìn)程調(diào)度的,但是大家從這個(gè)圖里面也能看出來進(jìn)程之間為什么要通信,因?yàn)檫M(jìn)程之間都是有空間隔離的,它們之間要想交流信息是沒有辦法的。但是也不是完全沒有辦法,好在它們都和內(nèi)核是連著的,雖然它們不能隨意訪問內(nèi)核,但是還有系統(tǒng)調(diào)用這個(gè)大門,進(jìn)程之間可以通過一些特殊的系統(tǒng)調(diào)用和內(nèi)核溝通從而達(dá)到和其它進(jìn)程通信的目的。
二、進(jìn)程間通信的框架?
通過上一章的描述,我們明白了進(jìn)程間為什么要通信、為什么能通信,現(xiàn)在我們來看看進(jìn)程間通信機(jī)制該如何實(shí)現(xiàn)。
2.1 進(jìn)程間通信機(jī)制的結(jié)構(gòu)
進(jìn)程間通信機(jī)制都要有兩部分組成,一是存在于內(nèi)核空間的通信中樞,二是存在于用戶空間的通信接口,這兩者的關(guān)系就好比是郵局與信紙的關(guān)系、基站與手機(jī)的關(guān)系。通信中樞提供通信機(jī)制,通信接口提供使用方法。我們使用通信接口來讓通信中樞幫我們建立通信信道或者傳遞通信信息。
下面我們畫個(gè)圖看一下進(jìn)程間通信機(jī)制的基本結(jié)構(gòu)。

2.2 進(jìn)程間通信機(jī)制的類型
進(jìn)程間通信機(jī)制的類型有兩種,一種是媒婆式,給你倆牽線搭橋,然后就不管了,你倆自己聊吧,另一種是保姆式,一直在中間傳話。這兩種模式用計(jì)算機(jī)的術(shù)語來說分別叫做共享內(nèi)存式和消息傳遞式。共享內(nèi)存式進(jìn)程間通信,通信中樞建立好通信信道之后,就不再管了,通信雙方之后的通信不需要通信中樞的協(xié)助。消息傳遞式進(jìn)程間通信,通信中樞建立好通信信道之后,每次通信還都需要通信中樞的協(xié)助。共享內(nèi)存式進(jìn)程間通信,由于通信信息的傳遞不需要通信中樞的協(xié)助,所以通信雙方還需要進(jìn)程間同步,來保證數(shù)據(jù)讀寫的一致性,以避免踩踏數(shù)據(jù)或者讀到垃圾數(shù)據(jù)。消息傳遞式進(jìn)程間通信,由于通信信息是通過通信中樞傳遞的,所以不需要進(jìn)程間同步。消息傳遞式進(jìn)程間通信又可以分為兩類,有邊界消息和無邊界消息。無邊界消息就是字節(jié)流,發(fā)過來是一個(gè)一個(gè)的字節(jié),要靠進(jìn)程自己設(shè)計(jì)如何區(qū)分消息的邊界。有邊界消息的進(jìn)程間通信的發(fā)送和接收都是以消息為基本單位的。
2.3 進(jìn)程間通信機(jī)制的接口設(shè)計(jì)
按照通信雙方的關(guān)系,可以把通信類型分為對稱型通信和非對稱型通信。對稱型通信的雙方關(guān)系是對等的,非對稱型通信的雙方關(guān)系是不對等的,可能是命令執(zhí)行關(guān)系、客戶服務(wù)關(guān)系、生產(chǎn)消費(fèi)關(guān)系等。這種關(guān)系是通信雙方邏輯上的關(guān)系,并不是進(jìn)程間通信機(jī)制本身的特征。消息傳遞式進(jìn)程間通信一般用于非對稱型通信,共享內(nèi)存式進(jìn)程間通信一般用于對稱型通信,也可以用于非對稱型通信。
進(jìn)程間通信機(jī)制一般要實(shí)現(xiàn)下面三類接口,但是有些機(jī)制不一定要這三類接口都實(shí)現(xiàn)。
1.如何建立通信信道,誰去建立通信信道。
2.后者如何找到并加入這個(gè)通信信道。
3.如何使用通信信道。
對于對稱型通信來說,誰去建立通信信道無所謂,有一個(gè)人去建立就可以了,后者直接加入通信信道。對于非對稱型通信,一般是由服務(wù)端、消費(fèi)者建立通信信道,客戶端、生產(chǎn)者則加入這個(gè)通信信道。如何建立信道呢,不同的進(jìn)程間通信機(jī)制,有不同的接口來創(chuàng)建信道,這個(gè)在下一章講。后者如何找到并加入前者建立的通信信道呢?一般情況是,雙方通過提前約定好的信道名稱找到信道句柄,通過信道句柄加入通信信道。但是有的是通過繼承把信道句柄傳遞給對方,有的是通過其它進(jìn)程間通信機(jī)制傳遞信道句柄,有的則是通過信道名稱直接找到信道,不需要信道句柄。如何使用信道呢?對于消息傳遞式進(jìn)程間通信來說,一般都要提供特殊的接口。對于共享內(nèi)存式進(jìn)程間通信來說,則不需要提供這個(gè)接口,因?yàn)榫秃驮L問普通內(nèi)存一樣訪問共享內(nèi)存就行。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ? ?


零聲白金VIP體驗(yàn)卡(含基礎(chǔ)架構(gòu)/高性能存儲(chǔ)/golang/QT/音視頻/Linux內(nèi)核)課程:

三、進(jìn)程間通信機(jī)制簡介
前面我們對進(jìn)程間通信的本質(zhì)和框架有了基本的了解,下面我們來簡單介紹一下Linux中的所有進(jìn)程間通信機(jī)制。我們先來看一下總圖。

我們先把這張圖簡介瀏覽一下。首先從大類上分,進(jìn)程間通信方法可以分為3類,消息傳遞式、共享內(nèi)存式、進(jìn)程間同步。為啥這里會(huì)有進(jìn)程間同步呢?進(jìn)程間同步是為了同步兩個(gè)進(jìn)程對共享內(nèi)存的讀寫,進(jìn)程間同步也算是在兩個(gè)進(jìn)程間傳遞了信息,所以把進(jìn)程間同步也放在了進(jìn)程間通信中。
可以看到共享內(nèi)存式機(jī)制比消息傳遞式機(jī)制要少,我們就先介紹共享內(nèi)存式。共享內(nèi)存式進(jìn)程間通信的原理很簡單,就是通過修改頁表,使得兩個(gè)虛擬進(jìn)程空間的一部分虛擬內(nèi)存對應(yīng)到相同的物理內(nèi)存上。雖然原理是一樣的,但是具體怎么實(shí)現(xiàn),接口怎么設(shè)計(jì),又產(chǎn)生了許多不同的共享內(nèi)存式進(jìn)程間通信機(jī)制。
3.1 SysV共享內(nèi)存
SysV共享內(nèi)存是一種非常古老的共享內(nèi)存方法,是在UNIX誕生早期就有的方法。SysV共享內(nèi)存創(chuàng)建共享內(nèi)存的方法是使用接口shmget,它有三個(gè)參數(shù),分別是key、size、flag。其中key是一個(gè)整數(shù),是表示通信信道的名稱,兩個(gè)進(jìn)程要提前約定好key。Size代表共享內(nèi)存的大小。Flag用來表示創(chuàng)建的行為,flag IPC_CREAT 表示如果通信信道存在就直接獲取它,如果還不存在就創(chuàng)建它,沒有IPC_CREAT的話表示只獲取不創(chuàng)建。如果再加上IPC_EXCL的話,表示只創(chuàng)建,如果已經(jīng)被別人創(chuàng)建了則返回失敗。shmget返回的是共享內(nèi)存的id,代表通信信道的句柄。然后拿著通信信道的句柄通過shmat接口就可以把底層的物理內(nèi)存映射到本進(jìn)程空間了。函數(shù)返回值就是映射到本進(jìn)程虛擬內(nèi)存空間的一個(gè)指針,然后就可以像訪問普通內(nèi)存一樣讀寫這段內(nèi)存了。任務(wù)完成之后就可以通過shmdt接口釋放信道。注意這只是釋放了本進(jìn)程的通信信道,沒有釋放底層的物理內(nèi)存,要釋放底層物理內(nèi)存的話,需要使用接口shmctl()并選擇IPC_RMID操作。
3.2 POSIX共享內(nèi)存
相信大家對前面的敘述都有個(gè)疑惑,用一個(gè)整數(shù)當(dāng)做通信信道名稱,那豈不是很容易就選重了,那不就錯(cuò)亂了嘛!而且如果有人惡意猜測使用你的key,你也沒有辦法。針對這個(gè)問題,POSIX設(shè)計(jì)出了一個(gè)新的共享內(nèi)存方案,叫做POSIX共享內(nèi)存,很好地解決了這個(gè)問題。POSIX共享內(nèi)存使用接口shm_open來創(chuàng)建共享內(nèi)存通信信道句柄,它的參數(shù)和open是一樣的,但是它不創(chuàng)建磁盤文件。這樣以來,我們使用的是一個(gè)路徑名作為通信信道的名稱,這就比一個(gè)整數(shù)key好多了,容易起名字還不容易重復(fù)。并且它的參數(shù)是和open一樣的,所以它的第三個(gè)參數(shù)mode可以指定權(quán)限,這樣就更安全了。shm_open的第二個(gè)參數(shù)和open的第二個(gè)參數(shù)是一樣的,可以指定flag O_CREAT O_EXCL,這兩個(gè)flag和前面的shmget可以達(dá)到相同的效果,你可以選擇是僅加入已經(jīng)信道,還是非要自己親自創(chuàng)建信道,或者已有就加入沒有就創(chuàng)建。shm_open返回的是一個(gè)fd,這個(gè)fd就是通信信道的句柄。有了這個(gè)fd,我們可以通過接口ftruncate來設(shè)置共享內(nèi)存的大小。得到了信道句柄之后,我們加入信道的方式不是用的專用的方法,而是使用系統(tǒng)已有的接口,用的是shared mmap,這點(diǎn)和SysV共享內(nèi)存有很大的不同。mmap之后我們就加入了信道,其返回值是本進(jìn)程虛擬內(nèi)存空間的指針,我們就可以像操作普通內(nèi)存一樣操作它了。
3.3 共享內(nèi)存映射
系統(tǒng)調(diào)用mmap并不是專門用來做進(jìn)程間通信的,它是用來做內(nèi)存映射的。它的映射來源可以用文件也可以是匿名(也就是沒有來源,直接分配內(nèi)存并初始化為0)。它的映射方式可以是私有的,也可以是共享的。映射來源和映射方式兩者一組合是四種方式。當(dāng)我們使用共享映射方式的時(shí)候,正好可以用來做進(jìn)程間通信。對于共享文件映射,兩個(gè)進(jìn)程映射相同的文件就可以達(dá)到共享內(nèi)存的目的,文件名就是通信信道的名稱,由名稱直接加入信道,沒有信道句柄。對于共享匿名映射,是通過fork之后在父子進(jìn)程之間共享內(nèi)存的。fork之后父子進(jìn)程之間的內(nèi)存本來是COW(寫時(shí)復(fù)制)的,也就是說父子進(jìn)程之間不會(huì)共享內(nèi)存,但是被共享匿名映射的部分不會(huì)COW,而是在父子進(jìn)程之間共享物理內(nèi)存,這就達(dá)到了共享內(nèi)存的效果。這種方法既沒有信道名稱也沒有信道句柄,是通過繼承方式直接就獲得了信道。這兩種共享內(nèi)存的解除方法都是使用munmap函數(shù)。
3.4 Android ION
很多博客上都會(huì)介紹說ION是一個(gè)內(nèi)存分配管理器,這么說既對也不對,單看ION它確實(shí)是內(nèi)存分配管理器,但是我們不能單看ION,我們要把和dma-buf一起看。Dma-buf既不是dma也不是buffer,它是一個(gè)buffer sharing框架,重點(diǎn)是sharing。Dma-buf框架實(shí)現(xiàn)了進(jìn)程與進(jìn)程之間、進(jìn)程與內(nèi)核之間的內(nèi)存共享方案。但是它僅僅是一個(gè)框架,本身并沒有分配內(nèi)存的能力。ION則在dma-buf框架的基礎(chǔ)之上實(shí)現(xiàn)了內(nèi)存分配管理功能,所以應(yīng)該把ION與dma-buf當(dāng)做是一個(gè)整體,看成是共享內(nèi)存機(jī)制。ION與普通共享內(nèi)存機(jī)制不同的是,它不僅僅可以在進(jìn)程間共享內(nèi)存,還能在進(jìn)程與內(nèi)核之間共享內(nèi)存。ION在進(jìn)程之間共享內(nèi)存時(shí),是一方通過/dev/ion的ioctl ALLOC命令創(chuàng)建一個(gè)fd,這個(gè)fd就是信道句柄,通過對這個(gè)fd進(jìn)行mmap就可以通信了。這和POSIX共享內(nèi)存的模式有點(diǎn)像,不同的是對方是如何得到這個(gè)fd的。POSIX共享內(nèi)存是通過大家都shm_open打開相同的文件名得到了同一個(gè)信道的句柄(句柄值不一定相同,但是底層對應(yīng)的信道是相同的)。ION是通過Binder向另一個(gè)進(jìn)程傳遞fd的,Binder對fd做了特殊處理,對方收到的fd和自己的fd,數(shù)值不一定相同,但是底層對應(yīng)的東西是相同的。如果直接給一個(gè)進(jìn)程傳遞fd的值,那是沒有意義的。ION和內(nèi)核驅(qū)動(dòng)之間共享內(nèi)存有兩種情況,一種是內(nèi)核驅(qū)動(dòng)創(chuàng)建了底層的物理內(nèi)存然后把它包裝成一個(gè)fd,通過一些系統(tǒng)調(diào)用傳遞給進(jìn)程,進(jìn)程對這個(gè)fd進(jìn)行mmap就可以進(jìn)行進(jìn)程間通信了。另一種情況是進(jìn)程創(chuàng)建了通信信道的fd,然后通過一些系統(tǒng)調(diào)用傳遞給內(nèi)核驅(qū)動(dòng),內(nèi)核驅(qū)動(dòng)就根據(jù)這個(gè)fd找到其對應(yīng)的物理內(nèi)存。
ION里面有許多不同的堆,每個(gè)堆分配的物理內(nèi)存區(qū)域和方式并不相同,可以在使用ION接口的時(shí)候通過指定flag來選擇不同的堆。
3.5 dma-buf heaps
dma-buf heaps是ION的替代品。因?yàn)镮ON里面所有的堆都對應(yīng)同一個(gè)設(shè)備文件/dev/ion,不同的堆是通過在接口中指定flag來選擇的。那么這就存在一個(gè)問題,就是ION所有的堆對所有進(jìn)程都是開放的,沒法或者不太容易對不同的進(jìn)程做權(quán)限限制。dma-buf heaps正好解決了這個(gè)問題,它把不同的堆分拆成了不同的設(shè)備,都在目錄 /dev/dma_heap/ 下,比如 /dev/dma_heap/system 是默認(rèn)的堆。這樣不同的堆就可以設(shè)置不同的文件權(quán)限,還可以通過selinux進(jìn)行限制,這樣就大大提高了安全性。它的用法和底層邏輯與ION是一樣了,這里就不再過多介紹了。值得一提的是dma-buf heaps已經(jīng)合入了標(biāo)準(zhǔn)內(nèi)核,而且Android也正在逐步替換ION。
3.6 匿名管道
前面說的都是共享內(nèi)存式進(jìn)程間通信,下面我們來說一說消息傳遞式進(jìn)程間通信。我們先來說說無邊界的消息傳遞式進(jìn)程間通信。
匿名管道是UNIX上最早的進(jìn)程間通信機(jī)制了。它的出現(xiàn)來源于早期的操作系統(tǒng)都是命令行式的,我們經(jīng)常需要多個(gè)命令來協(xié)同完成一個(gè)任務(wù)。比如 ls -ef | grep process-name ,這個(gè)命令中前面命令的輸出要作為后面命令的輸入,中間的|豎線叫做管道符,代表像管道一樣從前往后傳遞數(shù)據(jù)。那么這個(gè)管道符的邏輯在程序中是怎么實(shí)現(xiàn)的呢,就是通過匿名管道實(shí)現(xiàn)的。Shell在執(zhí)行命令時(shí)先fork出一個(gè)子進(jìn)程A,然后在子進(jìn)程A中解析命令,發(fā)現(xiàn)命令需要執(zhí)行兩個(gè)程序,并通過管道連接。于是就使用匿名管道的創(chuàng)建接口int pipe(int fd[2]),此接口接收一個(gè)雙int元素的數(shù)組作為參數(shù)。接口執(zhí)行完成后返回兩個(gè)fd, fd[0]是讀端fd,fd[1]是寫端接口。然后fork A,生成進(jìn)程B,這樣進(jìn)程B也繼承了兩個(gè)fd。A和B都有兩個(gè)fd是沒啥意義的,于是進(jìn)程A close(fd[0]),進(jìn)程B close(fd[1])。然后進(jìn)程A執(zhí)行exec(“l(fā)s -l”),然后進(jìn)程B執(zhí)行exec(“grep process-name”),這樣進(jìn)程A就可以通過fd[1]輸出數(shù)據(jù),進(jìn)程B通過fd[0]讀取數(shù)據(jù)。這樣就實(shí)現(xiàn)了進(jìn)程間通信的目的。匿名管道通過通信雙方的父進(jìn)程創(chuàng)建通信句柄,然后通過fork傳遞給子進(jìn)程。父子進(jìn)程都通過file IO的方式來進(jìn)行消息傳遞。由于是使用的file IO,所以讀寫的都是字節(jié)流,并沒有消息邊界。如果進(jìn)程想要確定消息邊界,需要自己想辦法確定每個(gè)消息的邊界,比如每個(gè)換行符代表一個(gè)消息,或者每次遇到字符串AAAAAA,代表一個(gè)新消息。
3.7 命名管道
我們可以看到匿名管道雖然很好用,但是卻有一個(gè)很大的缺陷,就是只能父子進(jìn)程或者親屬進(jìn)程之間使用,因?yàn)橐獋鬟f信道句柄fd。有沒有辦法擴(kuò)大匿名管道的使用范圍呢,有,創(chuàng)建命名管道。管道有了名稱之后,其它進(jìn)程就可以通過名稱找到信道句柄從而加入信道了。命名管道的用法是,首先要使用mkfifo命令在文件系統(tǒng)創(chuàng)建一個(gè)文件,這個(gè)文件是真實(shí)的文件,但不是常規(guī)文件,而是fifo類型的文件。有個(gè)這個(gè)文件之后,通信雙方的寫者就可以用正常的open接口以O(shè)_WRONLY模式打開文件,讀者就可以用open接口以O(shè)_RDONLY方式打開文件。然后讀寫雙方就可以通過各自的fd讀寫管道了。命名管道的創(chuàng)建方式和匿名管道不同,但是消息傳遞方式是相同的。匿名管道也是無邊界消息,原理同匿名管道一樣。
3.8 SysV消息隊(duì)列
SysV消息隊(duì)列是一個(gè)有邊界的消息傳遞式進(jìn)程間通信。它的信道創(chuàng)建邏輯和SysV共享內(nèi)存差不多。創(chuàng)建接口是msgget,有兩個(gè)參數(shù)key和flag。Key是一個(gè)整數(shù),是信道名稱。Flag有兩個(gè),flag IPC_CREAT 表示如果通信信道存在就直接獲取它,如果還不存在就創(chuàng)建它,沒有IPC_CREAT的話表示只獲取不創(chuàng)建。如果再加上flag IPC_EXCL的話,表示只創(chuàng)建,如果已經(jīng)被別人創(chuàng)建了則返回失敗。msgget返回的是消息隊(duì)列的id,也就是信道的句柄。然后可以通過接口msgsnd和msgrcv來發(fā)送和接收消息,一個(gè)只能發(fā)送或者接收一個(gè)消息。當(dāng)通信完成之后,可以通過接口msgctl的IPC_RMID操作來銷毀消息隊(duì)列。
3.9 POSIX消息隊(duì)列
SysV消息隊(duì)列和SysV共享內(nèi)存存在的問題是一樣的,于是又設(shè)計(jì)了POSIX消息隊(duì)列。POSIX消息隊(duì)列的創(chuàng)建接口是mq_open,它的參數(shù)和open是類似的。用一個(gè)字符串類型的name作為信道名稱。還有一個(gè)flag參數(shù)和前面講的flag參數(shù)是一樣的,可以指定是創(chuàng)建信道還是加入已經(jīng)的信道。返回值叫做消息隊(duì)列描述符,是信道句柄。然后可以通過接口mq_send、 mq_receive來發(fā)送接收消息。當(dāng)通信完成后可以通過接口mq_close來關(guān)閉信道。如果所有的進(jìn)程都關(guān)閉信道了,底層信道才會(huì)被刪除。
3.10 套接字
套接字是分為網(wǎng)絡(luò)套接字和UNIX local套接字。網(wǎng)絡(luò)套接字不僅可以在本機(jī)進(jìn)行進(jìn)程間通信,還能在不同的機(jī)器間進(jìn)行通信。UNIX local套接字只能在本機(jī)的進(jìn)程間進(jìn)行通信。兩者都分為流式套接字和數(shù)據(jù)報(bào)套接字,前者是無邊界消息傳遞式進(jìn)程間通信,后者是有邊界消息傳遞式進(jìn)程間通信。套接字是區(qū)分服務(wù)端和客戶端的,服務(wù)端創(chuàng)建通信信道,客戶端加入通信信道。套接字的接口這里就不介紹了,大家可以找一些網(wǎng)絡(luò)編程相關(guān)的書籍或者博客來學(xué)習(xí)。
3.11 Android Binder
Android Binder是谷歌為Android開發(fā)的RPC,RPC是遠(yuǎn)程過程調(diào)用的意思。RPC也是一種進(jìn)程間通信,但又不僅僅是進(jìn)程間通信,它的使用接口表現(xiàn)為可以透明調(diào)用其它進(jìn)程的函數(shù)。Binder的通信中樞是內(nèi)核里的Binder驅(qū)動(dòng),它的用戶空間接口是對虛擬設(shè)備/dev/binder的一系列ioctl命令。但是進(jìn)程并不是直接使用這些ioctl命令的,而是使用谷歌封裝好的libbinder庫。Binder的具體細(xì)節(jié)這里就不講了,給大家推薦兩個(gè)學(xué)習(xí)博客:
3.12 信號(hào)機(jī)制
信號(hào)機(jī)制是在UNIX里面很早就存在的機(jī)制,它是內(nèi)核用來處理程序運(yùn)行時(shí)發(fā)生錯(cuò)誤的一種方法,也是給進(jìn)程發(fā)送一些簡單特定的消息的方法,所以也可以看做是一種進(jìn)程間通信機(jī)制。但是它又比較特殊,它和一般的進(jìn)程間通信機(jī)制的結(jié)構(gòu)都不太相同。它是不需要建立通信信道的,因?yàn)樗皇堑湫偷倪M(jìn)程間通信,或者說它的通信信道是天然建立好的,因?yàn)樗玫氖莗id來指定消息傳遞給誰。它的發(fā)送是內(nèi)核發(fā)送或者進(jìn)程通過kill等接口發(fā)送,指定pid就能發(fā)送給對方。對方可以設(shè)置信號(hào)處理函數(shù)來接收處理信號(hào),也可以不設(shè)置,內(nèi)核會(huì)進(jìn)行默認(rèn)處理。信號(hào)機(jī)制的具體細(xì)節(jié)請參看《深入理解Linux信號(hào)機(jī)制》。
3.13 偽終端
大家可能聽說過終端、虛擬終端、控制臺(tái)、終端模擬器、偽終端等這些詞。估計(jì)大家和我一樣也是對這些詞一頭霧水,理不清它們到底是什么意思,相互之間是什么關(guān)系。其實(shí)我對虛擬終端和控制臺(tái)也不太理解,但是對終端、終端模擬器、偽終端還是比較了解的,在這里給大家講解一下。最早的時(shí)候,一臺(tái)電腦還是一臺(tái)幾間房子那么大的大型機(jī),普通人根本買不起,有些大學(xué)或者科研單位或者政府機(jī)關(guān)也只能買得起一臺(tái)。然后是大家每人買一個(gè)終端連接到這臺(tái)電腦就可以使用了。終端就是一臺(tái)顯示器加一個(gè)鍵盤,只不過這個(gè)顯示器并不是像素顯示器,而是字符顯示器,一屏只能顯示80x25的字符。當(dāng)時(shí)的程序也都是命令行程序,從終端接收輸入,再把結(jié)果輸出到終端。具體到程序內(nèi)部來說,fd 0 對應(yīng)的就是終端輸入,fd 1就是終端輸出。終端并不是說鍵盤輸入的是什么它就原封不動(dòng)地傳給程序,而是會(huì)做一定的預(yù)處理。
后來隨著技術(shù)的不斷發(fā)展,計(jì)算機(jī)就變成了我們今天使用的計(jì)算機(jī)。每個(gè)人都可以買一臺(tái)獨(dú)立的電腦了,而且顯示器也變成了像素顯示器了,可以顯示豐富的畫面。而且很多程序的模式也從命令行模式轉(zhuǎn)變成了GUI模式。但是仍然有很多程序比較適合在命令行執(zhí)行,仍然保留了命令行模式。為此系統(tǒng)開發(fā)了一個(gè)GUI程序,叫做終端模擬器,也是我們平常說的命令行界面或者終端程序。它利用圖形界面模擬了之前的終端界面,讓我們看起來像是在使用終端,但是它本身是一個(gè)GUI程序。終端模擬器是怎么運(yùn)行命令行程序的呢?它會(huì)使用系統(tǒng)的接口創(chuàng)建一個(gè)偽終端,偽終端分為主端和從端兩部分,模擬器自己拿主端,命令行程序拿從端,這樣命令行程序仿佛就像運(yùn)行在終端環(huán)境里一樣。我們從鍵盤輸入的字符其實(shí)是先按照GUI程序的邏輯傳遞給了終端模擬器,終端模擬器再把輸入傳遞給偽終端的主端,然后偽終端在內(nèi)核里按照終端本身的邏輯進(jìn)行處理,再發(fā)給偽終端從端,這樣我們的命令行程序才會(huì)收到輸入。命令行程序的輸出先發(fā)給偽終端從端,然后再進(jìn)入內(nèi)核里的偽終端,然后再發(fā)給偽終端主端,然后終端模擬器才收到我們的輸出,然后它再按照GUI程序的方法把輸出繪制到它的窗口上,我們就看到了程序的輸出。所以說偽終端可以看做是終端模擬器和命令行程序之間的進(jìn)程間通信機(jī)制。
?四、總結(jié)回顧
本文中我們先分析了進(jìn)程間通信的本質(zhì),然后講解了進(jìn)程間通信的基本框架,最后簡單介紹了Linux系統(tǒng)中存在的各種進(jìn)程間通信機(jī)制。大家在實(shí)際的工作過程中可以根據(jù)自己的需求來選擇使用哪種進(jìn)程間通信機(jī)制。
原文作者:Linux閱碼場
