mmap


存儲映射

存儲映射I/O(Memory-mapped I/O)使一個磁盤文件與存儲空間中的一個緩沖區相映射. 於是當從緩沖區中取數據, 就相當於讀文件中的相應字節. 與此類似, 將數據存入緩沖區, 則相應的字節就自動寫入文件. 這樣, 就可在不適用read和write函數的情況下, 使用地址(指針)完成I/O操作

使用這種方法, 首先應通知內核, 將一個指定文件映射到存儲區域中. 這個映射工作可以通過mmap函數來實現

作用: 將磁盤文件的數據映射到內存, 用戶通過修改內存就能修改磁盤文件

mmap創建內存映射
  

匿名映射

通過使用我們發現, 使用映射區來完成文件讀寫操作十分方便, 父子進程間通信也較容易. 但缺陷是, 每次創建映射區一定要依賴一個文件才能實現. 通常為了建立映射區要open一個temp文件, 創建好了再unlink, close掉, 比較麻煩. 可以直接使用匿名映射來代替. 其實Linux系統給我們提供了創建匿名映射區的方法,無需依賴一個文件即可創建映射區。同樣需要借助標志位參數flags來指定

使用MAP_ANONYMOUS (或MAP_ANON), 如: int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

需注意的是, MAP_ANONYMOUS和MAP_ANON這兩個宏是Linux操作系統特有的宏. 在類Unix系統中如無該宏定義, 可使用如下兩步來完成匿名映射區的建立

fd = open("/dev/zero", O_RDWR);
p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

mmap無血緣關系進程間通信

實質上mmap是內核借助文件幫我們創建了一個映射區, 多個進程之間利用該映射區(需借助文件)完成數據傳遞. 由於內核空間多進程共享, 因此無血緣關系的進程間也可以使用mmap來完成通信. 只要設置相應的標志位參數flags即可. 若想實現共享, 當然應該使用MAP_SHARED了

基礎API

mmap

void *mmap(
	void *addr,		// 建立映射區首地址, 由linux內核指定, 傳NULL 
	size_t length, 	// 映射區的大小, 4k的整數倍, 不能為0, 一般文件有多大length就為多大
	int prot, 		// 映射區的權限, PROT_READ(映射區  必須  要有讀權限), PROT_WRITE
	int flags,		// 標志位參數, 	MAP_SHARED(數據同步到磁盤), MAP_PRIVATE(數據不會同步到磁盤), 有血緣關系通信需是MAP_SHARED
	int fd, 		// 文件描述符, 	要映射文件對應的fd(open得來的)
	off_t offset	// 映射文件的偏移量, 	映射時文件指針的偏移量, 必須是4k的整數倍, 一般為0
);

返回值: 成功, 映射區首地址; 失敗, MAP_FAILED

思考問題
如果對mmap的返回值(ptr)做++操作(ptr++), munmap是否能夠成功?
  不能對ptr做操作, 可以復制一份, 對復制的指針進行操作
如果open是O_RDONLY, mmap時prot參數指定PROT_READ|PROT_WRITE會怎樣
  mmap調用失敗, open文件指定權限應該大於等於mmap第三個參數prot指定的權限
如果文件偏移量為1000會怎樣
  必須是4096的整數倍
如果不檢測mmap的返回值會怎么樣
  沒什么影響
mmap什么情況下會調用失敗
  第二個參數length=0; 第三個參數沒有指定PROT_READ; 第五個參數fd對應的open權限必須大於port權限; offset必須是4096的整數倍
可以open的時候O_CREAT一個新文件來創建映射區嗎
  可以, 需要做文件拓展(lseek, truncate)
mmap后關閉文件描述符, 對mmap映射有無影響
  沒有影響
對ptr越界操作會怎樣
  段錯誤

munmap

int munmap(void *addr, size_t length);

參數:
  addr: mmap的返回值
  length: mmap的第二個參數

