1. 程式人生 > >Linux IPC實踐(8) --共享記憶體/記憶體對映

Linux IPC實踐(8) --共享記憶體/記憶體對映

概述

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

 

共享記憶體 VS. 其他IPC形式

管道/訊息佇列傳遞資料

 

共享記憶體傳遞資料

 

共享記憶體生成之後,傳遞資料並不需要再走Linux核心,共享記憶體允許兩個或多個程序共享一個給定的儲存區域,資料並不需要在多個程序之間進行復制,因此,共享記憶體的傳輸速度更快!

mmap記憶體對映

將檔案/裝置空間對映到共享記憶體區

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

引數:

addr:  要對映的起始地址, 通常指定為NULL, 讓核心自動選擇;

length: 對映到程序地址空間的位元組數;

prot: 對映區保護方式(見下);

flags: 標誌(見下);

fd: 檔案描述符;

offset: 從檔案頭開始的偏移量;

prot

說明

PROT_READ

頁面可讀

PROT_WRITE

頁面可寫

PROC_EXEC

頁面可執行

PROC_NONE

頁面不可訪問

flags

說明

MAP_SHARED

變動是共享的

MAP_PRIVATE

變動是私有的

MAP_FIXED

準確解釋addr引數, 如果不指定該引數, 則會以4K大小的記憶體進行對齊

MAP_ANONYMOUS

建立匿名對映區, 不涉及檔案

mmap返回值:

成功: 返回對映到的記憶體區的起始地址;

失敗: 返回MAP_FAILED;

記憶體對映示意圖:


(注意: 記憶體對映時, 是以頁面(4K)作為單位)

/** 示例1: 寫檔案對映
將檔案以可讀,可寫的方式對映到程序的地址空間, 然後向其中寫入內容
**/
struct Student
{
    char name[4];
    int age;
};
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("usage: ./main <file-name>");

    int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 0666);
    if (fd == -1)
        err_exit("file open error");

//為記憶體對映爭取空間
    if (lseek(fd, sizeof(Student)*5-1, SEEK_SET) == (off_t)-1)
        err_exit("lseek error");
    write(fd, "", 1);

    Student *p = (Student *)mmap(NULL, sizeof(Student)*5,
                                 PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        err_exit("mmap error");

    // 此時:操縱檔案就可以如同操縱記憶體一樣了
    char ch = 'a';
    for (int i = 0; i < 5; ++i)
    {
        memcpy((p+i)->name, &ch, 1);
        (p+i)->age = 20+i;
        ++ ch;
    }
    cout << "file initialized!" << endl;
    if (munmap(p, sizeof(Student)*5) == -1)
        err_exit("munmap error");
    cout << "process exit..." << endl;

    return 0;
}
/**示例2: 讀檔案對映
**/
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("usage: ./main <file-name>");

    //以只讀方式開啟
    int fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        err_exit("file open error");

    void *mmap(void *addr, size_t length, int prot, int flags,
               int fd, off_t offset);
    Student *p = (Student *)mmap(NULL, sizeof(Student)*5,
                                 PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        err_exit("mmap error");

    // 從記憶體中讀取資料(其實是從檔案中讀取)
    for (int i = 0; i < 5; ++i)
    {
        cout << "name: " << (p+i)->name << ", age: " << (p+i)->age << endl;
    }
    if (munmap(p, sizeof(Student)*5) == -1)
        err_exit("munmap error");
    cout << "process exit..." << endl;

    return 0;
}

map注意點:

    1. 記憶體對映不能(也不可能)改變檔案的大小;

    2. 可用於程序間通訊的有效地址空間不完全受限於對映檔案的大小, 而應該以記憶體頁面的大小為準(見下面測試);

    3. 檔案一旦被對映之後, 所有對對映區域的訪問實際上是對記憶體區域的訪問; 對映區域內容寫會檔案時, 所寫內容不能超過檔案的大小.

/** 測試: 注意點2
檔案以40K的內容進行建立, 而以120K的內容進行寫回
**/
//程式1: 寫檔案對映
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("usage: ./main <file-name>");

    int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 0666);
    if (fd == -1)
        err_exit("file open error");

    // 注意: 此處我們的檔案其實只有40個位元組
    if (lseek(fd, sizeof(Student)*5-1, SEEK_SET) == (off_t)-1)
        err_exit("lseek error");
    write(fd, "", 1);

    // 但是我們卻是以120個位元組的方式進行對映
    Student *p = (Student *)mmap(NULL, sizeof(Student)*15,
                                 PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        err_exit("mmap error");

    // 以120個位元組的方式進行寫入
    char ch = 'a';
    for (int i = 0; i < 15; ++i)
    {
        memcpy((p+i)->name, &ch, 1);
        (p+i)->age = 20+i;
        ++ ch;
    }
    cout << "file initialized!" << endl;
    // 以120位元組的方式解除安裝該記憶體區
    if (munmap(p, sizeof(Student)*15) == -1)
        err_exit("munmap error");

    // 注意: 要故意暫停一會兒, 以便讓read程式讀取該共享記憶體的內容
    sleep(20);
    cout << "process exit..." << endl;
}
//程式2: 讀檔案對映
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("usage: ./main <file-name>");

    //以只讀方式開啟
    int fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        err_exit("file open error");

    void *mmap(void *addr, size_t length, int prot, int flags,
               int fd, off_t offset);
    // 以120位元組的方式對映
    Student *p = (Student *)mmap(NULL, sizeof(Student)*15,
                                 PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        err_exit("mmap error");

    // 以120位元組的方式讀取
    for (int i = 0; i < 15; ++i)
    {
        cout << "name: " << (p+i)->name << ", age: " << (p+i)->age << endl;
    }
    if (munmap(p, sizeof(Student)*15) == -1)
        err_exit("munmap error");
    cout << "process exit..." << endl;
}

msync函式

int msync(void *addr, size_t length, int flags);

對對映的共享記憶體執行同步操作

引數:

addr: 記憶體起始地址;

length: 長度

flags: 選項

flags

說明

MS_ASYNC

執行非同步寫

MS_SYNC

執行同步寫, 直到核心將資料真正寫入磁碟之後才返回

MS_INVALIDATE

使快取記憶體的資料失效

返回值:

成功: 返回0;

失敗: 返回-1;