【操作系统】进程间通信的五种方式
- 引言
- 1.進程對白:管道、記名管道、套接字
- 1.管道
- 2.蟲洞:套接字
- 3.信號
- 4.信號旗語:信號量
- 5.進程擁抱:共享內存
引言
進程作為人類的發明,自然免不了脫離人類的習性,也有通信需求。如果進程之間不進行任何通信,那么進程所能完成的任務就要大打折扣。例如,父進程在創建子進程后,通常需要監督子進程的狀態,以便在子進程沒有完成給定的任務時,可以再創建一個子進程來繼續。這就需要父子進程間通信。 進程之間的交互稱為進程間通信(Inter-Process Communication, IPC)。那么進程之間的通信是如何進行的呢?
1.進程對白:管道、記名管道、套接字
人們最常使用的通信手段就是對白。對白的特點就是一方發出聲音,另一方接收聲音。而聲音的傳遞則通過空氣(當面或無線交談)、線纜(有線電話)進行傳遞。類似,進程對白就是一個進程發出某種數據信息,另外一方接收數據信息,而這些數據信息通過一片共享的存儲空間進行傳遞。 在這種方式下,一個進程向這片存儲空間的一端寫入信息,另一個進程從存儲空間的另外一端讀取信息。這看上去像什么?管道。管道所占的空間既可以是內存,也可以是磁盤。就像兩人對白的媒介可以是空氣,也可以是線纜一樣。要創建一個管道,一個進程只需調用管道創建的系統調用即可。該系統調用所做的事情就是在某種存儲介質上劃出一片空間,賦給其中一個進程寫的權利,另一個進程讀的權利即可。
1.管道
從根本上說,管道是一個線性字節數組,類似文件,可以使用文件讀寫的方式進行訪問。但卻不是文件。因為通過文件系統看不到管道的存在。另外,我們前面說了,管道可以設在內存里,而文件很少設在內存里(當然,有研究人員在研發基于內存的文件系統,但這個還不是主流)。 創建管道在殼命令行下和在程序里是不同的。殼(shell)命令行下,只需要使用符號“|”即可。例如,在UNIX殼下,我們可以鍵入如下命令: $sort<file1|grep zou 在兩個utility“排序”(sort)和“查找”(grep)之間創建了一個管道,數據從sort流向grep。即sort的結果將作為grep的輸入。上述命令的意思是對file1的內容進行排序,排完序的結果作為utility程序grep的輸入,在結果里面找出所有包括字符串zou的文本行。 在程序里面,創建管道需要使用系統調用popen()或者pipe()。popen()需要提供一個目標進程作為參數,然后在調用該函數的進程和給出的目標進程之間創建一個管道。這很像人們打電話時必須提供對方的號碼,才能創建連接一樣。
在Linux 0.11中,管道操作分為兩部分,一部分是創建管道,另一部分是管道的讀寫操作。
實例1代碼如下:
實例1表現了進程間共享數據的情景:父進程把str1中的數據寫入管道,子進程從管道中讀出數據,其中str1中字符長度為1024字節,即1kb
創建時還需要提供一個參數表明管道類型:讀管道或者寫管道。而pipe()調用將返回兩個文件描述符(文件描述符是用來識別一個文件流的一個整數,與句柄不同),其中一個用于從管道進行讀操作,一個用于寫入管道。也就是說,pipe()將兩個文件描述符連接起來,使得一端可以讀,另一端可以寫。通常情況下,在使用pipe()調用創建管道后,再使用fork產生兩個進程,這兩個進程使用pipe()返回的兩個文件描述符進行通信。 例如,下面的代碼段創建一個管道并利用它在父子進程間通信。
示例2
管道的一個重要特點是使用管道的兩個進程之間必須存在某種關系,例如,使用popen需要提供另一端進程的文件名,使用pipe()的兩個進程則分別隸屬于父子進程。
記名管道
如果要在兩個不相關的進程(如兩個不同進程里面的進程)之間進行管道通信,則需要使用記名管道。顧名思義,命名管道是一個有名字的通信管道。記名管道與文件系統共享一個名字空間,即我們可以從文件系統中看到記名管道。也就是說,記名管道的名字不能與文件系統里的任何文件名重名。例如,在UNIX下使用ls命令可以查看已經創建的記名管道。
%ls-l fifo1
prw-r——r——1 john users 0 Sep 22 23:11 fifo1|
一個進程創建一個記名管道后,另外一個進程可使用open來打開這個管道(無名管道則不能使用open操作),從而與另外一端進行交流。
記名管道的名稱由兩部分組成:計算機名和管道名,例如\\[主機名]\管道\[管道名]\。對于同一主機來講,允許有多個同一命名管道的實例并且可以由不同的進程打開,但是不同的管道都有屬于自己的管道緩沖區而且有自己的通信環境,互不影響。命名管道可以支持多個客戶端連接一個服務器端。命名管道客戶端不但可以與本機上的服務器通信也可以同其他主機上的服務器通信。 管道和記名管道雖然具有簡單、無需特殊設計(指應用程序方面)就可以和另外一個進程進行通信的優點,但其缺點也很明顯。首先是管道和記名管道并不是所有操作系統都支持。主要支持管道通信方式的是UNIX和類UNIX(如Linux)的操作系統。這樣,如果需要在其他操作系統上進行通信,管道機制就多半會力不從心了。其次,管道通信需要在相關的進程間進行(無名管道),或者需要知道按名字來打開(記名管道),而這 在某些時候會十分不便。
2.蟲洞:套接字
套接字(socket)是另外一種可以用于進程間通信的機制。套接字首先在BSD操作系統中出現,隨后幾乎滲透到所有主流操作系統中。套接字的功能非常強大,可以支持不同層面、不同應用、跨網絡的通信。使用套接字進行通信需要雙方均創建一個套接字,其中一方作為服務器方,另外一方作為客戶方。服務器方必須先創建一個服務區套接字,然后在該套接字上進行監聽,等待遠方的連接請求。欲與服務器通信的客戶則創建一個客戶套接字,然后向服務區套接字發送連接請求。
服務器套接字在收到連接請求后,將在服務器方機器上創建一個客戶套接字,與遠方的客戶機上的客戶套接字形成點到點的通信通道。之后,客戶方和服務器方就可以通過send和recv命令在這個創建的套接字通道上進行交流了。 服務區套接字有點類似于傳說中的蟲洞(worm hole)
蟲洞的一端是開放的,它在宇宙內或宇宙間飄移著,另外一端處于一個不同的宇宙,監聽是否有任何東西從蟲洞來。而欲使用蟲洞者需要找到蟲洞的開口端(發送連接請求),然后穿越蟲洞即可。
使用套接字進行通信稍微有點復雜,我們下面以一個網頁瀏覽的例子對套接字這種通信方式予以說明。對于一個網站來說,要想提供正常的網頁瀏覽服務,其網站服務器需要首先創建一個服務區套接字,作為外界與本服務器的通信信道。為了使該信道為外人所知,我們通常將該服務區套接字與某公共主機的一個眾所周知的端口進行綁定,如下:
對于客戶方來說,如要訪問上述網站,則需要點擊該網站的網址。在點擊網址后(我們這里假定該網站網址為www.sjtu.edu.cn),客戶機上的網絡瀏覽器進行若干步操作,如下
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect("www.sjtu.edu.cn,80);s.connect命令將向服務器www.sjtu.edu.cn在端口80打開的服務器套接字發送連接請求。而服務器端在接收到該連接請求后,將生成一個新的客戶端套接字與該客戶端套接字對接,從而建立一個套接字通信信道。如下為網站服務器上運行的主循環。
while(TRUE) { (clientsocket,address) = serversocket.accept(); newct = client_thread(clientsocket);//對clientsocket進行相關操作,如創建一個新進程來處理客戶請求 newct.run(); }至此,套接字通信信道成功創建。客戶端程序可以使用套接字s來發送請求、索取網頁,而服務器端則使用套接字clientsocket進行發送和接收消息。 這里需要指出的是服務區套接字既不發送數據,也不接收數據(指不接收正常的用戶數據,而不是連接請求數據),而僅僅生產出“客戶”套接字。當其他(遠方)的客戶套接字發出一個連接請求時,我們就創建一個客戶套接字。一旦創建客戶套接字clientsocket,與客戶的通信任務就交給了這個剛剛創建的客戶套接字。而原本的服務器套接字serversocket則回到其原來的監聽操作上。 套接字由于其功能強大而獲得了很大發展,并出現了許多種類。不同的操作系統均支持或實現了某種套接字功能。例如按照傳輸媒介是否為本地,套接字可以分為本地(UNIX域)套接字和網域套接字。而網域套接字又按照其提供的數據傳輸特性分為幾個大類,分別是:
★ 數據流套接字(stream socket):提供雙向、有序、可靠、非重復數據通信。
★ 電報流套接字(datagram socket):提供雙向消息流。數據不一定按序到達。
★ 序列包套接字(sequential packet):提供雙向、有序、可靠連接,包有最大限制。
★ 裸套接字(raw socket):提供對下層通信協議的訪問。 套接字從某種程度上來說非常繁雜,各種操作系統對其處理并不完全一樣。因此,如要了解某個特定套接字實現,讀者需要查閱關于該套接字實現的具體手冊或相關文檔。
3.信號
管道和套接字雖然提供了豐富的通信語義,并且也得到了廣泛應用,但它們也存在某些缺點,并且在某些時候,這兩種通信機制會顯得很不好用。 首先,如果使用管道和套接字方式來通信,必須事先在通信的進程間建立連接(創建管道或套接字),這需要消耗系統資源。其次,通信是自愿的。即一方雖然可以隨意向管道或套接字發送信息,但對方卻可以選擇接收的時機。即使對方對此充耳不聞,你也奈何不得。再次,由于建立連接消耗時間,一旦建立,我們就想進行盡可能多的通信。而如果通信的信息量微小,如我們只是想通知一個進程某件事情的發生,則用管道和套接字就有點“殺雞用牛刀”的味道,效率十分低下。 因此,我們需要一種不同的機制來處理如下通信需求:
★ 想迫使一方對我們的通信立即做出回應。
★ 我們不想事先建立任何連接,而是臨時突然覺得需要與某個進程通信。
★ 傳輸的信息量微小,使用管道或套接字不劃算。 應付上述需求,我們使用的是信號(signal)。
那么信號是什么呢?在計算機里,信號就是一個內核對象,或者說是一個內核數據結構。發送方將該數據結構的內容填好,并指明該信號的目標進程后,發出特定的軟件中斷。操作系統接收到特定的中斷請求后,知道是有進程要發送信號,于是到特定的內核數據結構里查找信號接收方,并進行通知。接到通知的進程則對信號進行相應處理。 信號非常類似我們生活當中的電報,如果你想給某人發一封電報,就擬好電文,將報文和收報人的信息都交給電報公司。電報公司則將電報發送到收報人所在地的郵局(中斷),并通知收報人來取電報。發報時無需收報人事先知道,更無需進行任何協調。如果對方選擇不對信號做出響應,則將被操作系統終止運行。
示例代碼:
有兩個用戶進程。一個進程用來接收及處理信號,名字叫做processsig。它所對應的程序源代碼如下:
操作系統需要具備以下三個功能,以支持信號機制。
系統要支持進程對信號的發送和接收 系統在每個進程task_struct中設置了用以接收信號的數據成員signal(信號位圖),每個進程接收到的信號就“按位”存儲在這個數據結構中。系統支持兩種方式給進程發送信號:一種方式是一個進程通過調用特定的庫函數給另一個進程發送信號;另一種方式是用戶通過鍵盤輸入信息產生鍵盤中斷后,中斷服務程序給進程發送信號。這兩種方式的信號發送原理是相同的,都是通過設置信號位圖(signal)上的信號位來實現的。
系統要能夠及時檢測到進程接收到的信號 系統通過兩種方式來檢測進程是否接收到信號:一種方式是在系統調用返回之前檢測當前進程是否接收到信號;另一種方式是時鐘中斷產生后,其中斷服務程序執行結束之前,檢測當前進程是否接收到信號。
系統要支持進程對信號進行處理 系統要能夠保證,當用戶進程不需要處理信號時,信號處理函數完全不參與用戶進程的執行;當用戶進程需要處理信號時,進程的程序將暫時停止執行,轉而去執行信號處理函數,待信號處理函數執行完畢后,進程程序將從“暫停的現場處”繼續執行。
4.信號旗語:信號量
信號量(semaphore)是由荷蘭人E.W.Dijkstra在20世紀60年代所構思出的一種程序設計構造。其原型來源于鐵路的運行:在一條單軌鐵路上,任何時候只能有一列列車行駛在上面(如圖6-9)。而管理這條鐵路的系統就是信號量。任何一列火車必須等到表明鐵路可以行駛的信號后才能進入軌道。當一列列車進入單軌運行后,需要將信號改為禁止進入,從而防止別的火車同時進入軌道。而當列車駛出單軌后,則需要將信號變回允許進入狀態。這很像以前的旗語, 在計算機里,信號量實際上就是一個簡單整數。一個進程在信號變為0或者1的情況下推進,并且將信號變為1或0來防止別的進程推進。當進程完成任務后,則將信號再改變為0或1,從而允許其他進程執行。
5.進程擁抱:共享內存
管道、套接字、信號、信號量,雖然滿足了多種通信需要,但還是有一種需要未能滿足。這就是兩個進程需要共享大量數據。這就像兩個人,他們互相喜歡,并想要一起生活時(共享大量數據量),打電話、握手、對白等就顯得不夠了,這個時候需要的是擁抱,只有將其緊緊擁抱于懷,感覺才最到位,也才能盡可能地共享。 進程的擁抱就是共享內存。共享內存就是兩個進程共同擁有同一片內存。對于這片內存中的任何內容,二者均可以訪問。要使用共享內存進行通信,一個進程首先需要創建一片內存空間專門作為通信用,而其他進程則將該片內存映射到自己的(虛擬)地址空間。這樣,讀寫自己地址空間中對應共享內存的區域時,就是在和其他進程進行通信。
共享內存有點像管道,有些管道不也是一片共享內存嗎?這是形似而神不似。首先,使用共享內存機制通信的兩個進程必須在同一臺物理機器上;其次,共享內存的訪問方式是隨機的,而不是只能從一端寫,另一端讀,因此其靈活性比管道和套接字大很多,能夠傳遞的信息也復雜得多。 共享內存的缺點是管理復雜,且兩個進程必須在同一臺物理機器上才能使用這種通信方式。共享內存的另外一個缺點是安全性脆弱。因為兩個進程存在一片共享的內存,如果一個進程染有病毒,很容易就會傳給另外一個進程。就如同人傳染病毒一樣。
需要注意的是,使用全局變量在同一個進程的進程間實現通信不稱為共享內存。
總結
以上是生活随笔為你收集整理的【操作系统】进程间通信的五种方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot使用HttpSess
- 下一篇: rabbitmq的启动命令和spring