【Linux 内核源码分析】堆内存管理

堆是一种动态分配内存的数据结构,用于存储和管理动态分配的对象。它是一块连续的内存空间,用于存储程序运行时动态申请的内存。

堆可以被看作是一个由各个内存块组成的堆栈,其中每个内存块都有一个地址指针,指向下一个内存块。当程序需要分配内存时,堆会根据分配算法找到一块足够大的连续内存空间,并将其分配给程序。程序可以在堆中动态创建和销毁对象,而不需要在编译时确定对象的数量或大小。

与静态分配的栈不同,堆的内存分配不是自动的,需要显式地通过内存分配函数(如malloc、new等)来申请内存空间,并在不使用时通过释放函数(如free、delete等)来释放已分配的内存。这种动态的内存管理方式使得程序能够根据实际需要来动态调整内存的使用情况。

堆内存管理

在Linux操作系统中,堆内存是指用于动态分配的一块内存区域。它与程序的堆栈(stack)不同,堆内存是由程序员通过函数如malloc()、calloc()或realloc()等来手动申请和释放的。

堆内存的特点包括:

  1. 大小可变:堆内存的大小可以在运行时动态地调整,适应不同需求。
  2. 手动管理:开发人员需要手动申请和释放堆内存,并且负责确保正确使用和及时释放,以避免内存泄漏或悬挂指针等问题。
  3. 随机访问:程序可以随机访问堆内存中的数据。
  4. 生命周期长:除非显式释放或程序结束,否则分配给堆内存的空间会一直存在。

堆内存是连续的内存区域:在大多数操作系统中,包括 Linux,堆内存是通过动态内存分配来管理的。它通常是一个连续的内存区域,用于动态分配和释放内存块。

堆内存的生长方向是自下而上:在传统的内存布局中,堆内存的生长方向是从低地址向高地址增长。这意味着每次分配新的内存块时,堆会从较低的地址向上移动。

堆内存的管理由 Linux 内核实现:Linux 内核提供了一些系统调用和函数,用于管理堆内存。这些系统调用和函数使开发者能够请求分配和释放堆内存,但开发者并不直接知道堆的管理细节。

系统调用用于调用相关函数:开发者可以使用系统调用来请求堆内存的扩展和收缩。其中一个常用的系统调用是 brk,它负责调整程序的堆边界,以便在需要时扩展或收缩堆内存。

在 Linux 内核中,mm_struct 结构体表示进程的内存管理信息,其中包含了堆的起始地址和结束地址:

  • start_brk 表示堆内存在虚拟地址空间中的起始地址,通常是初始的堆边界。
  • brk 表示堆内存在虚拟地址空间中的结束地址,即当前的堆边界。

start_brkbrk 之间的地址空间就是堆内存的大小。在进程运行过程中,可以通过相应的系统调用(如 brksbrk)来动态扩展或收缩堆内存的大小,从而改变堆边界的位置。

需要注意的是,mm_struct 结构体中还包含了其他与内存管理相关的信息,如代码段、数据段、栈等的起始地址和结束地址。这些信息共同构成了进程的虚拟地址空间,用于进行内存管理和保护。

Slab 是一种基于对象缓存的内存分配器。它将内核对象按照类型进行分类,并为每种类型分配一个独立的缓存池,缓存池中包含了若干个连续的 Slab 对象。当内核对象需要分配内存时,Slab 分配器会从相应的缓存池中申请一个 Slab 对象,并将其划分为多个小块以供程序使用。当程序释放内存时,Slab 分配器会将该内存块标记为空闲状态,并加入到 Slab 缓存池中以供后续的内存分配使用。Slab 分配器通过对象缓存机制,可以提高内存分配效率和内存利用率,并减少内存碎片的产生。

SLOB(Simple List Of Blocks)是一种基于 Free 链表的简单内存分配器。它通过维护一个链表来记录空闲块的位置和大小,当程序需要分配内存时,SLOB 分配器会在 Free 链表中查找一个大小合适的空闲块,并将该块分配给程序。当程序释放内存时,SLOB 分配器会将该内存块加入到 Free 链表中,以供后续的内存分配使用。SLOB 分配器由于实现简单,因此可以在内核体积和性能之间进行权衡。

内存描述符 mm_struct 结构体

在 Linux 内核源码的 include/linux/mm_types.h 文件中包含了 mm_struct 结构体的定义。

