Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

程序内存地址==>虚拟内存==>物理内存 #24

Open
6 of 11 tasks
Durant35 opened this issue Oct 21, 2017 · 15 comments
Open
6 of 11 tasks

程序内存地址==>虚拟内存==>物理内存 #24

Durant35 opened this issue Oct 21, 2017 · 15 comments

Comments

@Durant35
Copy link
Owner

Durant35 commented Oct 21, 2017

@Durant35
Copy link
Owner Author

Durant35 commented Oct 22, 2017

深入理解Linux虚拟内存管理的MMU机制原理

  • 现代操作系统普遍采用虚拟内存管理机制,这需要MMU的支持。
  • MMU通常是CPU的一部分,如果处理器没有MMU,或者有MMU但没有启用CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(物理内存)接收,这称为物理地址(Physical Address,PA)

    image

  • 如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址

    image

处理器内核(软核)是否带有 MMU 单元

https://blog.csdn.net/qq405180763/article/details/7777178 ???

MMU 通常是 CPU 的一部分,本身有少量的存储空间用来存放从虚拟地址到物理地址的查找表;所有数据请求都送往 MMU,由 MMU 决定数据是在 RAM 内还是在大容量存储设备内;也能将应用程序的执行对应到不同虚拟内存的空间中,让操作系统提供内存保护。

MMU 的功能

  • 在 Linux 的运行环境中,MMU 是必不可少的重要单元。如果没有 MMU 对 memory 进行管理,那么操作系统面对的是单一的物理地址,如果地址线是32位,那么操作系统能够管理的只有 4GB 空间。正是引入了 MMU:
    • 操作系统可使用虚拟地址来进行映射
    • MMU 的地址翻译和地址保护
  • Linux 操作系统在运行过程中,会涉及虚拟地址到物理地址的转换,这就是 MMU 功能模块完成的任务

MMU 工作原理

  • 如果软件需要控制 MMU,需要通过系统内部的协处理器对某些寄存器进行读操作,来获取 MMU 的状态;通过向某些寄存器发起写操作来控制 MMU 的操作。
  • MMU 单元可以提供全面的内存管理(执行多任务程序需要的地址转换和权限检查),因此多用户应用程序可以在这个 CPU 内核的支持下运行。
  • 在打开 MMU 功能模块后,它对数据池和指令池发出的地址称为虚拟地址:
    • 内存中找到该地址域对应的转换页,把虚拟地址的高位部分替换,形成可以访问内存、寄存器的物理地址;
    • 在这些转换页中,有对该部分地址域限定的权限信息。结合处理器的当前状态,这些转换页提供的权限信息即可让处理器判定此次的读写访问是否合法:
      • 如果合法,处理器会在存储空间中按照物理地址进行读写操作
      • 如果非法,处理器会发出数据访问或指令读取异常标志信号,这些标志信号将引导处理器进入异常中断向量中。

知乎:进程的虚拟地址和内核中的虚拟地址有什么关系?
image

@Durant35
Copy link
Owner Author

Durant35 commented Oct 22, 2017

硬件页表 vs 软件页表

