Linux中的内存管理机制

article/2025/10/11 13:00:29

Linux中的内存管理机制

​ 程序在运行时所有的数据结构的分配都是在堆和栈上进行的,而堆和栈都是建立在内存之上。内存作为现代计算机运行的核心,CPU可以直接访问的通用存储只有内存和处理器内置的寄存器,所有的代码都需要装载到内存之后才能让CPU通过指令寄存器找到相应的地址进行访问。

地址空间和MMU

​ 内存管理单元(MMU)是硬件提供的最底层的内存管理机制,是CPU的一部分,用来管理内存的控制线路,提供把虚拟地址映射为物理地址的能力。

​ 在x86体系结构下,CPU对内存的寻址都是通过分段方式进行的。其工作流程为:CPU生成逻辑地址并交给分段单元。分段单元为每个逻辑地址生成一个线性地址。然后线性地址交给分页单元,以生成内存的物理地址。因此也就是分段和分页单元组成了内存管理单元(MMU)。

在这里插入图片描述

​ 其中: + 虚拟地址:在段中的偏移地址 + 线性地址:在某个段中“基地址+偏移地址”得出的地址 + 物理地址:在x86中,MMU还提供了分页机制,假如没有开启分页机制,那么线性地址就等于物理地址;否则还需要经过分页机制换算后线性地址才能转换成物理地址。 一个段是由“基地址+段界限(该段长度)+类型”组成,主要确定了段的起始地址,段的界限长度和确定段的属性如是否可读、可写、段的基本粒度单位、表述该段是数据段还是代码段等。 分段允许进程的物理地址空间是非连续的,分页则是提供这一优势的另外一种内存管理方案,并且分页避免了外部碎片和紧缩,分段却不可以。在x86体系中MMU支持多级的分页模型,主要分为以下三种情况: 1. 32为系统分为2级分页模型 2. 32位系统开启了物理地址扩展模式(PAE),则分为3级分页模型 3. 64位系统分为4级分页模型 80x86的分页机制由CR0中的PG位开启,若PG=0则禁用分页机制,也就是直接将线性地址作为物理地址。32位的线性地址主要分为三个部分:

在这里插入图片描述

  • 22-31位指向页目录表中的某一项,页目录表中的每一项存有4子节地址指向页表。所以页表目录大小为4∗2^10=4K
  • 12-21位指向页表中的某一项,页表大小与页目录表相同为4K
  • 一个物理页为4K,刚好0-11位指向页表中的偏移,一个页表刚好4K

页表和页目录表可以存放在内存的任何地方,当分页机制开启后,需要让CR3寄存器指向页目录表的起始地址。

CR0-CR4这五个寄存器为系统内的控制寄存器,与分页机制密切相关。
CR0控制寄存器是一些特殊的寄存器,可以控制CPU的一些重要特性;
CR1是未定义的控制寄存器,供将来使用;
CR2是页故障线性地址寄存器,保存最后一次出现页故障的全32位线性地址;
CR3是页目录基址寄存器,保存页目录表的物理地址(页目录表总是放在4k为单位的存储器边界上,因此其低12位总为0不起作用,即使写上内容也不会被理会)
CR4在Pentium系列(包括486后期版本)处理器中才出现,处理事务包括何时启用虚拟8086模式等。

Linux中的分段与分页

​ MMU在保护模式下分段数据主要定义在GDT中。

//arch/x86/kernel/cpu/common.cDEFINE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page) = { .gdt = {
...[GDT_ENTRY_KERNEL_CS]       = GDT_ENTRY_INIT(0xc09a, 0, 0xfffff), //代码段[GDT_ENTRY_KERNEL_DS]       = GDT_ENTRY_INIT(0xc092, 0, 0xfffff), //数据段[GDT_ENTRY_DEFAULT_USER_CS] = GDT_ENTRY_INIT(0xc0fa, 0, 0xfffff),[GDT_ENTRY_DEFAULT_USER_DS] = GDT_ENTRY_INIT(0xc0f2, 0, 0xfffff),
...
} };
EXPORT_PER_CPU_SYMBOL_GPL(gdt_page);

​ 通过代码可知道这些段的基地址都是0,界限为4G。说明Linux只定义了一个段,并没有真正利用分段机制。

​ Linux中只用了一个段,而且基地址从0开始,那么在程序中使用的虚地址就是线性地址了。Linux为了兼容64位、32位及其PAE扩展情况,在代码中通过4级分页机制来做兼容。

Linux的内存分配与管理

​ 在32位的x86设备中,Linux为每个进程分配的虚拟地址空间都是0-4GB,其中

  • 0-3GB用于用户态使用

  • 3GB-3GB+896MB映射到物理地址的0-896MB处,作为内核态地址空间

  • 3GB+896MB-4GB之间的128MB空间用于vmalloc保留区域,该区域用于kmalloc、kmap固定地址映射等功能,可以让内核访问高端物理地址空间

    在这里插入图片描述

    Linux中进程的地址空间由mm_struct来描述,一个进程只会有一个mm_struct。系统中的内核态是共享的,不会发生缺页中断或者访问用户进程空间,所以内核线程的task_struct->mm为NULL。

页表的分配分为两个部分:

  1. 内核页表,也就是在系统启动中,最后会在paging_init函数中,把ZONE_DMAZONE_NORMAL区域的物理页面与虚拟地址空间的3GB-3GB+896MB进行直接映射
  2. 内核高端地址和用户态地址,都是通过MMU机制修改线性地址(虚拟地址)和物理地址的映射关系,然后刷新页表缓存来达到的
物理内存中ZONE_DMA的范围是0-16MB,该区域的物理页面专门供IO设备的DMA使用,之所以要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存不经过MMU,并且需要连续的缓冲区。为了能够提供物理上的连续缓冲区,必须从物理地址专门划分出一段区域用于DMA。 ZONE_NORMAL的范围是16MB-896MB,该区域的物理页面是内核能够直接使用的。 ZONE_HIGHMEM的范围是896MB-结束,该区域即高端内存,内核不能直接使用。

伙伴系统

​ 对于物理内存经过频繁地申请和释放后会产生外部碎片,Linux通过伙伴系统来解决外部碎片的问题。满足1.具有相同的大小;2.物理地址连续条件的两个块为伙伴。主要实现思路位伙伴系统在申请内存的时候让最小的块满足申请的需求,在归还的时候,尽量让连续的小块内存伙伴合并成大块,降低外部碎片出现的可能性。

​ 在Linux系统中伙伴系统维护了11个块链表,每个块链表分别包含了大小为20-211个连续的物理页。对1024个页的最大请求对应着4MB大小的连续RAM块。每个快的第一个页框的物理地址就是该块大小的整数倍。如大小为16个页框的块,其起始地址为16×212(212=4KB 这是一个页的大小) 的倍数。

​ 系统在初始化的时候把内各节点各区域都释放到伙伴系统中,每个区域还维护了per-cpu高速缓存来处理单页的分配,各个区域都通过伙伴算法进行物理内存的分配。

slab分配器

​ Linux系统通过伙伴算法解决了外部碎片的问题,此外还提供了slab分配器来处理内部碎片的问题。slab分配器也是一种内存预分配机制,是一种空间换时间的做法,并且其假定从slab分配器中获得的内存都是比页还小的小内存块。\

在这里插入图片描述

slab的设计思想就是把若干的页框合在一起形成一大存储块——slab,并在这个slab中只存储同一类数据,这样就可以在这个slab内部打破页的界限,以该类型数据的大小来定义分配粒度,存放多个数据,这样就可以尽可能地减少页内碎片了。在Linux中,多个存储同类数据的slab的集合叫做一类对象的缓冲区——cache。注意,这不是硬件的那个cache,只是借用这个名词而已。

Linux中slab的可分为以下三种状态:

  1. slabs_full:该链表中slab已经完全分配出去
  2. slabs_free:该链表中的slab都是空闲可分配状态
  3. slabs_partial:该链表中的slab部分已经被分配出去了

其中slab代表物理地址连续的内存块,由1-N个物理页面组成,在一个slab中可以分配多个object对象。

slab的优点:

  1. 内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题;
  2. slab 分配器还支持通用对象的初始化,从而避免了为同一目的而对一个对象重复进行初始化;
  3. slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。

slab的缺点:

  1. 较多复杂的队列管理。在slab分配器中存在众多的队列,例如针对处理器的本地缓存队列,slab中空闲队列,每个slab处于一个特定状态的队列之中。
  2. slab管理数据和队列的存储开销比较大。每个slab需要一个struct slab数据结构和一个管理者kmem_bufctl_t型的数组。当对象体积较小时,该数组将造成较大的开销(比如对象大小为32字节时,将浪费1/8空间)。同时,缓冲区针对节点和处理器的队列也会浪费不少内存。
  3. 缓冲区回收、性能调试调优比较复杂。
新版的Linux内核中已经没有slab结构体,slab的数据结构存储在page结构中,降低了slab结构数据的维护。

内核态内存管理

​ 根据之前的的Linux的内存管理机制,即伙伴系统和slab分配器。对于内核态的内存分配主要通过函数kmallocvmalloc完成。

在这里插入图片描述

​ 其中kmalloc函数可以为内核申请连续物理地址的内存空间,由于kmalloc是基于slab分配器实现的,所以比较适合较小块的内存申请。kmalloc函数的调用过程为:kmalloc->__kmalloc->__do_kmalloc,其中__do_kmalloc的实现主要分为两步:

  1. 通过kmalloc_slab找到一个合适的kmem_cache缓存
  2. 通过slab_alloc向slab分配器申请对象内存空间

​ Linux提供的vmalloc函数可以获得连续的虚拟空间,但是其物理内存不一定连续。vmalloc函数的调用过程为:vmalloc->__vmalloc_node_flags->__vmalloc_node->__vmalloc_node_range。其中__vmalloc_node_range函数也分为两步:

  1. 通过__get_vm_area_node分配一个可用的虚拟地址空间
  2. __vmalloc_node_range通过alloc_pages一页一页申请物理内存,再为刚才申请的虚拟地址空间分配物理页表映射

http://chatgpt.dhexx.cn/article/3L9A0V2y.shtml

相关文章

Linux - 内存管理

【1】前言 内存管理是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。 内存管理是操作系统很重要的一部分。作为一个后端开发来说,了解操作系统是如何进行内存…

Linux的内存管理

Linux的内存管理 Linux的内存管理是一个非常复杂的过程,主要分成两个大的部分:内核的内存管理和进程虚拟内存。内核的内存管理是Linux内存管理的核心,所以我们先对内核的内存管理进行简介。 一、物理内存模型 物理内存模型主要分为两种&…

Linux内存管理(上)

Linux内存管理(上) 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存地使用方法。力求从外自内、水到渠成地引导网友分析Linux地内存管理与使用。在本章最后我们给出一个内存映射…

【纯干货】Linux内存管理(最透彻的一篇)

摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux的内存管理与使用。在本章最后,我们给出一个内存映射的实例…

Linux内存管理方式

