小林coding 图解系统 chapter3

第三章:内存管理

3.1 虚拟内存

单片机没有操作系统,所以每一次写完程序之后,要把程序烧录进去,这样程序才能跑起来

单片机的cpu直接操作的时单片机的物理地址

因为cpu直接访问的是物理地址,所以单片机中不存在两个程序同时进行,如果两个程序同时进行的话,cpu同时访问两个不同的地址,这是cpu做不到的事情,会导致程序崩溃!!!

但是操作系统则不同,操作系统可以实现每一个进程都给分配一个虚拟内存地址!!!!,进程不能直接访问物理地址,但是操作系统可以实现虚拟地址对物理地址的映射

操作系统会提供⼀种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。 

所以,我们现在程序正在使用的地址叫做  虚拟地址(virtual memory address)

实际上在硬件中存储的地址叫做   物理地址(physical memory address)

操作系统引⼊了虚拟内存,进程持有的虚拟地址会通过 CPU 芯⽚中的内存管理单元(Memory Management Unit ---> MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存,如下图所示:

所以,操作系统如何管理虚拟地址和物理地址之间的关系呢?

答案是:通过内存分段和内存分页的方式来进行管理!!!

内存分段:不同程序是由不同的段组成的:常见的有代码段,数据分段,堆段和栈段等。。。

在分段机制下,虚拟内存被分割为:段选择子 + 段偏移量  ---> 具体内容如下图:

例:如果我们需要访问段3中的500偏移量的地址,我们就需要使用段3的基质+偏移量进行操作!!!!

内存分段导致了其他的问题:一个是内存碎片,第二个是内存交换律低下

举个例子:

游戏占⽤了 512MB 内存
浏览器占⽤了 128MB 内存
⾳乐占⽤了 256 MB 内存。这个时候,如果我们关闭了浏览器,则空闲内存还有 1024 - 512 - 256 = 256MB。
但是,这256M的数据它不是连续的:

如果我们还想新打开一个需要200M内存大小的程序的话,现在这个内存分段的方式我们是打不开的。

我们可以做的是,将音乐所占用的256M的内存写入硬盘上,把内存中的音乐占用的256M的内存释放掉,然后将硬盘中的256M的音乐运行程序加载到512M的游戏内存之后,这样内存就会释放出来完整的256M的内存!!

这样200M的其他的应用程序就会被释放出来!!!

这个与内存交换的硬盘空间在Linux系统上被称为swap!(交换空间)

因为访问硬盘比访问内存要慢得多,所以如果内存交换的时候,交换的是⼀个占内存空间很⼤的程序,这样整个机器都会显得

卡顿
 
为了解决 内存分段所带来的内存碎片化和内存交换效率低下的问题,我们提供了内存分页的方法!!!
由于内存空间都是预先划分好的,也就不会像分段会产⽣间隙⾮常⼩的内存,这正是分段会
产⽣内存碎⽚的原因。⽽采⽤了分⻚,那么释放的内存都是以⻚为单位释放的,也就不会产
⽣⽆法给进程使⽤的⼩内存。
如果内存空间不够,操作系统会把其他正在运⾏的进程中的「最近没被使⽤」的内存⻚⾯给
释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。⼀旦需要的时候,再加载进来,
称为换⼊(Swap In)。所以,⼀次性写⼊磁盘的也只有少数的⼀个⻚或者⼏个⻚,不会花太
多时间,内存交换的效率就相对⽐较⾼。
降低了内存碎片化的工作,减少了内存与硬盘的交换次数,提高了交换效率!
 
但是,如果为了使用方便且占用内存不是很大的情况下,还是使用多级页表  因为多级页表可以提升存储效率、降低存储时间!!!
如果某个⼀级⻚表的
⻚表项没有被⽤到,也就不需要创建这个⻚表项对应的⼆级⻚表了,即可以在需要时才创建
⼆级⻚表。
----> 如果一个一级内存的页表项没有被用到,那么就不需要创建对应的二级页表了;如果用到了一级页表,那么我们可以在用的时候在创建二级页表!!!
 
 
⻚表⼀定要覆盖全部虚拟地址空间,不分级的⻚表就需要有 100 多万个⻚表项来映射,⽽⼆级分⻚则只需要 1024 个⻚表项 !!!----> 页表一定要实现虚拟地址全覆盖!!!
 
对于64位操作系统来说,要实现有效率的查询,需要有四级页表:
全局⻚⽬录项 PGD(Page Global Directory);
上层⻚⽬录项 PUD(Page Upper Directory);
中间⻚⽬录项 PMD(Page Middle Directory);
⻚表项 PTE(Page Table Entry);
但是,多级页表的本质也是时间换空间 :以更长的时间代价来换取空间的缩小!!!
但是,程序是有限制的,所以我们可以专门做一个可以存放最常访问页表的cache中,这个cache就是TLB(transaction Lookaside buffer) ---> 页表缓存,转址旁路缓存,快表。。。etc

有了TLB之后,cpu在想寻址的话,就会现在TLB里面找,然后再去页表里面寻找,大大的提高了效率。且TLB命中率非常的高!!!

 

 

其实我们在用的时候,不是内存分段和内存分页分开来的,而是通过段页式存储方式在系统中进行使用的!!!

先将程序划分为多个有逻辑意义的段,也就是前⾯提到的分段机制;
接着再把每个段划分为多个⻚,也就是对分段划分出来的连续空间,再划分固定⼤⼩的⻚;
 
这样,就形成了段号,段内页号和页内位移三部分;

用段页式访问内存有三个步骤:

第⼀次访问段表,得到⻚表起始地址;
第⼆次访问⻚表,得到物理⻚号;
第三次将物理⻚号与⻚内位移组合,得到物理地址。(如上图)
 
 Linux 内存管理
 
逻辑地址:程序所使⽤的地址,通常是没被段式内存管理映射的地址,称为逻辑地址;(没有经过段式内存管理映射的之前的地址)
虚拟地址:通过段式内存管理映射的地址,称为线性地址,也叫虚拟地址;(经过段式地址映射过之后的地址)
Linux 内存主要采⽤的是⻚式内存管理,但同时也不可避免地涉及了段机制。

 

32位Linux系统中有总共4GB的空间地址,64位Linux系统中有2^64的内存地址,但是实际上使用的地址是2^48位,因为不需要用那么多的地址,以便造成浪费

 

进程在⽤户态时,只能访问⽤户空间内存;
只有进⼊内核态后,才可以访问内核空间的内存;
 
在Linux用户空间分布中,有如下分布:

 

程序⽂件段,包括⼆进制可执⾏代码;
已初始化数据段,包括静态常量;
未初始化数据段,包括未初始化的静态变量;
堆段,包括动态分配的内存,从低地址开始向上增⻓;
⽂件映射段,包括动态库、共享内存等,从低地址开始向上增⻓(跟硬件和内核版本有
关);
栈段,包括局部变量和函数调⽤的上下⽂等。栈的⼤⼩是固定的,⼀般是 8 MB 。当然系统也提供了参数,以便我们⾃定义⼤⼩;
posted @ 2022-06-02 14:23  Dyral_HAN  阅读(156)  评论(0编辑  收藏  举报