知乎:进程的虚拟地址和内核中的虚拟地址有什么关系?

  • 现在 Linux 内核是4级页表结构,3级页表的时代是10年前了。
  • X86_64 架构下,无论 Intel 还是 AMD 的 CPU, 都是四级的硬件页表,所以软件层面的页表至少要4级(否则,进程访问的空间将受限, 因为有一级虚拟页表被固定住了,所以3级页表时代,X86_64 只能访问 512GB 空间, 而 X86_64 的设计可访问空间达到 131 072( = 2^47) GB。)
  • i386 只有三级硬件页表:PUD -> PMD -> PTE, 怎么嵌入四级软件页表结构? 答案就是虚设一层
    • 用户空间的虚拟地址,就是按前面所述的走四级页表来翻译。
    • 内核空间虚拟地址是所有进程共享的,重要的是,从效率角度看, 如果同样走四级页表翻译的流程,速度太慢。于是,内核在初始化时,就创建内核空间的映射(因为所有进程共享,有一份就够了),并且,采用线性映射(MMU关闭后,可以简单认为MMU不存在了,CPU要访问物理内存就把地址直接放在总线上去访问物理内存),而不是走页表翻译这种类似哈希表的方式。
    • 为什么用户空间不能也像内核空间这么做?用户地址空间是随进程创建才产生的,它的页面可能散布在不同的物理内存中,无法这么做。另外,走页表的过程,不止是翻译的过程,还是一个权限检查的过程,对于不可控的用户态地址,这安全性检查必不可省。而内核空间,只有一份,所有可以提前固定下来一片连续的物理地址空间,按线性方法来映射(按前面的线性方法, 1G 内核空间,只能映射 1G 物理地址空间,这对内核来说,太掣肘了; Linux 内核只对 1G 内核空间的前 896 MB 按前面所说的方法线性映射, 剩下的 128 MB 的内核空间(高端内存), 采用动态映射的方式,即按需映射的方式 ,这样,内核态的访问空间更多了)。

    动态映射不全是为了内核空间可以访问更多的物理内存,还有一个重要原因: 当内核需要连续多页面的空间时,如果内核空间全线性映射,那么,可能会出现内核空间碎片化而满足不了这么多连续页面分配的需求。基于此,内核空间也必须有一部分是非线性映射,从而在这碎片化物理地址空间上,用页表构造连续虚拟地址空间,这就是所谓vmalloc空间。

几级页表+MMU

Linux 内存管理

  • 以32位的系统来说,对于4GB的虚拟内存,系统要怎样来管理该内存呢?一般就是采用所谓的分页机制,就是把这么大的内存按照每一页的大小分成很多页,内存的管理也就以页作为单位,而不是以字节作为单位。

4GB 的地址,如果按照每一页4K大小计算的话,那么总共需要的页数为2^20,这个时候就需要一个页目录来存储这些页的信息,以方便查找,每一个页表项存储的就是对应页的内存起始地址,每一项的大小为4Byte,这样的话,页目录所需要的内存大小为2^20*4Byte,也就是4MB的大小。

  • 对于进程来说,一般不会使用这么大的内存空间,加上程序对内存的访问具有局部性,这样的话,就会出现很多的页表项不会被用到,也就是程序所需要的页数很少。如果一直将所有的页目录存在内存的话(4MB),或造成很到的内存浪费,此时就出现了多级页表了
    • 以二级页表来说,将总的页目录按照页的大小(4KB)划分,总共需要的二级页数为:4MB/4KB=1K,此时引入一级页表,用来存储二级页表的信息,那么每一个一级页表项的大小为4B,所需要的一级页表大小为4KB,恰好也是一个页的大小。
    • 进程在运行的时候,只需要先读取一级页表,接着再根据需要对二级页表以及内存页进行配置,可以大大减少页的索引信息了(因为大部分都是不会被索引的到的,只需要记录目前需要索引的页信息)。
  • 对于一个线性地址(虚拟地址),内存怎样把他映射为对应的物理地址呢?
    • 在二级页表下,一级页的大小为4KB,也就是对应着1K的二级页表,所以要索引二级页表,需要将虚拟地址的高10位用来作为一级页表的表内偏移索引
    • 通过一级页表的表内偏移索引找到二级页表后,二级页表也有1K的页数,所以需要虚拟地址的中10位作为二级页表的表内偏移索引
    • 得到对应的物理页地址的时候,由于每一页有4K大小,想要找到具体的字节地址,那么需要12位的索引,也就是32位地址剩下的低12位。这样就完成了一个虚拟地址到实际的物理地址的映射。
    • 对于一级页表,其起始地址要怎样存储呢?
      • 一般的话,由于起始地址是一个4B的指针,可以存储在寄存器上
      • 每次进程运行的时候,每一个进程都有自己的一级页表起始地址,当进程被加载运行的时候,操作系统为其分配的一级页表地址就直接存在CR3寄存器中,这样开始了进程的虚拟地址访问。
  • 完成虚拟地址到物理地址的转换一般是MMU(Memory Management Unit)硬件来实现的。为了实现跟快的转换,就有了TLB(TranslationLook-aside Buffer),用来根据程序访问内存的局部性机制来缓存已经转换过的虚拟页与实际页的对应关系!
    • TLB 中包含了最近使用过的页面的内存映射信息,处理器提供了专门的电路来并发地读取并比较TLB中的页面映射项。
    • 对于频繁使用的虚拟地址,它们很可能在TLB中有对应的映射项,因而处理器可以绝对快速地将虚拟地址转译成物理地址;反之,如果一个虚拟地址没有出现在TLB中,那么处理器必须采用以上介绍的两次查表过程(意味着要两次访问内存)才能完成地址转译。在这种情况下,这一次内存访问会慢一些,但是,经过这次访问以后,此虚拟页面与对应物理页面之间的映射关系将被记录到TLB中,所以,下次再访问此虚拟页面时,处理器就可以从TLB 中实现快速转译,除非此映射项已经被 TLB 移除了。
    • 研究表明,由于计算机程序的内存访问有一定的局部性,因此,即使处理器只维护一个相对较小的TLB,程序的运行也能获得较显著的性能提升。

