dede网站名称更改不了,红酒首页网页设计素材,做网站的公司怎么转型,网站推广和精准seo文章目录前言连续分配单一连续分配分区式分配固定分区分配动态分区分配可重定位分区分配离散分配分段分页多级页表快表(TLB)段页式Linux前言
Linux 内存管理 | 虚拟内存管理#xff1a;虚拟内存空间、虚拟内存分配 Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器…
文章目录前言连续分配单一连续分配分区式分配固定分区分配动态分区分配可重定位分区分配离散分配分段分页多级页表快表(TLB)段页式Linux前言
Linux 内存管理 | 虚拟内存管理虚拟内存空间、虚拟内存分配 Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器
在之前的两篇博客中分别介绍了虚拟内存与物理内存的管理方式那么对于操作系统来说它是如何管理它们两个之间的关系的呢如何进行地址的映射呢
内存的分配方式有两种 连续分配 每个进程分配一段地址空间连续的内存空间。 连续内存分配的方式有 单一连续分配分区式分配 固定分区分配动态分区分配 可重定位分区分配 离散分配 允许将一个进程分散的分配到许多不相邻的分区中程序全部装入内存。
现在用到的更多的是离散的分配方式因此我们简单介绍一下连续分配再对离散分配加以详解。 连续分配
单一连续分配
使用这种内存分配方式内存空间会被分成 系统区 和 用户区 两部分系统区仅提供给OS使用系统区外的用户区提供给用户使用。
特点
只适用于 单道程序 的情况。若用户作业比用户区大则无法运行若用户作业比用户区小则造成内存浪费设置界限寄存器限制用户程序访问操作系统
单道程序的特点
资源独占性 任何时候位于内存中的程序可以使用系统中的一切资源不可能有其他程序与之竞争。执行的顺序性 内存中只有一个程序各个程序是按次序执行的。在做完一个程序的过程中不可能夹杂进另一个程序执行。结果的可再现性 只要执行环境和初始条件相同重复执行一个程序获得的结果总是一样的。运行结果的无关性 程序的运行结果与程序执行的速度无关。系统中的作业以串行的方式被处理CPU、内存的利用率低。 分区式分配
固定分区分配
固定分区分配是最简单的一种可以运行在 多道程序 的存储管理方式。
多道程序 是指在计算机内存中同时存放几道相互独立的程序使它们在管理程序控制下相互穿插运行两个或两个以上程序在计算机系统中同处于开始到结束之间的状态, 这些程序共享计算机系统资源。当然对于一个单CPU系统来说程序同时处于运行状态只是一种宏观上的概念他们虽然都已经开始运行但就微观而言任意时刻CPU上运行的程序只有一个。
基本原理
将内存空间划分为若干个固定大小的分区大小可以不等每个分区中只可以装入一道作业当有空闲分区时选择一个适当大小的作业装入该分区当作业结束时释放该分区。
缺点
程序的大小受分区大小的限制程序数受分区数限制每个分区都有可能产生 内部碎片引起内存的浪费。 动态分区分配
基本思想
作业要求装入内存时依照作业的大小划分分区。每个分区容纳一个进程。
可变分区的管理与组织方式
表格法将内存按是否空心啊分别存在 空闲分区表 和 已分配分区表 中。管理简单但是需要占用一部分内存空间。 链表法维护一个链首指针每个空闲区在首地址记录两个数据本空闲区的大小、下个空闲区的起始地址。 动态分区分配的内存回收方式上邻空闲区F1合并改大小下邻空闲区F2合并改大小首址。上、下邻空闲区F1、F2合并改大小。不邻接则建立一新表项。 动态分配分区内存分配算法
首次适应算法 将空闲分区按 地址顺序 排列进行内存分配时从低地址开始顺序查找分配第一个足够大的分区。 优点优先分配低地址部分的空闲分区保留高地址部分缺点 在低址部分集中了许多小分区难以利用。 循环首次适应算法 首次适应算法的改进版本。将空闲分区按地址顺序构成循环链表进行内存分配时不再从链首开始查找而是从 上次找到的空闲分区的下一个空闲分区 开始查找循环查找。 优点内存中的空闲分区分布均匀比起首次适应算法减少了查找空闲分区时的开销缺点缺乏大的空闲分区。 最佳适应算法 空闲分区按大小 递增排序 构成队列从队列头开始查找当找到第一个满足要求的空闲区时则停止查找。 优点找到的空闲分区最接近要求的大小缺点会产生非常小的碎片难以利用。 最差适应算法 空闲分区按大小递增排序构成队列查找最大的空闲区。与上面三个同属 顺序搜索法 。 优点剩余的分区空间最大缺点在空间利用率方面较差。 可重定位分区分配
假设现在有这样一个情况用户内存空间中有几个较小的空闲分区但是现在有一个作业请求连续的内存空间这几个较小的空闲分区任何一个都不能单独满足请求空间的大小。 现在一种可行的办法就是移动内存使所有空闲区域合并为一整块空闲区域。
这种通过移动内存中的作业位置将原来分散的小分区拼接成一个大分区的思想就是 紧凑 。 说的直白一点可重定位分区分配就是 动态分区分配紧凑 。 动态重定位的实现
作业装入内存后的所有地址都是相对地址在程序将要执行的时候才会将相对地址转换为物理地址。为了不影响指令执行的速度系统中增设了一个 重定位寄存器即段寄存器、基址寄存器 用它来存放程序数据在内存中的起始地址。
程序真正执行时访问的地址是 重定位寄存器中的地址相对地址 。 离散分配
分段
为了简化地址管理所以将虚拟内存空间中的 虚拟内存 按照其逻辑划分为代码段、数据段、堆段、栈段几部分。编译、连接、加载过程都以段为单位。 段的特点
虚拟内存空间是段的集合。每个段都有其名称和长度。地址是由段名段号和段内偏移构成。
地址结构
虚拟地址是二维的[段号段内位移] 。32位地址结构中 段号s16位表示216 个段段内位移d16位表示每段最大长度是64KB。
通过 段寄存器 中的 段表 将虚拟地址与物理地址进行映射。段表由三部分组成
段号用于区别每个段。段基址segment base该段在物理内存中的首地址。段长segment limit 记录该段的实际长度。
因此虚拟地址与物理地址的转换方式如下
根据虚拟地址中的段号查询段表得到对应的段的物理内存起始地址物理内存起始地址加上段内偏移即为其对应的物理地址。 分段存储方式解决了两个问题 —— 地址空间不隔离 和 程序运行的地址无法确定。但还存在 内存使用效率低 的问题。内存使用效率低主要是因为两个原因造成的内存碎片 和 内存交换的效率低 。 内存碎片问题 例如我们有 1G 的物理内存倘若我们运行了 512M 的程序A接着运行了 128M 的程序B128M 的程序C。剩余内存为 256M 。 倘若我们此时结束程序B释放内存此时总剩余空间为 384M 。 倘若我们此时需要运行 300M 的进程D但是这时候就会因为剩余空间不连续导致我们的程序无法运行这也就是我们常说的 内存外碎片 问题。
那么如何解决这个问题呢这就会使用到类似于 紧凑 的思想。先将程序C写入硬盘的 SWAP分区 (交换分区用于内存和硬盘的空间交换)。紧接着再将其从硬盘中读取回来让其紧挨着程序A的那块内存这样就能保证后面的空闲内存都是连续的了。 内存交换效率低 由于分段对物理内存的映射是以 程序 为单位按照其逻辑进行分段映射如果我们的内存不足那么被换入换出到硬盘中的都是整个程序这样就必然会造成大量的磁盘访问操作总所周知磁盘IO的速度特别慢因此就会严重影响我们的访问速度。
而且当一个程序在运行时在某个时间段内它只是 频繁地用到了一小部分数据 也就是说程序中的很多数据其实在一个时间段内都是不会被用到的。因此我们将整个程序装入内存其实是对内存的一种浪费。 分页
总结一下分段技术仍未解决的问题有
虽然分段式存储方式不畏惧 内存外碎片但将内存中的数据暂时写入到硬盘中之后再重新写回来这样的换入换出操作在程序很大时是很废时间的。值得一提的是两者都无法摆脱 内碎片 的桎梏。而且分段需要将程序全部装入内存这就对程序的大小有了限制——不能超过剩余空闲内存的大小。
而分页技术解决上述两个问题的方法是
使用页为单位后即使我们还是需要进行磁盘IO但是由于我们交换的容量仅仅只有几个页所以也不会花费过多的时间。分页技术下并不需要将程序整个装入内存。在建立了虚拟内存空间后并不会直接分配物理内存而是在程序运行中需要访问物理内存的时候再将其加载进内存中。所以如果在页表中查找不到时此时就会由内核的 请求分页机制 产生 缺页中断 然后进入 内核态 中分配物理内存、更新进程页表最后再返回用户态恢复进程的运行。
基本概念
帧/物理块/页框(frame) 物理内存分为固定大小的块。页(page) 逻辑内存分为同样大小的块在 Linux 中一页是 4KB 。页表(page table) MMU(内存管理单元) 中的页面映射表记录了 页 和 页框 的映射关系。 页表中不仅保存了页号物理内存地址还保留了该物理页的 访问权限 用以实现对页的访问控制。
在分页机制下虚拟地址由 页号 以及 页内偏移 组成
因此在分页机制下虚拟地址与物理地址的转换方式如下
根据虚拟地址中的页号查询页表获得对应的页的物理内存起始地址物理内存起始地址加上页内偏移即为其对应的物理地址。 多级页表
在上面所介绍的 页表 有一个非常致命的缺点就是空间占用大。
在 Linux 中可以并发的执行多个进程而每个进程都有其自己的虚拟内存空间那么也自然都有自己独有的页表。在32位Linux系统下我们的虚拟内存空间的大小为 4G 而每页的大小为 4K 这也就意味着我们至少有 220 个内存页倘若每个页表项为 4Byte 那么每个页表大小也至少为 4M 。
倘若我们此时并发了两百个进程那么占用则高达 800M 即使对于如今的操作系统而言这个数字也是非常庞大的因为并发数百个进程是非常常见的情况更别提64位的操作系统随着寻址范围的增加页表将更为庞大。
为了解决这个问题就引入了多级页表。
我们将 一级页表 再进行分页分成 1024 个 二级页表 并且每个 二级页表 中存有 1024 个页表项形成如下的 二级分页 的结构。 对于已分配的页表项如果存在最近一定时间未访问的页表在物理内存紧张的情况下操作系统会将页面换出到硬盘也就是说不会占用物理内存。
如果某个一级页表的页表项没有被用到也就不需要创建这个页表项对应的二级页表了即可以在需要时才创建二级页表。 如果一级页表所有表项都被用到那么此时二级页表大小为 4M(1024 * 4K) 假设我们只是用了一级页表的 20% 。 在这种情况下页表所占用的物理内存就只有 4K(一级页表大小) 20% * 4M存在的二级页表 即 0.804M 比起只用了二级页表的 4M 一级页表没有用到的表项不用创建对应的二级页表因此此时存在的二级页表共 20% * 4M 但单极页表时二级页表必须建满 4M大大的节约了内存。
而在64位系统中两级页表是肯定不够用的因此又演变成了四级目录
全局页目录项 PGD上层页目录项 PUD中间页目录项 PMD页表项 PTE 快表(TLB)
多级页表虽然解决了空间占用大的问题但是由于其复杂化了地址的转换因此也带来了 大量的时间开销 使得地址转换速度减慢。
解决这个问题最简单的方式就是降低查询页表的频率那么如何实现呢这时候就需要用到 缓存 的技术
对于热点资源我们可以将其提前缓存下来到以后使用时就可以直接到缓存中查找。对于操作系统来说也是这么一个道理。
在操作系统中这个缓存就是 CPU 中的 TLB 也就是我们通常所说的 快表 。我们将 最常访问的几个页表项存储到 TLB 中 在之后进行寻址时 CPU 就会先到 TLB 中进行查找如果没有找到这时才会去查询页表。 段页式
虽然分段和分页各有优缺点但他们直接并不是对立的所以如今大部分的内存管理方式都是将分段与分页相结合也就是我们常说的段页式。
它的原理非常简单就是先对 虚拟内存空间进行分段管理然后再对每一个段进行分页管理。 如下图 所以此时的虚拟地址结构就由 段号、段内页号、页内偏移 所组成。此时对于每个进程来说都会建立一个段表而对于段表中的每一个段又会再分别建立一个页表 以此时的虚拟地址转换为物理地址就需要以下三个步骤
访问段表得到页表的起始地址访问页表得到物理页的起始地址访问物理页加上页内偏移得到实际的物理地址。
这种方法虽然增加了系统开销以及硬件成本但是内存的利用率得到了巨大的提升。 Linux
由于硬件问题的限制Linux 内存主要采用的是页式内存管理但同时也不可避免地涉及了段机制。
在往常的机制中地址的转换流程如下 但是在 Linux 中并没有逻辑地址这一说(所有段起始地址相同)因为其将段机制进行了弱化此时段只用于进行访问控制以及内存保护。
Linux 系统中的每个段都是从 0 地址开始的整个 4GB 虚拟空间32 位环境下也就是所有的段的起始地址都是一样的。
这意味着Linux 系统中的代码包括操作系统本身的代码和应用程序代码所面对的地址空间都是线性地址空间虚拟地址这种做法相当于屏蔽了处理器中的逻辑地址概念段只被用于访问控制和内存保护。