虚拟地址空间:用户空间和内核空间物理内存管理:伙伴系统以及slab分配器

贺二公子 贺二公子     2022-12-05     443

关键词:

原文地址:https://blog.csdn.net/HUAERBUSHI521/article/details/118599134


文章目录

一.虚拟地址空间

直接使用物理内存面临的问题:

  1. 内存缺乏访问控制,安全性不足
  2. 各进程同时访问物理内存,可能会互相产生影响,没有独立性
  3. 物理内存极小,而并发进程所需又大,容易导致内存不足
  4. 进程所需空间不一,容易导致内存碎片化问题

基于以上几种原因,Linux通过mm_struct结构体描述了一个虚拟的,连续的,独立的地址空间.也就是所说的虚拟地址空间.

程序运行时,在建立了虚拟地址空间后,并没有分配实际的物理内存,而是当进程需要实际访问内存资源的时候就会由内核的请求分页机制产生缺页中断,这时才会建立虚拟地址和物理地址的映射,调入物理内存页.通过这种方式,就能够保证我们的物理内存只有在实际使用时才进行分配,避免了内存浪费的问题.

二.虚拟地址空间分布


在linux中,虚拟地址空间的内部又被划分为用户空间内核空间.

2.1 内核态与用户态的理解

  • 操作系统:是管理计算机硬件与软件资源的终端机及程序
  • 内核态:本质是一种特殊的软件程序,控制计算机的硬件资源,比如:协调CPU资源,分配内存资源,并且提供稳定的环境供应用程序运行.
  • 用户态:提供应用程序运行的空间,为了使应用程序能够访问的内核管理的资源
  • 系统调用:是操作系统最小的功能单位,是用户态和内核态交互的基本接口.
  • 库函数:实际是对系统调用进行封装,提供简单的基本接口给用户.屏蔽了复杂的底层实现细节,增强了程序的灵活性. 库函数根据不同的标准有不同的标准:glibc库,posix库
  • 内核态和用户态的区别本质是:权限不同
  • 从用户态到内核态切换的三种方式:系统调用,异常,外设中断

2.2 用户空间

  • :又叫堆栈,用于存放非静态局部变量,函数参数,返回值等,栈是向下增长的.每当一个函数被调用时,就会将参数压入进程调用栈中,调用结束后返回值也会被放回栈中。同时,每调用一次函数就会创建一个新的栈,所以在递归较深时容易导致栈溢出。栈内存的申请和释放由编译器自动完成,并且栈容量由系统预先定义。
  • 内存映射段: 是高效的I/O映射方式,用于装载一个共享的动态内存库.用户可使用系统接口创建共享内存,做进程间通信.
  • :用于存放程序运行时动态内存分配,堆内存由用户申请分配和释放,堆是向上增长的
  • BSS段:用来存放程序中未初始化的全局变量和静态变量
  • 数据段:用来存放程序中已初始化全局变量与静态变量
  • 代码段代码段用来存放程序执行代码,也可能包含一些只读的常量。这块区域的大小在程序运行时就已经确定,并且为了防止代码和常量遭到修改,代码段被设置为只读

2.3 内核空间

内核空间即进程陷入内核态后才能访问的空间.虽然每个进程都具有自己独立的虚拟地址空间,但是这些虚拟地址空间中的内核空间(前896M),其实关联的都是同一块物理内存.

通过这种方法,保证了进程在切换至内核态后能够快速的访问内核空间.

内核空间虚拟地址X 对于的物理内存地址:X-0xc0000000

内核空间虚拟地址分布:

内核空间主要分为直接映射区高端内存映射区.

  • 直接映射区
    • 从内核空间起始位置开始,从低地址往高地址增长,最大为896M的区域即为直接映射区。
    • 直接映射区的896M的虚拟地址与物理地址的前896M进行直接映射.因此虚拟地址和分配的物理地址都是连续的。
    • 相互转换: 偏移量PAGE_OFFSET:0xC0000000 物理地址 = 虚拟地址-PAGE_OFFSET
  • 高端内存映射区
    • 内核空间大小只有1G,但是物理内存可不止1G.内核空间利用直接映射区将896M的内存直接映射到物理内存中,那么剩下的物理内存寻址工作,就交给了高端内存映射区.
    • 将剩下的128M的空间划分为三个高端内存的映射区,从上往下分别是:
      • 固定内存映射区: 该区域的每个地址项都服务于特定的用途
      • 永久内存映射区:该区域可以访问高端内存。使用alloc_page(_GFP_HIGHMEM)分配高端内存页,或者使用kmap将分配的高端内存映射到该区域
      • 动态内存映射区:该区域的特点是虚拟地址连续,但是其对应的物理地址并不一定连续。该区域使用内核函数vmalloc进行分配,分配的虚拟地址的物理页可能会处于低端内存,也可能处于高端内存

三.虚拟地址空间的映射

3.1 物理内存分页

在Linux系统中,通过分段分页的机制,将物理内存划分为4k大小的内存页,并且将页作为物理内存分配与回收的基本单位.

内核会为每一个物理页帧创建一个struct page结构体,其中包含的重要信息有:

  • flags:描述page的状态和其它信息
  • index: 在映射的虚拟空间(vma_area)内的偏移
  • lru: 链表头,用于各种链表上维护该页,以便于按页将不同类别分组

3.2 管理区页框分配器

Linux内核通过一个管理区页框分配器管理着物理内存上所有的页框,在管理分配器里的核心系统就是伙伴系统和每CPU高速缓存. 在linux系统中,管理区页框分配器管理着所有物理内存,无论是内核还是用户进程,需要将一些内存占为己有时,都需要请求管理区页框分配器,这时才会给你分配物理内存页框.

3.3 vm_area_structs[区域结构链表]

一个具体的区域结构vm_area_struct包含的重要字段:

  • vm_start: 指向这个区域的起始处
  • vm_end: 指向这个区域的结束处
  • vm_port: 描述这个区域内包含的所有页面的读写许可权限
  • vm_falgs: 描述这个区域内的页面是否是与其它进程共享的,还是这个进程私有的
  • vm_next: 指向链表中的下一个区域结构

    每一个 vm_area_struct 结构体描述了一片特定的虚拟地址空间.

四.物理内存的管理

4.1 内存碎片问题

在Linux中,通过分段和分页的机制,将物理内存划分为4K大小的内存页,并且将页作为物理内存分配与回收的基本单位.通过分页机制可以灵活的对内存进行管理.

  1. 如果用户申请小块内存,可以直接分配一页给用户,就可以必秒因为频繁的申请,释放小块内存而发起的系统调用带来的消耗
  2. 如果用户申请了大块内存,可以将多个页框组合成一大块内存后再进行分配,非常灵活.

但是这种直接的内存分配存在着大量的问题,非常容易导致内存碎片的问题.下面就分别介绍两种内存碎片:内部碎片和外部碎片.

外部碎片:

当我们需要分配大块内存的时,操作系统会将连续的页框组合起来形成大块内存,再将其分配给用户.但是频繁的申请和释放内存页,就会带来内存外碎片的问题.

当需要分配大块内存的时候,要用好几页组合起来才够,而系统分配物理内存页的时候会尽量分配连续的内存页面,频繁的分配与回收物理页导致大量的小块内存夹杂在已分配页面中间,形成内存外碎片.

内部碎片:

由于页是物理内存分配的基本单位,因此即使我们需要的内存很小,Linux也会至少给我们分配4K的内存页.

倘若我们需求的只有几个字节,那么该内存中有大量的空间未被使用,造成了内存浪费的问题.而我们频繁进行小块内存的申请,这种浪费现象就会愈发严重,这也就是内存内碎片的问题.

4.2 伙伴系统(buddy system)

要想解决内存外碎片的问题,无非就两种方法

  1. 内存外碎片问题的本质就是空间不连续,所以可以将非连续的空闲页框映射到连续的虚拟地址空间
  2. 记录现存的空闲连续页框块的情况,尽量避免为了满足小块内存的请求而分割大的空闲块。

Linux选择了第二种方法来解决这个问题,即引入伙伴系统算法,来解决内存外碎片的问题。

伙伴系统就是把相同大小的连续空闲页框块用链表串起来,这样页框之间看起来就像是手拉手的伙伴,这也是其名字的由来.

伙伴系统将所有的空闲页框分组为11块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块,即2的0~10次方,最大可以申请1024个连续页框,对应4MB(1024*4K)大小的连续内存.每个页框块的第一个页框的物理地址应该是该块大小的整数倍.

因为任何正整数都可以由 2^n 的和组成,所以我们总能通过拆分与合并,来找到合适大小的内存块分配出去,减少了外部碎片产生 。

4.3 slab分配器

伙伴系统很好的解决了内存外碎片的问题,但是它还是以页作为内存分配和释放的基本单位.而我们在实际的应用中则是以字节为单位.例如我们申请2个字节的空间,但是其还是会向我们分配一页,也就是4096字节的内存,因此还是会存在内存碎片的问题.

