问答媒体

 找回密码
 立即注册
快捷导航
搜索
热搜: 活动 交友 discuz
查看: 128|回复: 1

【Linux内核】—— 内存管理基础

[复制链接]

1

主题

2

帖子

4

积分

新手上路

Rank: 1

积分
4
发表于 2023-4-4 14:27:43 | 显示全部楼层 |阅读模式
【推荐阅读】
一文看懂linux内核详解
linux内核内存管理-写时复制
深入了解使用linux查看磁盘io使用情况
经过多年探索,人们提出了分层存储器体系(memory hierarchy)的概念,即在这个体系中,计算机有若干兆(MB)快速、昂贵且易失性的高速缓存(cache), 数千兆(GB)速度与价格适中且同样易失性的内存,以及几兆兆(TB)低速、廉价、非易失性的磁盘存储,另外还有诸如 DVD 和 USB 等可移动存储装置**。操作系统的工作是将这个存储体系抽象为一一个有用的模型并管理这个抽象模型。**
操作系统中管理分层存储器体系的部分称为存储管理器(memory manager)。它的任务是有效地管理内存,即记录哪些内存是正在使用的,哪些内存是空闲的;在进程需要时为其分配内存,在进程使用完后释放内存。
内存管理的主要功能:

  • 内存空间的分配与回收
  • 地址转换
  • 内存空间的扩充
  • 内存共享
  • 存储保护
基础知识

逻辑地址与物理地址空间

程序的装入,实现逻辑地址到物理空间地址的转换。
物理地址 :加载到内存地址寄存器中的地址,内存单元的真正地址。在前端总线上传输的内存地址都是物理内存地址,编号从 0 开始一直到可用物理内存的最高端。物理地址是明确的、最终用在总线上的编号,不必转换,不必分页,也没有特权级检查
逻辑地址 :CPU 所生成的地址。逻辑地址是内部和编程使用的、并不唯一。例如,你在进行 C 语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址(偏移地址),不和绝对物理地址相干。
程序的链接与装入

创建进程首先要将程序和数据装入内存。将用户源程序变为可在内存中执行的程序,通常需要以下几个步骤:

  • 编译:由编译程序将用户源代码编译成若干目标模块。
  • 链接:由链接程序将编译后形成的一组目标模块及它们所需的库函数链接在一起,形成一个完整的装入模块。
  • 装入:由装入程序将装入模块装入内存运行。


程序的链接


  • 静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块) ,之后不再拆开。
  • 装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。
  • 运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。
装入方式

绝对装入

灵活性极低,只适用于单道程序环境
在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。
可重定位装入(静态重定位)

在一般情况下, 一个作业在装入时分配到的内存空间和它的地址空间是不一-致的,因此,作业在CPU上运行时,其所要访问的指令、数据的物理地址和逻辑地址是不同的。显然,若在作业装入或执行时,不对有关的地址部分加以相应的修改,则会导致错误的结果。这种将作业的逻辑地址变为物理地址的过程称为地址重定位。
静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间 ,如果没有足够的内存,就不能装入该作业。作业一旦进入内存,运行期间就不可以移动 。可重定位是指在装入时把逻辑地址转换成物理地址,但装入后不能改变
在多道程序环境下,多个目标模块的起始地址通常都从 0 开始,程序中的其他地址都是相对于起始地址的,此时应采用可重定位装入方式。根据内存的当前情况,将装入模块装入内存的适当位置。 在装入时对目标程序中指令和数据地址的修改过程称为重定位,又因为地址变换通常是在进程装入时一次完成的,故称为静态重定位。
动态运行时装入(动态重定位)

编译、链接后的装入模块的地址都是从 0 开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行 ,因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持。


内存空间的分配和回收


  • 内部碎片,是指分配给某进程的内存区域中,如果有些部分没有用上。
  • 外部碎片,是指内存中的某些空闲分区由于太小而难以利用。
连续分配管理方式

连续分配:指为用户进程分配的必须是一个连续的内存空间。
单一连续分配

在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。内存中只能有一道用户程序,用户程序独占整个用户区空间。只支持单道程序。
优点:实现简单;无外部碎片;可以采用覆盖技术扩充内存;不一定需要采取内存保护。
缺点:只能用于单用户、单任务的操作系统中;有内部碎片,存储器利用率极低。
固定分区分配

将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。

  • 分区大小相等:缺乏灵活性,但是很适合用于用一台计算机控制多个相同对象的场合(比如:钢铁厂有n个相同的炼钢炉,就可把内存分为n个大小相等的区域存放n个炼钢炉控制程序)
  • 分区大小不等:增加了灵活性,可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分
    操作系统需要建立一个数据结构----分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)


优点:无外部碎片,实现简单
缺点: 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能;会产生内部碎片,内存利用率低。
动态分区分配 ⭐

动态分区分配又称为可变分区分配 。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要 。因此系统分区的大小和数目是可变的
动态分配内存常用两种方法管理内存分配

  • 空闲分区表:每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息

    • 分配:若分区大小大于进程大小,则在分区表中修改大小和起始地址




  • 回收:
    若回收后,空闲分区前后有相邻的空闲分区,则需要将这些空闲分区合并为一个空闲分区,并改变表中其大小和起始地址。
  • 空闲分区链:


动态分区分配没有内部碎片,但是有外部碎片。
动态分区分配算法

在动态分区分配方式中,当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?
首次适应算法(First Fit)


  • 每次都从低地址开始查找,找到第一个能满足大小的空闲分区
  • 空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
最佳适应算法(Best Fit)


  • 由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,优先使用更小的空闲区
  • 空闲分区按容量递增次序链接 。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。每次回收后或者分配后需要对分区进行重新排序
  • 每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片。
最坏适应算法(Worst Fit)


  • 为了解决最佳适应算法的问题--即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
  • 空闲分区按容量递减次序链接 。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。每次回收后或者分配后需要对分区进行重新排序
  • 每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完 。如果之后有“大进程”到达,就没有内存分区可用了。
邻近适应算法(Next Fit)


  • 首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索 ,就能解决上述问题。
  • 空闲分区以地址递增的顺序排列(可排成一个循环链表) 。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表) ,找到大小能满足要求的第一个空闲分区。
非连续分配管理方式 ⭐

为用户进程分配的可以是一一些分散的内存空间。
【文章福利】小编推荐自己的Linux内核技术交流群:【977878001】整理一些个人觉得比较好得学习书籍、视频资料;进群私聊群管理领取内核资料包(含视频教程、电子书、实战项目及代码)


内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
基本分页存储管理

将内存分成若干固定大小的页,通过分配若干页将整个进程载入其中。
内存分页管理是在硬件和操作系统层面实现的,对用户、编译系统、连接装配程序等上层是不可见的。
将内存分为一个个大小相等的分区, 这些分区称作为(页框、页帧、内存块、物理块、物理页面 )若对分区进从编号,则又有了对应的(页框号、页帧号、内存块号、物理块号、物理页号 ),从 0 开始
进程的信息都是要存在内存中的,既然内存有了分区,那么进程逻辑地址 上也会有相应的大小相等的分区,称为(页、页面),对应编号为(页号、页面号) ,编号从 0 开始。


操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系 。各个页面不必连续存放,可以放到不相邻的各个页框中 。逻辑---物理
页表 ⭐⭐:常存在 PCB(进程控制块)中
为了知道进程每个页面在内存中的存储位置,操作系统会为每一个进程建立一张页表,页表反映的是页和页框的一一对应关系 。每个页表项的大小是相同的,页号是隐含的( 就是逻辑地址除以页表项大小的整数部分,逻辑地址空间中的地址A,页面大小L,则页号P=int(A/L))


在多个进程并发执行时,所有进程的页表大多数驻留在内存中,在系统中只设置一个页表寄存器(PTR),它存放页表在内存中的始址和长度。平时,进程未执行时,页表的始址和页表长度存放在本进程的PCB中,当调度到某进程时,才将这两个数据装入页表寄存器中。每个进程都有一个单独的逻辑地址,有一张属于自己的页表。
每个页表项占多少字节?
页表项的大小是相同的,页号是隐含的,因此页号是不需要字节的


如何实现地址的转换?
计算机中用二进制表示,如果页面大小是二的整数幂,则逻辑地址可以直接拆成页号和页内偏移量。








基本地址变换机构 ⭐

用于实现逻辑地址到物理地址转换的一组硬件机构。
通常会在系统中设置一个**页表寄存器(PTR) **,存放页表在内存中的起始地址 F 和页表长度 M。
进程未执行时,页表的始址和页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统会把它们放到页表寄存器中。
页表长度 指的是这个页表中总共有几个页表项,即总共有几个页;
页表项长度 指的是每个页表项占多大的存储空间;
页面大小 指的是一个页面占多大的存储空间;
页表中页号 P 对应的页表项地址=页表起始地址 F+ 页号 P*页表项长度 ,取出该页表项内容 b,即为内存块号;
地址变换过程:⭐

  • 根据逻辑地址算出页号、页内偏移量
  • 页号的合法性检查(与页表长度对比,判断是否越界)
  • 若页号合法,再根据页表起始地址、页号找到对应页表项
  • 根据页表项中记录的内存块号、页内偏移量得到最终的物理地址
  • 访问物理内存对应的内存单元


在分页存储管理(页式管理)的系统中,只要确定了每个页面的大小,逻辑地址结构就确定了。因此,页式管理中地址是一维的。即,只要给出一个逻辑地址,系统就可以自动地算出页号、页内偏移量两个部分,并不需要显式地告诉系统这个逻辑地址中,页内偏移量占多少位。
具有快表(TLB)的地址变换机构(基本地址变换机构的改进)⭐

快表,又称联想寄存器(TLB, translation lookaside buffer ) ,是一种访问速度比内存快很多的高速缓存,用来存放最近访问的页表项的副本,可以加速地址变换的速度。与此对应,内存中的责表常称为慢表。
如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址 ,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。若未命中块表,再去访问内存对应的慢表。


注意区分快表慢表同时查找和先查询快表后查询慢表这两者不同的情况 的时间。



两级页表与多级页表

问题 1:页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。
问题 2:根据局部性原理可知,很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存。(解决方法:虚拟内存 ,后续详说)


问题 1 解决方法:将若干个页表项分为一组,随后,再将这些组离散的分到各个内存块中,为离散分配的页表再建立一张页表,称为页目录表 ,页目录表的内存块号,代表的就是他指向的页表后续的页号存在内存中的位置。




需注意细节:

若采用多级页表机制,则各级页表的大小不能超过一个页面 ⭐⭐⭐⭐


多级页表的访存次数(假设没有快表机构)则 N 级页表访问一个逻辑地址需要 N+ 1 次访存
基本分段存储管理



分段:分段是指在用户编程时,将程序按照逻辑划分为几个逻辑段。进程的地址空间按照程序自身的逻辑关系划分为若干个段,每个段都有一一个段名(在低级语言中,程序员使用段名来编程),每段从 0 开始编址;
内存分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。
与页表主要区别在段长不是一样的,各个段段长不一致。
段表

为每个进程建立的一张段映射表,简称“段表;类似页表,但是多了个内容--段长;



  • 每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称“基址”)和段的长度。
  • 各个段表项的长度是相同的。
    例如:某系统按字节寻址,采用分段存储管理,逻辑地址结构为(段号16位,段内地址16位),因此用16位即可表示最大段长。物理内存大小为4GB(可用32位表示整个物理内存地址空间)。因此,可以让每个段表项占16+32 =48位,即6B。由 于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为M,则K号段对应的段表项存放的地址为M+K*6
地址变换机构

段表的始址和段表长度放在进程控制块(PCB)中,当进程被调度时,操作系统会把它们放到段表寄存器中。


分段与分页管理的对比


  • 页是信息的物理单位 。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的
  • 段是信息的逻辑单位 。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名
  • 页的大小固定且由系统决定。段的长度却不固定
  • 分段比分页更容易实现信息的共享和保护。因为分页各个项大小都一致,不方便对其进行标记是否允许其他进程访问,而段表项则方便标记。
段页式管理方式



分页和分段优点的结合,先分段再分页。



  • 段号的位数决定了每个进程最多可以分几个段
  • 页号位数决定了每个段最大有多少页
  • 页内偏移量决定了页面大小、内存块大小是多少



  • 由逻辑地址得到段号、页号、页内偏移量
  • 段号与段表寄存器中的段长度比较,检查是否越界
  • 由段表始址、段号找到对应段表项
  • 根据段表中记录的页表长度,检查页号是否越界
  • 由段表中的页表地址、页号得到查询页表,找到相应页表项
  • 由页面存放的内存块号、页内偏移量得到最终的物理地址
  • 访问目标单元
对内存空间进行扩充(虚拟性)

覆盖技术(已淘汰)

将程序分为多个段(多个模块)常用的段常驻内存,不常用的段在需要时调入内存。
交换技术

就是利用中级调度(内存调度):就是要决定将哪个处于挂起状态的进程重新调入内存。什么是挂起态?[^1]
交换(swapping) 技术,即把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘。空闲进程主要存储在磁盘上,所以当它们不运行时就不会占用内存(尽管其中的一些进程会周期性地被唤醒以完成相关工作,然后就又进人睡眠状态。

  • 应该在外存的什么位置保存被换出的进程?
  • 应该什么时候交换?
  • 应该换出哪些进程?




虚拟存储技术

后续细讲
虚拟内存(virtual memory),该策略甚至能使程序在只有-部分被调入内存的情况下运行。
地址转换

操作系统负责实现逻辑地址到物理空间地址的转换。
装入方式
绝对装入
灵活性极低,只适用于单道程序环境
在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。
可重定位装入(静态重定位)
在一般情况下, 一个作业在装入时分配到的内存空间和它的地址空间是不一-致的,因此,作业在CPU上运行时,其所要访问的指令、数据的物理地址和逻辑地址是不同的。显然,若在作业装入或执行时,不对有关的地址部分加以相应的修改,则会导致错误的结果。这种将作业的逻辑地址变为物理地址的过程称为地址重定位。
静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间 ,如果没有足够的内存,就不能装入该作业。作业一旦进入内存,运行期间就不可以移动 。可重定位是指在装入时把逻辑地址转换成物理地址,但装入后不能改变
在多道程序环境下,多个目标模块的起始地址通常都从 0 开始,程序中的其他地址都是相对于起始地址的,此时应采用可重定位装入方式。根据内存的当前情况,将装入模块装入内存的适当位置。 在装入时对目标程序中指令和数据地址的修改过程称为重定位,又因为地址变换通常是在进程装入时一次完成的,故称为静态重定位。
动态运行时装入(动态重定位)
编译、链接后的装入模块的地址都是从 0 开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行 ,因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持。


内存保护


  • 在 CPU 中设置一对上、下限寄存器,存放进程的上、下限地址 。进程的指令要访问某个地址时,CPU 检查是否越界。
  • 采用重定位寄存器(又称基址寄存器)和界地址寄存器(又称限长寄存器)进行越界检查。重定位寄存器中存放的是进程的起始物理地址 。界地址寄存器中存放的是进程的最大逻辑地址
原文作者:首页 - 内核技术中文网 - 构建全国最权威的内核技术交流分享论坛
原文地址:【Linux内核】-- 内存管理基础(版权归原文作者所有,侵权留言联系删除)

回复

使用道具 举报

0

主题

6

帖子

11

积分

新手上路

Rank: 1

积分
11
发表于 2025-7-2 03:01:21 | 显示全部楼层
大人,此事必有蹊跷!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver| 手机版| 小黑屋| 问答媒体

GMT+8, 2025-7-10 13:30 , Processed in 0.111650 second(s), 24 queries .

Powered by Discuz! X3.4

Copyright © 2020, LianLian.

快速回复 返回顶部 返回列表