進程間通信, 不阻塞, 數據直接才內存中處理,
  有血緣關系,
    父子進程共享內存映射區, 可以創建匿名映射區, 可以不需要磁盤文件進行通信
  沒有血緣關系
    不能使用匿名映射的方式, 只能借助磁盤文件創建映射區
    A(a.c) B(b.c)
    a.c: int fd1 = open("XXX"); void *ptr = mmap(,,,fd1, 0); 對映射區(ptr)進行讀寫操作
    b.c: int fd2 = open("hello"); void ptr* = mmap(,,,fd2, 0); 對映射區(ptr)進行寫操作

示例程序

利用內存映射區讀文件

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>

int main(int argc, const char* argv[]) {
    // 打開一個文件
    int fd = open("english.txt", O_RDWR);
    int len = lseek(fd, 0, SEEK_END);
    // 創建內存映射區
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("error");
        exit(1);
    }

    printf("%s", (char*)ptr);

    // 釋放內存映射區
    munmap(ptr, len);
    close(fd);

    return 0;
}

MAP_PRIVATE與MAP_SHARED測試

父子進程共享:

  1. 打開的文件
  2. mmap建立的映射區(但必須要使用MAP_SHARED)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, const char* argv[]) {
    // 打開一個文件
    int fd = open("english.txt", O_RDWR);
    int len = lseek(fd, 0, SEEK_END);
    
    // 通信測試
    //void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);    // MAP_PRIVATE, 父子進程不可通信
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);   // MAP_SHARED, 父子進程可通信
    if (ptr == MAP_FAILED) {
        perror("error");
        exit(1);
    }
    close(fd);

    //printf("%s", (char*)ptr);

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork error");
        exit(1);
    }
    if (pid > 0) {
        // 父進程寫數據
        strcpy((char*)ptr, "haha, hehe");
        // 回收
        wait(NULL);
    }
    else if (pid == 0) {
        // 讀數據
        printf("%s\n", (char*)ptr);
    }

    // 釋放內存映射區
    //ptr++;
    int ret = munmap(ptr, len);
    if (ret == -1) {
        perror("mmap error");
        exit(1);
    }

    return 0;
}

有血緣關系匿名映射區通信

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, const char* argv[]) {
    // 創建匿名內存映射區
    int len = 4096;

    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);      // MAP_SHARED可通信
    //void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);   // MAP_PRIVATE, 不可通信
    if (ptr == MAP_FAILED) {
        perror("error");
        exit(1);
    }

    //printf("%s", (char*)ptr);

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork error");
        exit(1);
    }
    if (pid > 0) {
        // 父進程寫數據
        strcpy((char*)ptr, "haha");
        // 回收
        wait(NULL);
    }
    else if (pid == 0) {
        // 讀數據
        printf("%s\n", (char*)ptr);
    }

    // 釋放內存映射區
    //ptr++;
    int ret = munmap(ptr, len);
    if (ret == -1) {
        perror("mmap error");
        exit(1);
    }

    return 0;
}

無血緣關系利用內存映射區通信

讀端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>

int main(int argc, const char *argv[]) {
    int fd = open("temp", O_RDWR | O_CREAT, 0664);
    ftruncate(fd, 4096);
    int len = lseek(fd, 0, SEEK_END);

    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    while (1) {
        sleep(1);
        printf("%s\n", (char*)ptr+1024);
    }

    // 釋放
    int ret = munmap(ptr, len);
    if (ret == -1) {
        perror("munmap");
        exit(1);
    }

    return 0;
}

寫端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>

int main(int argc, const char *argv[]) {
    int fd = open("temp", O_RDWR | O_CREAT, 0664);

    void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    while (1) {
        char *p = (char *)ptr;
        p += 1024;
        strcpy(p, "haha, I'm fine ~\n");
        sleep(2);
    }

    int ret = munmap(ptr, 4096);
    if (ret == -1) {
        perror("munmap");
        exit(1);
    }

    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM