1. 程式人生 > >Linux下程序間的通訊

Linux下程序間的通訊

程序間通訊就是在不同程序之間傳播或交換資訊。但是,我們知道程序是具有獨立性的,因此,程序之間不可能直接進行通訊。人們之間交流需要空氣作為介質,程序之間“交流”也需要一個媒介,那麼程序之間存在著什麼雙方都可以訪問的介質呢?

  • 程序的使用者空間是互相獨立的,一般而言是不能互相訪問的,唯一的例外是共享記憶體區。
  • 系統空間是“公共場所”,各程序均可以訪問,所以核心也可以提供這樣的條件。
  • 還有雙方都可以訪問的外設。兩個程序當然也可以通過磁碟上的普通檔案交換資訊,或者通過“登錄檔”或其它資料庫中的某些表項和記錄交換資訊。廣義上這也是程序間通訊的手段,但是一般都不把這算作“程序間通訊”。

程序間通訊官方的說法:

程序間通訊(IPC,Interprocess communication)是一組程式設計介面,讓程式設計師能夠協調不同的程序,使之能在一個作業系統裡同時執行,並相互傳遞、交換資訊。這使得一個程式能夠在同一時間裡處理許多使用者的要求。因為即使只有一個使用者發出要求,也可能導致一個作業系統中多個程序的執行,程序之間必須互相通話。IPC介面就提供了這種可能性。每個IPC方法均有它自己的優點和侷限性,一般,對於單個程式而言使用所有的IPC方法是不常見的。

簡單來講程間通訊就是不同程序之間進行資料的交換。

程序間通訊主要包括管道, 系統IPC(包括訊息佇列,訊號,共享儲存), 套接字(SOCKET).

管道分為匿名管道和命名管道,匿名管道只能用於具有親緣關係(父子、兄弟)程序之間的通訊,而命名管道則可用於任意程序之間。

系統IPC的三種方式類同,都是使用了核心裡的識別符號來識別。

管道與檔案描述符,檔案指標的關係?

 其實管道的使用方法與檔案類似,都能使用read,write,open等普通IO函式. 管道描述符來類似於檔案描述符. 事實上, 管道使用的描述符,檔案指標和檔案描述符最終都會轉化成系統中SOCKET描述符. 都受到系統核心中SOCKET描述符的限制. 本質上LINUX核心原始碼中管道是通過空檔案來實現.

  • IPC目的

1)資料傳輸:一個程序需要將它的資料傳送給另一個程序,傳送的資料量在一個位元組到幾兆位元組之間。

2)共享資料:多個程序想要操作共享資料,一個程序對共享資料的修改,別的程序應該立刻看到。

3)通知事件:一個程序需要向另一個或一組程序傳送訊息,通知它(它們)發生了某種事件(如程序終止時要通知父程序)。

4)資源共享:多個程序之間共享同樣的資源。為了做到這一點,需要核心提供鎖和同步機制。

5)程序控制:有些程序希望完全控制另一個程序的執行(如Debug程序),此時控制程序希望能夠攔截另一個程序的所有陷入和異常,並能夠及時知道它的狀態改變。

程序通過與核心及其它程序之間的互相通訊來協調它們的行為。Linux支援多種程序間通訊(IPC)機制,訊號和管道是其中的兩種。除此之外,Linux還支援System V 的IPC機制(用首次出現的Unix版本命名)。

從IPC的角度看,管道提供了從一個程序向另一個程序傳輸資料的有效方法。但是,管道有一些固有的侷限性:

因為讀資料的同時也將資料從管道移去,因此,管道不能用來對多個接收者廣播資料。

管道中的資料被當作位元組流,因此無法識別資訊的邊界。

如果一個管道有多個讀程序,那麼寫程序不能傳送資料到指定的讀程序。同樣,如果有多個寫程序,那麼沒有辦法判斷是它們中那一個傳送的資料。

  • 管道特性

  1.     半雙工單向通訊
  2.     讀寫特性
  3.     管道自帶同步與互斥保護操作
  4.     提供位元組流服務:資料傳輸比較靈活,但有可能會造成資料粘連
  5.     生命週期隨程序
  • 管道簡單理解:核心的一塊緩衝區用於在程序間傳輸資料資源,作業系統為了介面統一,對於管道這塊緩衝區的操作,和io操作使用同一套介面。

匿名管道基本使用CODE:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>


int main()
{
	  //int pipe(int pipefd[2])
	  //pipefd 用於獲取管道的操作描述符
	  //pipefd[0] 用於從管道讀取資料
	  //pipefd[1] 用於向管道寫入資料
	  //返回值: 成功:0  失敗:-1
	  int pipefd[2];
	  //建立匿名管道
	  int ret=pipe(pipefd);
	  if(ret<0)
	  {
		    perror("pipe error!");
		    return -1;
	  }
	  //建立子程序
	  int pid=fork();
	  if(pid<0)
	  {
		    perror("fork error!");
		    return -1;
	  }
	  else if(pid==0)
	  {
		    //子程序用於從管道中讀取資料
		    //因為子程序讀取在父程序寫入之前,這時管道中沒有資料,此時子程序掛起等待資料寫入
		    close(pipefd[1]);
		    char buff[1024]={0};
		    read(pipefd[0],buff,1023);
		    printf("%s\n",buff);
          }
	  else
	  {
		    //父程序用於向管道中寫入資料
		    close(pipefd[0]);
		    char *ptr="leagu of lengend!";
		    write(pipefd[1],ptr,strlen(ptr));
	  }
	  waitpid(pid,NULL,0);
	  close(pipefd[0]);
	  close(pipefd[1]);
	  return 0;
}

命名管道基本使用CODE:

//命名管道寫入

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>

int main()
{
	  int ret =mkfifo("./test.fifo",0664);
	  if(ret<0)
	  {
		    if(errno!=EEXIST)
		    {
			      perror("mkfifo error!");
			      return -1;
		    }
	  }
	  int fd =open("./test.fifo",O_WRONLY);
	  if(fd<0)
	  {
		    perror("open error!");
		    return -1;
	  }
	  printf("open fifo success!\n");
	  while(1)
	  {
		    char buff[1024]={0};
		    printf("please input:");
		    fflush(stdout);
		    scanf("%s",buff);
		    write(fd,buff,strlen(buff));
	  }
	  close(fd);
	  return 0;
}
//命名管道讀取

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>


int main()
{
	  int ret =mkfifo("./test.fifo",0664);
	  if(ret<0)
	  {
		    if(errno!=EEXIST)
		    {
			      perror("mkfifo error!");
			      return -1;
		    }
	  }
	  int fd =open("./test.fifo",O_RDONLY);
	  if(fd<0)
	  {
		    perror("open error!");
		    return -1;
	  }
	  printf("open fifo success!\n");
	  while(1)
	  {
		    char buff[1024]={0};
		    read(fd,buff,1023);
		    printf("read buff:%s\n",buff);
	  }
	  close(fd);
	  return 0;
}

Message Queues(訊息佇列)

訊息佇列就是訊息的一個連結串列,它允許一個或多個程序向它寫訊息,一個或多個程序從中讀訊息。Linux維護了一個訊息佇列向量表:msgque,來表示系統中所有的訊息佇列。其定義如下:

struct msqid_ds *msgque[MSGMNI];

該向量表中的每一個元素都是一個指向msqid_ds資料結構的指標,而一個msqid_ds資料結構完整地描述了一個訊息佇列

MSGMNI的值是128,就是說,系統中同時最多可以有128個訊息佇列。

當建立訊息佇列時,一個新的msqid_ds資料結構被從系統記憶體中分配出來,並被插入到msgque 向量表中。

Linux提供了四個訊息佇列操作。

1. 建立或獲得訊息佇列(MSGGET)

2. 傳送訊息   

3. 接收訊息

4. 訊息控制

共享記憶體

通常由一個程序建立,其餘程序對這塊記憶體區進行讀寫。得到共享記憶體有兩種方式:對映/dev/mem裝置和記憶體映像檔案。前一種方式不給系統帶來額外的開銷,但在現實中並不常用,因為它控制存取的是實際的實體記憶體;常用的方式是通過shmXXX函式族來實現共享記憶體:

對於共享記憶體,linux本身無法對其做同步,需要程式自己來對共享的記憶體做出同步計算,而這種同步很多時候就是用訊號量實現。

訊號量

本質上,訊號量是一個計數器,它用來記錄對某個資源(如共享記憶體)的存取狀況。訊號量,分為互斥訊號量,和條件訊號量。一般說來,為了獲得共享資源,程序需要執行下列操作:

(1)測試控制該資源的訊號量;

(2)若此訊號量的值為正,則允許進行使用該資源,程序將訊號量減去所需的資源數;

(3)若此訊號量為0,則該資源目前不可用,程序進入睡眠狀態,直至訊號量值大於0,程序被喚醒,轉至步驟(1);

(4)當程序不再使用一個訊號量控制的資源時,訊號量值加其所佔的資源數,如果此時有程序正在睡眠等待此訊號量,則喚醒此程序。