Linux内核内存分配接口函数分析

2023-10-31 12:10

本文主要是介绍Linux内核内存分配接口函数分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:这里来分析一下Linux内核内存分配的原理

1、物理内存的管理

先来简单了解几个概念

1.1 内存节点 node

在计算机世界中,有两种物理内存的管理方式被广泛使用,他们分别是:
UMA(一直内存访问)模型
NUMA(非一致内存访问)模型

两种模型的区别如下:
在这里插入图片描述
在linux源码中以struct pglist_data数据结构来表示单个的内存节点,对于NUMA模型,多个内存节点通过链表链接起来,对于UMA模型,因为只有一个这种node,所以不存在链表。

1.2 内存区域ZONE

内存区域属于单个内存节点的概念,linux将每个内存节点管理的物理内存划分为不同的内存区域,linux中使用struct zone数据结构表示 每一个内存区域,内存区域的类型用zone_type表示,是一枚举变量:
enum zone_type{
ZONE_DMA,
ZONE_DMA32,
ZONE_NORMAL,
ZONE_HIGHMEM,
ZONE_MOVEBLE,
_MAX_NR_ZONES
};

1.3 内存页

内存页是物理内存管理中的最小单位,也叫页帧,linux会为系统物理内存的每个页都创建一个struct page对象,系统用一个全局变量struct page *mem_map 来存放所有的物理页page对象的指针,页的大小取决于系统中的内存管理单元MMU,后者用来将虚拟空间的地址转化为物理空间的地址。

2.页面分配器

Linux系统对物理内存进行分配的核心建立在页面级的伙伴系统之上,在系统的初始化期间,伙伴系统负责对物理内存页面进行跟踪,记录哪些是已经被内核使用的页面,哪些是空闲页面。
这里先给出一个图,供大家了解:
在这里插入图片描述
由上图可以看出,每个物理内存被分为三个区域,mem_map链表与这三个物理内存空间也是对应的,linux初始化期间,会将虚拟地址的物理页面直接映射区做线性地址映射到ZONE_NORMAL与ZONE_DMA,这就意味着如果页面分配器分配的页面位于这两个区,对应的内核虚拟地址到物理地址的映射的页目录表项已经建立,而且是线性映射。如果页面分配器去ZONE_HIGHMEM区域分配页面,这种情况下首先需要内核在动态映射区或者固定映射区分配一个虚拟地址,然后映射到该物理页面上。当然内核实现了这些接口函数,我们下面来看看这些接口函数

2.1 gpf_mask

gpf_mask是页面分配函数的重要参数,使用用于控制分配行为的掩码,并告诉内核应该到哪个zone中区分配空间。我们这里重点说一下GPF_KERNEL和GPF_ATOMIC:
GPF_ATOMIC内核模块中最长使用的掩码之一,用于原子分配,此掩码告诉分配器,在分配内存页面时,绝对不行中断当前进程或把当前进程移除调度器,在驱动程序中,一般在中断例程后者非进程上下文的代码中使用。这两种情况下分配都必须保证当前进程不能睡眠。
GPF_KERNEL内核模块中最长使用的掩码之一,带有该掩码的 内存分配可能导致当前进程进入睡眠状态。

对于驱动开发人员来说,我们可能更加关心分配器到那个区域去分配物理页面,如果gpf_mask中没有明确指定**_GFP_DMA或者_GFP_HIGHMEM**,那么默认的就去ZONE_NORMAL中区分配,如果当前区没有,则去ZONE_DMA。
若指定了**_GFP_DMA**,则只能在ZONE_DMA中分配物理页面,若无法满足,则分配失败。
若指定了**_GFP_HIGHMEM**,则先去ZONE_HIGHMEM区域中查找内存,如果无法满足,则去ZONE_NORMAL,若是还无法满足,则去ZONE_DMA区域中。

2.2 alloc_pages

在linux源码中alloc_pages以宏的形式出现,
在这里插入图片描述
_alloc_pages函数负责分配2的order次方个连续的物理页面并返回起始页面的struct page实例。在调用这个函数的时候,分为两种情况,如下:
1)如果gpf_mask没有明确指定_GFP_HIGHMEM,那么分配的页面就来自于ZONE_NORMAL或者ZONE_DMA。由于这两个区域内核在初始化阶段就为之建立了映射关系,所以内核可以使用page_address来获得对应页面的内核虚拟地址KVA。
2)如果指定了_GFP_HIGHMEM,那么页分配器将优先在ZONE_HIGHGMEM中分配物理页,但是也不排除没有足够空间而导致去另外两个区域分配。对于新分配的高端屋里页面,由于内核尚未在页表中为之建立映射关系,所以此时需要:
a) 在内核的动态映射区分配一个KVA
b) 通过操作页表,将1中的KVA映射到该物理页面上,内核为此提供了一个函数Kmap与Kunmp。

