操作系统-虚拟内存的一些思考

为什么 32 位最大寻址内存空间为 4GB 呢

32 位代表有32最小是32个0,最大是32个1 ,这样可以表示的空间数量,那这个数量有多少呢? 有2的32次方,假如我们以字节(Byte)作为单位,
也就是说 32位用来寻址到2的32次方个字节 , 也就是 4GB

已知页的大小为 4KB , 计算一下大概需要多大的页表 ,每一页的页表项为 4Byte

4GB 的空间,页大小为 4KB , 共分成了2的20次方个页 , 假如用一个页表项来表示一页,那么就会有2的20次方个页表项,一个页表项为 4Byte ,所以所有页表项的
大小就为 4 乘以 2的20次方 的Byte , 也就是 4MB , 假如我们的页表也是从磁盘中加载的话, 4MB 换算成页,共需要 1024 页 ,

这样的话 ,我们的页表项数量太多了,放在一张页表中 ,所以一般的操作系统系统都使用“多级页表” , 如下图 :

1297993-20211103193929504-616088899.png

于是出现了“根目录”和“分级目录” , 现在我们将 4MB 的大页表继续集成到小一点的页表,这个小一点的页表也是“根目录” ,根目录的大小为 4KB ,刚好是一个页的大小 , 所以当我们在缺页的时候 ,而该进程从来就没加载过 ,那么最多只需要加载两次, 一个根目录,一个分级目录,就可以找到对应的页框号

这里需要注意 , 我们上面讲到我们已经通过页表,页表项找到对应的页框号, 页表项中有一项是来表示“该页”是否在内存中的, 假如不在那么就需要通知磁盘去加载页到内存中去, 这个过程都是由 MMU 来完成的 。

过程如下 :

1297993-20211103221816971-1233187490.png

1297993-20211103221908532-503426218.png

页表在哪里

