進程IPC通信方式7種


轉自:https://www.jianshu.com/p/c1015f5ffa74

一、進程間通信的概念

每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到,所以進程之間要交換數據必須通過內核,在內核中開辟一塊緩沖區,進程1把數據從用戶空間拷到內核緩沖區,進程2再從內核緩沖區把數據讀走,內核提供的這種機制稱為進程間通信(IPC,InterProcess Communication)

進程間通信模型

二、進程間通信的7種方式

第一類:傳統的Unix通信機制
1. 管道/匿名管道(pipe)

  • 管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道。

  • 只能用於父子進程或者兄弟進程之間(具有親緣關系的進程);

  • 單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中。

  • 數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩沖區的末尾,並且每次都是從緩沖區的頭部讀出數據。

    進程間管道通信模型

管道的實質:
管道的實質是一個內核緩沖區,進程以先進先出的方式從緩沖區存取數據,管道一端的進程順序的將數據寫入緩沖區,另一端的進程則順序的讀出數據。
該緩沖區可以看做是一個循環隊列,讀和寫的位置都是自動增長的,不能隨意改變,一個數據只能被讀一次,讀出來以后在緩沖區就不復存在了。
當緩沖區讀空或者寫滿時,有一定的規則控制相應的讀進程或者寫進程進入等待隊列,當空的緩沖區有新數據寫入或者滿的緩沖區有數據讀出來時,就喚醒等待隊列中的進程繼續讀寫。

管道的局限:
管道的主要局限性正體現在它的特點上:

  • 只支持單向數據流;
  • 只能用於具有親緣關系的進程之間;
  • 沒有名字;
  • 管道的緩沖區是有限的(管道制存在於內存中,在管道創建時,為緩沖區分配一個頁面大小);
  • 管道所傳送的是無格式字節流,這就要求管道的讀出方和寫入方必須事先約定好數據的格式,比如多少字節算作一個消息(或命令、或記錄)等等;

2. 有名管道(FIFO)
匿名管道,由於沒有名字,只能用於親緣關系的進程間通信。為了克服這個缺點,提出了有名管道(FIFO)。
有名管道不同於匿名管道之處在於它提供了一個路徑名與之關聯,以有名管道的文件形式存在於文件系統中,這樣,即使與有名管道的創建進程不存在親緣關系的進程,只要可以訪問該路徑,就能夠彼此通過有名管道相互通信,因此,通過有名管道不相關的進程也能交換數據。值的注意的是,有名管道嚴格遵循先進先出(first in first out),對匿名管道及有名管道的讀總是從開始處返回數據,對它們的寫則把數據添加到末尾。它們不支持諸如lseek()等文件定位操作。有名管道的名字存在於文件系統中,內容存放在內存中。

匿名管道和有名管道總結:
(1)管道是特殊類型的文件,在滿足先入先出的原則條件下可以進行讀寫,但不能進行定位讀寫。
(2)匿名管道是單向的,只能在有親緣關系的進程間通信;有名管道以磁盤文件的方式存在,可以實現本機任意兩個進程通信。
(3)無名管道阻塞問題:無名管道無需顯示打開,創建時直接返回文件描述符,在讀寫時需要確定對方的存在,否則將退出。如果當前進程向無名管道的一端寫數據,必須確定另一端有某一進程。如果寫入無名管道的數據超過其最大值,寫操作將阻塞,如果管道中沒有數據,讀操作將阻塞,如果管道發現另一端斷開,將自動退出。
(4)有名管道阻塞問題:有名管道在打開時需要確實對方的存在,否則將阻塞。即以讀方式打開某管道,在此之前必須一個進程以寫方式打開管道,否則阻塞。此外,可以以讀寫(O_RDWR)模式打開有名管道,即當前進程讀,當前進程寫,不會阻塞。

延伸閱讀:該博客有匿名管道和有名管道的C語言實踐

