当前位置:首页>Linux>嵌入式Linux“内存分配”的“休眠陷阱”——全志嵌入式面试题

嵌入式Linux“内存分配”的“休眠陷阱”——全志嵌入式面试题

  • 2026-10-12 11:02:30
嵌入式Linux“内存分配”的“休眠陷阱”——全志嵌入式面试题

大家好,我是一个爱分享的牛马程序员,工作中碰到,加上自己理解,很高兴给大家分享。

-begin-

题目:在内核模块中,为何在中断服务程序里调用kmalloc(GFP_KERNEL)会导致系统崩溃?不同内存分配标志(GFP_KERNEL/GFP_ATOMIC)的适用场景有何区别?

分析流程:

1.现象解析:不少开发者在编写内核模块时,为方便分配内存,在中断服务程序或软中断中直接使用kmalloc(GFP_KERNEL),结果引发内核恐慌,错误信息常包含“BUG: sleeping function called from invalid context”。这是对内存分配标志的上下文限制理解不足导致的。
2.深层原因:

内核内存分配函数的行为由分配标志(gfp_t)决定,不同标志对应不同的内存获取策略,而策略的选择必须匹配当前的执行上下文,就像不同路况需要不同的驾驶模式(高速路用巡航,拥堵路段用蠕动):

◦GFP_KERNEL的特性:当内存不足时,GFP_KERNEL会触发内核的内存回收机制(如换出不常用页面),若仍无法分配,会让当前进程进入休眠状态,等待内存释放。而中断服务程序运行在中断上下文(不可调度、不能休眠),强制休眠会破坏内核状态,导致崩溃;
◦GFP_ATOMIC的限制:GFP_ATOMIC不会休眠,内存不足时会立即返回NULL,但只能分配“紧急内存池”中的内存,分配大小和成功率有限;
◦上下文与分配标志的匹配:进程上下文(如系统调用、内核线程)可休眠,适合用GFP_KERNEL;中断上下文(ISR、软中断)不可休眠,必须用GFP_ATOMIC。

可以结合生活常识理解:GFP_KERNEL就像“到餐厅点餐”,没座位时可以坐下等;GFP_ATOMIC就像“便利店买东西”,没货就立即走,不能等。中断服务程序就像“正在灭火的消防员”,不能坐下等(休眠),必须用“买了就走”的方式。

我之前开发一款触摸屏驱动时,曾在触摸中断处理中用GFP_KERNEL分配缓冲区,结果在系统内存紧张时,触摸操作会导致设备重启。后来改用GFP_ATOMIC并提前预留内存,才解决了问题——这就是忽视上下文特性的教训。

3.内存分配标志的正确使用:
◦GFP_KERNEL适用场景:进程上下文(如sys_call、workqueue工作项),需要较大内存且可接受短暂等待,例如:

// 内核线程中分配内存(进程上下文)

static int my_kernel_thread(void *data) {

   char *buf = kmalloc(4096, GFP_KERNEL);  // 内存不足时可休眠

   if (buf) {

       // 使用缓冲区

       kfree(buf);

   }

   return 0;

}

◦GFP_ATOMIC适用场景:中断上下文、软中断、持有spinlock时,需要立即返回,例如:

// 中断服务程序中分配内存(中断上下文)

irqreturn_t touch_isr(int irq, void *dev_id) {

   char *buf = kmalloc(128, GFP_ATOMIC);  // 不休眠,内存不足返回NULL

   if (buf) {

       // 快速处理数据

       kfree(buf);

   }

   return IRQ_HANDLED;

}

◦其他特殊标志:
▪GFP_DMA:分配可直接被DMA访问的内存(物理地址连续),适合硬件DMA传输;
▪GFP_NOWAIT:类似GFP_ATOMIC,但不允许访问紧急内存池,成功率更低;
▪__GFP_ZERO:分配内存并初始化为0(可与其他标志组合,如GFP_KERNEL | __GFP_ZERO)。

避免内存分配失败的技巧:

•提前预留内存:在模块初始化时(进程上下文)用GFP_KERNEL分配内存池,供中断上下文使用,避免动态分配的不确定性;

static char *g_buf_pool;

static int __init my_module_init(void) {

   g_buf_pool = kmalloc(8192, GFP_KERNEL);  // 初始化时分配

   if (!g_buf_pool) return -ENOMEM;

   return 0;

}

// 中断中直接使用预留的内存

irqreturn_t data_isr(...) {

   memcpy(g_buf_pool, data, len);  // 无需动态分配

}

•检查返回值:kmalloc可能返回NULL(尤其GFP_ATOMIC),必须判断分配结果,避免空指针访问;
•控制分配大小:GFP_ATOMIC适合分配小内存(如几百字节),大内存分配应在进程上下文完成。

结论:内核内存分配的核心是“上下文匹配”,中断上下文的“原子性”要求必须使用非休眠的分配标志。记住:不同的执行环境有不同的“规则”,就像在手术室不能用家用电器,中断上下文也不能用会休眠的内存分配方式。

-end-

如果文章对你有提升,帮忙点赞,分享,关注。十分感谢

最新文章

随机文章