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

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An