虚拟内存与mmap,brk

2024-03-02 22:20
文章标签 虚拟内存 mmap brk

本文主要是介绍虚拟内存与mmap,brk,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

虚拟内存与mmap,brk

  1. 基本概念及相关术语

1.1 基本概念

虚拟内存使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。即将不完整,不连续的物理内存映射为连续的虚拟内存。虚拟内存主要有以下三个作用:

(1) 它将主存看成是磁盘的一个高速缓存,只在主存中保存活动区域(通常一个进程只有执行上下文被加载到主存,其余的在磁盘中,随用随加载);

(2) 为每个进程提供一致的地址空间,简化了内存管理;

(3) 它保护每个进程的地址空间不被其他进程破坏(在页表的PTE条目中加入额外控制信息实现内存保护)。

虚拟内存有两个重要的地址,虚拟地址(virtual address, VA)和物理地址(physical address)。在访问某个对象时,CPU给出虚拟地址,通过查询计算得到物理地址,然后访问物理地址上的对象。整个过程如下图:

                                        图1  CPU访问主存

1.2 相关术语

在表述虚拟内存相关概念时,有些约定的缩写和表达方式

N=2n:虚拟地址数量,n表示虚拟地址位数;
M=2m:物理地址数量,m表示物理地址位数;
P=2p:页大小,p表示页偏移量的位数;
VPO(virtual page offset):虚拟地址页偏移;
VPN(virtual page number):虚拟地址页号;
PPO(physical page offset):物理地址页偏移;
PPN(physical page number):物理地址页号。
页表(Page Table, PT):记录虚拟地址到物理地址的映射的表
页表项(Page Table Entry, PTE):页表中一行,PTE的索引即VPN;
页表项地址(PTEA):在CPU中有个页表基址寄存器,记录页表起始地址,页表基址寄存器+PTE索引=PTEA;
MMU(Memory Management Unit):内存管理单元,用于虚拟地址到物理地址寻址的硬件。
一个页表的常见结构如下图:

                                  图2 页表常见结构(有效位表示该PTE是否有VP到PP的映射)

eg: 给定一个32位虚拟地址空间和一个24位物理地址空间,,对于下面的页大小,确定VPN,VPO,PPN,PPO的位数。

P VPN位数 VPO位数 PPN位数 PPO位数
1KB 22 10 14
10

4KB 20 12 12
12

注:VPO表示对象在页中的偏移,VPO=PPO,VPO位数=log2§,VPN表示虚拟页号,对应PTE表索引,PPN表示物理页号。

一个虚拟地址翻译成物理地址,方法如下图:

                        图3 虚拟地址翻译为物理地址

地址翻译时,给定虚拟地址,低p位表示页偏移,其中VPO=PPO,高n-p位表示虚拟页号,即PTE的索引号,找到对应PTE记录,得到物理页号PPN,跟PPO组合得到物理地址。所以访问一个对象,首先访问页表,从虚拟地址转化为物理地址,再从访问物理地址得到对象。由于页表和物理地址都在内存中,因此存在两次内存访问。

1.3 地址翻译加速

从1.2中得知为了访问对象,需要两次内存访问,每次内存访问一般几十到几百个周期,为了加快地址翻译,减少内存访问次数,有两种辅助设备:SRAM缓存和TLB缓存。

SRAM缓存:在CPU和主存(DRAM)之间,还有L1, L2, L3三级高速缓存(SRAM)。因此,可以将部分PTE条目和对象存到SRAM中,减少内存访问次数,添加了SRAM的访问机制如下图。

                                 图4 加入SRAM的对象访问过程

可见,在访问时,优先访问SRAM获取PTE和数据,没有再访问主存,还没有则引起缺页中断。SRAM的访问通常几个时间周期。

TLB缓存:在MMU中的虚拟地址缓存器,称为翻译后备寄存器(Translantion Lookaside Buffer)。每一行由一个或多个PTE条目组成,其中TLBI用于行号索引,TLBT用于同一行某个PTE的选择。

                                                      图5 虚拟地址在TLB中的含义

比如某一时刻,TLB中的快照如下:

                                                                     图6 TLB快照,四组,四路组相联 

页面大小64字节,虚拟地址长度14位,物理地址长度为12位。给定虚拟地址0x03d4,其二进制表示为0b 00 0011 1101 0100,低6位0b 01 0100为VPO,因为四路组相联,所以第6-7位为TLBI(TLB索引),为0b 11,剩余为TLBT(TLB标记),TLBI表示TLB表的行号,找到TLBT为0x03的位置,得到PPN为0D。结合VPO,得到物理地址为0b 0011 0101 0100,即0x0354。加入TLB之后的对象访问过程如下:

                                                图7 加入TLB的对象访问过程
  1. Linux虚拟内存

2.1 Linux虚拟内存组织机制

Linux系统为每个进程维护一个单独的地址空间,如图8(a)所示,同时为每个进程维护一个结构体,其中包含虚拟内存相关信息,如图8(b)所示。

(a) Linux进程的虚拟内存 (b)管理虚拟内存的结构体

                          图8 Linux虚拟内存

其中vm_prot描述虚拟内存页的读写权限,vm_flags记录该虚拟页是共享还是私有等其他常见信息。

2.2 内存映射

Linux系统将虚拟内存和一个磁盘对象关联起来,以初始化虚拟内存区域的内容,称为内存映射。有两种类型的内存映射:

(1) 映射到Linux文件系统中的普通文件;

(2) 映射到匿名文件,匿名文件是由内核创建的全是二进制0的文件,CPU第一次使用该虚拟页面时,内核就选择一个物理页面进行覆盖(整个过程没有跟磁盘发生数据交互)。

一个对象映射到虚拟内存中,要么以共享对象存在,要么以私有对象存在。不论哪一种模式,在物理内存中只有一份副本。共享对象一个进程的写操作,其他进程都可见,并且能反映到磁盘上;私有对象一个进程的写操作,其他进程不可见,并且不能反映到磁盘上。

        (a) 内存映射到共享区域                                                  (b) 内存映射到私有区域图9 多个进程映射同一对象

对于多个进程内存映射到私有区域时,物理内存只有一份副本,此时采用一种"写时复制"策略。即进程在写时,复制修改的部分到内存其他区域。这样对其他进程来说,对象没有修改过。

2.3 mmap函数

mmap函数提供用户级的内存映射,该函数能够把某个磁盘文件映射到内存中,函数的主要格式如下:

复制代码
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:内存起始地址,通常为NULL,让系统自己选择
length:内存的长度
prot:
PROT_READ:数据可读
PROT_WRITE:数据可写
PROT_EXEC:数据可执行
PROT_NONE:数据不可访问
flags:
MAP_SHARED:共享对象,进程间可察觉修改,并能反映到磁盘
MAP_PRIVATE:私有对象,一切操作只在本进程可见,修改不会写入磁盘
MAP_FIXED:基本不用
fd:映射的文件的描述符,通常应先打开文件,再调用mmap,此后关闭文件映射仍然存在
offset:文件偏移量,一般为0
该函数返回内存中对应的地址
复制代码
调用mmap之后,内存与磁盘文件之间就建立了映射关系,如下图所示:

munmap用于解除映射关系

int munmap(void* start,size_t length);
使用mmap的作用主要有以下两个:

(1) 将磁盘文件映射到内存中,这样所有读写均针对内存读写(可以使用memcpy等内存操作函数,而不是read,write等IO操作函数),加快访问速度;

(2) 在无亲缘关系的进程间提供共享内存。

使用mmap函数,需要注意以下问题:

(1) 在文件映射之前,必须打开该文件,而且mmap的prot权限不能超过打开的权限。比如open打开时只设置了读文件,那么prot就不能设置PROT_WRITE;

