【Linux】浅析Linux内存管理机制

本文主要讲解Linux内存管理机制中的一个核心子模块:内核内存分配与回收机制。通过了解这个机制,对内核内存分配失败时,应该怎么看日志、分析原因有一定帮助。

目录

1、基础知识

[1.1 虚拟内存](#1.1 虚拟内存)

[1.2 页与页表](#1.2 页与页表)

[1.3 伙伴系统](#1.3 伙伴系统)

[1.4 slab 分配器](#1.4 slab 分配器)

2、常见内存问题

[2.1 Out Of Memory](#2.1 Out Of Memory)

[2.2 Segment Fault](#2.2 Segment Fault)

[2.2.1 段错误成因](#2.2.1 段错误成因)

[2.2.2 段错误定位](#2.2.2 段错误定位)

1、基础知识

该部分仅作简单介绍,详细的内存管理机制可结合内核源码mm目录下的代码分析。

5.10版本的瑞芯微 RK3588/Rock5b BSP内核下载链接https://github.com/redroid-rockchip/kernel-rock5b/tree/linux-5.10-gen-rkr3.4

1.1 虚拟内存

虚拟内存是现代通用操作系统(如Linux、Windows)采用的一种内存管理技术。它为每个进程提供了一个巨大、连续且私有的地址空间,即"虚拟地址空间"。实际上,程序的代码和数据存储在物理内存(RAM)和磁盘上。当CPU执行指令需要访问内存时,会通过内存管理单元(MMU)来将虚拟地址"翻译"成真实的物理地址。好处:

  • 内存隔离与保护:每个进程都有自己独立的虚拟地址空间,一个进程无法直接访问另一个进程的内存,从而保证了进程和操作系统自身的安全性和稳定性。(核心作用)
  • 扩展地址空间:虚拟内存允许程序使用的内存量超过实际可用的物理内存。操作系统可以将暂时不用的内存数据交换到硬盘上(这个区域通常称为交换空间(Swap Space)或页面文件),在需要时再加载回物理内存。
  • 简化内存管理:为每个进程提供了一个从0开始的、连续的地址空间,这极大地简化了程序的链接、加载和内存分配过程。

RAM(Random Access Memory,随机存取存储器)是一类易失性存储器:

  • 作用:高速读写(GB/s 级),作为"临时运算工作台",保障系统实时性和高性能
  • 分类:stm32f407 的 SRAM 为 192KB,RK3588 的板载 RAM(4/8/16GB),手机的运行内存(8/12/16GB),电脑内存(8/16/24/32GB)。嵌入式平台多为板载 LPDDR 颗粒,PC 为内存条。

磁盘:非易失性存储,断电后数据长期保留:

  • 作用:长期数据仓库,实现数据持久化留存。
  • 分类:分为机械硬盘(HDD)、固态硬盘(SSD),嵌入式多为 eMMC(Embedded MultiMediaCard 嵌入式多媒体卡)、SD卡、NVMe SSD(基于PCIe 接口的高速固态存储)

1.2 页与页表

为了便于管理,虚拟和物理内存都被划分为固定大小的块,通常是4KB,这个块被称为页(笔者在"Socket编程"博客中也有提到,CPU 读取内存时,并不是一个字节一个字节地读,而是以 "块" 为单位进行读取,这样可以提高内存读取效率)。

操作系统为每个进程维护一个页表(Page Table)。页表就像一个翻译词典,记录了哪个虚拟页对应哪个物理页(在RAM中),或者记录了该页被临时存放在硬盘的哪个位置。当进程访问一个虚拟地址时,MMU(CPU中的内存管理单元,是个硬件)会查询页表,找到对应的物理地址,然后访问数据。

1.3 伙伴系统

伙伴系统是Linux内核中管理物理页框的基础机制,核心目标是解决物理内存分配中的外部碎片问题,并高效提供连续的页框块(物理内存最小分配单位是页框,通常 4KB)。它是内核内存分配的底层支撑(slab 分配器、vmalloc 等均依赖它)

设计思想 :物理内存被划分为固定大小的页框,所有空闲页框按块大小 分组管理,块大小必须是 2 的幂次(如 1 个页框、2 个页框、4 个页框、8 个页框)。两个伙伴块需满足:大小相同、物理地址连续、来自同一父块(即两个块是同一更大块拆分的结果),释放时可合并为一个更大的块。

内部碎片:分配给进程 / 内核的内存块 大于实际需求,导致块内未被使用的空间被 "浪费",这部分空闲空间仅属于该块,无法被其他请求复用。

外部碎片:内存中存在多个 分散的空闲块,总空闲内存大小足够满足某个连续内存请求,但由于这些空闲块不连续,无法合并成一个足够大的连续块,导致请求失败。

1.4 slab 分配器

打破伙伴系统 "2^k 页框块" 的固定粒度限制,通过 "按小对象尺寸定制缓存池 + 大块页框拆分 + 对象复用",让分配粒度精准匹配小内存需求,从根源消除 "分配块远大于实际需求" 导致的内部碎片。

Slab 分配器的小内存申请在单个缓存池内,优先复用 "部分满的 slab 块",再用 "空闲 slab 块",最后才向伙伴系统申请新页框创建新 slab 块------ 不同大小的小内存对应不同的 "专属缓存池",缓存池之间互不干扰。

  • 每个 Slab 缓存池 = 一个专属货架
  • 每个 slab 块 = 货架上的一个盒子(从伙伴系统申请的 4KB 页框,拆成 16 个 256B 格子)
  • 小对象 = 要存放的 "商品"(如256B 传感器配置数据)

2、常见内存问题

2.1 Out Of Memory

OOM 错误是 Linux 系统(尤其是嵌入式平台)因物理内存 + 交换空间耗尽,内核无法满足新的内存分配请求时触发的致命错误,最终会通过 OOM Killer 杀死 "得分最高" 的进程释放内存(极端情况可能导致系统崩溃)。

OOM 发生时,内核会在 dmesg 中输出详细日志,包括被杀死的进程、内存使用状态、OOM 得分等,这是最关键的定位依据:

bash 复制代码
# 查看 OOM 相关日志(嵌入式终端执行)
dmesg | grep -i oom
# 或查看完整内核日志,按时间排序
dmesg | tail -200

|--------|----------------------------------------------------------------------------------------------|
| dmesg | 输出Linux内核环形缓冲区中的所有日志内容 |
| 管道符 | | 将前一个命令的标准输出作为后一个命令的标准输入,实现 命令串联处理 |
| grep | 文本过滤工具,逐行扫描输入内容,匹配指定字符串并输出匹配行 -i 忽略大小写,匹配 OOM/oom/Oom 等所有大小写组合 oom 匹配关键词,内核触发 OOM 时日志中必含该关键词 |
| tail | 读取文件 / 输入的末尾行,默认显示最后 10 行,-200 显示最后200行 |

2.2 Segment Fault

Segment Fault(段错误)是嵌入式 Linux 中用户态程序最常见的崩溃类型,本质是程序访问了内核不允许的内存地址(如空指针、内存越界、野指针等)

2.2.1 段错误成因

段错误由内核触发(SIGSEGV 信号,信号编号 11),本质是内存访问违规

类别 具体场景
非法指针操作 空指针解引用(char *p = NULL; *p = 1;)、野指针
内存越界 数组越界(char buf[10]; buf[10] = 'a';)、字符串拷贝越界(strcpy无长度限制)
栈溢出 ISR 中定义大局部变量、递归调用函数
内存释放异常 重复 free、free 非堆内存(栈内存 / 常量区)、释放后复用该内存
权限 / 硬件问题 用户态访问内核态内存、非法外设寄存器地址
多线程 / 进程问题 线程访问已销毁的内存、共享内存未同步导致野指针

NULL 指针:

C/C++ 预定义的宏,表示指针指向内存地址 0(地址 0 是操作系统保留的、不可读写的受保护内存区域,系统不允许用户程序操作这个地址)

野指针:

解引用野指针时,程序大概率崩溃,或篡改随机内存的数据。

  • 1、定义指针变量时未赋值,编译器会给它分配一个随机的内存地址(栈上的垃圾值),此时指针直接变成野指针。
  • 2、用 free()(C)/ delete(C++)释放指针指向的内存后,指针本身的地址值不会变(仍指向已释放的内存),此时指针变为野指针。
  • 3、指针超出数组 / 缓冲区的合法范围后,指向的是数组外的随机内存,变成野指针。

规避野指针的方法

  • 1、指针定义时立即初始化(要么指向有效内存,要么置 NULL)
  • 2、释放内存后,立即将指针置 NULL(切断野指针):free(p); p = NULL;
  • 3、C++ 中优先使用智能指针(自动管理生命周期)

非法外设寄存器地址导致段错误成因:

地址错误:硬编码的外设物理地址与芯片手册不符(如写错寄存器偏移量)

未映射:外设物理地址(如0x10000000)未通过mmap映射到用户态虚拟地址,直接访问物理地址

权限不足:映射时未指定正确权限(如只读地址强行写)

2.2.2 段错误定位

段错误会被内核记录到dmesg,执行命令:

bash 复制代码
dmesg | grep -i segfault  # 过滤段错误日志
# 或查看最新50行上下文
dmesg -T | tail -50
相关推荐
且从容.1 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道2 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.4 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落6 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨6 小时前
WSL2的安装与使用
linux·wsl
微小的鱼6 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng7 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
Android系统攻城狮7 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
Linux-lucky19 小时前
45-Linux学习之旅之防火墙与WEB加固
linux·前端·网络·学习·ubuntu
禁默19 小时前
启元 Q1 二次开发实践:Ubuntu 部署与仿真验证,从 PrimeConsole 到 ROS 2 Humble + RViz
linux·运维·ubuntu