描述来自[知乎](https://www.zhihu.com/question/63375062),非原创

页表在哪里?一般来说,任何进程切换都会暗示着更换活动页表集。Linux内核为每一个进程维护一个task_struct结构体(即进程描述符PCB),task_struct->mm_struct结构体成员用来保存该进程的页表。在进程切换的过程中,内核把新的页表的地址写入CR3控制寄存器。CR3中含有页目录表的物理内存基地址,因此该寄存器也被称为页目录基地址寄存器PDBR(Page-Directory Base address Register)。

虚拟地址怎么转化为物理地址?

描述来自[知乎](https://www.zhihu.com/question/63375062),非原创

对于每次转换,MMU首先在TLB中检查现有的缓存。如果没有命中,根据CR3寄存器,Table Walk Unit将从内存中的页表查询。

1297993-20211103225712627-1920617830.jpg

MMU 相关

MMU 的工作如下 :

1297993-20211103224659437-1669130.png

MMU 是一个硬件来的,那么为什么不能用软件来实现 MMU 呢? 可以参考这个问题

主要的原因在于防止程序的跨域访问不该访问的内存空间这一点。

MMU 的作用

下面引用自 : https://whatis.techtarget.com/definition/memory-management-unit-MMU
讲述 MMU 的三个作用
The work of the MMU can be divided into three major categories:

- Hardware memory management, which oversees and regulates the processor's use of RAM (random access memory) and cache memory.
- OS (operating system) memory management, which ensures the availability of adequate memory resources for the objects and data structures of each running program at all times.
- Application memory management, which allocates each individual program's required memory, and then recycles freed-up memory space when the operation concludes.

问题

描述 : 32位的cpu,Linux虚拟内存有1g的内核空间,低端内存线性映射到这个内核空间的一部分,高端内存采用其他办法动态映射,保证内核空间有办法映射到所有的物理内存。我的问题是:内核运行又不需要全部的物理页框,为什么要映射所有?

内核的虚拟地址空间映射了物理内存,是不是意味着这个物理内存将不能被用户空间使用了?

如果不是,那内核代码占用物理内存页框与内核映射物理内存这两个概念的区别是什么?

还有,内核线性映射了的物理内存是可以直接使用,不需要再请求伙伴系统分配页框了吗?这段线性映射的物理内存怎么寻址呢?直接用内核空间的虚拟地址减去page_offset吗?那如此还需要内核页表干什么?

来源 : https://www.zhihu.com/question/321040802

试着回答

页表的作用是物理内存到虚拟内存的映射 , 而软件在被操作系统加载之前只是分配了虚拟空间(例如:0x003c), 只有在真正运行的时候才会分配内存,
首先,这个问题的答案大多在内存子系统初始化的过程之中,更明确地说在伙伴系统初始化之前。伙伴系统 , 是什么呢 ? 就是操作系统来派来的内存管家 ,我们之前讲到操作系统在启动后会去探明内存,而探明的内存将会交给Buddy 管理算法去管理 .

问题一 : 内核运行又不需要全部的物理页框,为什么要映射所有? 映射的动机是什么???
见下面参考 ,原因是方便管理 ,可以申请多点虚拟空间, 内核就可以尽量地多放东西到虚拟内存中去, 要是一开始分配一点点, 日后要用,还得一点点分配 (注意这个地方映射的虚拟的内存)

参考 : 
作者:北极
链接:https://www.zhihu.com/question/321040802/answer/664205133
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


具体没研究过Linux的行为,但其实所有操作系统的行为都差不多,先映射一大块物理内存(建立页表),这个大小远远大于内核需要的内存,然后内核起来以后再做进一步拆分。

原因概括一下就是方便管理。对于操作系统来说,内存的页管理(Page Management)和内存池管理(主要是堆,Heap)是分开的,内核映射足够多的页,但并不一定都交给内存池直接使用,可能是给cache,给shm设备等。

页管理是多多益善,最好是把所有内存都占满,但这些内存页仍然可以交给用户使用的,因为一个物理地址可以映射给多个虚拟地址。只要这些页没有分配给指定的内存池(**这句话是什么意思呢?指定的内存池是什么,指的是buddy算法中的内存块吗**),就仍然可以被用户使用,甚至同时被用户空间和内核空间使用(比如为了零拷贝)。内核先占用了足够多的内存,这样就不用再做二次页分配的动作了,能保证内核的流畅性。如果内核一开始只占用比较小的内存区域,那么每次加载新的模块,就需要扩展这个内存区域,效率低不说,还可能有内存碎片的风险。对于用户程序来说,因为用户程序一开始都是在磁盘上的,需要一点点的加载到内存上,甚至可以边运行边加载,所以没必要一下映射足够多的内存,因为可能在整个用户程序的生命周期了,某些内存一直都用不上,完全分配太浪费了。


对于某些大型程序来说,任何操作系统都推荐一次性的从页管理器里直接申请足够多的内存,而不是使用编译器库函数或者操作系统提供内存池API去访问,因为后者的效率比较低,并不适用于某些特定的场景。

问题二 : 既然内核的虚拟地址空间映射了物理内存,是不是意味着这个物理内存将不能被用户空间使用了?
映射而已 , 映射和分配是两回事

问题三 : 内核代码占用物理内存页框与内核映射物理内存这两个概念的区别是什么?

问题四 : 内核线性映射了的物理内存是可以直接使用,不需要再请求伙伴系统分配页框了吗?

问题五 : 这段线性映射的物理内存怎么寻址呢?直接用内核空间的虚拟地址减去page_offset吗?那如此还需要内核页表干什么?

问题六 : 刚启动那会 ,现在虚拟地址的概念 , 那么加载内核的时候 ,是指定某个物理地址吗

问题2

描述 : x86 中我们知道,一个进程最多只能使用 3G 的内存空间 ,而内核空间则可以全部 ,那么这 3G 分配在物理内存的哪些空间呢?

用户进程

参考资料

posted @ 2021-11-03 22:58  float123  阅读(112)  评论(0编辑  收藏  举报