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);
下面说一下内存映射的步骤:
- 用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; }