Linux程式間通訊之共享記憶體

一輩子的碼農發表於2019-06-04

什麼是共享記憶體

百度百科定義:共享記憶體指 (shared memory)在多處理器的計算機系統中,可以被不同中央處理器(CPU)訪問的大容量記憶體。由於多個CPU需要快速訪問儲存器,這樣就要對儲存器進行快取(Cache)。任何一個快取的資料被更新後,由於其他處理器也可能要存取,共享記憶體就需要立即更新,否則不同的處理器可能用到不同的資料。

共享記憶體是 Unix下的多程式之間的通訊方法,這種方法通常用於一個程式的多程式間通訊,實際上多個程式間也可以通過共享記憶體來傳遞資訊。

特點

所謂共享記憶體就是使得多個程式可以訪問同一塊記憶體空間,是最快的可用IPC形式。是針對其他通訊機制執行效率較低而設計的。

但內部沒有共享記憶體互斥訪問機制,所以往往與其它通訊機制,如訊號量結合使用,來達到程式間的同步及互斥。

涉及函式

  • ftok:系統建立IPC通訊 (訊息佇列、訊號量和共享記憶體) 時必須指定一個ID值

    標頭檔案

    #include <sys/types.h>
    #include <sys/ipc.h>
    複製程式碼

    函式原型

    key_t ftok( const char * fname, int id )
    fname:指定的檔名(已經存在的檔名),一般使用當前目錄,如:"."
    id:id是子序號。雖然是int型別,但是隻使用8bits(1-255)。
    在一般的UNIX實現中,是將檔案的索引節點號取出,前面加上子序號得到key_t的返回值。
    
    如:指定檔案的索引節點號為65538,換算成16進製為0x010002,而你指定的ID值為38,
    換算成16進製為0x26,則最後的key_t返回值為0x26010002。
    
    查詢檔案索引節點號的方法是: ls -i
    
    當刪除重建檔案後,索引節點號由作業系統根據當時檔案系統的使用情況分配,
    因此與原來不同,所以得到的索引節點號也不同。
    
    複製程式碼
  • shmget:得到一個共享記憶體識別符號或建立一個共享記憶體物件並返回共享記憶體識別符號

    • 標頭檔案
    #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來確定操作。通常要求此值來源於ftok返回的IPC鍵值
    
    size
    大於0的整數:新建的共享記憶體大小,以位元組為單位
    0:只獲取共享記憶體時指定為0
    
    shmflg
    0:取共享記憶體識別符號,若不存在則函式會報錯
    IPC_CREAT:當shmflg&IPC_CREAT為真時,如果核心中不存在鍵值與key相等的共享記憶體,則新建一個共享記憶體;如果存在這樣的共享記憶體,返回此共享記憶體的識別符號
    IPC_CREAT|IPC_EXCL:如果核心中不存在鍵值與key相等的共享記憶體,則新建一個共享記憶體;如果存在這樣的共享記憶體則報錯
    使用時需要與IPC物件存取許可權(如0600)進行|運算來確定訊號量集的存取許可權
    
    函式返回值
    成功:返回共享記憶體的識別符號
    出錯:-1,錯誤原因存於error中
    
    錯誤程式碼
    EINVAL:引數size小於SHMMIN或大於SHMMAX
    EEXIST:預建立key所指的共享記憶體,但已經存在
    EIDRM:引數key所指的共享記憶體已經刪除
    ENOSPC:超過了系統允許建立的共享記憶體的最大值(SHMALL)
    ENOENT:引數key所指的共享記憶體不存在,而引數shmflg未設IPC_CREAT位
    EACCES:沒有許可權
    ENOMEM:核心記憶體不足
    複製程式碼
  • shmat:連線共享記憶體識別符號為shmid的共享記憶體,連線成功後把共享記憶體區物件對映到呼叫程式的地址空間,隨後可像本地空間一樣訪問

    • 標頭檔案
    #include <sys/types.h>
    #include <sys/shm.h>
    複製程式碼
    • 函式原型
    void *shmat(int shmid, const void *shmaddr, int shmflg)
    hmid:共享記憶體識別符號
    shmaddr:指定共享記憶體出現在程式記憶體地址的什麼位置,直接指定為NULL讓核心自己決定一個合適的地址位置
    shmflg:SHM_RDONLY:為只讀模式,其他為讀寫模式
    
    fork後子程式繼承已連線的共享記憶體地址。
    exec後該子程式與已連線的共享記憶體地址自動脫離(detach)。
    程式結束後,已連線的共享記憶體地址會自動脫離(detach)
    
    函式返回值
    成功:附加好的共享記憶體地址
    出錯:-1,錯誤原因存於errno中
    
    錯誤程式碼
    EACCES:無許可權以指定方式連線共享記憶體
    EINVAL:無效的引數shmid或shmaddr
    ENOMEM:核心記憶體不足
    
    複製程式碼
  • shmdt:與shmat函式相反,是用來斷開與共享記憶體附加點的地址,禁止本程式訪問此片共享記憶體

    • 標頭檔案
    #include <sys/types.h>
    #include <sys/shm.h>
    複製程式碼
    • 函式原型
    int shmdt(const void *shmaddr)
    shmaddr:連線的共享記憶體的起始地址
    本函式呼叫並不刪除所指定的共享記憶體區,而只是將先前用shmat函式連線(attach)好的共享記憶體脫離(detach)目前的程式
    
    函式返回值
    成功:0
    出錯:-1,錯誤原因存於error中
    
    錯誤程式碼
    EINVAL:無效的引數shmaddr
    複製程式碼
  • shmctl:完成對共享記憶體的控制

    • 標頭檔案
    #include <sys/types.h>
    #include <sys/shm.h>
    複製程式碼
    • 函式原型
    int shmctl(int shmid, int cmd, struct shmid_ds *buf)
    shmid:共享記憶體識別符號
    
    cmd
    IPC_STAT:得到共享記憶體的狀態,把共享記憶體的shmid_ds結構複製到buf中
    IPC_SET:改變共享記憶體的狀態,把buf所指的shmid_ds結構中的uid、gid、mode複製到共享記憶體的shmid_ds結構內
    IPC_RMID:刪除這片共享記憶體
    
    buf:共享記憶體管理結構體。具體說明參見共享記憶體核心結構定義部分
    
    函式返回值
    成功:0
    出錯:-1,錯誤原因存於error中
    
    錯誤程式碼
    EACCESS:引數cmd為IPC_STAT,確無許可權讀取該共享記憶體
    EFAULT:引數buf指向無效的記憶體地址
    EIDRM:識別符號為shmid的共享記憶體已被刪除
    EINVAL:無效的引數cmd或shmid
    EPERM:引數cmd為IPC_SET或IPC_RMID,卻無足夠的許可權執行
    
    複製程式碼

