内存从连续到离散(整块内存-->内存分段-->内存分页),大概脉络的随记,大白话讲解

前提知识–硬编码和重定位:
适用设备:
硬编码:嵌入式,专用设备
重定位:现代操作系统.

硬编码:cpu执行时,在遇到关于内存地址的代码时,不翻译,直接执行程序关于内存地址的代码. 在单片机中,或者卫星中,设备的功能是单一的,执行的代码总是固定的,例如 总是从300这个内存位置开始执行播放音乐,则直接将播放音乐的代码烧在300处,事实上,现代操作系统的启动前,BIOS就是提前烧录在主板上的.

重定位:由操作系统将程序中的内存地址翻译到另一个内存地址(由绝对地址变为相对地址),就是重定位,重定位可以在程序编译时,或者程序首次载入时,或者程序运行时.(重定位是整个内存管理的基本实现原理,简单理解:一个地址可以转换为另一个地址的技术就是重定位,如果无法重定位,那还分个锤子段和页)

①编译时重定位:还是太硬,你必须在编译前选一块此时未被使用的内存地址,然后将这个地址作为偏移编译进程序,最后在载入时将这个程序载入到这块内存地址中,但这tm和硬编码几乎无区别.

②首次载入时重定位:在操作系统选一块未使用的内存地址(称为基地址),将程序加载进去,然后当程序执行内存相关代码时,就加上基地址作为偏移,不过一旦载入后内存地址就变不了了,Linux操作系统有一个操作叫 SWap(交换分区) ,它会把暂时不用的程序从内存中取出来放硬盘上,到了要运行的时候在从硬盘取出来放内存里,你如果只在载入时重定位一次,那当程序经历 内存 - 硬盘 - 内存 ,此时程序来到新的内存地址,那此时对内存地址相关的代码就要出错.

③运行时重定位:相当于 载入时重定位的Max版,将基地址存入PCB(进程控制表),每当程序运行时,只要执行内存地址相关代码,就把这个逻辑内存地址加上PCB中保存的基地址作为物理地址.若 执行了SWap,则更新PCB中的基地址,确保在新的内存地址空间中可以照常运行.

开始引入分段的概念:

内存不分段:
一个完整的程序,刨析开来的话,你会发现它有不同的内存区域负责不同的事情,代码段只负责code的执行,可执行但不可被写,数据段只负责数据的存放,可读写但不可被执行,栈段 可读写不可执行,但会动态伸缩.此时想一个事情,如果若是把这些负责不同事情的内存段全部放置在一大块内存区域.
①是OS层–内存地址的浪费: 假设有10内存(1–10,单位省略),A占3,申请载入内存地址1-3处,B也占3,申请载入内存地址6-8处,此时仍有空闲4,刚好C占4,但是却无法执行,因为剩余内存地址空间在4-5 9-10,是非连续的,C无法载入.
②是用户层–程序员编写程序时不好管理: 数据段不可以执行,你把他和代码段紧紧放在一起,cpu可不区分这内存地址中的是代码还是数据,如
果程序员写数据时内存地址搞错,写到了代码段怎么办?
③想一想,如果栈区一直在膨胀,数据都要溢出来影响到其他区域了,此时必须申请内存扩大栈区,一个栈区改变,就得将整个内存块中的程序都搬到其他内存区域,这段时间,程序无法执行,岂不是浪费?

内存分段:
为了综合解决上面的问题,分段来了! ①程序在载入的时候,不同的段载入不同的内存区域,程序可以更有效率利用内存,部分解决了内存空间够,但是程序无法载入的事情(总有些旮旯拐角的内存区域连一个段都放不进去). ②分而治之,让程序员可以用不同的段寄存器来管理不同的段,用[段+偏移]的方式来寻址不同的内存区域,即使偏移写大了,也不会影响到其他段. ③关于栈区膨胀问题,若内存区域不够,栈申请更大内存,若要移到其他内存地址,只用栈移动就行了,不用大家都搬家.

小综合:
将运行时重定位和内存分段结合:,内存分段,则不同的段寄存器有不同的基址,需要一个地方来存放虚拟地址和物理地址的对应关系,此时引出了LDT(每个进程都有它的PCB(进程控制表),PCB中有LDT(局部描述符表)),ldtr寄存器存放LDT的首地址,LDT中存放了段寄存器对应的内存地址.每次CPU寻址时,不同的段寄存器中存放的不同的段选择子(也叫段描述符),根据段选择子在LDT中索引到对应的段描述符,然后取出其中的内存地址作为基址,将此基址加上程序代码中的偏移,就是物理地址.

(分页出现的原因)👇
由分段引入的问题:
启用分段,则 以段为基本单位的离散的内存地址的总体 构成一个程序.此时程序是分开的,可充分利用内存空间,部分提高了内存使用效率,但段仍是连续存放的,仍会造成总体内存空间足够,但无法连续存放一个段的情况仍会出现(或者要放进去也可以,进行一次内存紧缩,把不用的内存碎片整理在一起,但内存紧缩需要时间,此时计算机就处在无响应状态,类比于未分段时,整体内存空间足够,但没有足够的连续空间来存放整体程序) 那怎么解决呢? 那就仿照 将整个程序离散为段,来充分利用内存的思想,将段继续打散为更小的内存空间,来更高效率的利用内存,此之谓 分页.

操作系统可以单独实现分段机制,也可以单独实现分页机制,但现代操作系统的内存管理都是段页结合机制.
只分段:则程序中的逻辑地址查段表确定段基址,再加上偏移,就得出了物理地址.
只分页:则程序中的逻辑地址–>换算+查页表+换算–>物理地址.
段页结合:则程序中的逻辑地址先查段表确定段基址,再加上偏移,得出虚拟地址(也叫线性地址),此时再根据这个地址值,进行换算+查页表+换算,就得出了物理地址,原理实际就这么简单,把清这个脉络,再来看看对应实现,就不会只见树木,不见森林了.

分段实现了,现在来分页
为什么分页?段还是太大了呗.你一个段几十M,物理内存里这放一段,那放一段,那物理内存里待会儿就充满了几M几K的小碎片了,食之无味,弃之可惜,这些内存碎片加起来其实总量很大,这么贵的内存条,浪费这么多,怎么办?所以,想想分段的经验,那就继续把内存打散,可不能随便打散,乱打散了不好管理,首先,必须打散为固定的块大小,如果一个段浪费了内存空间,最多也只能浪费这一块的大小.这一个块,就称为一页.这就是分页.
linux0.11里面,每页分为4K,那么4G的内存空间,会将内存均分为1M(4G÷4K)个页框,每个页框映射为一段连续的物理内存.程序段的页分好了,此时虚拟地址需要映射到具体某个页框,就需要建立页表,将段中的某一页,映射到某一个页框(页框后就是真实的连续的一段物理内存空间). 一个映射关系(页表项)占4Byte,1M个映射关系的整体就是页表. 页表占用内存空间大小就是4Byte1M=4M 一个段分配一个页表,一个程序一般四个段,那就是说,一个程序的页表在16M.
每一页都是固定大小的,那页多大合适呢?显然,页越小,则内存空间管理的越精细,内存可浪费空间越小,但是页表(每一页的索引表)相应也会越大,你程序只有100KB,页表却有100M,这不合道上的规矩吧?现在后台任务随便都有几十个,假设为30个任务,岂不是页表占用的额外空间都有30
100M=3000M,相对于现在大家都普遍的4G,8G,16G内存的配置,这反而造成了额外内存浪费,这并不是分页的初衷,显然,页不是越小越好,会适得其反. 那如果页分配大呢?页表是小了,那每页的浪费空间又会增大. 于是,可以预见,页的大小和实际内存的大小是息息相关的,这便是linux每页分为4k的原因,在页表和内存管理粒度之间的平衡.
但是16M的页表还是太大了,所以我们就得想办法,第一个想法就是,用多少内存,分配多少页表,也就是页表的内容不再是每个页不管用没用都需要映射,而是一个段分了多少页,就只构造这些页的映射项,那问题就来了,若要了解这个问题,就必须了解程序段的页是怎么分配的.
上小学数学,首先将 程序段中的逻辑地址 ÷ 页大小 = 页号 余 偏移地址(要用到),然后直接用页号查一次表(重点!因为此时的页表是连续的,所以取几号页的页框,直接指针偏移,就直接可以取到),取到了页框号,用 页框号 x 页块大小 = 物理地址基址,物理地址基址+偏移地址=物理地址, 了解这些,就可以看到接下来的问题了.
以前虚拟地址转换为物理地址时,只需要查一次页表,就可以确定物理地址的内容.如果为了缩小页表,用多少内存,分配多少映射项,那此时用虚拟地址确定页号后,必须要查多次页表,才能确定页框号,因为页表不再是连续的了,无法一次直接索引到对应映射项.必须进行比较查询,假设一个段占4M内存,那就需要分配 4M÷4K = 1024个页表项,也就是说,为了用页号确定页框,需要查询1024次(最差情况),平均查询512次,即使算法再优化,查内存会拖慢CPU的运行速度终究是改变不了,执行一个小程序,每执行一步,都需要每次几百次的查询,这个内存管理机制无疑是不行的.
那内存空间我又想要,执行速度我还不想慢,兄弟萌,怎么办呢?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值