mmap的幾種使用場景


本文搬運自:

https://izualzhy.cn/mmap

https://introspelliam.github.io/2017/09/19/code/Linux%E5%86%85%E5%AD%98%E6%98%A0%E5%B0%84%E5%87%BD%E6%95%B0mmap%E5%87%BD%E6%95%B0%E8%AF%A6%E8%A7%A3/


 

mmap是linux中用處非常廣泛的一個系統調用。

mmap將一個文件或者其它對象映射進內存。文件被映射到多個頁上,如果文件的大小不是所有頁的大小之和,最后一個頁不被使用的空間將會清零

mmap 必須以PAGE_SIZE為單位進行映射,而內存也只能以頁為單位進行映射,若要映射非PAGE_SIZE整數倍的地址范圍,要先進行內存對齊,強行以PAGE_SIZE的倍數大小進行映射

函數原型:

void mmap(void start,size_t length,int prot,int flags,int fd,off_t offset);

int munmap(void* start,size_t length);

下面說一下內存映射的步驟:

  1. 用open系統調用打開文件, 並返回描述符fd.
  2. 用mmap建立內存映射, 並返回映射首地址指針start.
  3. 對映射(文件)進行各種操作, 顯示(printf), 修改(sprintf)
  4. 用munmap(void *start, size_t length)關閉內存映射.
  5. 用close系統調用關閉文件fd.

 

這里簡單總結一下其幾個用法,以及一些注意事項:

用法一:當要map的文件是/dev/mem的時候,就可以實現把物理地址映射到虛擬空間中【比如在SV驗證的時候在linux應用層訪問設備寄存器】

#define MAP_SIZE 4096
#define MAP_MASK (MAP_SIZE - 1)

int dev_mem_fd = -1void* g_map_base = NULL; void initMmap() { dev_mem_fd = open("/dev/mem", O_RDWR | O_SYNC); } void* MapPaddr2Vaddr(void* paddr) { void* vaddr = NULL; void* vaddr_map_base = NULL; void* map_base = (long)paddr & ~MAP_MASK; vaddr_map_base = mmap(NULL, MAP_SIZE, PORT_READ | PORT_WRITE, MAP_SHARED, dev_mem_fd, (long)map_base); if(vaddr_map_base != MAP_FAILED) { vaddr = vaddr_map_base + ((long)paddr & MAP_MASK); g_map_base = vaddr_map_base; } return vaddr; } void UnmapAddr() { if(g_map_base ) { munmap(g_map_base, MAP_SIZE); } }

void WriteReg(void* paddr, uint32_t val)
{
void* vaddr = MapPaddr2Vaddr(paddr);
*(volatile uint32_t*)vaddr = val;
__asm("DC CIVAC, %0\n", :: "r" ((uint64_t)vaddr));
UnmapAddr(); // makesure update sync to file
}

補充:系統虛擬存儲頁的大小獲取方式

printf("%ld\n", getpagesize());
printf("%ld\n", sysconf(_SC_PAGESIZE));

參數說明

  1. 參數fd為即將映射到內存空間的文件描述符,一般由open返回。同時fd也可以指定為-1,此時須指定flags參數中的MAP_ANON,表名進行的是匿名映射。
  2. len是映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始算起。
  3. prot參數指定共享內存的訪問權限,可指定為
    PROT_NONE:映射區不可訪問
    或者以下幾個值的或:
    PROT_READ:可讀
    PROT_WRITE:可寫
    PROT_EXEC:可執行
  4. flags參數影響映射存儲區的多種屬性:
    MAP_FIXED: 返回值必須等於addr。因為這不利於可移植性,所以不建議使用此標志。如果未指定此標志,而且addr非0,則內核只把addr視為在何處設置映射區的一種建議,但是不保證會使用所要求的地址。將addr指定為0可獲得最大可移植性。
    MAP_SHARED: 這一標志說明了本進程對映射區所進行的存儲操作的配置。此標志指定存儲操作修改映射文件,也就是說,存儲操作相當於對該文件的write。
    MAP_PRIVATE: 本標志說明,對映射區的存儲操作導致創建該映射文件的一個私有副本。所有后來對該映射區的引用都是引用該副本,而不是原始文件。(此標志的一種用途是用於調試程序,它將一程序文件的正文部分映射至一存儲區,但允許用戶修改其中的指令。任何修改只影響程序文件的副本,而不影響原文件)。
  5. offset參數一般設置為0,表示從文件頭開始映射。
  6. 參數addr指定文件應被映射到進程空間的起始地址,一般被指定為一個空指針,此時選擇起始地址的任務留給內核來完成。返回值為最后文件映射到進程空間的起始地址,進程可以直接操作該地址。

用法二:將一個普通文件映射到內存中,通常在需要對文件進行頻繁讀寫時使用,這樣用內存讀寫取代I/O讀寫,以獲得較高的性能

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

