首页 > 编程知识 正文

内存映射技术,目的地址映射

时间:2023-05-03 07:57:15 阅读:22654 作者:781

一页缓存? 1.1什么是页面缓存? 为什么需要页面缓存? 1.1.1什么是页面缓存? 页面缓存是人们常说的page cache。 这是Linux操作系统实现的磁盘缓存,它通过将磁盘数据缓存到物理内存中,将对磁盘的访问转换为对物理内存的访问。 页面缓存减少了从核心到磁盘的I/O,从而提高了I/O性能。

1.1.2为什么需要页面缓存? 1.1.2.1内存与磁盘的速度差异内存的读取速度远远快于磁盘的读取速度,因此从内存访问数据要快于从磁盘读取数据

1.1.2.2本地原理当前被访问的数据很可能在短时间内继续被访问,或者在一个存储位置被访问之后,他的相邻存储位置很可能被访问。 所以它很适合缓存,因为它可能经常访问

1.2如何根据文件描述符找到找到文件的inode信息? 访问打开用户进程的程序时,必须获取文件的第一个物理位置。 如何找到这个物理位置呢?

我们知道,当操作系统读取磁盘上的数据时,逐扇区读取效率不高,而且一次读取多个连续扇区。 由多个扇区组成的称为块,是文件读写的最小单位。 一般来说,一个块的大小为4K,即8个扇区。 数据存储在块中,但与文件相关的元数据信息存储在哪里? 例如,这些数据(包括文件名、文件大小、创建时间、所属用户和用户组以及数据块的位置信息)存储在I节点上,而I节点存储在I节点表中。 因此,内核可以基于I节点找到对应的数据块。

第一,打开文件并返回文件描述符

第二,根据文件描述符在文件打开表中找到inode编号

第三,基于I节点在系统I节点表中找到文件I节点信息

第四,基于I节点信息找到物理位置

1.3什么是address _ space? 缓存在页面缓存中的页面可能包含多个不连续的物理磁盘块,如单个页面8K大小。 然而,物理块4K,一页可能包含两个物理块,但同一页上的物理块不一定是顺序的。 那么,如何知道要读取的文件的内容是否在页面缓存中? Linux通过address_space管理索引节点和页面缓存。

一个文件只有一个address_space。 这意味着inode和address_space是一一对应的,通过inode可以找到address_space。 而且,每个address_space都可以关联多个缓存页面,因此inode可能基于address_space,如果找不到,则必须从磁盘加载到页面缓存中。

1.4页面缓存工作流第一:进程发起文件读取请求

第二,内核从进程打开的文件信息中找到对应的文件I节点信息

第三,根据要读取文件的内容的偏移量计算要读取的页

第四,根据此文件的inode在address_space中查找是否存在此页面

第五,缓存命中时,直接返回文件内容; 如果页面缓存未命中,即出现缺页中断,内核将请求调整页面。 在页面协调过程中,首先找到要从交换缓存区域访问的页面。 如果有负载内存,则物理内存可能不足,并且可能已更换为磁盘。 如果没有,请创建新页面,读取磁盘上该页面的内容,并将其输入到在页面缓存中创建的新页面中

1.5页面缓存与内存的区别之一是,页面缓存是内存的一部分,因为页面缓存占用物理内存

第二,页面缓存中的所有进程共享与分配给进程的存储器区域不同,分配给进程的存储器区域一般是进程专用的

第三,用户程序读取文件时,通常声明缓冲区,为内存分配空间,并调用系统调用。 进程首先根据文件描述符和偏移从页面缓存中检查是否缓存命中,如果是缓存命中,则将其数据复制到分配的内存中。 如果未命中,则可以从交换分区中进行搜索;如果没有交换分区,则只能读取磁盘数据。

由于第四个:内存限制,Linux可以使用交换分区根据一定的算法将物理存储器页面交换到磁盘。 页面缓存也是物理页,因此页面缓存的一部分可能位于内存中,一部分可能位于交换分区中。

2传统的I/O读/写过程

2.1传统I/O读取过程第一:用户程序发起文件读取请求,调用读库函数

第二,CPU切换到内核状态,调用read或pread系统调用

第三,根据文件描述符从进程打开的文件表中检索inode索引,然后根据inode索引从系统的inode表中检索inode

根据第四:inode找到其对应的address_space,从address_space获取与该文件相关的页面缓存

第五,如果缓存命中或未命中但在交换分区中找到,则将数据复制到内存(读缓冲区)的用户区域

第六,如果未命中页面缓存,内核必须进行磁盘读取。 读取完成后,必须更新信息(如address_space ),然后分配物理页,将数据填充到物理页中,最后将数据复制到用户空间(读取缓冲区)

2.2传统的I/O写入过程首先,用户程序调用write库函数

第二,CPU将用户切换到内核状态,并调用write或pwrite系统调用

第三,根据打开文件的文件描述符在文件打开表中找到inode索引,并根据inode索引找到inode

第四,基于I节点获得对应的address_space对象,并从地址

_space中查询这个文件对应的页缓存
第五:找到要写入的页,即缓存命中,然后写入数据就返回
第六:如果缓存没有命中,找不到页,则创建一个物理页,然后写入数据
第七:如果用户进程调用了刷盘操作,即fsync、fdatasync、sync则会将数据刷入磁盘;如果没有调用刷盘操作,操作系统后台有单独的刷盘线程定期自动刷盘

三 文件内存映射(mmap) 3.1 什么是文件内存映射? 它存在的意义是什么? 3.1.1 什么是文件内存映射?

文件内存映射: 将磁盘文件部分或者全部映射到进程的虚拟地址空间(逻辑地址空间)一段连续的内存当中。
注意:
#1 文件内存映射是对文件大小有限制,不能太大,对于超过限制的就不能进行全部映射,只能映射部分文件
#2 映射到的虚拟地主空间的一段连续内存,不代表在物理内存空间上也是连续的

