内存那些事

虚拟内存、物理内存

我们知道操作系统中内存可以分为虚拟内存、物理内存和共享内存,以Linux系统为例,使用

free -m 命令查看当前系统内存使用情况:

[root@izbp1adsay9ekfu7vgv7zpz ~]# free -m total used free shared buff/cache availableMem: 1839 65 1222 0 550 1616Swap: 0 0 0

上图中的total表示系统总共物理内存大小,used、free分别表示已使用、未使用的物理内存,buff表示写缓存,cache表示读缓存。写缓存就是将将要写的数据写到页缓存而不是直接写到磁盘,这样做的好处是防止进程对页缓存中的数据再次修改。读缓存表示内核在读文件时,首先在已有的页缓存中查找所读取的数据是否已经存在。如果该页缓存不存在,则一个新的页将被添加到告诉缓存中,然后用从磁盘中读取到的数据填充它。如果当前物理内存足够空闲,那么该页将长期保留在高速缓存中,使得其他进程再使用该页中的数据时不再访问磁盘。

而如果想查看每个进程的内存使用情况则可以通过top命令查看:

 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND1005 root 20 0 131004 13944 8580 S 0.3 0.7 10:11.53 AliYunDun 1 root 20 0 43268 3632 2488 S 0.0 0.2 0:04.83 systemd

其中,VIRTRESSHR分别表示进程使用的虚拟内存、物理内存和共享内存。我们知道物理内存大小是固定的,比如说某台笔记本物理内存是4g,但是可以运行大于4g的游戏软件,操作系统是如何做到的呢?答案是虚拟内存。虚拟内存就是对主存和I/O设备的抽象,也就是说它将物理内存存放不了的内容,存放到I/O设备上,如下图所示。同时,虚拟内存为每个进程提供了一个一致的地址空间,比如说32位计算机,每个进程的地址范围是[0,4G](0x00000000 ~ 0xbfffffff分配给用户态,而0xc0000000 ~ 0xffffffff分配给核心态)。此外,虚拟内存保护每个进程的地址空间不被其他进程破坏。

内存那些事

要实现虚拟内存和物理内存,以及它们之间的映射,需要通过物理地址、虚拟地址和分页、分段等机制实现,相关内容在后续给出。

共享内存

共享内存就是两个或更多进程访问同一块物理内存,当一个进程改变了这块地址中的内容的时候,其它进程都获取相应的变更信息。在Linux系统中通过mmap内存共享映射、XSI共享内存和POSIX共享内存三种内存共享方式。为了帮助大家理解,我详细介绍下mmap内存共享这种方式。

内存那些事

从图中可知,每个进程描述符(task_struct)有一个内存管理属性(mm,memory manage)指向mm_struct结构体,而mmap就是该结构体中一个属性,mmap通过指向一个vm_area_struct(区域结构)的链表来控制数据的起始地址,从而实现直接使用内存地址对文件进行访问。

分页、分段、段页结合

在介绍完虚拟内存、物理内存和共享内存大致情况之后,我们来看下虚拟内存和物理内存在操作系统层面是如何实现。首先看下物理地址和逻辑地址。

  • 物理地址和逻辑地址

当未引入逻辑地址之前,如果多个程序同时对同一个物理地址进行操作就会冲突,为了解决这种问题我们才引入逻辑地址。同时,由于程序最后调用的是物理地址,因此逻辑地址和物理地址之间就存在某种映射关系,这种映射通过一个硬件设备MMU(内存管理单元)实现,它包含一个基址寄存器,存储每个程序的开始地址。为了帮助大家理解,举个简单例子,假设程序1的基址寄存器是1000,偏移量是200,那么,它的物理地址就是1200。

内存那些事

  • 分页

通过逻辑地址我们解决了地址冲突问题,但是依然存在一个问题:对于进程来说,它是独占虚拟内存空间的,比如说32位计算机,它的地址范围是[0,4G],但是大于4G的程序却无法运行。为了解决这个问题,我们想出了将部分数据加载到内存中,而不是一次性加载全部数据,其中数据的最小单位是页(Page),这也是分页技术名字由来,它的理论基础是局部性原理:空间局部性和时间局部性。

分页技术强调每次加载部分数据到内存中,同时,我们将虚拟内存和物理内存按照页进行划分,通过页表是维护虚拟页和物理页的映射关系,需要注意的是每个进程维护一张页表。当页表中的虚拟页对应的物理页是空白时,操作系统会发生缺页中断。

内存那些事

分页具体流程。以CPU执行MOV (0x560) EAX为例,CPU内部会将逻辑地址进行拆分成页号和偏移量,然后将逻辑地址转换成物理地址。

内存那些事

  • 分段

我们知道分页对于用户来说是没什么逻辑意义的,分页是为了完成离散存储,所有的页面大小都一样。为了让用户更好的理解程序,我们又提出了分段概念,它将程序划分为若干段部分,也就是说分段大小是不固定的,而且每一个分段都有独立的功能,例如:代码段,数据段,栈,堆等。通过分段技术,我们把内存空间分成一个个可以自治的段,而且把内存从一维空间变成了一个二维空间。此时虚拟地址有段号和偏移量组成。

内存那些事

  • 段页结合

分段技术的段长可以根据需要动态改变,而且便于用户理解,但是物理内存分配比较麻烦,基于此,我们提出了段页结合,也就是说对于逻辑地址我们采用分段管理,而物理地址我们采用分页机制。它的流程大致如下:首先根据段表信息,将逻辑地址转换成另一个逻辑地址,在转换的过程中会判断偏移量是否超过指定长度,如果没有超过,则根据页表将逻辑地址转换成物理地址。

内存那些事

页面置换算法

当访问的页不在内存中时就会发生缺页中断,此时就需要进行页面置换。目前常见的置换算法有FIFO、LRU和Clock算法。

  • FIFO(先进先出)

先进先出算法思想很简单,当内存满了,优先置换出最先进入内存的页面。但是它存在一个问题:经常被访问的数据有可能被换入换出,下面我就举个简单的栗子。假设只有3个物理页面,逻辑页面的访问次序是:

7 0 1 2 0 3 0 4

内存那些事

  • LRU(最近最少使用)

LRU算法就是所有页用栈组成,当栈满了,且新增加元素没有命中,则将栈底元素淘汰,新增元素放到栈顶;当栈满了,且新增元素命中,则只需要将新增元素移动到栈顶位置即可。假设只有3个物理页面,逻辑页面的访问次序是: 7 0 1 2 0 3 0 4

内存那些事

  • Clock算法

Clock算法是LRU算法的近似实现,它为每个页加一个引用位,默认值为0,无论读还是写,都置为1,它把所有的页组成一个循环队列,选择淘汰页的时候,扫描引用位,如果是1则改成0(相当于再给该页面一次存活的机会),并扫描下一个;如果该引用位是0,则淘汰该页,换入新的页面。假设只有3个物理页面,逻辑页面的访问次序是:

3 4 2 6

内存那些事


分享到:


相關文章: