進程間通信是什麼?如何實現進程間的通信

進程間通信

  • 由於進程之間在執行時,是完全獨立執行的,所有,進程間如果需要進行通信,就需要一塊共享的區域,我們可以通過創建一個多個進程共享的區域,將所需要共享的數據放在這個共享區當中,當進程間需要通信的時候,寫數據的進程將數據寫入該區域,需要讀數據的進程將數據從該區域中讀取出來。
    ##進程間通信的分類
  • 管道
  • System V進程間通信
  • POSIX IPC
    ##管道
  • 管道是進程間通信的一種手段,通過在內核中創建一個共享區域,來實現進程間的通信,注意,所在區域是 內核,內核,內核
    ###管道應該如何理解呢 ?
  • 管道在邏輯結構上就是一根管道,你可以理解爲水管道,這根管道有兩端,一個是讀端,一個是寫端
  • 管道的設計是半雙工的,也就是,一個進程在對管道進行寫操作的時候,其他進程只能讀取,這個寫的進程佔據了這個管道的寫端,除非這個進程關閉這個管道的寫端,否則其他進程不能對這個管道進行寫入操作,讀端同理,也是隻能有一個進程進行讀取,直到這個進程關閉讀端。那麼爲什麼不能多個進程同時對這個管道進行讀取或者寫入呢? 因爲如果多個進程對管道進行寫入的話,那管道里存儲的數據順序就會亂,因爲操作系統在進行進程調度的時候,每一個進程都可能會被隨時切走和切入,這樣的話,如果多個進程同時擁有同一個管道的讀端或寫端,就會出現讀寫混亂.大概可以理解爲,一根管道同時只能被兩個進程所使用,如果有多個進程同時使用一根管道的話,就會出現讀寫混亂的情況。
  • 在使用管道的時候,如果進程只進行寫操作,那就關閉讀操作。反之亦然。
  • 在linux中,一切皆文件,既然如此,那管道是就是文件,那進程在打開文件的時候,就會在PCB中的file_struct記錄文件的文件描述符,後面對管道的一切操作都是對文件描述符的操作。(如果你不是很懂文件描述符是什麼東西,我還有一個博客是介紹文件描述符的,可以去看看)

如何開闢一個匿名管道以及使用這個匿名管道

  • 在linux中,系統已經提供了管道的系統調用 ,下面的這個是匿名管道,在這個後面還有命名管道
  • 匿名管道只能進行有親緣關係的進程進行通信,常用於父子進程間的通信
  • 命名管道可以進行任意進程間的通信
#include<unistd.h>

int pipe(fd[2]);//匿名管道
  • 這裏面的參數 fd[0] 是管道的讀端 fd[1]是管道的寫端。我們等會對管道進行操作的時候,就是通過這兩個文件描述符進行操作的。返回值如果是0,就表示管道申請成功。如果失敗,返回失敗碼
  • 在下面的例子中,我用兩個進程的方式,進行了一個進程間管道通信的演示,父進程讀取,子進程寫數據,最後,父進程將數據打印在屏幕上
  • 這個代碼是匿名管道的創建方法,匿名管道只能進行有親緣關係的進程進行通信,常用於父子進程
#include<stdio.h>
  #include<string.h>
  #include<unistd.h>
  #include<error.h>
  
  int main()
  {
      if(pipe(fd) != 0)//創建管道
      {perror("pipe create");}
      int id = fork();//創建進程
      if(id > 0)//父進程
      {
          char buf[100];
          close(fd[1]);
          sleep(5);
          ssize_t s = read(fd[0],buf,sizeof(buf));//從讀端讀取數據
          if(s < 0)
          {perror("read");}
          buf[s] = 0;//將讀到的最後一個字符的後一個字符設置爲 \0,方便打印
          printf("%s\n",buf);
      }
      else if( id == 0 )//子進程
      {
          int i = 0;
          const char *msg = "hello world\n";
          close(fd[0]);
          while(1)
          {
              write(fd[1],msg,strlen(msg));//向寫端寫數據
              sleep(1);
              i++;
              if(i > 5)
              {break;}
          }
      }
      else//創建進程失敗
      {perror("fork create");}
      return 0;
  }

  • 以上便是匿名管道的創建和使用了

