网站首页源码,十大美妆电商平台,深圳网页设计模板,wordpress 博客实例大家好#xff0c;我叫徐锦桐#xff0c;个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识#xff0c;还有日常折腾的经验#xff0c;欢迎大家访问。 前言
每个进程都有一套自己的虚拟地址#xff0c;尽管进程可能有相同的虚拟地址#xff0c;… 大家好我叫徐锦桐个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识还有日常折腾的经验欢迎大家访问。 前言
每个进程都有一套自己的虚拟地址尽管进程可能有相同的虚拟地址但经过映射后就是不同的物理地址了以此来实现进程隔离等功能。 内存分段
介绍
一开始使用分段来进行内存管理的但是用多了会发现使用分段管理会产生大量的外部碎片。 说到分段肯定要讲到GDT(全局描述符表) 和 段选择子 还有段描述符。 简单来说它们之间的关系就是GDT是一个数组里面存着段描述符通过段选择子(索引)找到对应的段描述符。 具体的寻址方式就是虚拟地址段选择子段内偏移量。 通过段选择子找到在GDT表中找到对应的段描述符然后就是判断特权级特权级允许后找到对应的段基址然后再加上偏移量就是对应的物理地址了。 段描述符的结构如下图 在进程中我们一般将进程的TSS描述符一种特殊的段描述符里面存着进程的数据段、代码段等段的位置和进程运行所需所有的寄存器的值和进程特权级等 作为段内描述符当进行任务切换的时候系统加载进程对应的TSS结构来恢复进程。
// 这是我做过的一个极简版32位操作系统中的代码
// 项目地址https://github.com/xjintong/SimilarLinux0.11
typedef struct _tss_t {uint32_t pre_link; uint32_t esp0, ss0, esp1, ss1, esp2, ss2;uint32_t cr3;uint32_t eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;uint32_t es, cs, ss, ds, fs, gs;uint32_t ldt; uint32_t iomap;
}tss_t;不足
分段会产生两个问题。
外部内存碎片
分段会产生外部内存碎片。就是因为段的长度是不固定的对于内存的分配有可能不是很合理就比如说下面空闲内存大于新进程的内存但是由于两个空闲内存是分开的导致新进程不能加载到内存中。
解决这个问题的方法就是内存交换。 也就是先将进程2放到磁盘中去然后把新进程放到内存中挨着进程1放然后再把磁盘中的进程2重新读取到内存这个在磁盘上和内存进行数据交换的空间就是swap空间。
内存交换率低
磁盘读写数据相对内存来说是非常慢的如果是一个内存占用非常大的进程要从内从交换到磁盘所消耗的时间是非常大的。
内存分页
一级页表
我们可以将所有的物理内存和虚拟均匀分成等份的一般就是4KB页然后创建一个表里面存储着虚拟页和物理页的映射关系到时候就直接查表找到对应的物理页最后再加上页偏移就是物理地址了。 一级页表的起始位置放在CR3寄存器中。 采用分页很好的解决了外部内存碎片化和内存交换率低的问题。但是如果只用一级页表还是有着很大的问题。 我们以32位操作系统为例内存总共4GB(2^32) 一个页4KB(212)那么就需要百万级(220)的映射关系一个页表项需要4字节存储然后所有的表项就需要4MB(2^22)内存存储。 看着不是很多吧但是每个进程都需要一个这样的表如果进程变多了这得多消耗多少没必要消耗的空间。 而且我们必须有这个表的全部因为是靠索引来找到对应的页的你如果只取一部分那它的索引号就变了。所以我们需要多级页表来解决这个问题。
多级页表
32位操作系统用二级页表就够了64位操作系统用四级页表这里只讲32位的。 我们先把内存分成10242^10个大的页目录一个页目录指向一个页表一个页表含有1024个页目录 第一级是页目录page directory一共1024210个目录项一个目录项4字节大小所以页目录占4KB(222)大小内存。 第二级是页表page table它也是10242^10个页表项一个页表也是4KB大小。一个页表的最大寻址空间是4MB因为1024个页表项对应1024个页物理地址一个页物理地址是4KB。 一个目录项对应着一个页表所以一个目录项最大寻址4MB一个目录有1024个目录项所以一个目录最大寻址4GB。 虚拟地址的高10位是页目录索引中间10位是页表索引最后12位是偏移量。
页目录的结构和页表的结构是一样的只不过他俩描述的主体不一样一个描述的是页目录一个描述的是页表。 通过页目录和页表可以获取物理地址的前20位后12位就是加上虚拟地址的后12位。虚拟地址的后12位存储的权限读写标志等等。 通过二级页表我们不用保存所有的映射关系了。有一个页目录就能扫完4GB内存然后再找对应的页表如果没有就再创建。如果某个一级页表的页表项没有被用到也就不需要创建这个页表项对应的二级页表了即可以在需要时才创建二级页表 就像下面这样二级表有1024个每个二级表有1024个表项并且索引都是从0开始的这样我们单独拿出一个二级表它的索引也不会变化。由于局部性原理我们用不到那么多的页表项。
TLB
从虚拟地址到物理地址的转换还需要查两次表64位的还要查4次时间也是有消耗的。TLB里面保存了一些之前已经转换好的虚拟地址和物理地址的映射关系。 详细的请看我写的另一篇文章
Linux内存布局
inter处理器早期是用分段管理内存后来发现不是很好用后来又整了分页但是还保留着分段就是虚拟地址是通过分段获取的然后再通过分页。 可以看到最左边还有分段的是通过分段获取的线性地址。 但是分段确实有点弊端Linux无法改变硬件只能改变自身了。Linux用了一个技巧使分段相当于失效了。 Linux 系统中的每个段都是从 0 地址开始的整个 4GB 虚拟空间32 位环境下也就是所有的段的起始地址都是一样的。这意味着Linux 系统中的代码包括操作系统本身的代码和应用程序代码所面对的地址空间都是线性地址空间虚拟地址这种做法相当于屏蔽了处理器中的逻辑地址概念段只被用于访问控制和内存保护。 Linux的地址空间分为内核空间和用户空间。 在内核空间是不开启分页机制的所有进程共享内核内存。有人认为这是为了防止从用户态到内核态的性能消耗。
最后
每个进程都有完全属于自己的地址转换表所以即便是相同的虚拟地址空间也会映射到不同的物理地址上以此来达到隔离进程的效果。 在进程看来自己是独享整个内存自己可以随便找个地址但其实是通过页表映射到个各个物理上。
参考博客 小林coding–为什么要有虚拟内存 图文详解 Linux 分页机制 MIT6.S081 4.3 页表Page Table