3.1.2 文件内存映射的意义

第一:传统I/O的读和写必须涉及到2次拷贝,即一次CPU拷贝和一次DMA拷贝;但是mmap对于CPU拷贝却不是必须的,除非你需要通过用户缓冲区缓存读写数据
第二:对于读取一个设备的数据,写入另一个设备只需要3次映射,传统I/O读写需要四次。比如需要读取磁盘某文件全部数据写入网络,那么首先对磁盘文件建立映射,然后DMA拷贝数据到页缓存,然后将页缓存的数据通过CPU拷贝到socket缓冲区,最后通过socket缓冲区通过DMA拷贝到网卡,然后写出数据到网络。如图所示:

3.2 文件内存映射工作原理 3.2.1 启动映射过程,在虚拟地址空间创建虚拟映射区域

#1 用户进程调用mmap库函数,指定映射的起始位置和映射大小以及映射模式等等
#2 在当前进程的虚拟地址空间寻找一段映射大小连续内存区域
#3 为这个虚拟地址空间连续内存区域分配一个vm_area_struct结构,对这个结构初始化
#4 将这个vm_area_struct结构插入到进程虚拟地址空间区域链表中

3.2.2 实现文件物理地址和虚拟地址的映射

#1 根据返回的文件描述符,从系统文件打开表获取inode索引,根据inode索引获取inode信息
#2 调用系统内核mmap系统调用,通过inode找到对应文件信息,定位到文件磁盘物理地址
#3 通过remap_pfn_range函数,实现文件物理地址和虚拟地址的映射,说白了也就是了内核缓冲区(页缓存)和虚拟地址映射

3.2.3 访问映射的区域

#1 访问虚拟内存映射区域,读取文件内容
#2 在页缓存中读取文件内容,由于只是建立了映射,页缓存还没有数据,则出发缺页中断;
#3 请求调页过程首先从交换分区查找,交换分区也没有则调用nopage函数把所缺的页载入主存的页缓存
#4 内核此时会进行磁盘I/O 根据要读取数据的起始位置和偏移量找到对应的文件,然后通过DMA拷贝到主存的内核缓冲区(页缓存),并且会更新文件inode对应的address__page对象的页信息
#5 因为页缓存已经有数据了,所以用户进程就可以直接看到数据了;如果是写的流程那么也可以直接写入数据到页缓存了

3.2.3 数据刷盘

对于写操作,会更新页缓存,产生脏页,这些脏页可以通过调用msync函数刷盘,或者释放映射内存区域munmap函数。那如果既不退出映射,也没有调用刷盘,难道就一直就在缓冲区吗,肯定不是的,操作系统后台与一个flusher刷盘的线程每隔一段时间就会自动刷盘。

3.3 内存映射相关的函数mmap、munmap和msync比较 3.3.1 mmap 文件映射内存

void *mmap(void *addr, size_t length, int prot , int flags ,int fd, off_t offset);
addr: 开始映射的地址,属于进程的逻辑地址
length: 从开始映射地址,映射多长,一般是一个页大小,4KB
prot:期望的内存保护标志,不能与文件打开的标志冲突,比如文件只可读,这里的就不能可写
#1 PROT_EXEC 页内容可以被执行
#2 PROT_READ 页内容可以被读取
#3 PROT_WRITE 页可以被写入
#4 PROT_NONE 页不可访问
flags: 指定映射对象的类型,映射选项和映射页是否可以共享
MAX_FIXED: 如果参数start所指的地址无法成功建立映射时,则放弃映射
MAP_SHARED: 与其他映射这个文件的进程共享映射内存,可能存在并发修改
MAP_PRIVATE: 对映射区域的写入操作会产生一个映射文件的复制,类似于写时复制,对此区域作的任何修改都不会写回原来的文件内容。
fd: 文件描述符
offset: 文件映射的偏移量,已经映射了多少

3.3.2 munmap 解除指定部分的映射

int munmap(void *addr, size_t length);
解除从addr地址开始的length长的部分的映射,当然解除了映射,内存中的改变也会刷入磁盘

3.3.3 msync 将内存中的改变刷入磁盘

int msync(void *addr, size_t length, int flags);
我们知道munmap,解除映射后,可以将内存映射的修改刷入磁盘,但是如果还没有解除映射呢,也想把内存映射中修改的东西刷入磁盘,就可以使用msync了
而且操作系统也会自动在后台同步

3.4 文件内存映射和传统I/O比较

#1比传统I/O少了一次CPU拷贝,其余的其实也没什么可比较的
#2 文件映射内存对文件大小有限制,所以映射的大小不一定可以和文件大小一致。

和传统I/O相比,少了一步内核空间到用户空间的数据CPU拷贝。我们知道传统I/O的读必须要将数据从内核拷贝到用户空间的用户缓冲区;传统I/O的写必须先写入用户缓冲区在拷贝到内核缓冲区。但是基于mmap的读,可以从内核缓冲区读取数据,然后再拷贝到一个内核缓冲区,但这不是必须的,我们可以获取单个字节,也可以将内核空间的数据直接拷贝到其他的内核缓冲区;我们也可以将数据写入缓冲区,然后再把缓冲区数据写入内核缓冲区,但这也不是必须的,我们可以直接写入单个字节也可以从内核缓冲区直接拷贝数据到其他内核缓冲区。因为我们操作的虚拟内存映射区域是和内核缓冲区(Page Cache)映射的,我们读的数据直接可以从Page Cache读 ,写的数据也直接可以写入到Page Cache, 但是传统的I/O读写是做不到这一点的。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。