Linux内核4级页表的演进

内存-用户空间
image

@Durant35

This comment was marked as resolved.

@Durant35

This comment was marked as resolved.

@Durant35

This comment was marked as resolved.

@Durant35

This comment was marked as resolved.

@Durant35

This comment was marked as resolved.

@Durant35

This comment was marked as resolved.

@Durant35

This comment was marked as resolved.

@Durant35
Copy link
Owner Author

页框管理

服务器体系与共享存储器架构

virt_to_page(addr) 产生线性地址对应的页描述符地址。
pfn_to_page(pfn)产生对应页框号的页描述符地址。

  • 由于CPU对内存的非一致性访问,系统的物理内存被划分为几个节点(每个节点的描述符 pg_data_t),每个节点的物理内存又可以分为3个管理区:ZONE_DMA(低于16M的页框地址),ZONE_NORMAL(16MB-896MB的页框地址)和ZONE_HIGHMEM(高于896MB的页框地址)。每个管理区又有自己的描述符,描述了该管理区空闲的页框,保留数目等。每个页描述符都有到内存节点和到节点管理区的连接(被放在flag的高位字段)。 内核调用一个内存分配函数时,必须指明请求页框所在的管理区,内核通常指明它愿意使用哪个管理区。

Linux 如何描述物理内存?

  • 存储节点(Node)
  • 管理区(Zone)
  • 页面(Page)
  • 保留的页框池
    • 如果有足够的空闲内存可用、请求就会被立刻满足。否则,必须回收一些内存,并且将发出请求的内核控制路径阻塞,直到有内存被释放。
    • 有些控制路径不能被阻塞,例如一些内核路径产生一些原子内存分配请求。尽管无法保证一个原子内存分配请求不失败,但是内核会减少这种概率。为了做到如此,内核采取的方案为原子内存分配请求保留一个页框池,只有在内存不足时才使用。
    • 页框池有ZONE_DMA和ZONE_NORMAL两个区贡献出一些页框。
    • 常用的请求页框和释放页框函数
// 获得连续的页框,返回页描述符地址,是其他类型内存分配的基础
alloc_pages(gfp_mask, order);
// 获得连续的页框,返回页框对应的线性地址
// 线性地址与物理地址是内核直接映射方式
// 不能用于大于896M的高端内存
__get_free_pages(gfp_mask, order);
__free_pages(page,order);
__free_pages(addr,order);

@Durant35

This comment was marked as resolved.

@Durant35

This comment was marked as resolved.

@Durant35

This comment was marked as resolved.

@Durant35
Copy link
Owner Author

Durant35 commented Nov 8, 2017

Repository owner deleted a comment from syl262 Apr 9, 2022
Repository owner deleted a comment from xiaoqixian Apr 9, 2022
@Durant35

This comment was marked as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant