程序間通訊學習
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代表有幾個程序和共享記憶體建立連線