为了解决这个问题,slab分配器就应运而生了。其以字节为基本单位,专门用于对小块内存进行分配。slab分配器并未脱离伙伴系统,而是对伙伴系统的补充,它将伙伴系统分配的大内存进一步细化为小内存分配。

对于内核对象,生命周期通常是这样的:分配内存->初始化->释放内存。而内核中如文件描述符、pcb等小对象又非常多,如果按照伙伴系统按页分配和释放内存,不仅存在大量的空间浪费,还会因为频繁对小对象进行分配-初始化-释放这些操作而导致性能的消耗。

所以为了解决这个问题,对于内核中这些需要重复使用的小型数据对象,slab通过一个缓存池来缓存这些常用的已初始化的对象。

当我们需要申请这些小对象时,就会直接从缓存池中的slab列表中分配一个出去。而当我们需要释放时,我们不会将其返回给伙伴系统进行释放,而是将其重新保存在缓存池的slab列表中。通过这种方法,不仅避免了内存内碎片的问题,还大大的提高了内存分配的性能。

下面就由大到小,来画出底层的数据结构

kmem_cache是一个cache_chain的链表,描述了一个高速缓存,这个缓存可以看做是同类型对象的一种储备,每个高速缓存包含了一个slab的列表,这通常是一段连续的内存块,并包含3种类型的slabs链表:

  • slabs_full(完全分配的slab)
  • slabs_partial(部分分配的slab)
  • slabs_empty(空slab,或者没有对象被分配)。

slab是slab分配器的最小单位,在具体实现上一个slab由一个或者多个连续的物理页组成(通常只有一页)。单个slab可以在slab链表中进行移动,例如一个未满的slab节点,其原本在slabs_partial链表中,如果它由于分配对象而变满,就需要从原先的slabs_partial中删除,插入到完全分配的链表slabs_full中

内核中slab分配对象的全过程:

  1. 根据对象的类型找到cache_chain中对应的高速缓存kmem_cache
  2. 如果slabs_partial链表中还有未分配的空间,则为其分配对象。如果分配对象之后空间已满,则移动slab节点到slabs_full链表
  3. 如果slabs_partial链表没有未分配的空间,则去查看slabs_empty链表
  4. 如果slabs_empty链表还有未分配的空间,则为其分配对象,同时移动slab节点进入slabs_partial链表中
  5. 如果slabs_empty链表也没有未分配的空间,则说明此时空间不足,就会请求伙伴系统分页,并创建新的空闲slab节点放入slabs_empty链表中,回到步骤4。

从上面可以看出,slab分配器的本质其实就是通过将内存按使用对象不同再划分成不同大小的空间,即对内核对象的缓存操作.

slab分配器的优点:

  1. slab内存管理基于内核小对象,不用每次都分配一页内存,充分利用空间,避免内部碎片
  2. slab对内核中频繁创建和释放的小对象做缓存,重复利用一些相同的对象,减少内存分配次数.

linux-用户态内存映射和内核态内存映射

...理内存的管理,相当于会议室管理员管理会议室。第二,虚拟地址的管理,也即在项目组的视角,会议室的虚拟地址应该如何组织。第三,虚拟地址和物理地址如何映射,也即会议室管理员如果管理映射表。那么虚拟地址和物理... 查看详情

内存管理--虚拟内存管理技术(代码片段)

操作系统环境都是 x86架构的32位 Linux系统。虚拟地址即使是现代操作系统中,内存依然是计算机中很宝贵的资源,看看你电脑几个T固态硬盘,再看看内存大小就知道了。为了充分利用和管理系统内存资源,Linux采用虚拟... 查看详情

linux内核内存虚拟地址映射物理地址

