ISP驱动块中频繁、无序地 malloc/free 导致的问题

目录

[一、频繁无序 malloc/free 为什么会让 malloc 执行耗时、拉高 CPU](#一、频繁无序 malloc/free 为什么会让 malloc 执行耗时、拉高 CPU)

[1. 外部碎片加剧,分配流程变长](#1. 外部碎片加剧,分配流程变长)

[2. 多线程下 arena 锁自旋空转](#2. 多线程下 arena 锁自旋空转)

[3. 频繁 mmap/munmap 系统调用](#3. 频繁 mmap/munmap 系统调用)

[4. 堆裁剪(trim)额外开销](#4. 堆裁剪(trim)额外开销)

总结耗时链路

[二、中断上下文(硬中断 / 软中断)绝对不能调用 malloc /free 的核心原因](#二、中断上下文(硬中断 / 软中断)绝对不能调用 malloc /free 的核心原因)

[1. malloc/free 会睡眠,中断上下文禁止睡眠(最核心)](#1. malloc/free 会睡眠,中断上下文禁止睡眠(最核心))

[2. 中断上下文没有进程上下文,无法调度睡眠](#2. 中断上下文没有进程上下文,无法调度睡眠)

[3. 锁递归死锁风险](#3. 锁递归死锁风险)

[4. 内存分配器依赖进程页表、用户态堆结构](#4. 内存分配器依赖进程页表、用户态堆结构)

[5. 实时性破坏 + 中断处理超时](#5. 实时性破坏 + 中断处理超时)

[补充区分:内核中断 / 用户程序信号回调](#补充区分:内核中断 / 用户程序信号回调)

三、总结


ISPPipeline中会有很多处理模块需要进行分块处理,而对其进行数据逻辑处理的时候需要分配较大的一些数组保存中间变量。比如对某个模块需要分配64*32或者32*24这么大的二维数组。如果在ISP驱动库中每一次调用都进行malloc进行内存分配,然后再进行释放的话,频繁调用会带来以下问题:

  1. 带来内存碎片化问题;
  2. 消耗CPU
  3. 频繁分配释放内存后,后续malloc时间将增加;
  4. 频繁操作之后,可能某一阶段分配不到较大内存;

一般不建议在驱动块底层进行如此频繁的分配和释放操作。一般尽可能的初始化时一次性分配好所需要的内存,然后再最终结束时释放掉分配的内存。较小的数组变量可以使用栈中保存。

一、频繁无序 malloc/free 为什么会让 malloc 执行耗时、拉高 CPU

1. 外部碎片加剧,分配流程变长

无序分配释放会把堆切成大量分散小空闲块:

  1. 分配时先遍历 fastbin 找匹配块,碎片多则链表节点极多,循环遍历耗时上涨;
  2. fastbin 找不到会走 smallbin,再次遍历;
  3. 分配大块时触发 malloc_consolidate,批量把 fastbin 里所有空闲块取出、检查相邻、合并,海量循环计算消耗 CPU;
  4. 所有 bin 都无合适连续块,只能调用sbrk/brk扩充堆,触发系统调用、内核页表操作。

free 时每次都要检查左右相邻块、执行合并逻辑,碎片越多合并操作越频繁。

2. 多线程下 arena 锁自旋空转

glibc ptmalloc 每个 arena 一把互斥锁,malloc/free 全程持锁: 多线程并发频繁分配释放,大量线程抢锁失败后自旋忙等,CPU 空耗,火焰图可见大量 pthread_mutex_lock 占用。

3. 频繁 mmap/munmap 系统调用

若分配尺寸接近 / 超过 MMAP_THRESHOLD,每次分配释放都会陷入内核: 修改虚拟地址、页表、刷新 TLB、缺页异常,系统调用本身开销巨大。

4. 堆裁剪(trim)额外开销

大量 free 后分配器会调用sbrk收缩堆归还内存给 OS,频繁 trim 反复系统调用,进一步拖慢分配速度。

总结耗时链路

无序高频 malloc/free → 堆碎片暴增 → bin 链表变长、批量合并频繁、锁冲突剧烈、频繁系统调用 → 单次 malloc 执行时间显著变长,整体 CPU 占用飙升。


二、中断上下文(硬中断 / 软中断)绝对不能调用 malloc /free 的核心原因

1. malloc/free 会睡眠,中断上下文禁止睡眠(最核心)

malloc 底层存在多处可阻塞操作:

  • 申请内存不足时,内核内存回收、swap 换页会阻塞进程;
  • arena 互斥锁是可睡眠锁,拿不到锁时线程会进入休眠;
  • sbrk/mmap 系统调用存在阻塞场景。

Linux 硬性规则: 硬中断、软中断上下文不允许睡眠。 一旦中断里调用 malloc,发生睡眠会直接触发内核死锁、Oops、系统宕机。

2. 中断上下文没有进程上下文,无法调度睡眠

睡眠依赖进程调度器保存 / 切换进程栈、上下文; 中断是抢占当前进程的临时执行流,不属于任何进程,没有独立 task_struct,无法休眠等待资源。

3. 锁递归死锁风险

进程上下文已经持有 arena 锁时,硬件中断触发,中断内再次调用 malloc 抢同一把锁: 锁已被占用,中断不能睡眠,只能自旋;若持有锁的进程被该中断打断,永远无法释放锁 → 死锁,整机卡死。

4. 内存分配器依赖进程页表、用户态堆结构

malloc 管理的用户堆、arena、mmap 虚拟内存都属于当前用户进程; 中断上下文不属于任何进程,访问进程堆内存存在地址访问异常、页表错乱风险。

5. 实时性破坏 + 中断处理超时

malloc 内部大量链表遍历、块合并、系统调用,执行时长不可控; 硬中断要求快速退出,长时间占用中断会丢失硬件中断、设备异常、内核看门狗重启。


补充区分:内核中断 / 用户程序信号回调

  1. 内核驱动硬 / 软中断 :严禁 kmalloc /kfree 带 GFP_KERNEL(会睡眠),只能用 GFP_ATOMIC
  2. 用户态 signal 信号处理函数:同样不建议 malloc/free。信号会任意抢占主线程,若主线程正在 malloc 持锁,信号处理函数再次 malloc 会发生死锁。

三、总结

  1. 频繁无序 malloc/free 产生大量堆碎片,加长链表遍历、块合并、锁竞争、系统调用流程,导致 malloc 单次耗时拉长、CPU 飙升;
  2. 中断上下文禁止 malloc/free,根源是分配器会睡眠,而中断不允许休眠,同时存在锁死锁、中断超时、上下文不匹配等致命问题。