操作系統核心原理-3.進程原理(下):進程通信


  進程作為人類的發明,自然也免不了脫離人類的習性,也有通信的需求。如果進程之間不進行任何通信,那么進程所能完成的任務就要大打折扣。人類的通信方式無外乎對白(通過聲音溝通)、打手勢、寫信、發電報、擁抱等方法。同理,進程也可以通過同樣的方式來進行通信。本篇我們就來看看進程的這些交互方式。

一、進程對白:管道、套接字

  人們最常用的通信手段就是對白,一方發出聲音,另一方接收聲音。而聲音的傳遞需要通過一些介質,例如:空氣(face to face)、線纜(有線電話)等。類似的,進程對白就是一個進程發出某種數據信息,另外一方接收數據信息,而這些數據信息通過一片共享的存儲空間進行傳遞。

1.1 管道

  一個進程向存儲空間的一端寫入信息,另一個進程存儲空間的另外一端讀取信息,這個就是管道。就像兩個人對白的媒介是空氣也可以是線纜一樣,管道所占的空間既可以是內存也可以是磁盤。

  要創建一個管道,一個進程只需要調用管道創建的系統調用即可,該系統調用所做的事情就是在某種存儲介質上划出一片空間,賦給其中一個進程寫的權利,另一個進程讀的權利即可。

  例如在Linux下,我們通過Shell命令輸入兩個命令,中間通過符號“|”來創建兩個命令之間的管道:

$ sort < file1 | grep zou

  上面一個命令表示:對file1的內容首先進行排序,排序完成后的結果將作為grep的輸入,在結果里面找出所有包括字符串zou的文本行。也就是說,在兩個任務“排序“(sort)和”查找”(grep)之間創建了一個管道,數據從sort流向了grep。

1.2 套接字

socket

  套接字(Socket)的功能非常強大,可以支持不同層面、不同應用、跨網絡的通信。使用套接字進行通信需要雙方均創建一個套接字,其中一方作為服務器方,另外一方作為客戶方。服務器方必須首先創建一個服務區套接字,然后在該套接字上進行監聽,等待遠方的連接請求。客戶方也要創建一個套接字,然后向服務器方發送連接請求。服務器套接字在受到連接請求之后,將在服務器方機器上新建一個客戶套接字,與遠方的客戶方套接字形成點到點的通信通道。之后,客戶方和服務器方便可以直接通過類似於send和recv的命令在這個創建的套接字管道上進行交流了。

  例如,在C#中我們可以輕松地創建一個服務器方的Socket:

    // 創建Socket->綁定IP與端口->設置監聽隊列的長度->開啟監聽連接
    socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    socketWatch.Bind(new IPEndPoint(IPAddress.Parse(txtIPAddress.Text), int.Parse(txtPort.Text)));
    socketWatch.Listen(10);

1.3 不足之處

  (1)必須首先在通信的進程間建立連接(管道或套接字),這需要消耗系統資源;

  (2)通信是自願的,而管道和套接字需要強制雙方進行通信;

  (3)由於建立連接需要消耗時間,一旦建立就應該盡可能多的通信,如果通信信息量很小,則就是“殺雞用牛刀”了;

二、進程電報與旗語:信號與信號量

2.1 電報:信號

  信號類似於我們生活中的電報,如果你想給某人發一封電報,就擬好電文,然后將報文和收報人的信息都交給電報公司。電報公司則將電報發送到收報人所在地的郵局,並通知收報人來取電報。其中,發報文時無需收報人實現知道,也無需進行任何協調。如果對方選擇不對信號做出響應,則將被OS終止運行。

  在計算機中,信號就是一個內核對象或者是一個內核數據結構。發送方將該數據結構的內容填好,並指明該信號的目標進程后,發出特定的軟件中斷(這就是一個發電報的操作)。OS接收到特定的中斷請求后,知道是有進程要發送信號,於是到特定的內核數據結構里查找信號接收方,並進行通知。接到通知的進程則對信號進行相應處理。

2.2 旗語:信號量

  信號量來源於鐵路的運行:在一條單軌鐵路上,任何時候只允許有一列火車行駛在該鐵路上,而管理這條鐵路的系統就是信號量。任何一列火車必須等到表明該鐵路可以行駛的信號后才能進入軌道。當列車進入后,需要將信號改為禁止狀態進入來防止別的列車同時進入。而當列車駛出單軌后,則需要將信號變回允許進入狀態,這很像以前的旗語。當然,通過聯想到我們實際開發中經常用的鎖,這就更容易理解了。

  在計算機中,信號量實際上就是一個簡單整數。一個進程在信號變為0或1的情況下推進,並將信號變為1或0來防止別的進程同時推進。當該進程完成任務后,則將信號再改為0或1,從而允許其他進程執行。從而我們也可以看出,信號量已經不只是一種通信機制,更是一種同步機制

三、進程擁抱:共享內存

  前面通過對話、發電報、旗語已經滿足了多種通信需要,但是當兩個進程要共享大量數據時就沒法十分滿足需求。就如同兩個墜入愛河的騷年,它們互相喜歡並想要在一起同居(共享大量數據),這時打電話、發電報、握手對話就顯得不夠了。這時候,它們需要的就是擁抱,只有緊緊擁抱才能盡可能地共享,feeling so good!

3.1 共享內存

  進程的擁抱就是共享內存,兩個進程共同擁有同一片內存。對於這片內存中的任何內容,二者均可以訪問。要使用共享內存進行通信,進程A首先需要創建一片內存空間作為通信用,而其他進程B則將片內存映射到自己的(虛擬)地址空間。這樣,進程A讀寫自己地址空間中對應共享內存的區域時,就是在和進程B進行通信。

3.2 不足之處

  (1)使用共享內存機制通信的兩個進程必須在同一台物理機上;

  (2)安全性脆弱,假如一個進程有病毒,會很容易傳給另外一個進程;

四、信件發送:消息隊列

  消息隊列是一列具有頭和尾的消息排列,新來的消息放在隊列尾部,而讀取消息則從隊列頭部開始,如下圖所示:

  這樣看來,它和管道十分類似,一頭讀,一頭寫?的確,看起來很像管道,但又不是管道:

  (1)消息隊列無固定的讀寫進程,任何進程都可以讀寫;而管道需要指定誰讀和誰寫;

  (2)消息隊列可以同時支持多個進程,多個進程可以讀寫消息隊列;即所謂的多對多,而管道是點對點;

  (3)消息隊列只在內存中實現,而管道還可以在磁盤上實現;

參考資料

鄒恆明,《操作系統之哲學原理》,機械工業出版社

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM