1. 程式人生 > >【Linux】程序間通訊

【Linux】程序間通訊

1.程序間通訊的目的

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

2.程序間通訊的本質

不同的程序共同管理一份共同的資源(檔案)

3.程序通訊的不同方式

  • 管道

  • System V IPC a.訊息佇列 b.共享記憶體 c.訊號量

  • POSIX IPC a.訊息佇列 b.共享記憶體 c.訊號量 d.互斥量 e.條件變數 f.讀寫鎖

4.管道

1)管道基本理解

我們把**從一個程序連線到另一個程序的一個數據流稱為一個“管道”,它是一塊檔案或共享區,一個往裡面寫資料,一個拿資料,是UNIX中最古老的程序間通訊的形式。**其實通俗來說,就像是現實中的水管道,一方將水接入管道,一方從管道接出使用水,所以不難理解,管道是單向的,先進先出的,它將一個程式的輸入與另一個程式的輸出連線起來,資料被一個程序讀取後,該資料便被刪除。

2)管道的分類

  • 匿名管道: 只能完成具有血緣關係間的程序(或者具有公共祖先的程序)。
  • 命名管道:允許同一個程序中任意兩個程序的通訊。

3)匿名管道(PIPE)

  • 建立匿名管道函式pipe
#include <unistd.h>
功能:建立一無名管道
原型:`int pipe(int fd[2])`
引數:
fd:檔案描述符陣列,其中fd[0]表示讀端,fd[1]表示寫端。結合Linux一切皆檔案可以理解為管道是一個檔案,使用者在這個檔案讀寫資料(讀寫核心緩衝區)。
返回值:成功返回0,失敗返回錯誤程式碼。

在這裡插入圖片描述 我們建立一個管道,從鍵盤讀取資料,寫入管道,讀取管道,寫到螢幕。

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

#define NUM 128



int main()
{
  int fds[2];//檔案描述符陣列,fd[0]表示讀端,fd[1]表示寫端。

  int p = pipe(fds);

  //建立管道
  if( p < 0 ){
    printf("建立管道失敗\n");
    exit(1);
  }

  //從鍵盤輸入讀取資料
  while(1){

  char buf[NUM] = {0}; 
    ssize_t s1 = read(0,buf,sizeof(buf)-1);
    if(s1<0){
      perror("read");
      exit(1);
    }
    buf[s1] = 0;
  //寫進管道
   write(fds[1],buf,strlen(buf));
  


  
  
  //從管道中讀取
  ssize_t s2 =read(fds[0],buf,sizeof(buf)-1);
  buf[s2] = 0;
  //輸出到螢幕上
  write(1,buf,strlen(buf));
  


   }
  return 0;
}

結果演示: 在這裡插入圖片描述

  • 站在檔案描述符號-深度理解管道

  • 父程序呼叫pipe()開闢一個管道,使用open得到兩個檔案描述符號,一個指向管道的讀端,一個指向管道的寫端。 在這裡插入圖片描述

  • 父程序呼叫fork()建立子程序,得到的子程序也有兩個檔案描述符指向這個管道的兩端。 在這裡插入圖片描述

  • 父程序關閉寫端,子程序關閉讀端,子程序往管道中寫入資料,父程序從中讀出,這樣就實現了程序間通訊。

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


int main()
{
  int fds[2];
  int p = pipe(fds);
  if(p < 0)
  {
    perror("pipe");
    return 1;
  }
  pid_t id =fork();
  if(id == 0)//子程序寫
  {
   close(fds[0]);
   write(fds[1],"haha",4);
   close(fds[1]);
   exit(EXIT_SUCCESS);
  }
  else if (id > 0){
    //父程序
    close(fds[1]);
    char buf[12]={0};
    read(fds[0],buf,12);
    printf("buf=%s\n",buf);
  }
  return 0;
}

  • 匿名管道的四種情況 a.當管道的寫端關閉後,這時讀取輸的的程序把管道中的資料讀完後,read就返回0,相當於讀到檔案末尾。 b.寫端沒有關閉當讀取資料程序的速度大於寫資料程序的速度時,在管道內的資料被讀取完後,read會阻塞,直到管道中有新的資料。 c.當管道的讀端被關閉後,若還寫資料,這時程序會受到SIGPIPE訊號,導致程序異常終止。 d.當管道讀端沒有關閉,寫的速度大於讀的速度,在管道寫滿後,write被阻塞直到管道有空位置。

  • 特點 管道提供流式服務(面向位元組流),半雙工,核心對管道操作進行同步與互斥。

