mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
mmap优点共有一下几点:
1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。
同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。
mmap使用细节
1、使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。
2、内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。
3、映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。
Linux可以在用户态下,通过mmap函数将设备节点/dev/mem进行映射,实现在用户态下将物理地址映射到虚拟地址,并通过对虚拟地址的修改来实现寄存器的修改。
/dev/mem是Linux系统下的物理内存的全镜像,可通过该节点实现对物理内存的访问。
一般用于在嵌入式中以用户态形式直接访问寄存器/物理IO设备等。
通常用法是open这个设备节点文件,然后mmap进行内存映射,就可以使用map之后的地址访问物理内存。
linux下的mmap函数和munmap函数
mmap函数可以将一个文件或者其它对象映射进内存。
这里我们使用mmap函数将设备节点/dev/mem映射到内存中。
在操作结束后,需要使用munmap函数反映射。
函数头文件:
#include <sys/mman.h>
mmap函数原型:
void mmap(void start,size_t length,int prot,int flags,int fd,off_t offset);
参数说明如下:
start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。需要按照页面大小对齐,否则会出错。
length:映射区的长度。长度单位是以字节为单位,不足一内存页按一内存页处理
prot:期望的内存保护标志,类似于可读可写等
flags:指定映射对象的类型
fd:文件标识符
offset:被映射对象内容的起点,需要按照页面大小对齐,否则会出错。
返回值:成功执行时,mmap()返回被映射区的指针,失败时,mmap()返回MAP_FAILED[其值为(void *)-1],错误原因会被errno记录。
注:获取系统页面大小的方式:
#include <unistd.h>
long page_size = sysconf(_SC_PAGESIZE);
munmap函数
munmap函数是mmap函数的反过程,取消对某地址的映射。
函数原型:
int munmap(void* start,size_t length);
参数:
start:虚拟内存起始地址
length:映射长度
返回值:
成功返回0,失败返回-1。错误原因也会被errno记录。
实现对物理地址的读取和写入,方便在用户态调试寄存器的实例代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#define ARGV_ADDR_POS 2
#define ARGV_DATA_POS 3
#define ARGV_WR_OFF_POS 3
#define ARGV_WR_DATA_POS 4
#define ARGV_CMD_POS 1
//显示使用方法
void printUsage(char* argv[])
{
printf("Usage: \r\n Read address as Byte: %s r [ADDR] [LEN] \r\n Read address as int: %s i [ADDR] [LEN] \r\n", argv[0], argv[0]);
printf(" Write to address: %s w [ADDR] [OFF] [DATA]\r\n", argv[0]);
}
//读取某个基地址addr+byte字节的数据并逐个字节显示。
//从DataSheet上看,页大小为0x400,因此这里的基地址addr必须是能够被0x400整除,否则Segment Error.
void readMMap(int dev_fd,unsigned int addr,unsigned int byte)
{
int mmapByte = (byte/4*4+4),iloop;
int realByte = (byte/4*4);
//这里的mmapByte计算多此一举,因为这里只要进行映射都会按照页面大小向上取整,一般不用担心超出地址的问题
unsigned char *map_base = (unsigned char * )mmap(NULL, mmapByte, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, addr ); //使用MMAP直接映射基地址的内存数据到map_base
int i,j;
if(map_base == (unsigned char *)-1)
{
printf("MMAP Error.\r\n");
return;
}
printf(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
printf("------------------------------------------------------------\r\n");
iloop = realByte/16+((realByte%16)?1:0);
//printf("iloop is %d\r\n",iloop);
for(i = 0;i < iloop;i++)
{
int loopbyte = (byte-i*16 > 16)?16:(byte-i*16);
//printf("loopbyte = %d\n",loopbyte);ARGV_WR_OFF_POS
printf("0x%08X | ",addr+i*16);
for(j = 0;j < loopbyte;j++)
{
printf("%02X ",*(volatile unsigned char *)(map_base+i*16+j));
}
printf("\r\n");
}
munmap(map_base,mmapByte);
}
//读取某个基地址addr+byte字节的数据并按照逐个int显示。
//从DataSheet上看,页大小为0x400,因此这里的基地址addr必须是能够被0x400整除,否则Segment Error.
void readMMapByUINT(int dev_fd,unsigned int addr,unsigned int byte)
{
int mmapByte = (byte/4*4+4),iloop;
int realByte = (byte/4*4);
unsigned char *map_base = (unsigned char * )mmap(NULL, mmapByte, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, addr );
unsigned int *map_base_i = (unsigned int * )map_base;
int i,j;
if(map_base == (unsigned char *)-1)
{
printf("MMAP Error.\r\n");
return;
}
printf(" | +0x00 +0x04 +0x08 +0x0C \r\n");
printf("------------------------------------------------------------\r\n");
iloop = realByte/16+((realByte%16 > 0)?1:0);
for(i = 0;i < iloop;i++)
{
int loopcnt = (byte-i*4 > 4)?4:byte-i*4;
printf("0x%08X | ",addr+i*16);
for(j = 0;j < loopcnt;j++)
{
printf("0x%08X ",*(volatile unsigned int *)(map_base_i+i*4+j));
}
printf("\r\n");
}
munmap(map_base,mmapByte);
}
//将数据data写入基地址addr+offset的位置。
void writeMMap(int dev_fd,unsigned int addr,unsigned int offset,unsigned int data)
{
unsigned int mmapByte = offset + 0x04;
unsigned char *map_base = (unsigned char * )mmap(NULL, mmapByte, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, addr );
if(map_base == (unsigned char *)-1)
{
printf("MMAP Error.\r\n");
return;
}
printf("Before Modify : addr 0x%08X = 0x%08X\r\n",(addr+offset),*(volatile unsigned int *)(map_base+offset));
*(volatile unsigned int *)(map_base + offset) = data;
printf("After Modify : addr 0x%08X = 0x%08X\r\n",(addr+offset),*(volatile unsigned int *)(map_base+offset));
munmap(map_base,mmapByte);
}
int main(int argc,char* argv[])
{
int addr = 0,byte = 0,fd = 0,off = 0;
if(argc < 4)
{
printf("ARG ERROR.\r\n");
printUsage(argv);
return 0;
}
addr = strtoul(argv[ARGV_ADDR_POS],0,0);
byte = strtoul(argv[ARGV_DATA_POS],0,0);
if(addr == 0)
{
printf("Addr Err.\r\n");
return 0;
}
fd = open("/dev/mem", O_RDWR | O_NDELAY);
if (fd < 0)
{
printf("open(/dev/mem) failed.");
return 0;
}
switch(argv[ARGV_CMD_POS][0])
{
case 'r':
if(byte == 0)
{
printf("Byte len Err.\r\n");
close(fd);
return 0;
}
printf("Now Read Memory at 0x%08X by %d\r\n",addr,byte);
readMMap(fd,addr,byte);
break;
case 'i':
if(byte == 0)
{
printf("Byte len Err.\r\n");
close(fd);
return 0;
}
printf("Now Read Memory By int at 0x%08X by %d\r\n",addr,byte);
readMMapByUINT(fd,addr,byte);
break;
case 'w':
if(argc < 5)
{
printf("Write ARGC Error.");
close(fd);
return 0;
}
off = strtoul(argv[ARGV_WR_OFF_POS],0,0);
byte = strtoul(argv[ARGV_WR_DATA_POS],0,0);
printf("Now Write Memory By int at 0x%08X + 0x%08X by 0x%08X\r\n",addr,off,byte);
writeMMap(fd,addr,off,byte);
break;
default:
printf("Error Cmd.\r\n");
break;
}
close(fd);
return 0;
}
编译
gcc mmap1.c -o mmap_test
运行
./mmap_test
ARG ERROR.
Usage:
Read address as Byte: ./mmap_test r [ADDR] [LEN]
Read address as int: ./mmap_test i [ADDR] [LEN]
Write to address: ./mmap_test w [ADDR] [OFF] [DATA]
sudo ./a.out r 0x6a600000 32
Now Read Memory at 0x6A600000 by 32
| 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0x6A600000 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6A600010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
sudo ./a.out r 0x6a600000 32
Now Read Memory at 0x6A600000 by 32
| 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0x6A600000 | 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6A600010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
sudo ./a.out w 0x6a600000 1 3
Now Write Memory By int at 0x6A600000 + 0x00000001 by 0x00000003
Before Modify : addr 0x6A600001 = 0x00000002
After Modify : addr 0x6A600001 = 0x00000003
sudo ./a.out r 0x6a600000 32
Now Read Memory at 0x6A600000 by 32
| 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0x6A600000 | 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6A600010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00