3. 信號(Signal)

  • 信號是Linux系統中用於進程間互相通信或者操作的一種機制,信號可以在任何時候發給某一進程,而無需知道該進程的狀態。
  • 如果該進程當前並未處於執行狀態,則該信號就有內核保存起來,知道該進程回復執行並傳遞給它為止。
  • 如果一個信號被進程設置為阻塞,則該信號的傳遞被延遲,直到其阻塞被取消是才被傳遞給進程。

Linux系統中常用信號:
(1)SIGHUP:用戶從終端注銷,所有已啟動進程都將收到該進程。系統缺省狀態下對該信號的處理是終止進程。
(2)SIGINT:程序終止信號。程序運行過程中,按Ctrl+C鍵將產生該信號。
(3)SIGQUIT:程序退出信號。程序運行過程中,按Ctrl+\\鍵將產生該信號。
(4)SIGBUS和SIGSEGV:進程訪問非法地址。
(5)SIGFPE:運算中出現致命錯誤,如除零操作、數據溢出等。
(6)SIGKILL:用戶終止進程執行信號。shell下執行kill -9發送該信號。
(7)SIGTERM:結束進程信號。shell下執行kill 進程pid發送該信號。
(8)SIGALRM:定時器信號。
(9)SIGCLD:子進程退出信號。如果其父進程沒有忽略該信號也沒有處理該信號,則子進程退出后將形成僵屍進程。

信號來源
信號是軟件層次上對中斷機制的一種模擬,是一種異步通信方式,,信號可以在用戶空間進程和內核之間直接交互,內核可以利用信號來通知用戶空間的進程發生了哪些系統事件,信號事件主要有兩個來源:

  • 硬件來源:用戶按鍵輸入Ctrl+C退出、硬件異常如無效的存儲訪問等。
  • 軟件終止:終止進程信號、其他進程調用kill函數、軟件異常產生信號。

信號生命周期和處理流程
(1)信號被某個進程產生,並設置此信號傳遞的對象(一般為對應進程的pid),然后傳遞給操作系統;
(2)操作系統根據接收進程的設置(是否阻塞)而選擇性的發送給接收者,如果接收者阻塞該信號(且該信號是可以阻塞的),操作系統將暫時保留該信號,而不傳遞,直到該進程解除了對此信號的阻塞(如果對應進程已經退出,則丟棄此信號),如果對應進程沒有阻塞,操作系統將傳遞此信號。
(3)目的進程接收到此信號后,將根據當前進程對此信號設置的預處理方式,暫時終止當前代碼的執行,保護上下文(主要包括臨時寄存器數據,當前程序位置以及當前CPU的狀態)、轉而執行中斷服務程序,執行完成后在回復到中斷的位置。當然,對於搶占式內核,在中斷返回時還將引發新的調度。

信號的生命周期

4. 消息(Message)隊列

  • 消息隊列是存放在內核中的消息鏈表,每個消息隊列由消息隊列標識符表示。
  • 與管道(無名管道:只存在於內存中的文件;命名管道:存在於實際的磁盤介質或者文件系統)不同的是消息隊列存放在內核中,只有在內核重啟(即,操作系統重啟)或者顯示地刪除一個消息隊列時,該消息隊列才會被真正的刪除。
  • 另外與管道不同的是,消息隊列在某個進程往一個隊列寫入消息之前,並不需要另外某個進程在該隊列上等待消息的到達。延伸閱讀:消息隊列C語言的實踐

消息隊列特點總結:
(1)消息隊列是消息的鏈表,具有特定的格式,存放在內存中並由消息隊列標識符標識.
(2)消息隊列允許一個或多個進程向它寫入與讀取消息.
(3)管道和消息隊列的通信數據都是先進先出的原則。
(4)消息隊列可以實現消息的隨機查詢,消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取.比FIFO更有優勢。
(5)消息隊列克服了信號承載信息量少,管道只能承載無格式字 節流以及緩沖區大小受限等缺。
(6)目前主要有兩種類型的消息隊列:POSIX消息隊列以及System V消息隊列,系統V消息隊列目前被大量使用。系統V消息隊列是隨內核持續的,只有在內核重起或者人工刪除時,該消息隊列才會被刪除。