4)命名管道(FIFO)

  • 概念:命名管道不同於匿名管道之處在於命名管道是一個裝置檔案,它真真正正的存在於硬體之上,存在於檔案系統中。而匿名管道存在於記憶體或者核心中,它對檔案系統不可見,也因為這樣,命名管道可以完成任意兩個程序間的通訊。命名管道的建立有一個路徑名path與之關聯,以命名管道檔案的形式儲存在檔案系統中,所以只要訪問該路徑,就能夠通過命名管道互相通訊。FIFO也是先進先出的規則。
  • 建立管道: a.在shell命令上可以通過 mkfifo 檔名建立命名管道檔案 b.在程式內通過mkfifo函式建立命名管道檔案
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *filename,mode_t mode)
  • 匿名管道與命名管道的區別 a.匿名管道由pipe函式建立並開啟 b.命名管道由mkfifo函式建立,開啟open c.FIFO(命名管道)與pipe(匿名管道)之間唯一的區別在它們創建於開啟的方式不同,一旦這些工作完成,它們具有相同的語義 d. 對於命名管道檔案系統中的路徑名是全域性的,各個程序都可以訪問,因此可以用檔案系統的路徑名來標識一個IPC通訊。 e.命名管道是真實存在於硬碟上的,匿名管道是存在於記憶體中的特殊檔案。
  • 使用命名管道完成server&client通訊 a.makefile檔案
.PHONY:all
all:client server

client:client.c
	gcc -o [email protected] $^
server:server.c
	gcc -o [email protected] $^

.PHONY:clean
clean:
	rm -rf server client 

b.server.c檔案

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

int main()
{
  
  if(mkfifo("mypipe",0644)<0){
    perror("mkfifo");
    exit(EXIT_FAILURE);
  }
  int fd = open("mypipe",O_RDONLY);
  if(fd <0 ){
    perror("open");
  }
  char buf[1024];
  while(1){
    buf[0] = 0;
    printf("Please  wait......\n");
    ssize_t s =read(fd,buf,sizeof(buf)-1);
    if(s > 0){
      buf[s-1] = 0;
      printf("client say# %s\n",buf);
    }else if(s == 0){
      printf("client quit\n");
      exit(EXIT_SUCCESS);
    }

  }


  close(fd);
  return 0;
}

c.client.c檔案

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


int main()
{
  int fd = open("mypipe",O_WRONLY);
  char buf[1024];  
  while(1){
    buf[0]=0;
    printf("Please Enter# ");
    fflush(stdout);
    ssize_t s =read(0,buf,sizeof(buf)-1);
    if(s >0 ){
        buf[s] = 0;
        write(fd,buf,strlen(buf));
    }else if(s <= 0)
    {
    perror("read");
    exit(1);
    }

    }
  close(fd);
  return 0;
}

程式碼執行結果:(先開啟server埠,再開啟一個client埠) 在這裡插入圖片描述在這裡插入圖片描述

5.訊息佇列

6.共享記憶體

1)基本概念

共享記憶體是最快的IPC形式,一旦這樣的記憶體對映到共享它的程序的地址空間,這些程序間資料傳遞不再涉及到核心,也就是說程序不再通過執行核心的系統呼叫來傳遞彼此的資料,所以共享記憶體沒有互斥與同步機制(使用者自主完成)。生命週期隨核心

2)共享記憶體示意圖

在這裡插入圖片描述

3)共享記憶體資料結構

在這裡插入圖片描述

