1. 内存映射

在unix/linux平台下读写文件,一般有两种方式。第一种是首先open文件,接着使用read系统调用读取文件的全部或一部分。于是内核将文件的内容从磁盘上读取到内核页高速缓冲,再从内核高速缓冲读取到用户进程的地址空间。这么做需要在内核和用户空间之间做四次数据拷贝。而且当多个进程同时读取一个文件时,则每一个进程在自己的地址空间都有这个文件的副本,这样也造成了物理内存的浪费。如下图所示: 




mq RequestedHeartbeat 导致内存暴增 mmap内存占用_内存空间

第二种方式是使用内存映射的方式。首先open文件,接着调用mmap系统调用,将文件的内容的全部或一部分直接映射到进程的地址空间,映射完成后,进程可以像访问普通内存一样做memcpy等操作,不必再调用read/write等操作。mmap并不分配物理地址空间,它只是会占用进程的虚拟内存空间。而第一种方式则需要进程预先分配好物理内存,内核才能将页高速缓冲中的文件数据拷贝到用户进程指定的内存空间中。

使用第二种方式,当多个进程同时访问读取一个文件时,每个进程都将文件内容在内核中的页高速缓冲映射到自己的地址空间。当第一个进程访问内核中的页缓冲时,进程的机器指令会触发一个缺页中断。内核将文件的这一页数据读入到页高速缓冲,并更新进程的页表,使页表指向内核缓冲中的这个页。之后有其他进程再次访问同一页时,该页已经在内存中,内核只需要将进程的页表登记并指向内核中的页高速缓冲即可。如下图所示: 

mq RequestedHeartbeat 导致内存暴增 mmap内存占用_内存空间_02

 

 



 



 



 



在说mmap之前我们先说一下普通的读写文件的原理,进程调用read或是write后会陷入内核,因为这两个函数都是系统调用,进入系统调用后,内核开始读写文件,假设内核在读取文件,内核首先把文件读入自己的内核空间,读完之后进程在内核回归用户态,内核把读入内核内存的数据再copy进入进程的用户态内存空间。实际上我们同一份文件内容相当于读了两次,先读入内核空间,再从内核空间读入用户空间。



mmap是系统调用,mmap的作用是将进程的虚拟地址空间和文件在磁盘的位置做映射,做映射之后,读写文件虽然同样是调用read和write但是触发的机制已经不一样了



 

 

linux 内核为用户提供了一个/dev/mem的驱动程序,使用户直接访问系统物理内存成为可能,利用mmap和/dev/mem可以建立起直接读写系统物理内存的渠道。

/dev/mem是linux下的一个字符设备,源文件是kernel/drivers/char/mem.c,这个设备文件是专门用来读写物理地址用的。里面的内容是所有物理内存的地址以及内容信息。通常只有root用户对其有读写权限。源引网络资源对/dev/mem是这么评价的“/dev/mem是个好玩的东西,你竟然可以直接访问物理内存,这在linux下简直太神奇了,就想一个小偷想偷银行,可是发现银行戒备森严,正在小偷苦无对策的时候,突然发现银行有个后门,而且这个后门直通银行的金库。”

我们可以使用map之后的地址来访问物理内存

 

 

mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存,普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问

头文件 sys/mman.h 
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 作用: 

参数说明:
start:映射区的开始地址
length:映射区的长度
prot:期望的内存保护标志
—-PROT_EXEC //页内容可以被执行
—-PROT_READ //页内容可以被读取
—-PROT_WRITE //页可以被写入
—-PROT_NONE //页不可访问
flags:指定映射对象的类型
—-MAP_FIXED
—-MAP_SHARED 与其它所有映射这个对象的进程共享映射空间
—-MAP_PRIVATE 建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件
—-MAP_ANONYMOUS 匿名映射,映射区不与任何文件关联
fd:如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1
offset:被映射对象内容的起点

CPU要访问该PCIE设备空间,只需访问对应的内存空间.

检查内存地址,如果发现该内存空间地址是某个PCIe设备空间的映射,就会触发其产生TLP,去访问对应的PCIe设备,读取或者写入PCIe设备