5. 共享內存(share memory)

  • 使得多個進程可以可以直接讀寫同一塊內存空間,是最快的可用IPC形式。是針對其他通信機制運行效率較低而設計的。

  • 為了在多個進程間交換信息,內核專門留出了一塊內存區,可以由需要訪問的進程將其映射到自己的私有地址空間。進程就可以直接讀寫這一塊內存而不需要進行數據的拷貝,從而大大提高效率。

  • 由於多個進程共享一段內存,因此需要依靠某種同步機制(如信號量)來達到進程間的同步及互斥。
    延伸閱讀:Linux支持的主要三種共享內存方式:mmap()系統調用、Posix共享內存,以及System V共享內存實踐

    共享內存原理圖

6. 信號量(semaphore)
信號量是一個計數器,用於多進程對共享數據的訪問,信號量的意圖在於進程間同步。
為了獲得共享資源,進程需要執行下列操作:
(1)創建一個信號量:這要求調用者指定初始值,對於二值信號量來說,它通常是1,也可是0。
(2)等待一個信號量:該操作會測試這個信號量的值,如果小於0,就阻塞。也稱為P操作。
(3)掛出一個信號量:該操作將信號量的值加1,也稱為V操作。

為了正確地實現信號量,信號量值的測試及減1操作應當是原子操作。為此,信號量通常是在內核中實現的。Linux環境中,有三種類型:Posix(可移植性操作系統接口)有名信號量(使用Posix IPC名字標識)Posix基於內存的信號量(存放在共享內存區中)System V信號量(在內核中維護)。這三種信號量都可用於進程間或線程間的同步。

兩個進程使用一個二值信號量

兩個進程所以用一個Posix有名二值信號量

一個進程兩個線程共享基於內存的信號量

信號量與普通整型變量的區別:
(1)信號量是非負整型變量,除了初始化之外,它只能通過兩個標准原子操作:wait(semap) , signal(semap) ; 來進行訪問;
(2)操作也被成為PV原語(P來源於荷蘭語proberen"測試",V來源於荷蘭語verhogen"增加",P表示通過的意思,V表示釋放的意思),而普通整型變量則可以在任何語句塊中被訪問;

信號量與互斥量之間的區別:
(1)互斥量用於線程的互斥,信號量用於線程的同步。這是互斥量和信號量的根本區別,也就是互斥和同步之間的區別。
互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。
在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源
(2)互斥量值只能為0/1,信號量值可以為非負整數。
也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多線程互斥問題。信號量可以實現多個同類資源的多線程互斥和同步。當信號量為單值信號量是,也可以完成一個資源的互斥訪問。
(3)互斥量的加鎖和解鎖必須由同一線程分別對應使用,信號量可以由一個線程釋放,另一個線程得到。

7. 套接字(socket)
套接字是一種通信機制,憑借這種機制,客戶/服務器(即要進行通信的進程)系統的開發工作既可以在本地單機上進行,也可以跨網絡進行。也就是說它可以讓不在同一台計算機但通過網絡連接計算機上的進程進行通信。

Socket是應用層和傳輸層之間的橋梁

套接字是支持TCP/IP的網絡通信的基本操作單元,可以看做是不同主機之間的進程進行雙向通信的端點,簡單的說就是通信的兩方的一種約定,用套接字中的相關函數來完成通信過程。