struct mm_struct {
	struct {
		struct vm_area_struct *mmap;     /* VMA链表 */
		struct rb_root mm_rb;
		u64 vmacache_seqnum;              /* 每个线程的vmacache */

		// ...

		unsigned long hiwater_rss;        /* RSS使用的高水位标记 */
		unsigned long hiwater_vm;         /* 虚拟内存使用的高水位标记 */

		unsigned long total_vm;           /* 映射的总页数 */
		unsigned long locked_vm;          /* 设置了 PG_mlocked 的页面 */
		atomic64_t    pinned_vm;          /* 引用计数永久增加的页面 */
		unsigned long data_vm;            /* VM_WRITE & ~VM_SHARED & ~VM_STACK */
		unsigned long exec_vm;            /* VM_EXEC & ~VM_WRITE & ~VM_STACK */
		unsigned long stack_vm;           /* VM_STACK */
		unsigned long def_flags;

		spinlock_t arg_lock;              /* 保护下面的字段 */
		unsigned long start_code, end_code, start_data, end_data;   /* 代码段、数据段的起始和结束地址 */
		unsigned long start_brk, brk, start_stack;                  /* 堆、栈的起始地址 */
		unsigned long arg_start, arg_end, env_start, env_end;       /* 命令行参数、环境变量的起始和结束地址 */

		unsigned long saved_auxv[AT_VECTOR_SIZE]; /* 用于 /proc/PID/auxv */

		// ...
	} __randomize_layout;

	/*
	 * mm_cpumask 需要放在 mm_struct 的末尾,因为它的大小是根据 nr_cpu_ids 动态确定的。
	 */
	unsigned long cpu_bitmap[];
};

mm_struct 结构体中的 start_brk、brk 成员

mm_struct 结构体中,start_brkbrk 是与进程的堆内存相关的成员变量。

  • start_brk 表示进程堆的起始地址。
  • brk 表示进程堆的结束地址。

虚拟地址空间是一个抽象的概念,用于表示进程可用的地址范围。它将进程的内存分为多个区域,包括代码段、数据段、堆、栈等。堆是其中的一个区域,用于动态分配内存。

start_brk 是堆内存的起始地址,表示堆的开始位置。在该地址之前的内存区域属于其他区域(如数据段),而在该地址之后的内存区域则属于堆。

brk 是堆内存的结束地址,表示堆的结束位置。堆的所有分配的内存都位于 start_brkbrk 之间。

通过控制 brk 的值,可以动态调整堆内存的大小。当需要分配更多的堆内存时,可以通过增加 brk 的值来扩展堆,使其占用更多的虚拟地址空间。相反,当释放不再需要的堆内存时,可以通过减小 brk 的值来缩小堆的大小,从而释放占用的虚拟地址空间。

在 Linux 内核源码的 include/linux/mm_types.h 文件中,包含了 start_brkbrk 成员的定义。

unsigned long start_brk; // 堆内存的起始地址
unsigned long brk;       // 堆内存的结束地址

动态分配堆内存

在 Linux 系统中,有两种常见的方式用于动态分配堆内存:

  1. brk() 和 sbrk():这是最基本和最原始的动态内存分配方式。brk() 函数用于将进程的堆结束地址设置为指定的值,从而控制堆内存的大小。sbrk() 函数则通过增加进程的堆结束地址来分配内存,通过减小堆结束地址来释放内存。这些函数存在于 POSIX 标准中,是 C 语言标准库中的一部分。
  2. malloc() 和 free():这是更高级的动态内存分配方式。malloc() 函数用于在堆内存中分配指定大小的内存块,并返回指向该内存块的指针。free() 函数用于释放先前分配的内存块。这些函数在 C 标准库中实现,通常会比 brk() 和 sbrk() 更易于使用和管理。

这两种动态内存分配方式各有优点和缺点。brk() 和 sbrk() 的优势在于它们非常简单且直接,可以轻松地控制堆内存的大小。但是,它们的缺点是需要手动管理内存,并且容易出现内存泄漏和其他问题。malloc() 和 free() 则提供了更高级的内存管理功能,使得内存分配和释放更容易且更安全。但是,它们的实现可能会比较复杂,可能需要使用锁和其他机制来确保多线程环境下的正确性。

brk系统调用

在这里插入图片描述