如何開闢一個命名管道,並使用這個命名管道進行進程間的通信

  • 命名管道相比於匿名管道,更像一個文件,命名管道通過在目錄下創建一個管道文件,通過這個管道文件進行通信,而不是匿名管道那樣,在內核中進行管道的創建。

相關函數

int fifo(const char *filename,mode_t mode);
  • 如果使用命名管道,需要注意的是mode的值,權限mode決定了這個管道可以進行寫和讀。
  • 爲了驗證管道可以進行不同非親緣進程間的通信,我們可以採用兩個程序,一個負責向管道里寫,一個負責讀,然後打印出來,這樣的程序,來驗證進程間的通信。
  • 命名管道在打卡的時候,進程必須知道,要打開的管道文件的路徑,然後才能打開這個管道文件
  #include<stdio.h>   //這個進程負責讀取管道的數據
  #include<stdlib.h>
  #include<unistd.h>
  #include<fcntl.h>
  #include<sys/types.h>
  #include<sys/stat.h>
  
  #define FIFO "./myfifo"
  
  int main()
  {
      umask(0);
  
      if(mkfifo(FIFO,0644) < 0)
      {
          perror("mkfifo");
          return 1;
      }
  
      char buf[1024];
      int fd = open(FIFO,O_RDONLY);
      if(fd > 0)
      {
          while(1)
          {
  
  
          ssize_t s = read(fd, buf, sizeof(buf) - 1);
          if(s > 0)
          {
              buf[s]= 0;
              printf("client->server : %s",buf);
  
          }else{
              printf("read error\n");
              exit(1);
  
          }
  
          }
      }
      else{
          perror("open file");
          return 1;
      }
      close(fd);
      return 0;
  }

 #include<stdio.h>//這個進程負責往管道里面寫數據
 #include<string.h>
 #include<stdlib.h>
 #include<unistd.h>
 #include<fcntl.h>
 #include<sys/types.h>
 #include<sys/stat.h>
 
 #define FIFO "./myfifo"
 
 int main()
 {
     char buf[1024];                                         
     int fd = open(FIFO,O_WRONLY);
     if(fd < 0)
     {
         perror("open file");
     }
 
     while(1)
     {
         printf("Please Enter# ");
         fflush(stdout);
         ssize_t s = read(0,buf,sizeof(buf) - 1);
         if(s > 0)
         {
             buf[s] = 0;
             write(fd,buf,s);
         }
         else{
             break;
         }
     }
 
 
     close(fd);
     return 0;
 }

管道的四種情況

  • 讀端不讀,但是寫端已經把緩衝區寫滿了
    這種情況的話,由於寫端已經把緩衝區寫滿了,但是讀端不讀,管道就會阻塞,寫端不能進行寫入,直到讀端開始將數據讀走,才能繼續進行寫入
  • 寫端不寫,但是讀端已經把緩衝區讀完了
    如果讀端已經將緩衝區讀完了,緩衝區裏已經沒有數據了,讀端就會阻塞,直到寫端往緩衝區裏寫數據,讀端才能繼續讀
  • 讀端不讀,同時關閉了讀端
    讀端如果不讀數據,同時關閉了讀端的話,就說明,寫端寫入的數據不會被讀端讀走,那麼寫端所寫的數據就是沒有意義的,所以,操作系統對此的處理就是,結束寫進程
  • 寫端不寫,同時關閉了寫端
    寫端不寫數據,但是關閉了寫端,由於已經沒有人會給寫端裏寫數據了,讀端在讀完緩衝區裏的所有數據後,讀進程就會被操作系統殺掉

共享內存

  • 共享內存就和他的名字一樣,是一個被多個進程所共享的一個內存區域,共享內存所在的區域是用戶態,不需要像管道一樣,如果需要數據的訪問,就必須進行用戶態和內核態的切換,提高了效率。
  • 共享內存存在的時間,是不受進程結束限制的,即使一塊共享內存沒有一個進程掛接,但是依舊是存在於內存中的數據區的,所以,在使用完共享內存之後,一定要記得釋放共享內存。
    
  • 共享內存在使用的時候,需要進程顯示的掛接共享內存,才能進行共享內存的使用,當共享內存的掛接數不爲0時,是不能被釋放的。一塊共享內存可以被多個進程所掛接,一個進程也可以掛接多個共享內存
  • 共享內存在開闢的時候,需要一個唯一的共享內存標識,記住,這個共享內存標識,是多少不重要,重要的是,必須是唯一的,要產生這個唯一的共享內存標識, 就需要調用一個共享內存標識生成方法 ---- ftok ,通過這個方法,可以產生一個這樣的唯一的標識,然後用這個標識,去向操作系統申請共享內存

共享內存接口展示

int shmget(key_t key,size_t size,int shmflg);//申請共享內存
//key :共享內存段的名字,必須是唯一的  
//size:你所要開闢的共享內存的大小
//shmflg :權限,具體是啥,可以查看man手冊
void *shmat(int shmid, const *shmaddr, int shmflg);//掛接共享內存
//shmid :在進行shmget的時候,返回的值就是id
//shmaddr:連接指定的地址,想讓核心自動選擇的話,設置爲NULL
//shmflg: 也是權限 兩個值 SHM_RND和SHM_RDONLY
int shmdt(const void *shmaddr);//刪除掛接共享內存
//shmaddr:由shmat所返回的指針
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//刪除共享內存
//shmid:有shmget所獲得的標識
//cmd:要採取的動作 三個 有:IPC_STAT IPC_SET IPC_RMID,最後一個是刪除共享內存段
//指向一個保存着共享內存模式狀態和訪問權限的數據結構,一般是NULL
  • 爲了實現兩個不相干的進程間進行通信,我和前面一樣,通過創建兩個進程,一個負責向共享內存裏寫數據,一個負責從共享內存裏讀數據,通過這種形式,實現共享內存的進行間通信

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666 
int main()
{
	int ret;
	key_t key = ftok(PATH_NAME,PROJ_ID);//ftok函數通過路徑名加一個proj_id來生成一個唯一的共享內存標識符 key
	if(key < 0)
	{
		perror("ftok");
		ret = 1;
		return ret;
	}
	int shm_id = shmget(key,4096,IPC_CREAT);//創建一個共享內存,如果這個共享內存已經存在了,就返回這個共享內存,如果不存在,就返回錯誤
	if(shm_id == -1)
	{
		perror("shmget");
		ret = 2;
		return ret;
	}
	void *str = shmat(shm_id,NULL,0);//掛接共享內存
	if((signed long long )str < 0)
	{
		perror("shmat");
		ret = 3;
		return ret;
	}
	int i;
	for(i = 0;i < 26;i++)
	{
		((char *)str)[i] = 'A' + i;//向這個共享內存裏寫數據
		sleep(1);
	}
	return 0;
}              

 #include<unistd.h>
 
 #include<string.h>
 #include<stdio.h>
 #include<sys/types.h>
 #include<sys/ipc.h>
 #include<sys/shm.h>
 
 #define PATH_NAME "/tmp"
 #define PROJ_ID 0x6666 
 int main()
 {
     int ret;
     key_t key = ftok(PATH_NAME,PROJ_ID);
     if(key < 0)
     {
         perror("ftok");
         ret = 1;
         return ret;
     }
     int shm_id = shmget(key,4096,IPC_CREAT|IPC_EXCL);//創建共享內存,如果這個共享內存不存在就創建它,然後返回
     if(shm_id == -1)
     {
         perror("shmget");
         ret = 2;
         return ret;
     }
     void *str = shmat(shm_id,NULL,0);//掛接共享內存
     if((signed long long )str < 0)
     {
         perror("shmat");
         ret = 3;
         return ret;
     }
     int i = 0;
     sleep(3);
     for(i = 0;i < 26; i++)
     {
         printf("cilent -> server:%s\n",(char *)str);//輸出這個共享內存裏的數據
         sleep(1);
     }
     shmdt(str);
     shmctl(shm_id,IPC_RMID,NULL);
 }

以上就是進程間通信的一部分東西了,包括匿名管道,命名管道,共享內存

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章