Linux内核堆栈分配方式导致的panic分析

本文探讨了在NFC驱动移植过程中遇到的系统panic问题,详细分析了i2c读取时DMA映射异常的原因。由于局部变量在非连续物理页上分配,导致DMA操作失败。通过采用kmalloc分配连续内存,问题得以解决。

在移植NFC驱动时遇到系统panic的问题,查看堆栈信息,发现是i2c传输导致。

可以看出,在i2c read时出现DMA映射异常,这份驱动已经在其他平台使用过,为什么在新平台i2c会出现异常了?

首先看pn544_dev_read实现, 定义局部数组char tmp [512],传递给i2c_master_read函数,感觉很多驱动都是这么写的,也从没有

出现过错误.

static ssize_t pn544_dev_read(struct file *filp, char __user *buf,
        size_t count, loff_t *offset)
{
    struct pn544_dev *pn544_dev = filp->private_data;
    char tmp[MAX_BUFFER_SIZE];
    int ret
    /* Read data */
    ret = i2c_master_recv(pn544_dev->client, pn544_dev->client, count);
    if (copy_to_user(buf, tmp, ret)) {
        pr_warning("%s : failed to copy to user space\n", __func__);
        return -EFAULT;
    }
    return ret;
}

既然是报DMA传输错误,而且显示GPD页表项为空

[ffffffc02fa33b40] *pgd=0000000000000000, *pud=0000000000000000

我们对DMA的通用理解是DMA内存必须要物理上连续, 那么tmp内存是在栈上,难道会不连续吗?

进程的内核堆栈建立过程:

fork->copy_process->alloc_thread_stack_node:

可以看到,如果定义了CONFIG_VMAP_STACK,那么线程内核堆栈采用vmalloc分配,否则直接分配连续的物理页面.

static unsigned long *alloc_thread_stack_node(struct task_struct *tsk, int node)
{
#ifdef CONFIG_VMAP_STACK
    void *stack;
    
    stack = __vmalloc_node_range(THREAD_SIZE, THREAD_ALIGN,
                     VMALLOC_START, VMALLOC_END,
                     THREADINFO_GFP,
                     PAGE_KERNEL,
                     0, node, __builtin_return_address(0));
    if (stack)
        tsk->stack_vm_area = find_vm_area(stack);
    return stack;
#else
    struct page *page = alloc_pages_node(node, THREADINFO_GFP,
                         THREAD_SIZE_ORDER);

    return page ? page_address(page) : NULL;
#endif
}

通过查看defconfig文件,发现还真定义了CONFIG_VMAP_STACK,所以在函数定义局部的tmp数组,是通过vmalloc而来,

tmp[512]虽然虚拟地址连续,但是这512Byte内存,可能分部在两个不连续的物理page里面。然后进行DMA操作,就有可能错误.

解决办法: 采用kmalloc分配tmp的内存,问题得到解决.

### 不使用栈实现判断字符串是否为可能的出栈序列 在不借助显式的栈结构的情况下,可以通过双指针技术以及数组模拟的方式完成这一任务。核心思想是利用两个指针分别指向原字符串和目标字符串的不同部分,并动态调整它们之间的关系以验证合法性。 #### 方法解析 定义两个指针 `pushPtr` 和 `popPtr` 分别表示当前正在处理的入栈字符位置与期望匹配的目标出栈字符位置。同时维护一个虚拟栈的状态,即记录哪些元素已经进入过栈但尚未被弹出。这一步可通过布尔型数组或者整数计数器来代替传统意义上的栈存储方式[^3]。 对于每次迭代: - 如果发现某个待入栈的新字符正好满足当前目标需求,则直接前进至下一组对应项; - 否则尝试查找之前已存入却还未移除的部分是否存在符合条件者——一旦找到便更新状态继续推进流程直至结束条件达成为止。 #### 实现代码 下面给出基于上述原理的具体 C++ 实现版本: ```cpp #include <iostream> #include <vector> using namespace std; // Function to check if the given sequence is valid pop order of elements pushed into a stack. bool IsPopOrder(const string& pushStr, const string& popStr){ int n = pushStr.length(); // Initialize two pointers and an array indicating whether each element has been popped or not. int pushIdx = 0; int popIdx = 0; vector<bool> inStack(n, false); for (; popIdx < n; ){ bool foundInStack = false; // Try finding match from already pushed but unpopped items first. for (int j=n-1 ;j>=0 && !foundInStack;j--){ if(!inStack[j]){ continue ; } if(popStr[popIdx]==pushStr[j]){ inStack[j]=false;// Mark as out-of-stack now since it&#39;s matched here. foundInStack=true; break; } } if(foundInStack){ popIdx++; continue; } // If no matching item within current &#39;virtual&#39; stack content then try pushing next one onto virtual stack. if(pushIdx<n&&popStr[popIdx]!=pushStr[pushIdx]){ inStack[pushIdx++]=true; }else{ if(pushIdx<n){ pushIdx++; // Move forward after successful direct comparison without needing intermediate storage simulation anymore. popIdx++; }else{ return false; } } } return true; } int main(){ string originalString="abc"; vector<string> testCases={"abc","bca","cab"}; for(auto testCase:testCases){ cout<<testCase<<" -> "<<(IsPopOrder(originalString , testCase )?"YES":"NO")<<endl; } return 0; } ``` #### 关键点解释 1. 使用了一个额外的空间 `inStack[]` 来标记哪些元素已经在所谓的“虚拟栈”里,从而避免了真实创建栈对象带来的开销。 2. 双重循环控制逻辑确保即使没有实际物理上的堆叠也能正确反映相对次序约束下的可行性判定标准[^4]. --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值