在Linux内核源码中的mm/mmap.c文件中,可以找到brk系统调用的定义。

SYSCALL_DEFINE1(brk, unsigned long, brk)
{
	struct mm_struct *mm = current->mm; // 获取当前进程的内存管理结构体

	unsigned long newbrk, oldbrk; // 定义新旧的堆结束地址

	down_write(&mm->mmap_sem); // 获取内存管理信号量,防止并发访问

	oldbrk = mm->brk; // 保存旧的堆结束地址
	newbrk = PAGE_ALIGN(brk); // 对传入的新堆结束地址进行页面对齐操作

	// 检查新的堆结束地址是否在合法范围内
	if (newbrk < mm->start_brk || newbrk > TASK_SIZE) {
		up_write(&mm->mmap_sem); // 释放内存管理信号量
		return -ENOMEM; // 返回内存分配错误
	}

	// 调用 expand_brk() 函数扩展堆内存
	if (expand_brk(mm, newbrk)) {
		up_write(&mm->mmap_sem); // 释放内存管理信号量
		return -ENOMEM; // 返回内存分配错误
	}

	mm->brk = newbrk; // 更新堆结束地址为新的值
	up_write(&mm->mmap_sem); // 释放内存管理信号量

	return oldbrk; // 返回旧的堆结束地址
}

通过使用brk系统调用,可以指定堆内存在虚拟内存空间的结束地址。

当需要扩展堆内存时,可以将结束地址设置为大于当前值。这样,系统会增加虚拟内存空间以容纳更多的堆内存,并将新的结束地址返回给调用者。

而当需要收缩堆内存时,可以将结束地址设置为小于当前值。这样,系统会释放超过该结束地址的部分虚拟内存空间,使其可供其他用途使用。

mmap系统调用

mmap系统调用的相关代码可以在Linux内核源码的mm/mmap.c文件中找到。

SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
		unsigned long, prot, unsigned long, flags,
		unsigned long, fd, off_t, offset)
{
	/* ...其他代码... */

	if (!(flags & MAP_FIXED)) {
		addr = vm_mmap_pgoff(file, addr, len, prot, flags, offset);
		if (unlikely(IS_ERR_VALUE(addr)))
			return addr;
		goto out;
	}

	/* ...其他代码... */

out:
	return addr;
}

mmap 系统调用执行成功时,返回映射区域的起始地址,该地址与请求的 addr 可能会有所不同(如果 addrNULL,则由操作系统自动分配合适的地址)。如果发生错误,mmap 返回 MAP_FAILED

heap_info

在 Linux 中,堆管理是通过内核提供的系统调用和库函数来实现的。其中,glibc 库是 Linux 上最常用的 C 语言库之一,它提供了一组用于堆管理的函数,如 malloc、free、calloc、realloc 等。

在 glibc 中,堆是由多个内存块组成的链表,每个内存块都有一个 heap_info 结构体来描述它所在的堆。heap_info 结构体定义在 glibc 的 malloc/malloc.c 文件中,它包含以下字段:

  • ar_ptr:指向 arena 结构体的指针,表示该堆所属的 arena。
  • prev:指向前一个 heap_info 结构体的指针,用于链接所有的 heap_info 结构体。
  • next:指向后一个 heap_info 结构体的指针,用于链接所有的 heap_info 结构体。
  • size:表示该堆的大小,以字节为单位。
  • mprotect_size:表示该堆末尾未使用部分的大小,以字节为单位。
  • pad:填充字段,保证 heap_info 结构体大小为 32 字节。
  • free_list:指向该堆中空闲内存块链表的头指针。

通过遍历 heap_info 结构体链表,程序可以获取当前进程所有的堆信息,并对其进行操作和监控。

typedef struct _heap_info
{
    mstate ar_ptr;              /* 指向该堆所属的 arena。*/
    struct _heap_info *prev;    /* 前一个堆。*/
    size_t size;                /* 当前堆的大小(以字节为单位)。*/
    size_t mprotect_size;       /* 已经通过 mprotect 保护的大小(以字节为单位),具有 PROT_READ|PROT_WRITE 权限。*/
    /* 确保以下数据正确对齐,特别是 sizeof(heap_info) + 2 * SIZE_SZ 是 MALLOC_ALIGNMENT 的倍数。 */
    char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; /* 填充字段,用于满足对齐要求。*/
} heap_info;

malloc_state

malloc_state 可以看作是堆管理中的 Arena Header,它维护了当前线程所属的堆内存的状态。每个线程只包含一个 malloc_state 结构体,用于管理该线程所使用的堆内存。在多线程环境下,不同线程的 malloc_state 结构体相互独立,互不干扰。

  • mutex:互斥锁,用于保证多线程下对 malloc_state 结构体的访问是线程安全的。
  • fastbinsY[NFASTBINS]:fastbins 数组,用于快速分配大小在某个范围内的小块内存。
  • top:指向堆内存中最顶部的未分配内存的位置。
  • last_remainder:最近一次小块内存分配时剩余的空闲内存块。
  • bins[NBINS * 2 - 2]:按照固定大小的范围组织的普通内存块链表。
  • binmap[BINMAPSIZE]:内存块链表的位图,表示每个链表是否为空。
  • max_fast:fastbins 范围的上限,即最大可以使用 fastbins 的大小。
  • fastbins[NFASTBINS]:快速分配内存的 fastbins,用于存放刚刚释放的小块内存,以便下次快速分配时能够优先使用。
  • least_addr:堆中最小的地址。
  • unsorted_chunks:未排序的内存块链表。
  • system_mem:从系统中分配的内存总量。
  • max_system_mem:允许从系统中分配的最大内存量。

参考教程 Linux内核源码分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/337518.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学生公寓智能控电系统的重要性

学生公寓智能控电系统石家庄光大远通电气有限公司学生宿舍内漏电&#xff0c;超负荷用电&#xff0c;违规用电等现象一直是困扰后勤管理的普遍问题。随着学生日常生活方式以及生活用品的改变&#xff0c;电脑以及各种电器逐渐的普及&#xff0c;导致用电量与日俱增&#xff0c;…

竞赛保研 机器视觉人体跌倒检测系统 - opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 机器视觉人体跌倒检测系统 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&…

翻译: Anaconda 与 miniconda的区别

Anaconda 和 miniconda 是广泛用于数据科学的软件发行版&#xff0c;用于简化包管理和部署。 1. 主要有两个区别&#xff1a; packages包数量&#xff1a; Anaconda 附带了 150 多个数据科学包&#xff0c;而 miniconda 只有少数几个。Interface接口&#xff1a;Anaconda 有…

mfc110.dll丢失是什么意思?全面解析mfc110.dll丢失的解决方法

在使用计算机的过程中&#xff0c;用户可能会遭遇一个常见的困扰&#xff0c;即系统提示无法找到mfc110.dll文件。这个动态链接库文件&#xff08;DLL&#xff09;是Microsoft Foundation Classes&#xff08;MFC&#xff09;库的重要组成部分&#xff0c;对于许多基于Windows的…

node.js项目express的初始化

目录 1.初始化项目2.配置跨域3.开始编写API3.1准备3.2路由处理函数router_make下的user.js3.3路由模块router下的user.js3.4入口文件app.js里面去新增这段代码3.5启动项目进行测试 &#x1f44d; 点赞&#xff0c;你的认可是我创作的动力&#xff01; ⭐️ 收藏&#xff0c;你…

设备管理——WinCC 给你神助功

要实现“设备高效”&#xff0c;就必须“管之有道”&#xff0c;来自设备层的数据支撑将是必不可少的&#xff0c;提高设备效能的2个关键在于降低平时停机时间 (MDT) 和提高平均无故障时间 (MTBF)。通常来说&#xff0c;设备维护可大致可分为三个层次&#xff1a;纠正性维护&am…

[Tool] python项目中集成使用Firebase推送功能

背景介绍 目前&#xff0c;App推送功能已经非常普遍&#xff0c;几乎所有App都有推送功能。推送功能可以自己实现&#xff0c;也可以使用第三方提供的推送服务&#xff08;免费的收费的都有&#xff09;。本文主要介绍使用Firebase提供的推送服务Firebase Cloud Messaging&…

matlab appdesigner系列-常用13-标签

标签&#xff0c;用来显示各类文本 此示例&#xff0c;就是在标签之外的画布上单击鼠标左键&#xff0c;显示王勃的《滕王阁诗》 操作如下&#xff1a; 1&#xff09;将2个标签拖拽到画布上&#xff0c;并修改相应文字。将第二个标签的右侧文本信息中的Wordwrap打开&#xf…

[自动化分布式] Zabbix自动发现与自动注册

abbix 自动发现&#xff08;对于 agent2 是被动模式&#xff09; zabbix server 主动的去发现所有的客户端&#xff0c;然后将客户端的信息登记在服务端上。 缺点是如果定义的网段中的主机数量多&#xff0c;zabbix server 登记耗时较久&#xff0c;且压力会较大 部署 添加zabb…

六、标准对话框、多应用窗体

一、标准对话框 Qt提供了一些常用的标准对话框&#xff0c;如打开文件对话框、选择颜色对话框、信息提示和确认选择对话框、标准输入对话框等。1、预定义标准对话框 &#xff08;1&#xff09;QFileDialog 文件对话框 QString getOpenFileName() 打开一个文件QstringList ge…

uniapp组件库Popup 弹出层 的使用方法

目录 #平台差异说明 #基本使用 #设置弹出层的方向 #设置弹出层的圆角 #控制弹窗的宽度 | 高度 #内容局部滚动 #API #Props #Event 弹出层容器&#xff0c;用于展示弹窗、信息提示等内容&#xff0c;支持上、下、左、右和中部弹出。组件只提供容器&#xff0c;内部内容…

树的一些经典 Oj题 讲解

关于树的遍历 先序遍历 我们知道 树的遍历有 前序遍历 中序遍历 后序遍历 然后我们如果用递归的方式去解决&#xff0c;对我们来说应该是轻而易举的吧&#xff01;那我们今天要讲用迭代&#xff08;非递归&#xff09;实现 树的相关遍历 首先呢 我们得知道 迭代解法 本质上也…

【征服Redis12】redis的主从复制问题

从现在开始&#xff0c;我们来讨论redis集群的问题&#xff0c;在前面我们介绍了RDB和AOF两种同步机制&#xff0c;那你是否考虑过这两个机制有什么用呢&#xff1f;其中的一个重要作用就是为了集群同步设计的。 Redis是一个高性能的键值存储系统&#xff0c;广泛应用于Web应用…

Python实现稳健线性回归模型(rlm算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 稳健回归可以用在任何使用最小二乘回归的情况下。在拟合最小二乘回归时&#xff0c;我们可能会发现一些…

人才测评,招聘工程技术经理胜任素质模型与任职资格

招聘工程技术经理是企业中的重要职位之一&#xff0c;为了确保招聘到胜任的人才&#xff0c;需要制定一个胜任力素质模型和任职资格。 1.专业知识技能&#xff1a;工程技术经理需要拥有深厚的专业技术知识&#xff0c;能够熟练掌握工程设计、生产制造、质量管理、安全管理等方面…

macOS 设置屏幕常亮 不休眠

Apple M1 Pro macOS Sonoma设置“永不”防止进入休眠 macOS Sonoma 设置“永不” 防止进入休眠

开源进程/任务管理服务Meproc使用之HTTP API

本文讲述如何使用开源进程/任务管理服务Meproc的HTTP API管理整个服务。 Meproc所提供的全部 API 的 URL 都是相同的。 http://ip:port/proc例如 http://127.0.0.1:8606/proc在下面的小节中&#xff0c;我们使用curl命令向您展示 API 的方法、参数和请求正文。 启动任务 …

Tensorflow 入门基础——向LLM靠近一小步

进入tensflow的系统学习&#xff0c;向LLM靠拢。 目录 1. tensflow的数据类型1.1 数值类型1.2 字符串类型1.3 布尔类型的数据 2. 数值精度3. 类型转换3.1 待优化的张量 4 创建张量4.1 从数组、列表对象创建4.2 创建全0或者1张量4.3 创建自定义数值张量 5. 创建已知分布的张量&…

Linux重定向:深入理解与实践

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;晴る—ヨルシカ 0:20━━━━━━️&#x1f49f;──────── 4:30 &#x1f504; ◀️ ⏸ ▶️ ☰ &…

深度学习记录--Momentum gradient descent

Momentum gradient descent 正常的梯度下降无法使用更大的学习率&#xff0c;因为学习率过大可能导致偏离函数范围&#xff0c;这种上下波动导致学习率无法得到提高&#xff0c;速度因此减慢(下图蓝色曲线) 为了减小波动&#xff0c;同时加快速率&#xff0c;可以使用momentum…