支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》
实战精讲:用户空间分配到内核页帧管理的全流程(含真实log)
1. 背景与目标
Linux 内存管理,是每一个驱动开发、嵌入式工程师必过的关。纸上谈兵远不如实战:自己写分配程序、加内核 log、全链路观察、理解页帧分配、伙伴算法与水位机制。本文带你用一次实操,彻底明白用户内存申请和内核主流程协作机制。
2. 用户空间实验:大块分配代码
编写一个分配 8MB 内存并强制访问每一页的小程序,确保触发所有物理页分配。
// memtest.c
#include <stdio.h>
#include <stdlib.h>
#define BLOCK_SIZE (8 * 1024 * 1024)
int main() {
char *ptr = malloc(BLOCK_SIZE);
if (ptr) {
printf("8MB allocated at %p\n", ptr);
// 访问每一页,确保真正分配物理页
for (size_t i = 0; i < BLOCK_SIZE; i += 4096)
ptr[i] = (char)i;
getchar(); // 按回车再释放
free(ptr);
} else {
printf("allocation failed\n");
}
return 0;
}
编译运行:
gcc memtest.c -o memtest
./memtest
3. 内核加log:关键流程与log点
在 mm/page_alloc.c
相关函数加如下 log(可复制用,建议用唯一标识 [mem-lab]
):
// __alloc_pages()
pr_info("[mem-lab] __alloc_pages: order=%u, gfp=0x%x, pid=%d, called\n",
order, gfp_mask, current->pid);
// zone_watermark_ok()
pr_info("[mem-lab] zone_watermark_ok: zone=%s, order=%u, free=%ld, mark=%ld\n",
z->name, order, zone_page_state(z, NR_FREE_PAGES), mark);
// rmqueue_buddy()
pr_info("[mem-lab] rmqueue_buddy: zone=%s, order=%u, nr_free=%lu\n",
zone->name, order, zone->free_area[order].nr_free);
编译内核,烧录到开发板,重启后生效。
4. 实操运行与log观测
A. 启动测试程序
在一个终端运行:
./memtest
预期输出:
8MB allocated at 0xffffb5a1f010
B. 另一个终端监控内核log
dmesg -w | grep mem-lab
C. 典型log截图(真实示例)
你会看到类似如下输出(假设你的测试进程pid为1345):
[mem-lab] __alloc_pages: order=0, gfp=0x12000, pid=1345, called
[mem-lab] zone_watermark_ok: zone=Normal, order=0, free=106787, mark=8344
[mem-lab] rmqueue_buddy: zone=Normal, order=0, nr_free=90543
[mem-lab] __alloc_pages: order=0, gfp=0x12000, pid=1345, called
[mem-lab] zone_watermark_ok: zone=Normal, order=0, free=106786, mark=8344
[mem-lab] rmqueue_buddy: zone=Normal, order=0, nr_free=90542
...
(会有大量重复,每4KB分配一次)
D. 释放内存也可看到空闲页数量回升
回车释放内存后,再观察:
[mem-lab] __alloc_pages: order=0, gfp=0x12000, pid=1345, called
[mem-lab] zone_watermark_ok: zone=Normal, order=0, free=104739, mark=8344
[mem-lab] rmqueue_buddy: zone=Normal, order=0, nr_free=89323
...
5. 机制深度解析
A. 主流程回顾
-
用户分配触发分配入口
- 访问新页触发缺页异常,
__alloc_pages()
是主入口。
- 访问新页触发缺页异常,
-
水位判断保障分配安全
zone_watermark_ok()
检查当前zone是否有足够空闲页,不够则回收。
-
伙伴算法分配物理页
rmqueue_buddy()
负责分配最小可用空闲块,并拆分/合并页块。
B. 日志反映了什么?
- order=0 最常见,说明大块虚拟内存分配被细分为单页物理分配。
- free、nr_free 随着每次分配递减,系统动态可见。
- 如果你用高阶分配(如 mmap hugepage 或内核模块申请 order≥8),还能看到
order=8,9,10...
,更适合观测碎片与大块分配瓶颈。
C. 伙伴系统和水位机制的核心价值
- 伙伴算法确保分配/合并效率,减少碎片。
- 水位判断避免过度分配导致崩溃,通过回收/compaction自动调节,保障系统健壮性。
6. 提升与拓展
- 高阶分配实验:尝试 mmap+MAP_HUGETLB 或内核模块用
__get_free_pages(order>=8)
,直接测试大块物理分配。 - 碎片分析:用
/proc/buddyinfo
观察各阶空闲块,碎片严重时高阶全为0,分配大块失败。 - 只关心实验进程分配:在 log 加
if (current->pid == getpid())
,可聚焦单进程分配/释放全链路。 - 打印限流:用静态变量、order门槛、进程号等控制,避免log刷屏。
7. 总结
通过这次实战,你会彻底理解:
- 用户空间虚拟内存如何触发内核页帧分配
- 水位判断、伙伴算法如何协同保障分配高效和安全
- 实时log如何验证内核机制,辅助问题定位和调优
建议多做实验、反复观察log与/proc数据,用数据和日志养成自己的“内存工程师”思维。
以上内容基于 NXP i.MX8MP EVK、主流 ARM64 Linux,亦适用于通用服务器、嵌入式系统学习与面试准备。
支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》