1. 程式人生 > >Linux 程序通訊之:記憶體對映(Memory Map)

Linux 程序通訊之:記憶體對映(Memory Map)

一、簡介

正如其名(Memory Map),mmap 可以將某個裝置或者檔案對映到應用程序的記憶體空間中。通過直接的記憶體操作即可完成對裝置或檔案的讀寫。.

通過對映同一塊實體記憶體,來實現共享記憶體,完成程序間的通訊。由於減少了資料複製的次數,一定程度上提高了程序間通訊的效率。

二、API 說明

1. 標頭檔案

#include <sys/mman.h>

2. 建立記憶體對映

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • addr
    : 將檔案對映到程序空間指定地址,可以為 NULL,此時系統將自動分配地址
  • length : 被對映到程序空間的記憶體塊大小
  • prot : 被對映記憶體的訪問許可權
    • PROT_EXEC : 記憶體頁可執行
    • PROT_READ : 記憶體頁可讀
    • PROT_WRITE : 記憶體頁可寫
    • PROT_NONE : 記憶體頁不可訪問
  • flags : 指定程式對記憶體塊所做的改變將造成的影響,通常有:
    • MAP_SHARED : 共享的形式,對記憶體塊所做的修改將儲存到檔案中
    • MAP_PRIVATE
      : 私有的,對記憶體塊的修改只在區域性範圍內有效
    • MAP_FIXED : 使用指定的對映起始地址
    • MAP_ANONYMOUS/MAP_ANON : 匿名對映,即不和任何檔案關聯,同時將 fd 設定為 -1。通常需要程序間有一定關係才能使用這種對映方式
  • fd : 檔案描述符,即 open() 函式返回的值
  • offset : 指定從檔案的哪一部分開始對映,必須是記憶體頁的整數倍,通常為 0
  • 返回值 : 成功返回指向對映記憶體的指標,失敗返回 -1,並設定合適的 errno 值

3. 解除記憶體對映

int munmap(void *addr,
size_t length);
  • addr : 對映記憶體的起始指標,必須是 mmap 方法返回的那個值
  • length : 對映到程序空間的記憶體塊大小
  • 返回值 : 成功返回 0,失敗返回 -1,並設定合適的 errno 值

三、示例

1. 無血緣關係程序通訊

寫端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
 
typedef struct _data {
    int a;
    char b[64];
} Data;
 
 
int main() {
    Data *addr;
    Data data = { 10, "Hello World\n" };
    int fd;
 
    fd = open("mmap_temp_file", O_RDWR|O_CREAT|O_TRUNC, 0644);
    if (fd == -1) {
        perror("open failed\n");
        exit(EXIT_FAILURE);
    }
    ftruncate(fd, sizeof(data));
 
    // 使用fd建立記憶體對映區
    addr = (Data *)mmap(NULL, sizeof(data), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap failed!\n");
        exit(EXIT_FAILURE);
    }
    close(fd); // 對映完後文件就可以關閉了
 
    memcpy(addr, &data, sizeof(data)); // 往對映區寫資料
    munmap(addr, sizeof(data)); // 釋放對映區
    return 0;
}

讀端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
 
typedef struct _data {
    int a;
    char b[64];
} Data;
 
 
int main() {
    Data *addr;
    int fd;
 
    fd = open("mmap_temp_file", O_RDONLY);
    if (fd == -1) {
        perror("open failed\n");
        exit(EXIT_FAILURE);
    }
 
    // 使用fd建立記憶體對映區
    addr = (Data *)mmap(NULL, sizeof(Data), PROT_READ, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap failed!\n");
        exit(EXIT_FAILURE);
    }
    close(fd); // 對映完後文件就可以關閉了
 
    printf("read form mmap: a = %d, b = %s\n", addr->a, addr->b); // 往對映區寫資料
    munmap(addr, sizeof(Data)); // 釋放對映區
    return 0;
}

執行結果:

在這裡插入圖片描述

2. 血緣關係程序通訊

存在血緣關係的話,可以使用 匿名 的方式建立對映區,這樣就不需要那個臨時檔案了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
 
int m_var = 100;
 
int main() {
    int *addr;
    pid_t child_pid;
 
    // 以匿名的方式建立記憶體對映區,適用於存在血緣關係的程序間
    addr = (int *)mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap failed!\n");
        exit(EXIT_FAILURE);
    }
 
    child_pid = fork(); // 建立子程序
    if (child_pid == 0) {
        *addr = 666; // 往記憶體對映區寫資料
        m_var = 200;
        printf("child process: *addr = %d, m_var = %d\n", *addr, m_var);
    } else {
        sleep(1);
        printf("parent process: *addr = %d, m_var = %d\n", *addr, m_var); // 讀記憶體對映區的資料
        wait(NULL);
 
        int ret = munmap(addr, sizeof(int)); // 釋放記憶體對映區
        if (ret == -1) {
            perror("munmap failed\n");
            exit(EXIT_FAILURE);
        }
    }
    return 0;
}

執行結果:

在這裡插入圖片描述
addr 的值,父子程序都成功改變了。全域性變數 m_var 的值,父子程序遵從 讀時共享,寫時複製 原則。