int main(int argc, char* argv[]) {
    int fd, offset;
    char* data;
    struct stat sbuf;

    if (argc != 2) {
        fprintf(stderr, "usage:mmapdemo offset\n");
        exit(1);
    }

    if ((fd = open("mmapdemo.c", O_RDONLY)) == -1) {//打開文件自身
        perror("open");
        exit(1);
    }

    if (stat("mmapdemo.c", &sbuf) == -1) {//文件大小,mmap的有效內存大小不超過該值
        perror("stat");
        exit(1);
    }

    offset = atoi(argv[1]);//文件偏移量
    if (offset < 0 || offset > sbuf.st_size - 1) {
        fprintf(stderr, "mmapdemo: offset must be in the range 0-%d\n",
                sbuf.st_size - 1);
        exit(1);
    }

    data = mmap((caddr_t)0, sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);

    if (data == (caddr_t)(-1)) {
        perror("mmap");
        exit(1);
    }

    printf("byte at offset %d is '%c'\n", offset, data[offset]);

    return 0;
}

用法三:進程間通信

//map_normalfile1.cpp
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

struct People{
    char name[4];
    int age;
};

const int file_struct_cnt = 5;
const int mem_struct_cnt = 10;

int main(int argc, char* argv[]) {
    int fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 00777);
    lseek(fd, sizeof(People) * file_struct_cnt - 1, SEEK_SET);//文件大小為8*5
    write(fd, "", 1);

    //內存大小為8*10
    People* pmap = (People*)mmap(NULL, sizeof(People) * mem_struct_cnt, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    //內存賦值
    for (int i = 0; i < 10; ++i) {
        char c = 'a' + i;
        memcpy((pmap + i)->name, &c, 1);
        (pmap + i)->age = 20 + i;
    }

    printf("initialize over.\n");
    sleep(10);//等待map_normalfile2讀取argv[1]
    if (munmap(pmap, sizeof(People) * 10) != 0) {
        printf("munmap error[%s]\n", strerror(errno));
        return -1;
    }

    return 0;
}
//map_normalfile2.cpp
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>

struct People{
    char name[4];
    int age;
};

const int mem_struct_cnt = 10;

int main(int argc, char* argv[]) {
    int fd = open(argv[1], O_CREAT | O_RDONLY, 00777);
    People* pmap = (People*)mmap(NULL, sizeof(People) * mem_struct_cnt, PROT_READ, MAP_SHARED, fd, 0);

    for (int i = 0; i < mem_struct_cnt; ++i) {
        printf("name:%s age:%d\n", (pmap + i)->name, (pmap + i)->age);
    }
    if (munmap(pmap, sizeof(People) * 10) != 0) {
        printf("munmap error[%s]\n", strerror(errno));
        return -1;
    }

    return 0;
}

其中: 兩個文件都定義了struct People,映射相同的文件,分別編譯為map_normalfile1 map_normalfile2。

  1. map_normalfile1寫數據到文件,首先把文件長度調整為5個struct的大小,mmap映射到內存后,寫入10個struct大小的數據,sleep 10秒,munmap程序退出。
  2. map_normalfile2讀文件,通過mmap映射到內存,然后讀取內存數據。

結論:

1、映射內存的實際長度不局限於文件大小,應該是虛存頁大小的整數倍,超出部分填充’\0’。

2、對內存超出文件大小部分的修改不會對文件產生影響,我們執行stat mmapdata也可以驗證這點(每個struct大小為8bytes,一共寫了40bytes)。

 

用法四:進程間共享內存

我們知道信號量sem是支持跨進程的,這里寫了一個簡化的例子(同時出於篇幅的原因,省略了include)

進程間共享信號量,其中一個進程負責初始化/銷毀信號量,並隨機sleep一段時間后 sem_post該信號量,另一個進程sem_wait該信號量。代碼如下:

const int mem_size = sizeof(sem_t);

int main() {
    int fd = open("mmap.data", O_CREAT | O_RDWR | O_TRUNC, 0777);
    lseek(fd, mem_size, SEEK_SET);
    write(fd, "", 1);

    sem_t *psem = (sem_t*)mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    int res = sem_init(psem, 1, 0);
    if (res != 0) {
        printf("sem_init error. %d %s\n", errno, strerror(errno));
        return -1;
    }

    while (true) {
        sleep(rand() % 10);
        printf("before post. ts:%ld\n", time(NULL));
        sem_post(psem);
    }

    res = sem_destroy(psem);
    assert(res != 0);

    munmap(psem, mem_size);
    printf("unmap.\n");

    return 0;
}
const int mem_size = sizeof(bin_sem);

int main() {
    int fd = open("mmap.data", O_CREAT | O_RDWR, 0777);
    sem_t *psem = (sem_t*)mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    while (true) {
        printf("before wait. ts:%ld\n", time(NULL));
        sem_wait(psem);
    }

    sem_destroy(psem);
    munmap(psem, mem_size);

    return 0;
}

 


免責聲明!

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



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