4)共享記憶體函式

a.shmget函式

功能:用來建立共享記憶體
原型:int shmget(key_t key,size_t size,int shmflg);
引數:
        key:這個共享記憶體段名字
        size:共享記憶體大小  (OS將記憶體分為頁size是頁的整數倍)
        shmflg:由9個許可權標誌志構成,它們的⽤法和建立⽂件時使⽤的mode模式標誌是⼀樣的
返回值:成功返回⼀個⾮負整數,即該共享記憶體段的標識碼;失敗返回-1

b.shmat函式

功能:將共享記憶體段連線到程序地址空間
原型
 void *shmat(int shmid, const void *shmaddr, int shmflg);
引數
 shmid: 共享記憶體標識
 shmaddr:指定連線的地址
 shmflg:它的兩個可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回⼀個指標,指向共享記憶體第⼀個節;失敗返回-1

說明:

shmaddr為NULL,核⼼⾃動選擇⼀個地址
shmaddr不為NULL且shmflg⽆SHM_RND標記,則以shmaddr為連線地址。
shmaddr不為NULL且shmflg設定了SHM_RND標記,則連線的地址會⾃動向下調整為SHMLBA的整數倍。公式:s
hmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表⽰連線操作⽤來只讀共享記憶體

c.shmdt函式

功能:將共享記憶體段與當前程序脫離
原型
 int shmdt(const void *shmaddr);
引數
 shmaddr: 由shmat所返回的指標
返回值:成功返回0;失敗返回-1
注意:將共享記憶體段與當前程序脫離不等於刪除共享記憶體段

d.shmctl函式

功能:⽤於控制共享記憶體
原型
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
引數
 shmid:由shmget返回的共享記憶體標識碼
 cmd:將要採取的動作(有三個可取值)
 buf:指向⼀個儲存著共享記憶體的模式狀態和訪問許可權的資料結構
返回值:成功返回0;失敗返回-1

在這裡插入圖片描述

5)例項程式碼

makefile檔案

.PHONY:all
all:server client

client:client.c comm.c
	gcc -o [email protected] $^
server:server.c comm.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -rf client server

comm.h檔案

#ifndef  __COMM_H__
#define  __COMM_H__

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

#define PATHNAME "."
#define PROJ_ID 0x6666

int creatShm(int size);
int destroyShm(int shmid);
int getShm(int size);




#endif 

comm.c檔案

#include"comm.h"


static int commShm(int  size,int flags)
{
  key_t _key = ftok(PATHNAME,PROJ_ID);
  if(_key < 0){
    perror("ftok");
    return -1;
  }
  int shmid = 0;
  if((shmid = shmget(_key,size,flags)) < 0){
        perror("shmget");
        return -3;
      }
      return shmid;
}

int destroyShm(int shmid)
{
  if(shmctl(shmid,IPC_RMID,NULL)< 0){
    perror("shmctl");
    return -1212;
  }
  return 0;
}
 int creatShm(int size)
{
  return commShm(size,IPC_CREAT|IPC_EXCL|0666);
}

 int getShm(int size)
{
  return commShm(size,IPC_CREAT);
}

server.c檔案

#include"comm.h"


int main()
{
  int shmid = creatShm(4096);

  char *addr =shmat(shmid,NULL,0);
  sleep(2);
  int i =0;
  while(i++<26){
     printf("client# %s\n",addr);
     sleep(1);
  }

  shmdt(addr);
  sleep(2);
  destroyShm(shmid);
  return 0;
}

client.c檔案

#include"comm.h"


int main()
{
  int shmid =getShm(4096);
  sleep(1);
  char *addr =shmat(shmid,NULL,0);
  sleep(2);
  int i = 0 ;
  while(i<26)
  {
    addr[i] = 'A' +i;
    i++;
    addr[i] = 0;
    sleep(1);
  }
  shmdt(addr);
  sleep(2);
  return 0;
}

6)ipcrm -m 與ipcrs -m表示刪除或檢視共享記憶體