1. 程式人生 > >程序間通訊學習

程序間通訊學習

1.什麼是程序間的通訊及程序間通訊的目的

a.什麼是程序間的通訊

程序間通訊(IPC,InterProcess Communication)是指在不同程序之間傳播或交換資訊。它的實質是讓不同的程序看到相同的檔案資源。

b.程序間通訊的目的

  • 資料傳輸:一個程序需要將它的資料傳送給另一個程序
  • 資源共享:多個程序之間共享同樣的資源
  • 通知事件:一個程序需要向另一個或一組程序傳送訊息,通知它發生了什麼事(例如:一個程序退出時要通知它的父程序)
  • 程序控制:有些程序希望完全控制另一個程序的執行,此時控制程序希望能夠攔截另一個程序的所有陷入和異常,並能夠及時知道它的狀態改變

2.程序間通訊的分類

a.管道

  • 匿名管道pipe
  • 命名管道

b.System V IPC

  • System V訊息佇列
  • System V 共享記憶體
  • System V 訊號量

c.POSIX IPC

  • 訊息佇列
  • 共享記憶體
  • 訊號量
  • 互斥量
  • 條件變數
  • 讀寫鎖

3.管道

(1)匿名管道

a.什麼是管道?

在使用命令時,我們經常可以用到管道,例如ps aux | grep test,作用是把前面命令的輸出當前後邊命令的輸入。實際上,管道是Unix作業系統的一種古老的程序間通訊形式。**我們把從一個程序連線到另一個程序的一個數據流稱為一個管道。**下邊這幅圖可以形象的描述管道的功能: 在這裡插入圖片描述

b.匿名管道的使用

在這裡插入圖片描述

功能:建立一個匿名管道

引數:為檔案描述符陣列,fd[0]代表讀端fd[1]代表寫端 返回值:成功返回1,失敗返回錯誤程式碼

在這裡插入圖片描述 上圖我們可以發現管道的兩端其實是兩個程序,一個程序負責從管道中讀,而另一個程序負責寫到管道中,在讀或寫的過程中,每一個程序只能開啟讀端或者寫端,不能同時開啟兩個。呼叫pipe函式時,首先在核心中開闢一塊緩衝區用於通訊,它有一個讀端和一個寫端,然後通過pipefd引數傳出給使用者程序兩個檔案描述符,pipefd[0]指向管道的讀端,pipefd[1]指向管道的寫段。在使用者層面看來,開啟管道就是打開了一個檔案,通過read()或者write()向檔案內讀寫資料,讀寫資料的實質也就是往核心緩衝區讀寫資料。

d.pipe匿名管道的兩個例項

2.fork子程序利用匿名管道實現兩個程序間的通訊,具體實現方法如下:

  • 呼叫pipe函式,由父程序建立管道,得到兩個檔案描述符指向管道的兩端
  • 父程序呼叫fork建立子程序,則對於子程序,也有兩個檔案描述符指向管道的兩端
  • 子程序關閉讀端,只進行寫操作父程序關閉寫端只進行讀操作。資料從寫段流入到讀端,這樣就形成了程序間通訊

e.匿名管道的特點

  • 只提供單向通訊,也就是說,兩個程序都能訪問這個檔案,假設程序1往檔案內寫東西,那麼程序2 就只能讀取檔案的內容。
  • 只能用於具有血緣關係的程序間通訊,通常用於父子程序建通訊
  • 管道是基於位元組流來通訊的
  • 依賴於檔案系統,它的生命週期隨程序的結束結束(隨程序)
  • 其本身自帶同步互斥效果

f.四種特殊情況

  • 如果寫端對應的檔案描述符被關閉,則read返回0
  • 如果有指向管道寫端的檔案描述符沒有關閉(管道寫段的引用計數大於0),而持有管道寫端的程序沒有向管道內寫入資料,假如這時有程序從管道讀端讀資料,那麼讀完管道內剩餘的資料後就會阻塞等待直到有資料可讀才讀取資料並返回
  • 如果讀端對應的檔案描述符被關閉,則write會產生SIGPIPE進而導致write程序退出
  • 如果有指向管道讀端的檔案描述符沒有關閉(管道讀端的引用計數大於0),而持有管道讀端的程序沒有從管道內讀資料,假如此時有程序通過管道寫段寫資料,那麼管道被寫滿後就會被阻塞直到管道內有空位置後才寫入資料並返回總結:誰快誰等待

(2)命名管道(FIFO)

雖然匿名管道可以實現兩個程序間的通訊,但是它也有一些缺點

  • 只適用於具有親緣關係的程序通訊
  • 只能單向通訊

為了使任意兩個程序之間能夠通訊,就提出了命名管道(named pipe 或 FIFO)

a.命名管道的建立

  • 命名管道可以通過命令列來建立,命令為:
$ mkfifo filename(檔名)
  • 命名管道也可以在檔案中根據函式建立
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char* filename,mode_t mode);//mode為許可權

//返回值:成功返回0,失敗返回-1

例如:

int main()
{
	mkfifo("myfifo",0664);
	return 0;
}

b.命名管道與命名管道的區別

  • 命名管道使用mkfifo建立,需要使用open開啟,匿名管道使用pipe函式建立並開啟 這是因為:命名管道是裝置檔案,它是儲存在硬碟上的,而管道是存在記憶體中的特殊檔案。但是需要注意的是,命名管道呼叫open()開啟有可能會阻塞,但是如果以讀寫方式(O_RDWR)開啟則一定不會阻塞;以只讀(O_RDONLY)方式開啟時,呼叫open()的函式會被阻塞直到有資料可讀;如果以只寫方式(O_WRONLY)開啟時同樣也會被阻塞,知道有以讀方式開啟該管道。

  • 命名管道提供了一個路徑名與之關聯,以FIFO檔案的形式儲存於檔案系統中,能夠實現任何兩個程序之間通訊。而匿名管道對於檔案系統是不可見的,它僅限於在父子程序之間的通訊

  • FIFO(first input first output)總是遵循先進先出的原則,即第一個進來的資料會第一個被讀走

c.利用命名管道實現server/client之間的通訊

服務端負責建立命名管道,並向管道檔案中寫入資料客戶端負責開啟管道檔案,並讀取檔案中的資料

4.訊息佇列

a.什麼是訊息佇列

  • 訊息佇列提供了一種從一個程序向另一個程序傳送一個數據塊的方法
  • 每個資料塊都被認為含有一個型別,接收程序可以獨立地接收含有不同型別的資料結構。
  • 我們可以通過傳送訊息來避免命名管道的同步和阻塞問題。但是訊息佇列與命名管道一樣,每個資料塊都有一個最大長度的限制。
  • 訊息佇列是地址空間中的內部連結串列。訊息可以順序地傳送到佇列中,並以幾種不同的方式從佇列中獲取。當然,每個訊息佇列都是由 IPC識別符號所唯一標識的。

b.建立訊息佇列

  • msgget函式
int msgget(key_t key,int msgflg);
功能:用來建立和訪問一個訊息佇列

//key:表示訊息佇列的名字,它必須具有唯一性

//msgflg:用法和建立檔案時mode模式標記是一樣的
//IPC_CREAT|IPC_EXCL 建立訊息佇列如果已經存在則出錯返回
//IPC_CREAT 嘗試建立訊息佇列,沒有則建立,已經存在則開啟它

//返回值:成功返回一個非負整數,既該訊息佇列的標識碼,失敗返回-1
  • ftok函式 在這裡插入圖片描述

功能:ftok函式的作用是形成唯一的key值

  • msgctl函式
功能:控制訊息佇列的函式

int msgctl(int msgid,int cmd,struct msgid_ds *buf);