套接字特性
套接字的特性由3個屬性確定,它們分別是:域、端口號、協議類型。
(1)套接字的域
它指定套接字通信中使用的網絡介質,最常見的套接字域有兩種:
一是AF_INET,它指的是Internet網絡。當客戶使用套接字進行跨網絡的連接時,它就需要用到服務器計算機的IP地址和端口來指定一台聯網機器上的某個特定服務,所以在使用socket作為通信的終點,服務器應用程序必須在開始通信之前綁定一個端口,服務器在指定的端口等待客戶的連接。
另一個域AF_UNIX,表示UNIX文件系統,它就是文件輸入/輸出,而它的地址就是文件名。
(2)套接字的端口號
每一個基於TCP/IP網絡通訊的程序(進程)都被賦予了唯一的端口和端口號,端口是一個信息緩沖區,用於保留Socket中的輸入/輸出信息,端口號是一個16位無符號整數,范圍是0-65535,以區別主機上的每一個程序(端口號就像房屋中的房間號),低於256的端口號保留給標准應用程序,比如pop3的端口號就是110,每一個套接字都組合進了IP地址、端口,這樣形成的整體就可以區別每一個套接字。
(3)套接字協議類型
因特網提供三種通信機制,
一是流套接字,流套接字在域中通過TCP/IP連接實現,同時也是AF_UNIX中常用的套接字類型。流套接字提供的是一個有序、可靠、雙向字節流的連接,因此發送的數據可以確保不會丟失、重復或亂序到達,而且它還有一定的出錯后重新發送的機制。
二個是數據報套接字,它不需要建立連接和維持一個連接,它們在域中通常是通過UDP/IP協議實現的。它對可以發送的數據的長度有限制,數據報作為一個單獨的網絡消息被傳輸,它可能會丟失、復制或錯亂到達,UDP不是一個可靠的協議,但是它的速度比較高,因為它並一需要總是要建立和維持一個連接。
三是原始套接字,原始套接字允許對較低層次的協議直接訪問,比如IP、 ICMP協議,它常用於檢驗新的協議實現,或者訪問現有服務中配置的新設備,因為RAW SOCKET可以自如地控制Windows下的多種協議,能夠對網絡底層的傳輸機制進行控制,所以可以應用原始套接字來操縱網絡層和傳輸層應用。比如,我們可以通過RAW SOCKET來接收發向本機的ICMP、IGMP協議包,或者接收TCP/IP棧不能夠處理的IP包,也可以用來發送一些自定包頭或自定協議的IP包。網絡監聽技術很大程度上依賴於SOCKET_RAW。

原始套接字與標准套接字的區別在於:
原始套接字可以讀寫內核沒有處理的IP數據包,而流套接字只能讀取TCP協議的數據,數據報套接字只能讀取UDP協議的數據。因此,如果要訪問其他協議發送數據必須使用原始套接字。

套接字通信的建立

Socket通信基本流程

** 服務器端**
(1)首先服務器應用程序用系統調用socket來創建一個套接字,它是系統分配給該服務器進程的類似文件描述符的資源,它不能與其他的進程共享。
(2)然后,服務器進程會給套接字起個名字,我們使用系統調用bind來給套接字命名。然后服務器進程就開始等待客戶連接到這個套接字。
(3)接下來,系統調用listen來創建一個隊列並將其用於存放來自客戶的進入連接。
(4)最后,服務器通過系統調用accept來接受客戶的連接。它會創建一個與原有的命名套接不同的新套接字,這個套接字只用於與這個特定客戶端進行通信,而命名套接字(即原先的套接字)則被保留下來繼續處理來自其他客戶的連接(建立客戶端和服務端的用於通信的流,進行通信)。

客戶端
(1)客戶應用程序首先調用socket來創建一個未命名的套接字,然后將服務器的命名套接字作為一個地址來調用connect與服務器建立連接。
(2)一旦連接建立,我們就可以像使用底層的文件描述符那樣用套接字來實現雙向數據的通信(通過流進行數據傳輸)。
延伸閱讀 :Java socket編程

三、參考引用

1. 進程間通信--管道
2. Linux進程間通信——使用共享內存
3. 進程間通信---共享內存
4. 信號量與互斥鎖
5. 信號量


免責聲明!

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



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