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

相关文章

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

linux下shell脚本启动jar包实现过程

《linux下shell脚本启动jar包实现过程》确保APP_NAME和LOG_FILE位于目录内,首次启动前需手动创建log文件夹,否则报错,此为个人经验,供参考,欢迎支持脚本之家... 目录linux下shell脚本启动jar包样例1样例2总结linux下shell脚本启动jar包样例1#!/bin