【内存管理】内存管理概述

文章目录

    • 内存管理硬件结构
      • 早期内存的使用方法
      • 分段
      • 分页
      • 逻辑地址,线性地址(intel架构)
      • 虚拟地址
      • 物理地址
      • 结构图
    • 虚拟地址到物理地址的转换
    • 内存管理总览
      • 系统调用
      • vm_area_struct
      • 缺页中断
      • 伙伴系统
      • slab分配器
      • 页面回收
      • 反向映射
      • KSM
      • huge page
      • 页迁移
      • 内存规整
      • OOM
    • 内存管理的一些数据结构
      • 线性映射
      • struct page
      • zone
    • 进程角度看内存管理

内存管理硬件结构

常见的内存分配函数有malloc,mmap等,但大家有没有想过,这些函数在内核中是怎么实现的?换句话说,Linux内核的内存管理是怎么实现的?

内存管理的目的是管理系统中的内存,俗称内存桥,换成专业属于叫DDR。我们有必要先了解下计算机对内存管理的硬件结构。我们先看下关于地址的一些概念。

早期内存的使用方法

在计算机早期的发展阶段,要运行一个程序,要把计算机程序,全部装载在内存中,程序访问的内存地址就是实际的物理地址。所以,当运行多个程序时,必须保证运行程序的使用的总的内存量要小于总的内存大小。那这种方式存在什么问题呢?

一个问题是进程地址空间不合理,任意的进程可以随意修改其他进程的地址数据;二是内存使用效率很低,内存紧张时需要把整个进程交换到交换分区中,导致程序的使用效率很低。

分段

为了解决这两个问题,当时的人们提出了分段的机制。它的核心思想是建立一个 虚拟地址空间,将一个程序分成代码段,数据段,堆栈段什么的,每个段各自管理不同的数据。在虚拟地址空间和物理地址空间之间做映射,实现进程的隔离。

分页

在分段机制中,程序也是全部装载在内存中的,效率也很低。这个时候就提出了分页机制:分页这个技术仍然是一种虚拟地址空间到物理地址空间映射的机制。但是,粒度更加的小了。单位不是整个程序,而是某个“页”,一段虚拟地址空间组成的某一页映射到一段物理地址空间组成的某一页。

程序在运行的时候,需要哪个页面,我再把相关页面交换进来。经常不用的页面会交换到swap分区。分页机制也是按需分配,这是操作系统的核心思想。

逻辑地址,线性地址(intel架构)

逻辑地址和线性地址是intel架构的概念,逻辑地址是程序产生的和段相关的那个部分,线性地址是逻辑地址转换为物理地址的一个中间层。

在分段的方式中,逻辑地址是段的偏移地址,再加上基地址就是线性地址了。如果是做arm架构的,可以不用关注这部分。

虚拟地址

简单的说就是可以寻址的一片空间。如果这个空间是虚拟的,我们就叫做虚拟地址空间;如果这个空间是真实存在的,我们就叫做物理地址空间。虚拟地址空间是可以任意的大的,因为是虚拟的。而物理地址空间是真实存在的,所以是有限的

物理地址

物理地址是CPU通过外部总线直接访问的外部内存地址。如果系统启动了分页机制,系统启动后必须通过查页表的方式去获取物理地址。

如果没有启动分页机制,系统启动后就通过直接变为了物理地址。

结构图

在启动MMU后,CPU访问的是虚拟地址,虚拟地址经过MMU后转换为物理地址,这种转换通过查询存储在主存储器的页表完成。频繁访问主存储器比较耗时,因此引入了TLB的概念。

TLB缓存了上一次虚拟地址到物理地址的转换,TLB不存储具体的数据,存储的是页表的表项。如果能在TLB中找到本次访问的页表项,就不需要再访问主存了。我们把这个过程叫做TLB命中。如果没有找到页表项,这个时候只能去查询页表,我们叫做TLB Miss。如何查询页表的后面我们会详细介绍。

假设,现在虚拟地址已经转换为了物理地址。这个时候就会去找一级缓存。看一级缓存有没有需要的数据。我们这里采用的是物理索引(PI),物理标签(PT)的方式。现在的大部分cache都采用组相联的方式,访问cache地址会被分为偏移域,索引域,标记域三部分。如果一级缓存没有相应的数据,就要访问二级缓存了,如果二级缓存没有数据,就要访问主存储器了。

还有一种情况,当系统物理内存短缺的时候,Linux内核中,有页面回收的机制,会把不常用的页面交换到swap分区中,这个动作叫做swap。这张图就从硬件结构的角度解释了内存管理的基本构成。

虚拟地址到物理地址的转换

虚拟地址的32个bit位可以分为3个域,最高12bit位20~31位称为L1索引,叫做PGD,页面目录。中间的8个bit位叫做L2索引,在Linux内核中叫做PT,页表。最低的12位叫做页索引。

在ARM处理器中,TTBRx寄存器存放着页表基地址,我们这里的一级页表有4096个页表项。每个表项中存放着二级表项的基地址。我们可以通过虚拟地址的L1索引访问一级页表,访问一级页表相当于数组访问。

二级页表通常是动态分配的,可以通过虚拟地址的中间8bit位L2索引访问二级页表,在L2索引中存放着最终物理地址的高20bit位,然后和虚拟地址的低12bit位就组成了最终的物理地址。以上就是虚拟地址转换为物理地址的过程。

MMU访问页表是硬件实现的,但页表的创建和填充需要Linux内核来填充。通常,一级页表和二级页表存放在主存储器中。

内存管理总览

系统调用

Linux内核把用户空间分为两部分:用户空间和内核空间。用户进程运行在用户空间,如果需要内存的话通过C库提供的mallocmmapmlockmadvicemremap函数。C库的这些函数最终都会调用到内核的sys_xxx接口分配内存空间。如malloc函数是依赖内核的sys_brk接口分配内存空间的。mmap对应接口为sys_mmap

我们以malloc函数为例,假设现在用户态的内存短缺,就会通过sys_brk调用去堆上分配内存。在用户空间分配的是虚拟内存,因此,在堆上分配的也是虚拟内存。

vm_area_struct

Linux内核把这些地址称为进程地址空间。内核使用struct vm_area_struct 来管理这些进程地址空间。VMA主要管理内存的创建,插入,删除,合并等操作。

由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:

vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。

缺页中断

缺页中断是实现了按需分配的思想。站在用户角度,缺页中断后可分配的页面有匿名页面和page cache。匿名页面指的是没有关联任何文件的页面,比如进程通过mlock从堆上分配的内存。page cache是关联了具体缓存的页面。比如在看视频时的缓存就是page cache。匿名页面和page cache的产生需要页面分配器完成。

伙伴系统

页面分配器是以页框为单位的。典型的页面分配器就是伙伴系统。伙伴系统是一个结合了2的方幂个分配器和空闲缓冲区合并计技术的内存分配方案, 其基本思想很简单。

内存被分成含有很多页面的大块, 每一块都是2个页面大小的方幂。 如果找不到想要的块, 一个大块会被分成两部分, 这两部分彼此就成为伙伴。 其中一半被用来分配,而另一半则空闲。 这些块在以后分配的过程中会继续被二分直至产生一个所需大小的块。 当一个块被最终释放时, 其伙伴将被检测出来,如果伙伴也空闲则合并两者。

虽然伙伴算法实现不复杂,但页面分配器是内核实现最复杂的系统之一。如果内存充足时,你需要多少内存,页面分配器会给你分配多少。但如果内存紧张时,页面分配器会做很多尝试,比如开启异步模式的页面回收,memory compaction(内存规整)。如果经过尝试后内存仍然不够,这个时候会拿出重型武器oom kill会杀死一些进程。

slab分配器

刚刚我们讲的都是以页为单位分配的内存。但有时候我们需要几个字节的内存怎么办。这个时候就需要slab分配器。slab可以管理特定大小的内存,对于固定大小的内存就不需要VMA去管理了。页面分配器是中央财政,slab是地方财政。如果地方需要种棵树就不要劳烦中央财政了。

页面回收

页面回收实现了页面换出的理念。当系统内存短缺的时候,系统需要换出一部分内存。这部分内存通常是page cache 或者匿名页面。内核里面有个swap守护线程,当系统内存低于某个水位时,会被唤醒去扫描LRU(最近最少使用)链表,一般匿名页面和page cache会添加到链表中。实际上,在内核中又将LRU链表做了细分,又细分为活跃链表,不活跃链表,匿名页面链表,page cache链表。

内核相对比较喜欢回收page cache,干净的page cache 直接合并就好了。对于脏的page cache需要写回磁盘的一个动作。对于匿名页面是不能直接合并的,匿名页面一般都是进程的私有数据。一般这些匿名页面数据需要回收时会swap out 到swap分区腾出空间,当这些进程再次需要这些数据时,才会从swap分区swap in。页面回收我们会在后面详细讲解。

如果分配好了页面,这个时候就要涉及到页表的管理了。页表分为内核页表和进程页表。内核提供了很多和内核页表相关的函数,后续我们再分析。

再往下分析就是硬件层,比如MMU,TLB,cache,物理内存等,对于这部分我们不做深入分析。

反向映射

当进程分配内存并发生写操作时,会分配虚拟地址并产生缺页,进而分配物理内存并建立虚拟地址到物理地址的映射关系, 这个叫正向映射。

反过来, 通过物理页面找到映射它的所有虚拟页面叫反向映射(reverse-mapping, RMAP),它可以从page数据结构中找到映射这个page的虚拟地址空间,也就是我们讲过的VMA这个东西,ramp系统是为页面回收服务的,如果要回收一个匿名页面或者page cache的时候, 需要把映射这个页面的用户PTE断开映射关系才可以去回收。

KSM

KSM,Kernel Samepage Merging,最早是用来优化KVM虚拟机来发明的一种机制。现在用来合并内容相同的匿名页面。

huge page

huge page,通常用来分配2M或者1G大小的页,目前在服务器系统中用的比较多。使用huge page可以减少TLB miss的次数,假如现在需要2M的页面,一个page是4K,最坏的情况下需要TLB miss 5次,如果使用2M的页面,只需要TLB miss 1次。每次TLB miss 对系统的损耗很大。

页迁移

页迁移,内核中有些页面是可以迁移的,比如匿名页面。页迁移在内核很多模块都被广泛使用,比如memory compaction(内存规整)。

内存规整

memory compaction,内存规整模块是为了缓解内存碎片化的,系统运行的时间越长,就越容易产生内存碎片,系统此时想分配连续的大块内存就变得越来越难。

大块连续的内存一般是内核所请求的,因为对于用户空间来讲,大块缺页内存都是通过缺页中断一块一块来分配的。

内存规整的实现原理也不复杂,在一个zoom中有两个扫描器,分别从头到尾和从尾到头扫描,一个去查找zoom中有那些页面可以迁移的,另外一个去扫描有那些空闲的页,两个扫描器在zoom中相遇的时候,扫描就停止了。这个时候内存规整模块就知道zoom中有那些页面可以迁移到空闲页面。经过这么一折腾,就可以腾出一个大的连续的物理空间了。

OOM

在经过内存规整,页面迁移等操作后,如果系统还不能分配出系统需要的页面,Linux就要使用最后一招了,杀敌一千,自损八百,OOM killer会找一些占用内存比较多的进程杀掉来释放内存。

之所以会发生这种情况,是因为Linux内核在给某个进程分配内存时,会比进程申请的内存多分配一些。这是为了保证进程在真正使用的时候有足够的内存,因为进程在申请内存后并不一定立即使用,当真正使用的时候,可能部分内存已经被回收了。

比如 当一个进程申请2G内存时,内核可能会分配2.5G的内存给它.通常这不会导致什么问题。然而一旦系统内大量的进程在使用内存时,就会出现内存供不应求,很快就会导致内存耗尽。这时就会触发这个oom killer,它会选择性的杀掉某个进程以保证系统能够正常运行。

内存管理的一些数据结构

线性映射

我们以32位系统为例,我们知道进程最大的地址访问空间是4G,0~3GB是用户空间,3 ~ 4GB是内核空间。

如果物理空间是大于1GB,内核空间如何访问大于1GB的空间呢?站在内核的角度,低地址段是线性映射,高地址段是高端映射。

