你有想过吗计算机如何申请一片连续内存?

今天在群里,报名了一个学习小队,一起组团学习数据结构。

这边突然想到在Java里面,ArrayList的底层是数组,数组是一块连续的内存空间。那么Java是如何申请到的呢?

头部分析我觉得应该和IO一样,不同的操作系统,JVM不同,那么就应该关注下Linux Win是如何申请连续内存的?

找到了一个知乎大佬的讲解,这边做下笔记。

计算机中有个重要的组件:MMU(Memory Management Unit) 内存管理组件。主要用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权、多任务多进程操作系统。

那么这边思考下,一个16G的内存插入主板的之后,MMU是如何进行内存管理的呢?

这里说的物理地址是内存中的内存单元实际地址,物理地址就是内存中每个内存单元的编号,这个编号是顺序排好的,物理地址的大小决定了内存中有多少个内存单元,物理地址的大小由地址总线的位宽决定!例如地址的总线有16根,那最大的物理地址就是16位的二进制数 对应的就是65535。同理可以继续往下推测。

而虚拟内存则是CPU进入保护模式之后,产生的一种东西。程序都是运行在虚拟内存中的。在远古时期,计算机的程序是可以直接运行在物理内存中的。但是后面慢慢发现会有很多问题。例如进程之间空间不隔离,恶意程序可以破坏别的程序的物理内存;同时这种使用效率很低,例如已经开启了两个程序,这时候开启了一个安装程序,内存不足了。那么就必须将之前两个转存到硬盘中。但同时安装也是一个持久化的过程。会导致效率很低下。

程序运行的地址不确定。当内存中的剩余空间可以满足程序C的要求后,操作系统会在剩余空间中随机分配一段连续的20M大小的空间给程序C使用,因为是随机分配的,所以程序运行的地址是不确定的。但是我们的某些硬件是需要在固定的地址上去开始运行的,但是如果这个地址后边被我们的程序占有,那么我们对这块内存的修改,就可能导致某些硬件不可用了。

由于这些情况的发生,就像Java在开发中发现使用本地缓存可能带来的OOM,不可控等情况,使用中间层思想进行隔离。增加一个中间层,利用一种间接的地址访问方法访问物理内存。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有重叠,就可以达到内存地址空间隔离的效果。
 
