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 join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的