天天看點

細說linux IPC(三):mmap系統調用共享記憶體

        前面講到socket的程序間通信方式。這樣的方式在程序間傳遞資料時首先須要從程序1位址空間中把資料複制到核心,核心再将資料複制到程序2的位址空間中,也就是資料傳遞須要經過核心傳遞。這樣在處理較多資料時效率不是非常高。而讓多個程序共享一片記憶體區則攻克了之前socket程序通信的問題。共享記憶體是最快的程序間通信 。将一片記憶體映射到多個程序位址空間中,那麼程序間的資料傳遞将不在涉及核心。

        共享記憶體并非從某一程序擁有的記憶體中劃分出來的。程序的記憶體總是私有的。共享記憶體是從系統的空暇記憶體池中配置設定的,希望訪問它的每一個程序連接配接它。這個連接配接過程稱為映射。它給共享記憶體段配置設定每一個程序的位址空間中的本地位址。

    mmap()系統調用使得程序之間通過映射同一個普通檔案實作共享記憶體。普通檔案被映射到程序位址空間後。程序能夠向訪問普通記憶體一樣對檔案進行訪問。不必再調用read(),write()等操作。

函數原型為:

#include <sys/mman.h>
     void *mmap(void *addr, size_t length, int prot, int flags,  int fd, off_t offset);       

        當中參數addr為描寫叙述符fd應該被映射到程序空間的起始位址,當指定為NULL時核心将自己去選擇起始位址,不管addr是為NULL,函數傳回值都是fd所映射到記憶體的起始位址;

        len是映射到調用程序位址空間的位元組數,它 從被映射檔案開頭offset個位元組開始算起,offset通常設定為0;

        prot 參數指定共享記憶體的訪問權限。可取例如以下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可運作), PROT_NONE(不可訪問)。該值常設定為PROT_READ | PROT_WRITE 。

        flags由下面幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,當中,MAP_SHARED(變動是共享的,對共享記憶體的改動全部程序可見) , MAP_PRIVATE(變動是私有的。對共享記憶體改動僅僅對該程序可見)   必選其一,而MAP_FIXED則不推薦使用 。

    munmp() 删除位址映射關系。函數原型例如以下:

#include <sys/mman.h>
       int munmap(void *addr, size_t length);      

        參數addr是由mmap傳回的位址。len是映射區大小。

        程序在映射空間的對共享内容的改變并不直接寫回到磁盤檔案裡。往往在調用munmap()後才運作該操作。能夠通過調用msync()實作磁盤上檔案内容與共享記憶體區的内容一緻。 msync()函數原型為:

#include <sys/mman.h>
       int msync(void *addr, size_t length, int flags);
      

        參數addr和len代表記憶體區,flags有下面值指定,MS_ASYNC(運作異步寫), MS_SYNC(運作同步寫),MS_INVALIDATE(使快速緩存失效)。當中MS_ASYNC和MS_SYNC兩個值必須且僅僅能指定一個,一旦寫操作排入核心。MS_ASYNC馬上傳回,MS_SYNC要等到寫操作完畢後才傳回。假設還指定了MS_INVALIDATE,那麼與其終于拷貝不一緻的檔案資料的全部記憶體中拷貝都失效。

        在使用open函數打開一個檔案之後調用mmap把檔案内容映射到調用程序的位址空間,這樣我們操作檔案内容僅僅須要對映射的位址空間進行操作。而無需再使用open。write等函數。

        使用共享記憶體的步驟基本是:

open()建立記憶體段;

用 ftruncate()設定它的大小。

用mmap() 把它映射到程序記憶體,運作其它參與者須要的操作;

當使用完時,原來的程序調用 munmap()然後退出。

以下來看一個實作:

server程式建立記憶體并向共享記憶體寫入資料:

int sln_shm_get(char *shm_file, void **shm, int mem_len)
{
    int fd;
    fd = open(shm_file, O_RDWR | O_CREAT, 0644);//1. 建立記憶體段
    if (fd < 0) {
        printf("open <%s> failed: %s\n", shm_file, strerror(errno));
        return -1;
    }
    ftruncate(fd, mem_len);//2.設定共享記憶體大小
    *shm = mmap(NULL, mem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); //mmap映射系統記憶體池到程序記憶體
    if (MAP_FAILED == *shm) {
        printf("mmap: %s\n", strerror(errno));
        return -1;
    }
    close(fd);
    return 0;
}
int main(int argc, const char *argv[])
{
    char *shm_buf = NULL;
    sln_shm_get(SHM_IPC_FILENAME, (void **)&shm_buf, SHM_IPC_MAX_LEN);
    snprintf(shm_buf, SHM_IPC_MAX_LEN, "hello share memory ipc! i'm server.");
    return 0;
}      

client程式映射共享記憶體并讀取當中資料:

int main(int argc, const char *argv[])
{
    char *shm_buf = NULL;
    sln_shm_get(SHM_IPC_FILENAME, (void **)&shm_buf, SHM_IPC_MAX_LEN);
    printf("ipc client get: %s\n", shm_buf);
    munmap(shm_buf, SHM_IPC_MAX_LEN);
    return 0;
}
      

先執行server程式向共享記憶體寫入資料,再執行客戶程式。執行結果例如以下:

# ./server
# ./client
ipc client get: hello share memory ipc! i'm server.
#      

共享記憶體不像socket那樣本身具有同步機制,它須要通過添加其它同步操作來實作同步,比方信号量等。同步相關操作在後面會有相關專欄具體叙述。