2.3 _get_free_pages

这个函数原型这里不贴出来了,这个函数主要的作用是在非高端内存区分配2的order次方个物理内存页面,返回起始页面所在的内核线性地址,因为行数内部自己调用了page_address函数。

这里再介绍两个在非高端分配屋里页面的函数:
get_zeroed_pages 用于分配一个物理页面并将页面对应的内容填充为0,函数返回页面所在的内核线性地址。
_get_dma_pages 用于从ZONE_DMA区域分配物理页。返回页面所在的线性地址。

2.4 释放函数_free_pages、free_pages

_free_pages()
在这里插入图片描述
两个函数的别去就是第一个参数的类型,_free_pages释放的是alloc_pages分配的page实例,所以传入的类型是page,free_pages释放的是_get_free_pages系列分配的内存,所以第一个参数是线性地址。

3.slab分配器

上面提到的连续物理页面的分配,但是只有物理页面的分配不够的,因为大多数情况下我们内核使用内存都是很小的,没有到达4KB,所以如果都使用页分配,物理空间是肯定不够用的,所以内核实现了slab分配器。slab分配器的思想就是先利用页分配器分配出一个单个或者一组的物理页面,然后将在此基础上将整个页面分割成多个相等的小内存单元,以满足小内存空间分配的需要。

slab的实现原理是很复杂的,这里我们就不讨论了,只列出下面几个常用的接口函数:
void *kmalloc(size_t size, gfp_t flags) ----->kzalloc(size_t size, gfp_t flags) 在kmalloc的基础上初始化分配的内存为0
功能:分配一块物理内存,这块内存是连续的,最大是128K,分配的内存的2的次幂的格式
参数:
@size :分配内存的大小
@flags: GFP_ATOMIC(不会休眠,可以在中断上下文使用)
GFP_KERNEL(使用这个宏分配内存的时候,可以休眠,只能在进程上下文使用)
返回值: 成功内存的地址 失败NULL

void kfree(const void *objp)
功能:释放内存
参数:
@objp :使用kmalloc分配到的内存的首地址

返回值:无

4.虚拟地址的管理

主流的32位处理器,能寻址2的32次方也就是4GB大小的地址空间,这部分空间称为虚拟地址空间。从虚拟地址空间到物理地址的转换通过处理器中的一个部件内存单元MMU。为了完成这种转换,操作系统必须建立适当的页表。我们知道4GB的虚拟地址空间,高的3G-4G属于内核空间,我们这里只研究内核空间的地址。

4.1 内核虚拟地址空间构成

在这里插入图片描述
由上图可知,内核空间分为三部分,每个部分中间有黑洞,空洞不做任何映射,防止越界。

我们可以看到其中有部分是vmalloc区,我们分配内存的接口中就有一个vmalloc函数,该函数就是对vmalloc区进行操作,他的特点是分配的虚拟地址空间是连续的,但是分配的物理地址不一定连续。

vmalloc实现的步骤如下:
1)在vmalloc区分配出一段连续的内存
2)通过伙伴系统获得物理页
3)通过对页表的操作,将步骤一种分配的虚拟地址内存映射到步骤2中获得的物理页上。
但是在驱动的开发过程中,不建议使用该函数来申请内存。原因比较多,这里就不研究了。

4.2 ioremap

在这里插入图片描述
ioremap的作用是将vmalloc区的某段虚拟内存块映射到IO空间,其实现原理与vmalloc完全一样。
释放使用iounmap。
在这里插入图片描述

这篇关于Linux内核内存分配接口函数分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/315261

相关文章

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变

github打不开的问题分析及解决

《github打不开的问题分析及解决》:本文主要介绍github打不开的问题分析及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、找到github.com域名解析的ip地址二、找到github.global.ssl.fastly.net网址解析的ip地址三

Linux系统中的firewall-offline-cmd详解(收藏版)

《Linux系统中的firewall-offline-cmd详解(收藏版)》firewall-offline-cmd是firewalld的一个命令行工具,专门设计用于在没有运行firewalld服务的... 目录主要用途基本语法选项1. 状态管理2. 区域管理3. 服务管理4. 端口管理5. ICMP 阻断

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流