本文搬運自:
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);
下面說一下內存映射的步驟:
- 用open系統調用打開文件, 並返回描述符fd.
- 用mmap建立內存映射, 並返回映射首地址指針start.
- 對映射(文件)進行各種操作, 顯示(printf), 修改(sprintf)
- 用munmap(void *start, size_t length)關閉內存映射.
- 用close系統調用關閉文件fd.
這里簡單總結一下其幾個用法,以及一些注意事項:
用法一:當要map的文件是/dev/mem的時候,就可以實現把物理地址映射到虛擬空間中【比如在SV驗證的時候在linux應用層訪問設備寄存器】
#define MAP_SIZE 4096 #define MAP_MASK (MAP_SIZE - 1)
int dev_mem_fd = -1; void* 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));
參數說明
- 參數fd為即將映射到內存空間的文件描述符,一般由open返回。同時fd也可以指定為-1,此時須指定flags參數中的MAP_ANON,表名進行的是匿名映射。
- len是映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始算起。
- prot參數指定共享內存的訪問權限,可指定為
PROT_NONE:映射區不可訪問
或者以下幾個值的或:
PROT_READ:可讀
PROT_WRITE:可寫
PROT_EXEC:可執行 - flags參數影響映射存儲區的多種屬性:
MAP_FIXED: 返回值必須等於addr。因為這不利於可移植性,所以不建議使用此標志。如果未指定此標志,而且addr非0,則內核只把addr視為在何處設置映射區的一種建議,但是不保證會使用所要求的地址。將addr指定為0可獲得最大可移植性。
MAP_SHARED: 這一標志說明了本進程對映射區所進行的存儲操作的配置。此標志指定存儲操作修改映射文件,也就是說,存儲操作相當於對該文件的write。
MAP_PRIVATE: 本標志說明,對映射區的存儲操作導致創建該映射文件的一個私有副本。所有后來對該映射區的引用都是引用該副本,而不是原始文件。(此標志的一種用途是用於調試程序,它將一程序文件的正文部分映射至一存儲區,但允許用戶修改其中的指令。任何修改只影響程序文件的副本,而不影響原文件)。 - offset參數一般設置為0,表示從文件頭開始映射。
- 參數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。
- map_normalfile1寫數據到文件,首先把文件長度調整為5個struct的大小,mmap映射到內存后,寫入10個struct大小的數據,sleep 10秒,munmap程序退出。
- 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; }