1. 程式人生 > 實用技巧 >【linux】系統程式設計-4-共享記憶體

【linux】系統程式設計-4-共享記憶體

目錄


前言

  • 知識點
    • 訊息佇列、訊號量共享記憶體 被統稱為 system-V IPC
      • 以上都是“持續性”資源,即它們被建立之後, 不會因為程序的退出而消失

6. 共享記憶體

6.1 概念

  • 共享記憶體
    • 共享記憶體是程序間通訊中最簡單的方式之一
    • 是效率最高的一種IPC通訊機制
    • 它允許多個不相關的程序訪問同一個邏輯記憶體
    • 注意:
      • 共享記憶體需要使用者自己維護,如同步、互斥等工作
      • 共享記憶體屬於 臨界資源
        ,在某一時刻只能一個程序對其操作
      • 所以,共享記憶體一般配合訊號量、互斥鎖等協調機制使用
  • 共享記憶體優點
    • 通訊方便
    • 介面簡單
    • 高效率
    • 任意程序都可對共享記憶體進行讀寫
  • 共享記憶體缺點
    • 沒有提個同步、互斥等機制,需要使用者自己維護

6.2 操作函式

  • 使用 shmget 函式建立或獲取一個共享記憶體物件
  • 使用 shmat 函式對映到程序的虛擬地址
  • 使用 shmdt 函式解除對映,解除對映後,該解除不能再訪問該共享記憶體
  • 使用 shmctl 函式獲取或設定共享記憶體的相關屬性

6.2.1 shmget()

  • 使用 shmget() 建立或獲取一個訊號量
  • 通過命令 man 瞭解更多
  • 所需要的標頭檔案:
    #include <sys/ipc.h>
    #include <sys/shm.h>
    
  • 函式原型:int shmget(key_t key, size_t size, int shmflg);
    • key:標識共享記憶體的鍵值
      • 0 或 IPC_PRIVATE:建立一個新的共享記憶體
      • 大於 0 的 32 位整數:視引數 shmflg 來確定操作
    • size
      • 新建的共享記憶體大小,以位元組為單位,但是會以 來取整
    • shmflg
      • 0:取共享記憶體識別符號,若不存在則函式會報錯
      • IPC_CREAT:如果核心中不存在該共享記憶體,則建立一個新的共享記憶體;如果存在,則呼叫
      • IPC_CREAT|IPC_EXCL:如果核心中不存在該共享記憶體,則建立一個新的共享記憶體;如果存在,則報錯
    • 返回:
      • 成功:返回該共享記憶體的識別符號
      • 失敗:返回 -1,錯誤原因記錄在變數 error 中:
        • EINVAL:引數size小於 SHMMIN 或大於 SHMMAX
        • EEXIST:預建立key所指的共享記憶體,但已經存在
        • EIDRM:引數key所指的共享記憶體已經刪除
        • ENOSPC:超過了系統允許建立的共享記憶體的最大值(SHMALL)
        • ENOENT:引數key所指的共享記憶體不存在,而引數shmflg未設IPC_CREAT位
        • EACCES:沒有許可權
        • ENOMEM:核心記憶體不足

6.2.2 shmat()

  • 使用 shmat() 函式對映到程序的虛擬地址
  • 通過命令 man 瞭解更多
  • 所需要的標頭檔案:
    #include <sys/types.h>
    #include <sys/shm.h>
    
  • 函式原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
    • shmid:共享記憶體標識
    • shmaddr
      • 不為 NULL:系統會根據 shmaddr 來選擇一個合適的記憶體區域
      • 為 NULL:系統會自動選擇一個合適的虛擬記憶體空間地址去對映共享記憶體
    • shmflg:操作共享記憶體的方式
      • SHM_RDONLY:以只讀方式對映共享記憶體(其它為讀寫模式
      • SHM_REMAP:重新對映,此時的 shmaddr 不能為 NULL
      • NULLSHM:自動選擇比 shmaddr 小的最大也對齊地址
    • 返回:
      • 成功:返回共享記憶體的起始地址
      • 失敗:返回 -1,錯誤原因記錄在變數 error 中:
        • EACCES:無許可權以指定方式連線共享記憶體
        • EINVAL:無效的引數shmid或shmaddr
        • ENOMEM:核心記憶體不足
  • 注意:
    • 共享記憶體只能以 只讀和讀寫方式對映,不能以只寫方式對映
    • shmat()第二個引數 shmaddr
      • 一般都設為 NULL ,讓系統自動找尋合適的地址。
      • 如果不為 NULL ,
        • 那麼要求 SHM_RND 在 shmflg 必須被設定,系統便會將選擇比 shmaddr 小而又最大的頁對齊地址(即為SHMLBA的整數倍)作為共享記憶體區域的起始地址。(相當於自動對齊
        • 如果沒有設定 SHM_RND,那麼 shmaddr 必須是嚴格的頁對齊地址。(手動對齊

6.2.3 shmdt()

  • 使用 shmdt 函式解除對映,解除對映後,該解除不能再訪問該共享記憶體
  • 通過命令 man 瞭解更多
  • 所需要的標頭檔案:
    #include <sys/types.h>
    #include <sys/shm.h>
    
  • 函式原型:int shmdt(const void *shmaddr);
    • shmaddr:對映的共享記憶體的起始地址
    • 返回:
      • 成功:返回 0
      • 失敗:返回 -1,錯誤原因記錄在變數 error 中:
        • EINVAL:無效的引數shmaddr
  • 說明:
    • 本函式呼叫並不刪除所指定的共享記憶體區,而只是將先前用shmat函式連線(attach)好的共享記憶體脫離(detach)目前的程序

6.2.4 shmctl()

  • 使用 shmctl 函式獲取或設定共享記憶體的相關屬性
  • 通過命令 man 瞭解更多
  • 所需要的標頭檔案:
    #include <sys/types.h>
    #include <sys/shm.h>
    
  • 函式原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    • shmid:共享記憶體識別符號
    • cmd:控制命令
      • IPC_STAT:獲取屬性資訊,放置到buf中
      • IPC_SET:設定屬性資訊為buf指向的內容
      • IPC_RMID:刪除該共享記憶體
      • IPC_INFO:獲得關於共享記憶體的系統限制值資訊
      • SHM_INFO:獲得系統為共享記憶體消耗的資源資訊
      • SHM_STAT:與IPC_STAT具有相同的功能,但shmid為該SHM在核心中記錄所有SHM資訊的陣列的下標, 因此通過迭代所有的下標可以獲得系統中所有SHM的相關資訊
      • SHM_LOCK:禁止系統將該SHM交換至swap分割槽
      • SHM_UNLOCK:允許系統將該SHM交換至swap分。
    • buf:共享記憶體管理結構體
      struct shmid_ds {
          struct ipc_perm shm_perm;    /* 所有權和許可權 */
          size_t          shm_segsz;   /* 共享記憶體尺寸(位元組) */
          time_t          shm_atime;   /* 最後一次對映時間 */
          time_t          shm_dtime;   /* 最後一個解除對映時間 */
          time_t          shm_ctime;   /* 最後一次狀態修改時間 */
          pid_t           shm_cpid;    /* 建立者PID */
          pid_t           shm_lpid;    /* 後一次對映或解除對映者PID */
          shmatt_t        shm_nattch;  /* 對映該SHM的程序個數 */
          ...};
        
      struct ipc_perm {
          key_t          __key;    /* 該共享記憶體的鍵值key */
          uid_t          uid;      /* 所有者的有效UID */
          gid_t          gid;      /* 所有者的有效GID */
          uid_t          cuid;     /* 建立者的有效UID */
          gid_t          cgid;     /* 建立者的有效GID */
          unsigned short mode;     /* 讀寫許可權 + SHM_DEST + SHM_LOCKED 標記 */
          unsigned short __seq;    /* 序列號 */
      };  
      
    • 返回:
      • 成功:0
      • 失敗:返回 -1,錯誤原因記錄在變數 error 中:
        • EACCESS:引數cmd為IPC_STAT,卻無許可權讀取該共享記憶體
        • EFAULT:引數buf指向無效的記憶體地址
        • EIDRM:識別符號為shmid的共享記憶體已被刪除
        • EINVAL:無效的引數cmd或shmid
        • EPERM:引數cmd為IPC_SET或IPC_RMID,卻無足夠的許可權執行

6.3 例子

  • 共享記憶體寫程序
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"

int main()
{
    int running = 1;
    void *shm = NULL; // 共享記憶體首地址(*本程序的虛擬地址*)
    struct shared_use_st *shared = NULL;
    char buffer[BUFSIZ + 1];
    int shmid; //共享記憶體識別符號
    int semid; //訊號量識別符號

    //建立共享記憶體    
    shmid = shmget((key_t)1234, 4096, 0644 | IPC_CREAT);    
    if(shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }
    //將共享記憶體連線到當前程序的地址空間    
    shm = shmat(shmid, (void*)0, 0);    
    if(shm == (void*)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
    printf("Memory attached at %p\n", shm);

    /* 開啟訊號量,不存在則建立一個訊號量 */
    semid = semget((key_t)6666, 1, 0666|IPC_CREAT);
    if(semid == -1)
    {
        printf("sem open fail\n");
        exit(EXIT_FAILURE);
    }

    while(running)//向共享記憶體中寫資料
    {
        //向共享記憶體中寫入資料
        printf("Enter some text: ");        
        fgets(buffer, BUFSIZ, stdin); // 阻塞等待標準輸入
        strncpy(shm, buffer, 4096);

        sem_v(semid);/* 釋放訊號量 */

        //輸入了end,退出迴圈(程式)
        if(strncmp(buffer, "end", 3) == 0)
            running = 0;
    }

    //把共享記憶體從當前程序中分離    
    if(shmdt(shm) == -1)    
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
    sleep(2);
    exit(EXIT_SUCCESS);
}
  • 共享記憶體讀程序
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"

int main(void)
{
    int running = 1;//程式執行標誌
    char *shm = NULL;// 共享記憶體首地址(*本程序的虛擬地址*)
    int shmid;// 共享記憶體識別符號
    int semid;// 訊號量識別符號

    //建立共享記憶體    
    shmid = shmget((key_t)1234, 4096, 0666 | IPC_CREAT);    
    if(shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }

    //將共享記憶體連線到當前程序的地址空間    
    shm = shmat(shmid, 0, 0);    
    if(shm == (void*)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
    printf("\nMemory attached at %p\n", shm);

    /* 開啟訊號量,不存在則建立一個訊號量 */
    semid = semget((key_t)6666, 1, 0666|IPC_CREAT); /* 建立一個訊號量 */    
    if(semid == -1)
    {
        printf("sem open fail\n");
        exit(EXIT_FAILURE);
    }
    init_sem(semid, 0); // 初始化訊號量的值為 0

    while(running)// 讀取共享記憶體中的資料
    {
        /** 等待訊號量 */
        if(sem_p(semid) == 0)
        {            
            printf("You wrote: %s", shm);            
            sleep(rand() % 3);

            //輸入了end,退出迴圈(程式)
            if(strncmp(shm, "end", 3) == 0)
                running = 0;
        }
    }

    del_sem(semid); /* 刪除訊號量 */

    //把共享記憶體從當前程序中分離
    if(shmdt(shm) == -1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }

    //刪除共享記憶體
    if(shmctl(shmid, IPC_RMID, 0) == -1)
    {
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

參考:

* 野火