那线性映射和高端映射是如何划分的呢?不同的体系结构有不同的划分方法。在ARM32中是线性映射大小为760M。线性映射就是直接把物理地址空间映射到3G ~ 4G的地址空间,这段映射关系就变得比较简单了,内核访问时直接使用虚拟地址减去偏移量(page offset)就得到物理地址了。

如果要访问高端内存就麻烦一点,1G的物理内存空间有限,不能把所有地址都映射到线性地址空间。如果要访问高端内存就要通过动态映射的方式访问了。

struct page

struct page数据结构是用来抽象物理页面的。这个数据结构很重要,很多内核代码都是围绕这个struct page 展开的。

此外还有个很重要的mem_map[]数组,是用来存放每一个struct page数据结构的。通过数组,我们可以很方便的通过page找到页帧号,页帧号全称叫page frame number ,pfm。

zone

除了page结构,还有个很重要的数据结构叫zone。前面讲到了物理内存划分为两部分,线性映射和高端内存。zone也是根据这个来划分的。线性映射部分叫zone normal,高端内存区域叫zone high。

页面分配器和页面回收都是基于zone来管理的。zone 也是一个很重要的管理物理内存的数据结构。

进程角度看内存管理

看完物理内存的管理结构,接下来从进程的角度看下虚拟内存是怎么管理的。

用户空间有3G的大小,这3GB的大小也做了划分,0 ~ 1GB 属于代码段,数据段,堆空间。 1G ~ 3G 属于mmap空间。

每个进程都有一个管理进程的数据结构,操作系统中叫做PCB,进程控制块,linux内核中就用task_struct描述进程控制块,task_struct内容非常多,后面我们会详细讲解,今天我们只关注mm成员。

mm成员会指向mm_struct描述进程管理的内存资源,我们这里只关注mmap,pgd。 mmap指向该进程的VMA的链表。我们知道进程地址空间使用VMA来管理,VMA是离散的,所以内核使用两种方式来管理VMA:链表和红黑树。

pgd指向进程所在的页表,这里指的是进程的页表,进程的一级页表在fork的时候创建,进程的二级页表在实际使用的时候动态创建,

以上这张图就从进程的角度讲述了内存管理的概貌。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/25663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[AI Google] 使用 Gemini 取得更多成就:试用 1.5 Pro 和更多智能功能

总结 Google 正在为超过 35 种语言的 Gemini Advanced 订阅者推出 Gemini 1.5 Pro。此次更新包括 100 万个 token 的上下文窗口、改进的数据分析功能和增强的多模态图像理解。新功能包括用于自然对话的 Gemini Live、先进的规划工具和可定制的 Gems。更新还集成了更多 Google …

【MySQL】(基础篇五) —— 排序检索数据

排序检索数据 本章将讲授如何使用SELECT语句的ORDER BY子句,根据需要排序检索出的数据。 排序数据 还是使用上一节中的例子,查询employees表中的last_name字段 SELECT last_name FROM employees;输出结果: 发现其输出并没有特定的顺序。其实&#xf…

电子电气架构 --- 信息安全测试模糊测试

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

Objective-C的初始化方法中,应该如何读写属性

除非有明确的原因需要使用setter, getter, 否则总是应该直接访问, 也就是直接使用实例变量(也称为 iVar)来读写数据 理由: 避免子类覆盖setter方法的影响:若在初始化方法中使用setter方法, 使用此方法实例化子类, 可能会调用子类…

纯理论容器实现的原理

近期在复习容器的原理,希望这篇文章可以帮助到大家。 一、什么是容器? 容器本质上就是主机上的一个进程。这个进程拥有自己的用户空间并且和主机共享内核空间。 容器内的进程可以通过系统调用与内核进行交互,使用内核提供的各种功能和资源。…

刷代码随想录有感(99):动态规划——使用最小花费爬楼梯

题干&#xff1a; 代码&#xff1a; class Solution { public:int minCostClimbingStairs(vector<int>& cost) {vector<int>dp(cost.size() 1);dp[0] 0;dp[1] 0;for(int i 2; i < cost.size(); i){dp[i] min(dp[i - 1] cost[i - 1], dp[i - 2] cost…

Leetcode 力扣114. 二叉树展开为链表 (抖音号:708231408)

给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。 示例 1&#xf…

KUKA机器人中断编程详细教程1—了解中断

在公众号查看更多内容。 在KUKA机器人编程与调试中&#xff0c;经常会用到中断编程。通过中断实现机器人暂停&#xff0c;或者停止当前的动作进入中断后的程序中接着运行&#xff0c;以此来满足实际的调试要求。 1、中断的概念 ①当出现诸如输入等定义的事件时&#xff0c;…

【算法篇】求最长公共前缀JavaScript版本

题目描述 给你一个大小为 n 的字符串数组 strs &#xff0c;其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀&#xff0c;返回这个公共前缀。 数据范围&#xff1a; 数据范围:0<n<5000&#xff0c;0<len(strsi)< 5000 进阶:空间复杂度 O(1)&a…

Typora Markdown编辑器 for Mac v1.8.10 安装

Mac分享吧 文章目录 效果一、准备工作二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2. 应用程序显示软件图标&#xff0c;表示安装成功 三、运行调试1、修改主题2、显示文档列表&#xff0c;如下图3、查看版本信息 **安装完成&…

【PR2019】怎样批量添加转场效果及修改默认持续时间

一&#xff0c;设置“交叉溶解”效果到所有素材 选择效果&#xff0c;右击“将所选过渡设置为默认过渡”&#xff1a; 框选所有素材&#xff0c;“Ctrl D”&#xff1a; 每个素材中间有有了交叉溶解的效果&#xff1a; 二&#xff0c;修改效果属性 2.1&#xff0c;单个修…

北航第五次数据结构与程序设计编程题复习

北航第五次数据结构与程序设计编程题复习 树叶节点遍历&#xff08;树-基础题&#xff09;计算器&#xff08;表达式计算-表达式树实现&#xff09;服务优化词频统计&#xff08;树实现&#xff09; 树叶节点遍历&#xff08;树-基础题&#xff09; 【问题描述】 从标准输入中…

CTFHUB-SQL注入-报错注入

目录 报错注入概述 报错注入的原理 报错注入的步骤 报错注入的常用函数 实战案例 结论 方法1&#xff1a;updatexml函数 查看数据库名 查看表名 查看表中数据 方法2&#xff1a;extractvalue函数 查看数据库名 查看数据库中的表名 查看表中字段名 查看表中数据 报…

第2回 从0x7c00到0x90000

将数据段寄存器ds的值变成了0x07c0,方便了之后访问内存时利用这个段基址进行寻址,接下来,我们带着这两行代码继续往下看6行: 此时ds寄存器的值已经是0x07c0了,然后用同样的方式将es寄存器的值变成0x9000,接着又把cs寄存器的值变成256。 好的,此时ds,es,cx寄存器的值,都…

CleanMyMac2024最新免费电脑Mac系统优化工具

大家好&#xff0c;我是你们的好朋友——软件评测专家&#xff0c;同时也是一名技术博主。今天我要给大家种草一个超级实用的Mac优化工具——CleanMyMac&#xff01; 作为一个长期使用macOS的用户&#xff0c;我深知系统运行时间长了&#xff0c;缓存文件、日志、临时文件等都会…

LLVM Cpu0 新后端5 静态重定位 动态重定位

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

每日一题——Python实现PAT乙级1019 数字黑洞(举一反三+思想解读+逐步优化)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 点评代码的优缺点&#xff1a; 时间复杂度&#xff1a; 空间复杂度&#…

【类型商店】字符字符串(下)

啊&#xff0c;哈喽&#xff0c;小伙伴们大家好。我是#Y清墨&#xff0c;今天呐&#xff0c;我要介绍的是字符与字符串。 导语 前两期&#xff0c;我们已经懂得了概念&#xff0c;今天来看些函数。 正题 一.增加或连接 &#xff08;1) 后面增加() string s1,s2; //定义 s…

Meta首席AI科学家Yann LeCun指出生成式AI的不足

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

express入门01服务器搭建以及get和post请求的监听

微搭提供了后端API的能力&#xff0c;但是不同的版本收费差别巨大&#xff0c;因为使用的门槛限制了中小企业使用低代码平台。那可不可以既要又要呢&#xff1f;答案是肯定的&#xff0c;那其实掌握一定的后端框架&#xff0c;借助我们在低代码中已经熟练掌握的技能其实是比较容…