You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

进程匿名内存保存系统调用中copy_from_user/memcpy复制失败求助

进程匿名内存上下文保存系统调用的Panic问题排查与修复

问题背景

我正在编写一个系统调用,用于保存进程的上下文(即进程动态分配的匿名内存)。当系统调用参数state为0时,需要将进程地址空间(mm->mmap)中的动态分配匿名内存复制到内核缓冲区,并添加到task_struct的list_head字段(context)中。

现有实现代码

struct page_context {
    unsigned long addr;
    unsigned char *buffer;
    struct list_head list;
 };

SYSCALL_DEFINE1(mmcontext, int, state)
{
    struct task_struct *task;
    int res;
    struct mm_struct *mm;
    struct vm_area_struct *vma;
    unsigned long start_addr, end_addr, curr_addr;
    struct page_context *pctx;

    if (state == 0) {
        task = current;
        if(!list_empty(&task -> context)) {
            printk(KERN_INFO "Process already has its context saved\n");
            return -EINVAL;
        }

        mm = task->mm;

        down_write(&mm->mmap_lock);

        // Traverse the VMA list and copy pages
        vma = mm->mmap;
        printk(KERN_INFO "PAGE_SIZE = %zu\n", PAGE_SIZE);
        while (vma) {
            if (vma->vm_flags & VM_WRITE) {
                start_addr = vma->vm_start;
                end_addr = vma->vm_end;
                curr_addr = start_addr;
                while (curr_addr < end_addr) {
                       pctx = kmalloc(sizeof(struct page_context), GFP_KERNEL);
                       if (!pctx) {
                       printk(KERN_ERR "Failed to allocate memory for page context\n");
                       return -ENOMEM;
                       }
                       printk(KERN_INFO "Allocated process context\n");
                       pctx->addr = curr_addr;
                       pctx->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
                       INIT_LIST_HEAD(&pctx->list);
                       if (!pctx -> buffer) {
                           printk(KERN_ERR "Failed to allocate memory for page context buffer\n");
                           kfree(pctx);
                           return -ENOMEM;
                       }
                       printk(KERN_INFO "Before memcpy from userspace to kernel\n");
                       // memcpy((void *)pctx->buffer, (void *)curr_addr, PAGE_SIZE);
                       res = copy_from_user((void *)pctx->buffer, (void *)curr_addr, PAGE_SIZE);
                       if (res != 0) {
                       printk(KERN_ERR "Failed to copy %d bytes from userspace to kernel space\n", res);
                       }
                       printk(KERN_INFO "Copied memory from curr_addr to buffer\n");
                       list_add(&pctx->list, &task -> context);
                       num_pages++;

                       curr_addr += PAGE_SIZE;
                }
            }
            vma = vma->vm_next;    
       }
       up_write(&mm->mmap_lock);

       return 0;
   }
}

遇到的问题

使用copy_from_user或memcpy复制VMA内存时触发内核panic,抛出“supervisor mode read access”异常,怀疑是内存块未对齐导致,但不确定具体修复方式,也想了解是否有其他实现思路。


问题根源分析

  1. 内存未实际分配:VMA只是地址空间的描述,其中部分地址可能尚未分配物理页(比如用户进程用mmap匿名映射但未写入过),直接访问会触发页错误,内核态下未处理的页错误会导致panic。
  2. VMA类型过滤错误:仅通过VM_WRITE判断会错误包含可写文件映射,而非目标匿名内存。
  3. 错误处理不完整:中途分配或复制失败时,已分配的内存未释放,造成内存泄漏。
  4. 边界地址未对齐:部分VMA的起始/结束地址可能未按页对齐,直接按PAGE_SIZE遍历会访问无效区域。

修复方案与优化实现

1. 精准过滤匿名VMA

只处理带VM_ANON标志的可写VMA,确保目标是匿名内存:

if ((vma->vm_flags & VM_WRITE) && (vma->vm_flags & VM_ANON)) {
    // 处理该VMA
}

2. 安全获取用户物理页

用get_user_pages_fast替代直接内存访问,该函数会自动处理页分配和权限检查,避免非法访问:

struct page *page;
int ret = get_user_pages_fast(curr_addr, 1, 1, &page);
if (ret != 1) {
    // 页未分配或无权限,跳过该页
    curr_addr += PAGE_SIZE;
    continue;
}

// 复制页内容到内核缓冲区
copy_page(pctx->buffer, page_address(page));

// 释放页引用
put_page(page);

3. 确保地址页对齐

对VMA的起始/结束地址做页对齐处理,避免访问页内无效区域:

start_addr = PAGE_ALIGN(vma->vm_start);
end_addr = vma->vm_end & PAGE_MASK;
if (start_addr >= end_addr) {
    vma = vma->vm_next;
    continue;
}

4. 完善错误清理逻辑

添加清理函数,在中途失败时释放已分配的所有资源:

static void cleanup_context(struct task_struct *task) {
    struct page_context *pctx, *tmp;
    list_for_each_entry_safe(pctx, tmp, &task->context, list) {
        list_del(&pctx->list);
        kfree(pctx->buffer);
        kfree(pctx);
    }
}

// 分配失败时调用示例
if (!pctx) {
    printk(KERN_ERR "Failed to allocate memory for page context\n");
    cleanup_context(task);
    up_write(&mm->mmap_lock);
    return -ENOMEM;
}

5. 优化后的核心遍历代码

while (vma) {
    if ((vma->vm_flags & VM_WRITE) && (vma->vm_flags & VM_ANON)) {
        start_addr = PAGE_ALIGN(vma->vm_start);
        end_addr = vma->vm_end & PAGE_MASK;
        if (start_addr >= end_addr) {
            vma = vma->vm_next;
            continue;
        }
        curr_addr = start_addr;
        while (curr_addr < end_addr) {
            pctx = kmalloc(sizeof(struct page_context), GFP_KERNEL);
            if (!pctx) {
                printk(KERN_ERR "Failed to allocate memory for page context\n");
                cleanup_context(task);
                up_write(&mm->mmap_lock);
                return -ENOMEM;
            }
            pctx->addr = curr_addr;
            pctx->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
            INIT_LIST_HEAD(&pctx->list);
            if (!pctx->buffer) {
                printk(KERN_ERR "Failed to allocate memory for page context buffer\n");
                kfree(pctx);
                cleanup_context(task);
                up_write(&mm->mmap_lock);
                return -ENOMEM;
            }

            struct page *page;
            int ret = get_user_pages_fast(curr_addr, 1, 1, &page);
            if (ret != 1) {
                printk(KERN_WARNING "Skipping unallocated page at 0x%lx\n", curr_addr);
                kfree(pctx->buffer);
                kfree(pctx);
                curr_addr += PAGE_SIZE;
                continue;
            }

            copy_page(pctx->buffer, page_address(page));
            put_page(page);

            list_add(&pctx->list, &task->context);
            num_pages++;

            curr_addr += PAGE_SIZE;
        }
    }
    vma = vma->vm_next;
}

其他实现思路

  1. 利用进程快照机制:借助ptrace的内存读取能力,或通过fork的写时复制特性间接获取内存内容,但需额外处理快照逻辑。
  2. 遍历进程页表:直接解析mm->pgd多级页表,定位匿名页并复制,实现复杂度较高,但效率更优。
  3. 用户态预分配:让用户进程先写入所有匿名内存区域触发页分配,再调用系统调用,降低内核态处理复杂度,但会增加用户态开销。

内容的提问来源于stack exchange,提问作者m_here

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 16:27:30