有了虚拟内存的概念,也是慢慢进步的。
第一次主要是为了用于解决内存不足的问题
引入了
  1. 动态装入(将程序最常用的部分驻留在内存中,而将一些不太常用的数据存放在磁盘里
  2. 覆盖装入(是把挖掘内存潜力的任务交给了程序员,程序在编写时必须手工分割成若干块,然后编写一个小的辅助代码来管理这些模块何时应该驻留内存,何时应该被替换掉。这个小的辅助代码就是所谓的覆盖管理器(Overlay Manager)
  3. 页映射(页映射是将内存和所有磁盘中的数据和指令按照“页”为单位划分成若干个页,以后所有的装载和操作的单位就是页。利用换入换出机制(如FIFO,LUR等)即可完成 )这边其实其实思考下页映射的思想,是不是也有一点中国 分治的味道。有省市级地州县等概念。
 

第二次的进化 主要是为了安全与共享控制。这玩意也是有意思,满足了需求,然后开发者水平也上来了,就有了安全问题.....

  1. 解决编译问题: 前面提到过在编译时地址覆盖的问题,可以通过分段来解决(后面会说什么是分段),从而简化编译程序。
  2. 重新编译: 因为不同类型的数据在不同的段中,但其中一个段进行修改后,就不需要所有的段都重新进行编译。
  3. 内存共享: 对内存分段,可以很容易把其中的代码段或数据段共享给其他程序,分页中因为数据代码混合在一个页面中,所以不便于共享。
  4. 安全性: 将内存分为不同的段之后,因为不同段的内容类型不同,所以他们能进行的操作也不同,比如代码段的内容被加载后就不应该允许写的操作,因为这样会改变程序的行为。而在分页系统中,因为一个页不是一个逻辑实体,代码和数据可能混合在一起,无法进行安全上的控制。
  5. 动态链接: 动态链接是指在作业运行之前,并不把几个目标程序段链接起来。要运行时,先将主程序所对应的目标程序装入内存并启动运行,当运行过程中又需要调用某段时,才将该段(目标程序)调入内存并进行链接。可见,动态链接也要求以段作为管理的单位。
  6. 保持兼容性

那么分页和分段又有什么区别呢?

(1) 页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率。段则是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好地满足用户的需要。

(2) 页的大小固定且由系统决定;而段的长度却不固定,决定于用户所编写的程序。

(3) 分页的地址空间是一维的,程序员只需利用一个记忆符,即可表示一个地址;而分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。

分页存储管理是将一个进程的逻辑地址空间分成若干个大小相等的片,称为页面或页,并为各页加以编号,从0开始,如第0页、第1页等。相应地,也把内存空间分成与页面相同大小的若干个存储块,称为(物理)块或页框(frame),也同样为它们加以编号,如0#块、1#块等等。在为进程分配内存时,以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中。由于进程的最后一页经常装不满一块而形成了不可利用的碎片,称之为“页内碎片”。

 分段

在分段存储管理方式中,作业的地址空间被划分为若干个段,每个段定义了一组逻辑信息。例如,有主程序段MAIN、子程序段X、数据段D及栈段S等。每个段都有自己的名字。为了实现简单起见,通常可用一个段号来代替段名,每个段都从0开始编址,并采用一段连续的地址空间。段的长度由相应的逻辑信息组的长度决定,因而各段长度不等。整个作业的地址空间由于是分成多个段,因而是二维的,亦即,其逻辑地址由段号(段名)和段内地址所组成。个人认为,是分段更像是颗粒度再细小一点的页。使用连续的地址空间,就不是小部分页了吗。但是如果段很大,例如是本来的两页,那就是大页。

然后就是第三次进化:段页式管理与虚拟内存

分段内存管理的优势在于内存共享和安全控制,而分页内存管理的优势在于提高内利用率。他们之间并不是相互对立的竞争关系,而是可以相互补充的。也就是可以把2种方式结合起来,也就是目前计算机中最普遍采用的段页式内存管理。段页式管理的核心就是对内存进行分段,对每个段进行分页。这样在拥有了分段的优势的同时,可以更加合理的使用内存的物理页。

OK那么拉回MMU的视野,MMU的作用其实也就是上面说的那些。

MMU:程序、数据、堆栈的总大小可以超过内存空间的大小,操作系统将当前运行的部分保存在内存中,未使用的部分保存在磁盘中。比如一个16MB的程序和一个内存只有4MB的机器,操作系统通过选择可以决定哪部分4MB的程序内容保存在内存中,并在需要时,在内存与磁盘中交换程序代码,这样16MB的代码就可以运行在4MB的机器中了。注意:这里面包含了虚拟地址和物理地址的概念

虚拟内存的哪个页面映射到物理内存的哪个页帧是通过页表(Page Table)来描述的,页表保存在物理内存中MMU会查找页表来确定一个VA应该映射到什么PA

操作系统和MMU是这样配合的:

  1. 操作系统在初始化或分配、释放内存时会执行一些指令在物理内存中填写页表,然后用指令设置MMU,告诉MMU页表在物理内存中的什么位置。

  2. 设置好之后,CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换操作,地址转换操作由硬件自动完成,不需要用指令控制MMU去做。

重要:我们在程序中使用的变量和函数都有各自的地址,在程序被编译后,这些地址就成了指令中的地址,指令中的地址就成了CPU执行单元发出的内存地址,所以在启用MMU的情况下, 程序中使用的地址均是虚拟内存地址,都会引发MMU进行查表和地址转换操作。

要么无论是Window还是Linux只需要能够发指令就可以了。

在Linux中申请用户态的内存:malloc(),申请内核的会用到kmalloc()、kzalloc()、vmalloc() 等。

至于细致的指令区分,这边不去了解了。毕竟在我的观念里面操作系统也只是一个中间层的概念。

详细的可以通过Linux内存操作总结进行了解。

原文地址:https://www.cnblogs.com/SmartCat994/p/14046760.html