1. 程式人生 > >程序間通訊之共享記憶體初步

程序間通訊之共享記憶體初步

基本概念

共享記憶體
  共享記憶體區是最快的IPC形式。一旦這樣的記憶體對映到共享它的程序的地址空間,這些程序間資料傳遞不再涉及到核心,換句話說是程序不再通過執行進入核心的系統呼叫來傳遞彼此的資料。
  
這裡寫圖片描述

用管道或者訊息佇列傳遞資料
  核心為每個IPC物件維護一個數據結構
這裡寫圖片描述

用共享記憶體傳遞資料

這裡寫圖片描述

相關API

shmget函式

  • 功能:用來建立共享記憶體
  • 原型
    int shmget(key_t key, size_t size, int shmflg);
  • 引數
    key:這個共享記憶體段名字
    size:共享記憶體大小
    shmflg:由九個許可權標誌構成,它們的用法和建立檔案時使用的mode模式標誌是一樣的
  • 返回值:成功返回一個非負整數,即該共享記憶體段的標識碼;失敗返回-1
  • 類比msgget函式

  • 示例程式碼

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

#define ERR_EXIT(m) \
  do \
  { \
    perror(m); \
    exit
(-1); \ }while(0) typedef struct _T { char name[64]; int age; }Teacher; //沒有IPC_CREAT int main01() { key_t key; int shm_id; key = ftok("./",'b'); //不存在會報錯 shm_id = shmget(key,sizeof(Teacher),0666); if(shm_id == -1) { if(errno == ENOENT)//如果沒有使用IPC_CREAT,開啟共享記憶體,共享記憶體檔案不存在 { printf
("no such shm\n"); } perror("shmget"); } return 0; } //使用IPC_CREAT int main02() { key_t key; int shm_id; key = ftok("./",'b'); //存在使用舊共享記憶體 //不存在就建立新的共享記憶體 shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT); if(shm_id == -1) { if(errno == ENOENT) { printf("no such shm\n"); } perror("shmget"); } return 0; } //IPC_CREAT和IPC_EXCL在一起 int main03() { key_t key; int shm_id; key = ftok("./",'b'); //存在會報錯 //不存在就建立新的共享記憶體 shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT|IPC_EXCL); if(shm_id == -1) { if(errno == ENOENT) { printf("no such shm\n"); } if(errno == EEXIST)//已經存在就報錯 { printf("have created shm\n"); } ERR_EXIT("shmget"); } return 0; }

shmat函式

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

  • shmaddr和shmflg的組合關係對共享記憶體地址的影響

shmaddr和shmflg的組合關係 共享記憶體最終地址
shmaddr為NULL,不管shmflg 核心自動選擇一個地址
shmaddr不為NULL且shmflg無SHM_RND標記 則以shmaddr為連線地址。
shmaddr不為NULL且shmflg設定了SHM_RND標記 則連線的地址會自動向下調整為SHMLBA的整數倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY 表示連線操作用來只讀共享記憶體

* 示例程式碼

//shmat函式的使用
int main04()
{
  key_t key;
  int shm_id;
  void * p = NULL;
  Teacher t1;

  bzero(&t1,sizeof(t1));
  strcpy(t1.name,"abcdef");
  t1.name[strlen(t1.name)] = '\0';
  t1.age = 32;
  key = ftok("./",'b');

  //存在會報錯
  //不存在就建立新的共享記憶體
  shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT); 
  if(shm_id == (void*)-1)
  {
    if(errno == ENOENT)
    {
      printf("no such shm\n");
    }
    if(errno == EEXIST)//已經存在就報錯
    {
      printf("have created shm\n");
    }
    ERR_EXIT("shmget");
  }

  //shmat讓共享記憶體可以被程序使用--在程序的地址空間就可以使用該塊記憶體--每個程序shmat以後得到的地址不一樣--屬於自己的共享記憶體地址
  p = shmat(shm_id,NULL,0);//讓本程序連結到共享記憶體--得到一個地址--共享記憶體的首地址--p
  if(p ==(void*)-11)
  {
    ERR_EXIT("shmat");
  }

  memcpy(p,&t1,sizeof(t1));

  printf("name : %s\tage:%d\n",((Teacher*)p)->name,((Teacher*)p)->age);

  return 0;
}

shmdt函式

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

注意:將共享記憶體段與當前程序脫離不等於刪除共享記憶體段

shmctl函式

  • 功能:用於控制共享記憶體
  • 原型
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 引數
    shmid:由shmget返回的共享記憶體標識碼
    cmd:將要採取的動作(有三個可取值)
    這裡寫圖片描述
    buf:指向一個儲存著共享記憶體的模式狀態和訪問許可權的資料結構
  • 返回值:成功返回0;失敗返回-1
  • 示例程式碼
//shmctl+shmdt刪除共享記憶體
int main05()
{
  key_t key;
  int shm_id;
  void * p = NULL;
  Teacher t1;
  int opt = 0;

  bzero(&t1,sizeof(t1));
  strcpy(t1.name,"abcdef");
  t1.name[strlen(t1.name)] = '\0';
  t1.age = 32;
  key = ftok("./",'b');

  //存在會報錯
  //不存在就建立新的共享記憶體
  shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT); 
  if(shm_id == (void*)-1)
  {
    if(errno == ENOENT)
    {
      printf("no such shm\n");
    }
    if(errno == EEXIST)//已經存在就報錯
    {
      printf("have created shm\n");
    }
    ERR_EXIT("shmget");
  }

  //shmat讓共享記憶體可以被程序使用--在程序的地址空間就可以使用該塊記憶體--每個程序shmat以後得到的地址不一樣--屬於自己的共享記憶體地址
  p = shmat(shm_id,NULL,0);//讓本程序連結到共享記憶體--得到一個地址--共享記憶體的首地址--p
  if(p == (void*)-1)
  {
    ERR_EXIT("shmat");
  }

  memcpy(p,&t1,sizeof(t1));

  printf("name : %s\tage:%d\n",((Teacher*)p)->name,((Teacher*)p)->age);

  printf("Please input your select:\n1-->don't delete\t2-->delete it force\n");
  scanf("%d",&opt);

  if(opt == 2)
  {
    //刪除之前要脫離對映unmap

    shmdt(p);

    shmctl(shm_id,IPC_RMID,NULL);
  }
  else if(opt == 1)
  {
    pause();
  }
  else
  {
    exit(0);
  }

  return 0;
}

綜合例項程式碼

涉及到共享記憶體的邊界操作–是否真的立即刪除。詳細內容看程式碼註釋複習!!!

//如果共享記憶體被別的程式佔用,連線數/引用計數不為0,則刪除共享記憶體不會立馬被刪除
//出現一個現象---共享記憶體key值變成了0,引用計數減1,且此時的共享記憶體已經不能被其他重新shmat的程序讀取了
//只能被在刪除之前就已經shmat的程序讀取,重啟一個程序重新shmat的時候由於key已經變為0,所以新開的程序是不能再繼續讀取共享記憶體的資料的
//只有當連線共享記憶體的所有程序都死掉Linux核心才會刪除共享記憶體
//是否能直接刪除共享記憶體--關鍵是看在刪除以後有無程序連結到該塊記憶體--等同於shmctl+shmdt刪除之前有多少程序已經執行了shmat
//多程序中shmat和shmctl操作的時序關係不同--導致刪除共享記憶體是否能直接刪除
int main()
{
  key_t key;
  int shm_id;
  void * p = NULL;
  Teacher t1;
  int opt = 0;
  pid_t pid = 0;

  bzero(&t1,sizeof(t1));
  strcpy(t1.name,"abcdef");
  t1.name[strlen(t1.name)] = '\0';
  t1.age = 32;
  key = ftok("./",'b');

  //存在會報錯
  //不存在就建立新的共享記憶體
  shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT); 
  if(shm_id == -1)
  {
    if(errno == ENOENT)
    {
      printf("no such shm\n");
    }
    if(errno == EEXIST)//已經存在就報錯
    {
      printf("have created shm\n");
    }
    ERR_EXIT("shmget");
  }
  pid = fork();
  if( pid == -1)
  {
    ERR_EXIT("fork");
  }
  else if(pid == 0)
  {
    p = shmat(shm_id,NULL,0);//子程序連結到共享記憶體
    sleep(3);
    if(p == (void*)-1)
    {
      ERR_EXIT("shmat");
    }
    printf("child p:%d\n",(int)p);

    //子程序脫離並刪除共享記憶體--此時不一定直接刪除

    //如果父程序在此之前已經shmat了--共享記憶體的key變為0--但因為父程序並沒有退出也沒有刪除共享記憶體--引用計數不為0--不刪除

    //如果直到此時此刻,父程序還沒有shmat--沒有連線到共享記憶體--
    //此時子程序可以直接刪除共享記憶體--因為再也沒有多餘的程序鏈到該塊記憶體
    shmdt(p);
    shmctl(shm_id,IPC_RMID,NULL);

    //子程序結束
    printf("child quit\n");
    exit(0);
  }
  wait(NULL);//如果wait放在這裡--程式會在父程序shmat的時候異常結束
            //父程序會等待子程序刪除共享記憶體之後再shmat--
            //此時共享記憶體已經被刪除,Linux核心已經回收該塊記憶體--
            //意味著需要重新shmget一塊共享記憶體,再shmat才可以給父程序使用
            //父程序在共享記憶體已經被刪除的情況下直接shmat是會core dump的


  //shmat讓共享記憶體可以被程序使用--在程序的地址空間就可以使用該塊記憶體--屬於自己程序的使用者空間的記憶體地址
  p = shmat(shm_id,NULL,0);//讓本程序連結到共享記憶體--得到一個地址--共享記憶體的首地址--p
  if(p == (void*)-1)
  {
    ERR_EXIT("shmat");
  }
  printf("parent p:%d\n",(int)p);
  memcpy(p,&t1,sizeof(t1));

  printf("name : %s\tage:%d\n",((Teacher*)p)->name,((Teacher*)p)->age);
  sleep(5);
  printf("Please input your select:\n1-->read shm after child delete it\n2-->delete it force\n");
  scanf("%d",&opt);

  if(opt == 2)
  {
    //刪除之前要脫離對映unmap
    shmdt(p);
    //刪除共享共享記憶體--不一定立即刪除--要看是否還有其他程序連結到該程序
    shmctl(shm_id,IPC_RMID,NULL);
  }
  else if(opt == 1)
  { 
    //這裡不一定能讀取成功:

    //如果父程序在子程序刪除共享記憶體之前之前就已經shmat的,就可以讀取記憶體裡面的資料

    //如果父程序在子程序結束之後/刪除共享記憶體之後才shmat--讀取失敗--實際上在shmat的時候就已經失敗了--返回-1無效地址
    printf("name : %s\tage:%d\n",((Teacher*)p)->name,((Teacher*)p)->age);
  }
  else
  {
    pause();
  }

  //如果wait放在這裡--程式正常執行
  //父程序會在子程序刪除共享記憶體之前shmat--所以在子程序刪除共享記憶體之後還可以讀取
  //wait(NULL);
  return 0;
}