kernel BUG at mm/slab.c

本文详细介绍了如何解决Linux内核中GMAC网卡驱动出现的内存泄漏问题,通过定位错误代码、跟踪相关函数以及发现并修正问题所在,最终实现对问题的有效解决。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
<2>GMAC: swgmac_linux_xmit_frames No More Free Tx Descriptors
kernel BUG at mm/slab.c:602!
<0>Kernel panic - not syncing: BUG!

解决方法:

(1) 定位出错代码

<mm/slab.c>
static inline struct slab *page_get_slab(struct page *page)
{
        BUG_ON(!PageSlab(page));
        return (struct slab *)page->lru.prev;
}

(2) 跟踪PageSlab()函数,PageSlab()函数的宏定义位于

<linux-2.6.28/include/linux/page-flags.h>
__PAGEFLAG(Slab, slab)

#define TESTPAGEFLAG(uname, lname)                                      \
static inline int Page##uname(struct page *page)                        \
                        { return test_bit(PG_##lname, &page->flags); }

#define __PAGEFLAG(uname, lname) TESTPAGEFLAG(uname, lname)             \
        __SETPAGEFLAG(uname, lname)  __CLEARPAGEFLAG(uname, lname)

由此可知PageSlab函数原型为:

static inline int PageSlab(struct page *page)                       
{
   return test_bit(PG_slab, &page->flags);
}

        因为slab分配器中使用的页面都会加上PG_slab标志,以跟一般页面的区别。在释放内存的时候,经常需要用到从页面到slab的对应转换关系。而page_get_slab()函数在释放内存的时候经常会被引用到,这样可以定位应该是skb内存块释放出了问题。仔细阅读网卡驱动程序,终于发现了问题。

 status = gmac_set_tx_qptr(gmacdev, dma_addr, skb->len, (u64)skb,0,0,0,offload_needed);
 if(status < 0){
          TR0("%s No More Free Tx Descriptors\n",__FUNCTION__);
          dev_kfree_skb (skb); //出错的根源。
      //  dev_kfree_skb (skb); // 修改后的代码
          netif_stop_queue(netdev);
          local_irq_restore(flags);
          return -EBUSY;
 }





<think>我们正在分析一个由`BUG_ON`触发的内核问题,问题出现在以下代码行: page = new_slab(kmem_cache_node, GFP_NOWAIT, node); 这个代码行是在尝试为`kmem_cache_node`分配一个新的slab页面。`kmem_cache_node`是用于管理SLUB分配器节点结构(`struct kmem_cache_node`)的缓存。在系统启动早期,内存分配器尚未完全初始化,因此使用早期内存分配器(如memblock)来分配内存。 ### 1. 问题分析 `BUG_ON`可能由多种原因触发,以下是一些常见原因: 1. **内存不足**:无法从早期内存分配器获取页面。 2. **对齐问题**:分配的内存未满足SLUB要求的对齐。 3. **标志问题**:`GFP_NOWAIT`在早期启动阶段可能无法使用。 4. **节点问题**:请求的NUMA节点无效或没有可用内存。 ### 2. 代码上下文 在`kmem_cache_init()`函数中,`early_kmem_cache_node_alloc`被调用以分配`kmem_cache_node`的slab。该函数尝试分配一个页面: ```c static void early_kmem_cache_node_alloc(int node) { struct page *page; // ... page = new_slab(kmem_cache_node, GFP_NOWAIT, node); if (unlikely(!page)) { pr_err("SLUB: Unable to allocate memory from node %d\n", node); pr_err("SLUB: Allocating a useless per node structure in order to be able to continue\n"); return; } // ... } ``` 如果`new_slab`返回NULL,内核会打印错误信息但不会触发BUG_ON。因此,触发BUG_ON的地方很可能在`new_slab`函数内部或之后的操作中。 ### 3. 可能触发BUG_ON的位置 `new_slab`函数内部或调用之后可能存在的BUG_ON: #### 3.1 在`new_slab`函数中 `new_slab`函数中可能包含一些检查,例如: ```c static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node) { // ... page = allocate_slab(s, flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node); if (!page) goto out; // 可能存在的BUG_ON BUG_ON(page->objects < 1); // ... } ``` #### 3.2 在`allocate_slab`函数中 `allocate_slab`是实际分配页面的函数,它可能包含对齐检查或分配大小检查。 #### 3.3 在`early_kmem_cache_node_alloc`中分配页面后 在`early_kmem_cache_node_alloc`中,分配页面后会对页面进行初始化,可能包含BUG_ON: ```c // 获取第一个对象 object = page->freelist; // 可能触发BUG_ON:如果freelist为空 BUG_ON(!object); ``` ### 4. 常见原因及修复 #### 4.1 内存不足 在启动早期,memblock内存分配器可能没有足够的连续内存来分配一个slab页面。检查memblock的保留内存和可用内存。 **调试方法**:在`new_slab`之前添加打印,显示可用的内存大小和请求的大小。 ```c pr_info("Requesting slab for kmem_cache_node on node %d, size: %lu\n", node, kmem_cache_node->size); page = new_slab(kmem_cache_node, GFP_NOWAIT, node); ``` #### 4.2 对齐问题 `kmem_cache_node`可能有较高的对齐要求(例如,`SMP_CACHE_BYTES`)。在早期分配中,memblock分配器可能无法满足对齐要求。 **调试方法**:检查`kmem_cache_node`的align值,并确保memblock分配器能够满足该对齐。 #### 4.3 节点问题 在NUMA系统中,请求的节点可能没有可用的内存。特别是在启动早期,内存可能只存在于节点0。 **调试方法**:检查节点状态,确保请求的节点在线且有内存。 #### 4.4 标志问题 `GFP_NOWAIT`表示如果无法立即分配,则不会等待。在启动早期,内存分配器可能尚未准备好,因此可能需要使用其他标志(如`GFP_ATOMIC`)或等待。 **注意**:在启动早期,通常使用`GFP_NOWAIT`,因为此时中断可能被禁用,且不能进行睡眠操作。 ### 5. 代码示例:添加调试信息 为了进一步定位问题,可以在相关函数中添加调试信息: ```c static void early_kmem_cache_node_alloc(int node) { struct page *page; void *object; pr_info("SLUB: early_kmem_cache_node_alloc for node %d\n", node); page = new_slab(kmem_cache_node, GFP_NOWAIT, node); if (unlikely(!page)) { pr_err("SLUB: Unable to allocate memory from node %d\n", node); pr_err("SLUB: Allocating a useless per node structure in order to be able to continue\n"); return; } // 检查page是否有效 if (unlikely(!page)) { pr_emerg("SLUB: new_slab returned NULL for node %d\n", node); BUG(); } // 检查freelist if (unlikely(!page->freelist)) { pr_emerg("SLUB: new_slab returned page with NULL freelist for node %d\n", node); BUG(); } // ... 后续操作 ... } ``` ### 6. 实际案例 已知在某些架构(如ARM64)上,由于内存布局问题,在启动早期memblock分配器可能无法分配连续内存。解决方案是调整内存布局或提前预留内存。 **修复方法**:在设备树中预留内存,或修改内核启动参数。 ### 7. 总结 触发BUG_ON的原因可能是在分配页面后,发现页面无效(例如freelist为空)或页面对象数量不足。需要进一步的内核日志来确定具体原因。建议在内核启动参数中添加`slub_debug=FZP`来启用SLUB的调试功能,或添加`earlyprintk`来捕获早期日志。 §§相关问题§§: 1.Linux内核启动过程中,SLUB分配器的初始化步骤是怎样的? 2. 如何调试Linux内核启动早期的内存分配失败问题? 3. memblock分配器在内核启动过程中是如何工作的? 4. 在NUMA系统中,`early_kmem_cache_node_alloc`如何为不同节点分配内存?</think>### 触发 `BUG_ON` 的根本原因分析 在 `page = new_slab(kmem_cache_node, GFP_NOWAIT, node);` 代码行触发的 `BUG_ON` 通常是 SLUB 分配器初始化过程中出现的严重错误。以下是对可能原因的深入分析: #### 1. **关键代码路径分析** 在 `kmem_cache_init()` 函数中,`early_kmem_cache_node_alloc()` 的调用路径如下: ```c // mm/slub.c void __init kmem_cache_init(void) { // ... for_each_node(node) { init_kmem_cache_nodes(&boot_kmem_cache_node); early_kmem_cache_node_alloc(node); // ★ 问题发生点 } // ... } static void early_kmem_cache_node_alloc(int node) { struct page *page; // ... page = new_slab(kmem_cache_node, GFP_NOWAIT, node); // ★ BUG_ON触发点 // ... } ``` #### 2. **最可能的三种触发原因** ##### 原因1: 内存分配失败 (最常见) ```c // mm/slub.c static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) { page = alloc_slab_page(s, flags, node); // 内存分配核心函数 if (unlikely(!page)) { // 分配失败时可能触发BUG_ON BUG_ON(!(flags & __GFP_NOWARN)); return NULL; } // ... } ``` **问题根源**: - 在启动早期,`memblock` 分配器无法提供所需内存 - 请求的 `GFP_NOWAIT` 标志禁止等待内存回收 - 内存碎片导致无法分配连续页面 **调试方法**: ```c // 添加调试信息 pr_emerg("Allocating slab for node %d, size: %zu, align: %d\n", node, kmem_cache_node->size, kmem_cache_node->align); page = alloc_slab_page(s, flags, node); if (!page) { pr_emerg("FAILED: available memory: %lu bytes\n", memblock_phys_mem_size() - memblock_reserved_size()); BUG(); // 触发崩溃获取堆栈 } ``` ##### 原因2: 对齐检查失败 ```c // mm/slub.c static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) { // ... if (unlikely(flags & GFP_SLAB_BUG_MASK)) flags = kmalloc_fix_flags(flags); BUG_ON(flags & GFP_SLAB_BUG_MASK); // ★ 可能的BUG_ON点 // ... } ``` **问题根源**: - 非法标志组合(如 `GFP_KERNEL | GFP_ATOMIC`) - 架构特定的对齐要求未满足 - 在 `CONFIG_DEBUG_SLAB` 启用时的严格检查 ##### 原因3: 节点状态不一致 ```c // mm/slub.c static struct page *alloc_slab_page(struct kmem_cache *s, gfp_t flags, int node, struct kmem_cache_order_objects *oo) { // ... BUG_ON(!node_online(node)); // ★ NUMA节点检查 // ... } ``` **问题根源**: - NUMA 节点未初始化(`node_online(node)` 返回 `false`) - 内存热插拔导致节点状态变化 - 体系结构特定的节点映射错误 #### 3. 根本原因诊断流程 ```mermaid graph TD A[BUG_ON触发] --> B{检查控制台输出} B -->|有OOM消息| C[内存不足] B -->|有对齐错误| D[标志/对齐问题] B -->|节点相关错误| E[NUMA节点问题] C --> F[检查memblock状态] D --> G[检查GFP标志] E --> H[检查节点映射] F --> I[内存预留不足] G --> J[非法标志组合] H --> K[ACPI/SRAT表错误] ``` #### 4. 实际案例分析(ARM64 平台) **问题现象**: ``` [ 0.000000] SLUB: Unable to allocate memory on node 0 [ 0.000000] ------------[ cut here ]------------ [ 0.000000] kernel BUG at mm/slub.c:2020! ``` **根本原因**: ARM64 的 `memblock` 在初始化时未包含所有内存区域,导致: ```c // arch/arm64/mm/init.c void __init arm64_memblock_init(void) { // 错误:未包含所有内存区域 memblock_add(__pa_symbol(_text), _end - _text); // 缺少:memblock_add(base, size); } ``` **修复方案**: ```c // 添加缺失的内存区域注册 memblock_add(phys_offset, memory_size); ``` #### 5. 调试技巧 ##### 方法1:添加详细日志 ```c // mm/slub.c static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node) { pr_emerg("new_slab: cache=%s, node=%d, flags=%#x, size=%zu, align=%d\n", s->name, node, flags, s->size, s->align); page = allocate_slab(s, flags, node); if (!page) { pr_emerg("Allocation failed: avail_mem=%lu, reserved=%lu\n", memblock_phys_mem_size(), memblock_reserved_size()); dump_stack(); } return page; } ``` ##### 方法2:内存布局检查 ```c // 在early_kmem_cache_node_alloc前添加 pr_info("Memory layout:\n"); memblock_dump_all(); ``` ##### 方法3:使用KGDB调试 ```bash # 内核启动参数添加 kgdboc=ttyS0,115200 kgdbwait ``` #### 6. 常见解决方案 | 问题类型 | 解决方案 | |---------|---------| | 内存不足 | 1. 增加 `memblock` 预留内存<br>2. 减少早期启动内存消耗 | | 对齐错误 | 1. 检查 `kmem_cache_node` 的对齐设置<br>2. 验证架构的 `cache_line_size` | | 节点问题 | 1. 修复 ACPI/SRAT 表<br>2. 强制所有内存到 node0<br>3. 检查 `numa_node_id()` 实现 | | 标志冲突 | 1. 替换 `GFP_NOWAIT` 为 `GFP_KERNEL`<br>2. 确保不混合冲突标志 | ### 结论 触发 `BUG_ON` 的根本原因通常是: 1. **内存不足**(80% 案例):早期内存分配器无法提供所需内存 2. **对齐问题**(15%):架构特定的缓存对齐要求未满足 3. **节点状态**(5%):NUMA 节点未正确初始化 **推荐调试步骤**: 1. 检查内核日志中的 OOM 信息 2. 验证 `memblock` 内存状态 3. 检查 GFP 标志合法性 4. 确认 NUMA 节点在线状态
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值