先说明下什么是虚拟地址。Linux内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。虚拟地址空间的内部又被分为内核空间和用户空间两部分。不同字长(也就是单个CPU指令可以处理数据的最... 查看详情

虚拟地址空间相关知识网络(段页存储--mmu--虚拟地址--内核区--用户区)

...9;,并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。虚拟地址空间虚拟地址空间对应一段连续的内存地址,起始为逻辑上的0(不是物理... 查看详情

进程的虚拟地址空间分布

  一个linux进程的虚拟地址空间分布如上图所示,分为内核空间和进程空间,对于一个32位操作系统来说,4GB的空间分成两部分,低地址的0~3G给用户空间,高地址的3G~4G给内核空间。内核空间与进程有关的数据结构段  每个... 查看详情

linux虚拟地址空间分布

...每个进程都运行在属于自己的内存沙盘中。这个沙盘就是虚拟地址空间(VirtualAddressSpace),在32位模式下它是一个4GB的内存地址块。在Linux系统中,内核进程和用户进程所占的虚拟内存比例是1:3,而Windows系统为2:2(通过设置Large-Address-... 查看详情

linux内核内存管理虚拟地址空间布局架构②(用户虚拟地址空间组成|内存描述符mm_struct结构体源码)(代码片段)

文章目录一、用户虚拟地址空间组成二、内存描述符mm_struct结构体源码一、用户虚拟地址空间组成"用户虚拟地址空间"包括以下区域:①代码段②数据段③未初始化数据段④动态库代码段,数据段,未初始化数据段;⑤堆内存:通... 查看详情

linux进程内存相关

参考技术A3种地址:虚拟地址、物理地址、逻辑地址物理地址:内存的电路地址,对应内存地址线上的高低电平,物理可见的。虚拟地址:分页机制的产物,也叫线性地址,是进程能看见的地址。逻辑地址:分段机制的产物,属... 查看详情

内核解读之内存管理(12)进程虚拟内存管理vm_area_struct与反向映射(代码片段)

...性地址空间,而内核独享最后1GB线性地址空间。由于虚拟内存的引入,每个进程都可拥有3GB的虚拟内存,并且用户进程之间的地址空间是互不可见、互不影响的,也就是说即使两个进程对同一个地址进行操作,... 查看详情

内核解读之内存管理(12)进程虚拟内存管理vm_area_struct与反向映射(代码片段)

...性地址空间,而内核独享最后1GB线性地址空间。由于虚拟内存的引入,每个进程都可拥有3GB的虚拟内存,并且用户进程之间的地址空间是互不可见、互不影响的,也就是说即使两个进程对同一个地址进行操作,... 查看详情

虚拟内存和物理内存(转)

...,其对应的存储空间称为物理存储空间或主存空间。2、虚拟存储器的容量限制:主存容量+辅存(硬盘)容量。3、物理内存:在应用中,真实存在的,插在主板内存槽上的内存条的容量的大小。从本质上来说,物理内存是代码和... 查看详情

操作系统内存管理单元mmutlb

...了解操作系统内存管理分页/分段/段页式管理、操作系统虚拟内存技术两篇文章后,接下来继续看看现代操作系统基本内存管理方式,本文详细介绍Linux操作系统下的内存管理单元MMU和TLB。内存管理子系统内存管理子系统... 查看详情

操作系统内存管理单元mmutlb

...了解操作系统内存管理分页/分段/段页式管理、操作系统虚拟内存技术两篇文章后,接下来继续看看现代操作系统基本内存管理方式,本文详细介绍Linux操作系统下的内存管理单元MMU和TLB。内存管理子系统内存管理子系统... 查看详情

c程序内存模型(代码片段)

...运行在它自己的内存“沙箱”中。这个沙箱是一个虚拟地址空间(virtualaddressspace),在32位系统中它总共有4GB的内存地址空间,包含内核空间和用户空间:这些虚拟地址是通过内核页表(pagetable)映射到物理地址的,并由... 查看详情

内核栈对应的虚拟地址是如何映射的?

】内核栈对应的虚拟地址是如何映射的?【英文标题】:Howarevirtualaddressescorrespondingtokernelstackmapped?【发布时间】:2020-09-2916:42:18【问题描述】:每个进程的虚拟地址空间由用户空间和内核空间组成。正如许多文章所指出的,所... 查看详情

linux的内核空间和用户空间是如何划分的(以32位系统为例)?

  通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。地址分配如下图所示 内核地址空间分布  直接映射区:线性空间中从3G开始最大896M的区间,为直接内存映射区,该区域的线性地址和物理地址存在线性转换... 查看详情

内核态(内核空间)和用户态(用户空间)的区别和联系

本文原地址:https://blog.csdn.net/qq_34228570/article/details/72995997 用户空间就是用户进程所在的内存区域,相对的,系统空间就是操作系统占据的内存区域。用户进程和系统进程的所有数据都在内存中。 是谁来划分内存空间的... 查看详情

linux内核内存管理虚拟地址空间布局架构⑤(linux内核中对“虚拟地址空间“的描述|task_struct结构体源码)(代码片段)

文章目录一、Linux内核中对"虚拟地址空间"的描述二、task_struct结构体源码一、Linux内核中对"虚拟地址空间"的描述进程的"虚拟地址空间"由mm_struct和vm_area_struct两个数据结构描述;mm_struct是“最高层次"上描述”... 查看详情