例項程式碼

shm_write.c

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

int main(int argc, const char * argv[]) {
    
    //生成一個key
    key_t key = ftok("./", 88);
    
    //建立共享記憶體,返回一個id
    //數字 4 、2 和 1表示讀、寫、執行許可權
    //使用者、所屬組、其他組都有讀寫許可權
    int shmid = shmget(key, 8, IPC_CREAT|0666); // IPC_CREAT: Create entry if key does not exist
    
    if (shmid == -1) {
        perror("shmget failed");
        //exit(0) 表示程式正常退出,exit⑴/exit(-1)表示程式異常退出。
        exit(1);
    }
    
    //對映共享記憶體,得到虛擬地址
    //shmaddr:指定共享記憶體出現在程式記憶體地址的什麼位置,直接指定為NULL讓核心自己決定一個合適的地址位置
    //shmflg: SHM_RDONLY:為只讀模式,其他為讀寫模式
    void *p = shmat(shmid, NULL, 0);
    if (p == (void *)-1) {
        perror("shmat failed");
        exit(2);
    }
    
    //寫共享記憶體
    int *pp = p;
    *pp = 0x123456;
    *(pp + 1) = 0xffffff;
    
    //解除對映
    if (shmdt(p) == -1) {
        printf("shmdt failed");
        exit(3);
    }
    
    printf("解除對映成功,點選回車銷燬共享記憶體\n");
    getchar();
    
    //IPC_RMID:刪除這片共享記憶體
    if (shmctl(shmid, IPC_RMID, NULL)) {
        perror("shmctl failed");
        exit(4);
    }
    
    return 0;
}

複製程式碼

shm_read.c

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

int main() {
    
    //生成key
    key_t key = ftok("./", 88);
    
    //獲取共享記憶體,返回id
    //0:只獲取共享記憶體時指定為0
    //shmflg0:取共享記憶體識別符號,若不存在則函式會報錯
    int shmid = shmget(key, 0, 0);
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }
    
    //對映共享記憶體,得到虛擬地址
    void *p = shmat(shmid, 0, 0);
    if (p == (void *)-1) {
        perror("shmat failed");
        exit(2);
    }
    
    //讀取共享記憶體
    int data1 = *(int *)p;
    int data2 = *((int *)p + 1);
    
    printf("從共享記憶體中讀取了,%x 和 %x\n", data1, data2);
    
    //解除對映
    if(shmdt(p) == -1) {
        perror("shmdt failed");
        exit(3);
    }
    
    
    return 0;
}
複製程式碼

編譯程式碼

clang -o shm_write shm_write.c
clang -o shm_read shm_read.c
複製程式碼

執行程式碼

./shm_write
./shm_read
複製程式碼

相關文章