目录 前言 内存管理方式 分段式 分页式 段页式 虚拟地址如何映射到物理地址 缺页中断 内存交换 内存置换算法 前言 之前说过linux中的程序地址空间是使用的虚拟地址,虚拟地址和真实的物理地址有着某种特殊的映射关系(MMU,全称Memory Ma…

一文讲透Linux内存管理

一、Linux内存管理概述 Linux内存管理是指对系统内存的分配、释放、映射、管理、交换、压缩等一系列操作的管理。在Linux中,内存被划分为多个区域,每个区域有不同的作用,包括内核空间、用户空间、缓存、交换分区等。Linux内存管理的目标是最…

Linux内存管理(一):内存管理概述

首先明确下面几个概念: 程序(Program):一组指令的有序集合,是静态的实体。进程(Process):执行程序后,操作系统将程序的可执行文件和它的相关依赖加载到内存中,得到的动态的实体称为进程。 程序和进程并不…

史上最全linux内存管理

Linux内存结构 Node 首先, 内存被划分为结点. 每个结点关联到系统中的一个处理器,内核中表示为pg_data_t的 实例. 系统中每个节点被链接到一个以NULL结尾的pgdat_list链表中<而其中的每个节点利用pg_data_tnode_next字段链接到下一节&#xff0e;而对于PC这种UMA结构的机…

一文掌握 Linux 内存管理

作者&#xff1a;dengxuanshi&#xff0c;腾讯 IEG 后台开发工程师 以下源代码来自 linux-5.10.3 内核代码&#xff0c;主要以 x86-32 为例。 Linux 内存管理是一个很复杂的“工程”&#xff0c;它不仅仅是对物理内存的管理&#xff0c;也涉及到虚拟内存管理、内存交换和内存回…

linux内存管理(一)-内存管理架构

文章目录 一、内存管理架构二、虚拟地址空间布局架构2.1内核地址空间布局2.2用户地址空间布局 三、物理内存体系架构3.1 正常内存3.2 设备内存四、内存结构五、内存模型六、虚拟地址和物理地址的转换七、页表八、内存映射原理分析 一、内存管理架构 内存管理子系统架构可以分为…

Linux内存管理(最透彻的一篇)

【转】Linux内存管理&#xff08;最透彻的一篇&#xff09; 摘要&#xff1a;本章首先以应用程序开发者的角度审视Linux的进程内存管理&#xff0c;在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux的内存管…

Linux内存管理机制(最透彻的一篇)

摘要&#xff1a;本章首先以应用程序开发者的角度审视Linux的进程内存管理&#xff0c;在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux的内存管理与使用。在本章最后&#xff0c;我们给出一个内存映射的实例…

概述 - Linux内存管理(一)

内存管理是从单板上电运行uboot启动引导linux并完成文件系统挂载&#xff08;文件系统管理Nandflash&#xff09;过程前两个环节都需要完成的重要工作&#xff0c;并且随着程序推进的内存管理也逐渐完善起来。如果一步到位直接编写一个非常完整的内存管理系统&#xff0c;这个过…

linux 内存管理

目录 1 Linux内存管理概述 1.1 内存的层次结构 1.2 虚拟内存概述 1.2.1 虚拟内存基本思想 1.2.2 进程虚拟地址空间 1.3 内核空间到物理空间的映射 1.3.1 内核空间的线性映射 1.3.2 内核镜像的物理存储 1.4 虚拟内存实现机制 2 进程用户空间管理 2.1 进程用户空间布局…

linux内存管理

内存管理 一、相关概念 ● 虚拟内存&#xff1a;内存管理的一种技术&#xff0c;它使得应用程序认为它拥有连续的可用内存&#xff08;一个连续完整的地址空间&#xff09;&#xff1b; ● 物理内存&#xff1a;相对于虚拟内存而言&#xff0c;指通过物理内存条而获得的内存空间…

Linux中内存管理详解

Linux中内存管理 内存管理的主要工作就是对物理内存进行组织&#xff0c;然后对物理内存的分配和回收。但是Linux引入了虚拟地址的概念。 虚拟地址的作用 如果用户进程直接操作物理地址会有以下的坏处&#xff1a; 1、 用户进程可以直接操作内核对应的内存&#xff0c;破坏内…

Linux 内存管理 详解(虚拟内存、物理内存,进程地址空间)

Linux -操作系统内存管理 存储系统存储器的层次结构 Linux的内存管理物理内存物理内存管理 虚拟内存虚拟地址空间(写时拷贝) 和物理地址映射关系页表虚拟内存优缺点 「在 4GB 物理内存的机器上&#xff0c;申请 8G 内存会怎么样&#xff1f;」 计算机硬件的五大组成部分为&…

逆向汇编与反汇编——汇编基础快速入门

一、常用32位寄存器介绍 不同位数的寄存器的名称&#xff1a; eax&#xff1a;累加寄存器。通常用于算数运算&#xff0c;将结果保留在eax当中&#xff0c;当然也可以用于其他用途&#xff0c;比如一般把返回值通过eax传递出去。 ebx&#xff1a;基址寄存器 。有点类似于ebp…

汇编语言(四)——编程语法入门

目录 0.第一个汇编程序 1.语言常量 &#xff08;1&#xff09;整数常量 &#xff08;2&#xff09;实数常量 &#xff08;3&#xff09;字符常量 2.保留字 3.标识符 4.伪指令 5.指令 &#xff08;1&#xff09;标号 &#xff08;2) 指令助记符 &#xff08;3&#…

汇编语言轻松入门

​通常说的学习编程其实就是学习高级语言编程&#xff0c;比如C语言、C语言、Python语言、JAVA语言等等&#xff0c;即那些为人类设计的计算机语言。 但是&#xff0c;我们的计算机它并不理解什么是高级语言&#xff0c;计算机只是一个机器&#xff0c;它只有电气的特性&#…