引數說明:
	msgid:訊息佇列標識碼
	cmd:要採取的動作(IPC_RMID:刪除訊息佇列)
返回值:失敗返回-1,成功返回0
  • msgsnd
功能:把一條訊息新增到訊息佇列中

int msgsnd(int msgid,const void *msgp,size_t msgsz,int msgflg);

引數說明:
	msgid:訊息佇列標識碼
	msgp:是一個指標,指標指向準備傳送的訊息
	msgsz:傳送訊息長度,不包括訊息型別的long int 型
	msgflg=IPC_NOWAIT表示佇列滿不等待,返回錯誤
  • 訊息結構
struct msgbuf{
	long mtype;//型別
	char mtext[];
}
//它必須以long int長整數開始,接受者函式利用這個廠整數確定訊息的型別,它必須小於系統規定的上限值
  • msgrcv函式
功能:從一個訊息佇列接收訊息

ssize_t msgrcv(int msgid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
引數說明:
	msgid:訊息佇列的標識(區別其他訊息佇列)
	msgp:是指向以上邊的呼叫者定義的結構:
	msgsz: 訊息佇列大小 msgsz=sizeof(struct msgbuf) - sizeof(long);
	msgflg:
		IPC_NOWAIT:立即返回,如果沒有請求型別的訊息在佇列中。 
		MSG_EXCEPT:與msgtyp大於0用於在佇列中與從msgtyp不同訊息型別讀取所述第一訊息。 
		MSG_NOERROR:如果大於msgsz位元組數進行截斷。

c.訊息佇列實現程序間通訊

d.ipcs和ipcrm命令

  • ipcs -q 顯示IPC資源
  • ipcrm -q 訊息佇列的msgid 手動刪除IPC資源
  • ipc資源隨核心

5.共享記憶體

a.什麼是共享記憶體

共享記憶體可以說是最有用的程序間通訊方式,也是最快的IPC形式。兩個不同程序A、B共享記憶體的意思是,同一塊實體記憶體被對映到程序A、B各自的程序地址空間。程序A可以即時看到程序B對共享記憶體中資料的更新,反之亦然。由於多個程序共享同一塊記憶體區域,必然需要某種同步機制,互斥鎖和訊號量都可以。採用共享記憶體通訊的一個顯而易見的好處是效率高,因為程序可以直接讀寫記憶體,而不需要任何資料的拷貝。因此,採用共享記憶體的通訊方式效率是非常高的。

共享記憶體的缺點:共享記憶體沒有進行同步與互斥機制

b.建立共享記憶體

  • shmget函式
功能:建立共享記憶體
int shmget(key_t key, size_t size,int shmflg);

引數:
	key:共享記憶體的名字
	size:共享記憶體的大小(必須是頁的整數倍,1=4kb=4096位元組)
	shmflg:和建立檔案是mode的使用一樣
返回值:成功返回共享記憶體的標識碼
  • shmat函式
void *shmat(int shmid,const void *shmaddr,int shmflg);
引數:
	shmid:共享記憶體識別符號
	shmaddr:指定記憶體的地址
返回值:成功返回指向內向記憶體的指標
  • shmdt函式
功能:將共享記憶體段與當前程序脫離

int shmdt(const void *shmaddr);

引數:
	shmaddr:由shmat所返回的指標
返回值:成功返回0,失敗返回-1
//將共享記憶體段與當前程序脫離不等於刪除共享記憶體段
  • shmctl函式
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
引數:
	shmid:共享記憶體標識碼
	cmd:將要採取的動作(IPC_RMID:刪除共享記憶體段)
	buf:指向一個儲存著共享記憶體的模式狀態和訪問許可權的資料結構
返回值:成功返回0,失敗返回-1

c.

d.ipcs&ipcrm

  • ipcs -m 檢視共享記憶體資源
  • ipcrm -m 刪除掛接共享記憶體 在這裡插入圖片描述

注:nattch代表有幾個程序和共享記憶體建立連線