1. 程式人生 > >【Linux】 程序通訊--匿名管道

【Linux】 程序通訊--匿名管道

程序通訊

每個程序各⾃自有不同的⽤使用者地址空間,任何⼀一個程序的全域性變數在另⼀一個程序中都看不到 ,所以進 程之間要交換資料必須通過核心,在核心中開闢⼀塊緩衝區,程序A把資料從⽤使用者空間 拷到核心緩衝區,程序B再從核心緩衝區把資料讀⾛走,核心提供的這種機制稱為程序間通訊 (IPC,InterProcess Com)

畫出簡單的示意圖:

                                               

管道pipe

管道是一種最基本的 IPC機制,由pipe函式建立: 

#include <unistd.h>

int pipe(int filedes[2]); 

調⽤用pipe函式時在核心中開闢⼀塊緩衝區(稱為管道)⽤用於通訊,它有一個讀端一個寫端,然後通 過filedes引數傳出給使用者程式兩個檔案描述符,filedes[0]指向管道的讀端,filedes[1]指向管道的寫端(很好記,就像0是標準輸入1是標準輸出⼀一樣)。所以管道在使用者程式看起來就像⼀個開啟 的檔案,通過read(filedes[0]);或者write(filedes[1]);向這個檔案讀寫資料其實是在讀寫核心緩衝區。pipe函式呼叫成功返回0,呼叫失敗返回-1。 開闢了管道之後如何實現兩個程序間的通訊呢?比如可以按下面的步驟通訊。


1. 父程序調⽤用pipe開闢管道,得到兩個檔案描述符指向管道的兩端。

2. 父程序調⽤用fork建立子程序,那麼子程序也有兩個檔案描述符指向同⼀管道。 
3. 父程序關閉管道讀端,子程序關閉管道寫端。父程序可以往管道里寫,子程序可以從管道里讀,管道是用環形佇列實現的,資料從寫端流入從讀端流出,這樣就實現了程序間通訊。

使用管道有一些限制: 

兩個程序通過一個管道只能實現單向通訊。比如上面的例子,父程序寫子程序讀,如果有時候也需要子程序寫父程序讀,就必須另開一個管道。

管道的讀寫端通過開啟的檔案描述符來傳遞,因此要通訊的兩個程序必須從它們的公共祖先那⾥裡繼承管道檔案描述符。上面的例子是父程序把檔案描述符傳給子程序之後父子程序之間通訊,也可以父程序fork兩次,把檔案描述符傳給兩個子程序,然後兩個子程序之間通訊, 總之需要通過fork傳遞檔案描述符使兩個程序都能訪問同一管道,它們才能通訊。 也就是說,管道通訊是需要程序之間有關係


無名管道通訊特點

  1).單向通訊

  2).用於有“血緣”關係的程序---父子程序中間

  3).流式服務--可任意位元組傳送和接收

  4).生命週期--隨程序的消亡而消亡

  5).提供同步和互斥功能

無名管道的四種情況

  1).如果所有指向管道寫端的檔案描述符都關閉了(管道寫端的引用計數等於0),而仍然有程序從管道的讀端讀資料,那麼管道中剩餘的資料都被讀取後,再次read會返回0,就像 讀到檔案末尾一樣。 

  2).如果有指向管道寫端的檔案描述符沒關閉(管道寫端的引用計數大於0),而持有管道寫 端的程序也沒有向管道中寫資料,這時有程序從管道讀端讀資料,那麼管道中剩餘的資料都被讀取後,再次read會阻塞,直到管道中有資料可讀了才讀取資料並返回。

  3).如果所有指向管道讀端的檔案描述符都關閉了(管道讀端的引用計數等於0),這時有程序向管道的寫端write,那麼該程序會收到訊號SIGPIPE,通常會導致程序異常終止。

  4).如果有指向管道讀端的檔案描述符沒關閉(管道讀端的引用計數大於0),而持有管道讀端的程序也沒有從管道中讀資料,這時有程序向管道寫端寫資料,那麼在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入資料並返回。

程式碼測試程式:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
	int fd_pipe[2];
	if(pipe(fd_pipe) < 0)
	{
		printf("errno:%d,strerror:%s\n",errno,strerror(errno));
	}

	pid_t id = fork();
	if(id < 0)
	{
		printf("error!\n");
	}
	else if(id == 0) //child
	{
		close(fd_pipe[0]);//讀端關閉
		const char* msg = "hello hacker!";
		int count = 5;
		while(count-- >= 0)
		{
			write(fd_pipe[1],msg,strlen(msg));
			sleep(1);
		}
	}
	else   //father
	{
		close(fd_pipe[1]);//寫端關閉

		char buf[1024];
		memset(buf,'\0',sizeof(buf));

		int count = 5;
		while(count-- > 0)
		{
			memset(buf,'\0',sizeof(buf));
			read(fd_pipe[0],buf,sizeof(buf)-1);
			printf("child->father: %s\n",buf);
		}

		pid_t ret = waitpid(id,NULL,0);
		if(ret > 0)
		{
			printf("wait success,%d\n",ret);
		}
		else
		{	
			printf("wait failed,%d\n",ret);
		}
	}
	return 0;
}

測試結果: