内存管理系统占用高导致系统卡顿:从应用到内核的全链路分析


支持作者,点击京东购买《Yocto项目实战教程》



一、问题背景:系统卡顿、UI 响应慢

在一次 i.MX8MP 开发板上进行的 UI 效果游玩中,我们发现热链接点应用手势操作后,整个系统的输入有明显卡顿(性能急剧降低)。表现为:

  • 点击或滑动时,UI 页面延迟数百毫秒才响应;
  • top 查看 CPU 使用率处于空闲,未发现明显占用瓶颈;
  • IO 读写、GPU 渲染无明显峰值负载。

因此,初步判断系统卡顿原因不在于 CPU 或 IO,可能与 内存系统管理机制有关,尤其是低延迟需求下频繁触发的 动态分配 问题。
在这里插入图片描述


二、应用层分析:调用 malloc 频繁

利用 strace 进行系统调用监控:

strace -ttT -e trace=mmap,brk,munmap ./my_app

观察结果:

  • UI 操作时应用进程不断 mmap/munmap 大量匿名页;
  • 有频繁 malloc/free 调用循环;
  • 某些接口中使用了大块临时内存。

进一步用 smem 分析最大应用内存使用:

smem -r -k | sort -nk 2 | tail

结果显示:

  • 匿名页占用居高不下(Anonymous);
  • 应用分配速度远超释放速度,导致系统压力加重;
  • slab/cache 逐渐增长,存在内核小对象频繁申请迹象。

三、Page Fault 分析:重点分析匿名页分配

通过 perf 分析 page-fault 事件:

perf record -e page-faults -g ./my_app
perf report

确认:

  • 每次 UI 操作均触发大量 page fault;
  • 跳转路径多指向 do_anonymous_page。

使用 ftrace 验证内核堆栈路径:

echo function > /sys/kernel/debug/tracing/current_tracer
echo do_page_fault > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on
sleep 3
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace

确认路径为:

do_page_fault()
→ handle_mm_fault()
→ __handle_mm_fault()
→ do_anonymous_page()
→ alloc_zeroed_user_highpage()
→ alloc_page()
→ __alloc_pages_nodemask()

四、内核分配机制梳理:buddy 与 slab 的联系

4.1 页分配器

最终负责分配页面的是 buddy allocator,接口核心:

struct page *__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
                                    int preferred_nid, nodemask_t *nodemask);

按 order 分配物理连续页。

4.2 slab/slub 分配器

用于管理小对象,如:task_struct、inode、dentry 等。

调用路径:

obj = kmem_cache_alloc(cachep, GFP_KERNEL);

其底层实际也从页分配器中获取整页,构建小对象缓存:

slab_alloc_node()__do_cache_alloc()alloc_pages()

五、slabinfo + meminfo 联合分析:

查看内核 slab 分配信息:

cat /proc/slabinfo | grep -v " 0      0"

查看内存使用总览:

cat /proc/meminfo | grep -E "Slab|MemFree|MemAvailable"

示例:

Slab:              52892 kB
SReclaimable:      21244 kB
SUnreclaim:        31648 kB

slab 占用了约 52MB,其中不可回收部分 31MB,属于偏高但尚不致命。


六、系统参数调优

通过调整 vm 参数减缓分配频率:

echo 16384 > /proc/sys/vm/min_free_kbytes
echo 20 > /proc/sys/vm/swappiness

含义:

  • 提高最小空闲页保留值,防止低水位频繁触发分配;
  • 降低系统对 swap 的偏好,延缓系统清页行为。

七、源代码分析与优化:减少频繁 malloc

原始代码:

char *buf = malloc(1024 * 1024);
memset(buf, 0, 1024 * 1024);
free(buf);

每次调用分配大块内存 → 触发 Page Fault → 回收前还保留在 inactive list 中。

优化方案:

static char *global_buf;
if (!global_buf)
    global_buf = malloc(1024 * 1024);
memset(global_buf, 0, 1024 * 1024);

避免频繁分配释放,通过复用全局 buffer 缓解 page fault 压力。


八、优化效果验证

  • 使用 perf 重新采样,page fault 降低超 60%;
  • UI 卡顿时间降低至原先的三分之一;
  • slabinfo 观察 slab 内存趋于稳定,未出现增长。

九、进阶扩展:分析自己内核中的 slab 缓存分配

创建自定义 slab 类型,便于排查内存泄漏或优化热点对象:

static struct kmem_cache *my_cache;

my_cache = kmem_cache_create("my_obj", sizeof(struct my_obj), 0,
                             SLAB_HWCACHE_ALIGN, NULL);
obj = kmem_cache_alloc(my_cache, GFP_KERNEL);
...
kmem_cache_free(my_cache, obj);

后续即可通过:

cat /proc/slabinfo | grep my_obj

定位自定义 slab 类型,结合 kmemleak 分析更具系统性。


十、结语:内存分配带来的卡顿问题不容忽视

  1. UI 卡顿不一定是 CPU 不够,Page Fault 更可能是根源;
  2. 应用层频繁 malloc/free 会在系统产生频繁页帧分配及回收;
  3. 结合 perf、ftrace、slabinfo 多维度分析内核行为;
  4. kmem_cache 与 alloc_pages 虽分属 slab/buddy,但密切协作,共同完成内存管理;
  5. 面对用户交互类场景,应尽可能复用内存、控制频率,避免每次输入都触发一次 Page Fault。

观看更多规范化 Linux 性能调优和高性能开发系列,请关注 B 站:「嵌入式 Jerry」


支持作者,点击京东购买《Yocto项目实战教程》


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值