关于Linux 虚拟内存和物理内存的理解
首先,让我们看下虚拟内存:
第一层理解
1. 每个进程都有自己独立的4G内存空间,各个进程的内存空间具有类似的结构
2. 一个新进程建立的时候,将会建立起自己的内存空间,此进程的数据,代码等从磁盘拷贝到自己的进程空间,哪些数据在哪里,都由进程控制表中的task_struct记录,task_struct中记录中一条链表,记录中内存空间的分配情况,哪些地址有数据,哪些地址无数据,哪些可读,哪些可写,都可以通过这个链表记录
3. 每个进程已经分配的内存空间,都与对应的磁盘空间映射
问题:
计算机明明没有那么多内存(n个进程的话就需要n*4G)内存
建立一个进程,就要把磁盘上的程序文件拷贝到进程对应的内存中去,对于一个程序对应的多个进程这种情况,浪费内存!
第二层理解
1. 每个进程的4G内存空间只是虚拟内存空间,每次访问内存空间的某个地址,都需要把地址翻译为实际物理内存地址
2. 所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。
3. 进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,需要用页表来记录
4.页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)
5. 当进程访问某个虚拟地址,去看页表,如果发现对应的数据不在物理内存中,则缺页异常
6.缺页异常的处理过程,就是把进程需要的数据从磁盘上拷贝到物理内存中,如果内存已经满了,没有空地方了,那就找一个页覆盖,当然如果被覆盖的页曾经被修改过,需要将此页写回磁盘
总结:
优点:
1.既然每个进程的内存空间都是一致而且固定的,所以链接器在链接可执行文件时,可以设定内存地址,而不用去管这些数据最终实际的内存地址,这是有独立内存空间的好处
2.当不同的进程使用同样的代码时,比如库文件中的代码,物理内存中可以只存储一份这样的代码,不同的进程只需要把自己的虚拟内存映射过去就可以了,节省内存
3.在程序需要分配连续的内存空间的时候,只需要在虚拟内存空间分配连续空间,而不需要实际物理内存的连续空间,可以利用碎片。
另外,事实上,在每个进程创建加载时,内核只是为进程“创建”了虚拟内存的布局,具体就是初始化进程控制表中内存相关的链表,实际上并不立即就把虚拟内存对应位置的程序数据和代码(比如.text .data段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射),等到运行到对应的程序时,才会通过缺页异常,来拷贝数据。还有进程运行过程中,要动态分配内存,比如malloc时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。
补充理解:
虚拟存储器涉及三个概念: 虚拟存储空间,磁盘空间,内存空间
可以认为虚拟空间都被映射到了磁盘空间中,(事实上也是按需要映射到磁盘空间上,通过mmap),并且由页表记录映射位置,当访问到某个地址的时候,通过页表中的有效位,可以得知此数据是否在内存中,如果不是,则通过缺页异常,将磁盘对应的数据拷贝到内存中,如果没有空闲内存,则选择牺牲页面,替换其他页面。
mmap是用来建立从虚拟空间到磁盘空间的映射的,可以将一个虚拟空间地址映射到一个磁盘文件上,当不设置这个地址时,则由系统自动设置,函数返回对应的内存地址(虚拟地址),当访问这个地址的时候,就需要把磁盘上的内容拷贝到内存了,然后就可以读或者写,最后通过manmap可以将内存上的数据换回到磁盘,也就是解除虚拟空间和内存空间的映射,这也是一种读写磁盘文件的方法,也是一种进程共享数据的方法 共享内存
虚拟地址
Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。虚拟地址空间的内部又被分为内核空间和用户空间两部分。不同字长(也就是单个 CPU 指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。
其中,所有进程的内核空间,关联的都是相同的物理内存。进程切换到内核态后,才可以访问内核空间内存。我们下面说到的分段只针对用户空间。
有两种方式管理虚拟地址与物理地址之间的关系。
1、段式管理(Segment):由段选择子和段内偏移量找到物理地址。
用户空间从低地址到高地址分别是五种不同的内存段。
代码段(只读段),包括代码和常量等。
数据段,包括全局变量等。
堆,包括动态分配的内存,从低地址开始向上增长。
文件映射段,包括动态库、共享内存等,从高地址开始向下增长。(本图没有画出)
栈,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。
分段容易出现碎片,内存交换效率低(不容易换出到磁盘)的问题。为了解决这两个问题,就出现了内存分页。
2、页式管理(Paging):虚拟地址分为两部分,页号和页内偏移。
MMU 规定了一个内存映射的最小单位,也就是页,通常是 4 KB 大小。为了解决页表项过多的问题,有多级页表和大页两种方式。
并不是给进程的所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存。这叫程序的局部性原理。根据此原理,为了提高访问速度,MMU(Memory Manage Unit)里配有一个硬件:TLB(Translation Lookaside Buffer)。用于缓存进程常用页表。
内存分段和内存分页并不是对立的,他们组合起来使用,通常称为段页式内存管理。
程序所使用的地址,称为逻辑地址;
通过段式内存管理映射的地址,称为虚拟地址(线性地址);
通过页式内存管理将线性地址映射成物理地址。
嵌入式 Linux驱动程序设计之物理地址到虚拟地址映射
裸机中的操作:
在单片机和裸机中,对于硬件是这样来操作的
unsigned int *p = 0x123456789;
*p = 0x987654321;
注意:这样的操作在Linux上是不行的,在Linux上,如果想要操作硬件,需要先把物理地址转换成虚拟地址,因为Linux使能了MMU,所以我们在Linux上不能直接操作物理地址。
使能了MMU让虚拟地址成为了可能; 可以让系统更加的安全,因为有了MMU,我们上层应用看到的内存都是虚拟内存,我们的应用就不能直接访问硬件,所以,这样就保障了系统安全。
物理地址到虚拟地址的转换:MMU非常的复杂,我们完成物理地址到虚拟地址的转换方法是内核给我们提供了相关的函数:
ioremap 和 iounmap
ioremap:
把物理地址转换成虚拟地址
函数的第一个参数
phys_addr_t offset:映射物理地址的起始地址
函数的第二个参数
size_t size:要映射多大的空间
函数是有一个返回值,如果成功,那么返回虚拟地址的首地址,如果失败,就返回NULL
iounmap:
释放掉ioremap映射的地址
这里就一个参数
*addr:取消映射地址的虚拟地址的首地址
注意:物理地址只能被映射一次,多次映射的就会失败
————————————————
转载:Linux 虚拟内存和物理内存的理解 - panchanggui - 博客园
转载:https://blog.csdn.net/daocaokafei/article/details/121551201
转载:https://blog.csdn.net/weixin_45787652/article/details/114180597