(2) 内存映射通常都是按虚拟内存的页为基本单位的。比如一个页512字节,但是映射的文件只有12字节。那么剩下的500字节会自动填充为零,即时修改了后面的500字节,也不会写入到文件(所以较好的操作是直到文件大小,直接加长文件);

(3) 如果试图访问不存在的映射关系,比如页面大小512字节,实际文件大小为12字节,用mmap映射的时候映射1000个字节,那么实际可操作的结果如下:

(4) 将内存写入磁盘的操作通常由页守护进程完成,如果想人为控制将内存数据写入磁盘,可以调用以下函数:

复制代码
#include <sys/mman.h>

int msync(void *addr,size_t len,int flags);

flags:
MS_ASYC:异步写入
MS_SYC:同步写入,等待写入之后才会返回
复制代码
(5) 进程终止或调用munmap时解除映射关系,关闭文件描述符不会解除映射关系。

下面举一个简单的例子:父子进程同时修改一个文件写入数据:

复制代码
1 #include <sys/mman.h>
2 #include <stdio.h>
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <sys/wait.h>
6 #include <sys/types.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)
11
12 int main()
13 {
14 int fd;
15 if((fd=open(“map.txt”,O_RDWR|O_CREAT|O_TRUNC,FILE_MODE))<0)
16 {
17 printf(“open file failed\n”);
18 exit(1);
19 }
20
21
22 if(ftruncate(fd,50)<0) //文件大小50字节
23 {
24 printf(“ftruncate error\n”);
25 exit(1);
26 }
27
28 char buf;//起始地址
29
30 buf=(char
)mmap(NULL,50,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
31 close(fd);
32 pid_t pid;
33 if((pid=fork())<0)
34 printf(“fork error\n”);
35
36 char* msg=“hello world\n”;
37 char* msg1= “good news”;
38 if(pid==0) //子进程
39 {
40 memcpy(buf,msg,strlen(msg));
41 exit(0);
42 }
43 else
44 {
45 int stat;
46 wait(&stat);
47 memcpy(buf+strlen(msg),msg1,strlen(msg1));
48 }
49 return 0;
50
51 }
复制代码
第22-26行就是申请文件大小为50字节,那么实际内存可修改的部分就是buf~(buf+49)。注释该段再执行就会报SIGBUS错误。

执行结果是当前目录多了map.txt,其内容为:

hello world

good news

2.4 Linux进程分配内存的方式

关于此部分详细介绍参考博文:https://www.cnblogs.com/vinozly/p/5489138.html

简单来说,当我们调用分配内存的函数时(如malloc),底层通过调用brk()或mmap()实现。当遇到小于128KB的内存时,调用brk()函数将数据段堆的_edata地址往高地址推(即图8a中brk指向的指针,此时只分配虚拟内存,没有物理内存。当产生缺页中断时,才调用物理内存)。当申请内存大于128KB时,调用mmap()在堆栈之间的共享区域分配内存(此部分内存可以单独释放)。

标签: 虚拟内存 , 计算机基础 , 操作系统 , mmap
好文要顶 关注我 收藏该文 微信分享
晨枫1
粉丝 - 2 关注 - 0
+加关注
00
升级成为会员
« 上一篇: QT下多线程调用TCP的问题及可能的解决方案
» 下一篇: epoll,select,poll的区别
posted @ 2020-05-18 12:17 晨枫1 阅读(1291) 评论(0) 编辑 收藏 举报

这篇关于虚拟内存与mmap,brk的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/767600

相关文章

内存管理篇-21 虚拟内存管理:线性映射区

1.线性映射区的定义         这部分讲线性映射区的内容。一般老的嵌入式平台,它内存很小只有几百兆,都会直接把整个物理内存映射到线性映射区了,只有当物理内存大于1GB以上,线性映射区无法cover的时候就把剩下的放到高端内存。所以这个区域是最简单的。         线性映射区一般是指内核空间的某个部分,直接映射到低端内存的区域。并且他们之间是线性映射的。         PAGE_O

解密虚拟内存0x400000以下的地方

一. 前言   最近看CSAPP时,对以前没有仔细注意的一处知识盲区产生了兴趣,所以进行了深入研究,并写下此文一记录。 二. 问题   二话不说直接上图。下图是CSAPP第七章的虚拟内存分析图。书中提到 在X86-64位Linux系统中,代码段总是从地址0x400000处开始,后面是数据段。堆在数据段之后,通过调用malloc向上增长…   但是0X400000以下呢?为什么没有

内存管理篇-20 Linux虚拟内存管理

1.虚拟地址的经典布局          这里的内容比较少。只要就是内核用户空间的划分。内核空间又有自己的划分。也需要注意一下每个区域的性能。理论上线性映射是最简单的,所以性能最高。同时,注意内核空间是可以配置的,并不是都3:1。       2.ARM32下的内存布局         经典内部布局的基本结构页很重要。user_space -> pdg->.text ... .bss

06.进程的虚拟内存管理.md

正好遇到 华庭(庄命强)的glibc内存管理Ptmalloc2 源代码分析 一文,非常开心。真是大佬。我只是借着这篇文章稍微整理一下,为了以后自己回顾的时候能够更好的排查问题。 文章目录 6.1 linux进程内存布局6.1.1 32 位模式下进程默认内存布局6.1.2 64位进程虚拟地址空间 6.1.3 linux各个内存区域的存放 6.2 操作系统内存分配的相关函数6.2.1 Heap

mmap实现原理

转载处    http://blog.csdn.net/joejames/article/details/37958017 觉得写得非常不错,转载一下以作学习 一直都对内存映射文件这个概念很模糊,不知道它和虚拟内存有什么区别,而且映射这个词也很让人迷茫,今天终于搞清楚了。。。下面,我先解释一下我对映射这个词的理解,再区分一下几个容易混淆的概念,之后,什么是内存映射就很明朗了。

xp系统怎么设置虚拟内存

参考: 1、​​​​​​xp系统怎么设置虚拟内存|xp在哪里设置虚拟内存-系统城 2、32位Windows7上8G内存使用感受+xp 32位下使用8G内存 (转) - 3D入魔 - 博客园

MIT6.S081最详解析与归纳——lab10:mmap

Lab10主题:mmap (一)前置知识:mmap(1)VMA(2)mmap (二)Lab:mmap(1)前置工作(2)实现sys_mmap()(3)实现pagefault(4)实现sys_munmap(5)脏页位设置(六)其它函数的小修改 (三)感言 (一)前置知识:mmap (1)VMA VMA(Virtual Memory Area) 代表虚拟内存区域,它描述了一个进程

【Linux】什么是虚拟内存?

虚拟内存介绍 Linux虚拟内存(1)什么是虚拟内存?(2)虚拟内存的工作原理(3)虚拟内存的优点(3)Linux中的虚拟内存管理工具总结 Linux虚拟内存 虚拟内存(Virtual Memory)是现代操作系统的重要组成部分。它使得操作系统能够通过虚拟化内存地址来管理物理内存,并提供了更高效的内存使用方式。在Linux系统中,虚拟内存的机制为运行中的程序提供了一个更大、

虚拟内存和linux(操作系统part1)

一个操作系统的虚拟内存和linux部分知识点的笔记整理,资料大多参考于:小林coding和Javaguide。 虚拟内存的作用 第一,虚拟内存可以使得进程运行内存超过物理内存大小,因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性,对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域。第二,由于每个进程都有自己的页表,所以每个进程

os 虚拟内存

虚拟内存不仅能隔离进程,还能提高内存利用率,请解释虚拟内存如何提高内存利用率??? 虚拟内存(Virtual Memory)通过以下几个机制来提高内存利用率: 1. 内存分页(Paging) 虚拟内存将物理内存划分为固定大小的块,称为页框(page frame),同时将进程的虚拟内存空间划分为同样大小的页(page)。虚拟内存系统通过页表(page table)将虚拟页映射到物理页框。由于每