在 Linux 中,虚拟地址的读写是内核与用户空间交互的核心操作。由于进程运行在虚拟内存环境中,内核不能直接访问用户空间指针,而必须通过进程的页表进行地址转换与权限校验。读写虚拟地址的本质是将用户空间的虚拟地址映射到内核地址空间,或通过安全的内核API完成数据复制。

针对用户态虚拟地址,内核提供了两组主要接口:copy_to_user / copy_from_user 与 put_user / get_user。这些函数在内部会调用 access_ok 对地址范围进行合法性检查,并处理缺页异常。在 x86 架构上,它们利用 movs 指令配合异常修复机制(exception table)实现安全拷贝。例如,copy_from_user 将用户空间数据复制到内核缓冲,若用户地址非法,则返回未复制的字节数,而不会产生内核崩溃。
对于内核态虚拟地址的读写,情况不同。内核地址空间直接映射到物理内存(如 x86 的 线性映射区),此时只需通过 phys_to_virt 与 virt_to_phys 进行地址转换。若需要访问vmalloc 分配的区域,则必须使用 vread / vwrite 函数,因为这些区域对应的页表可能不连续,需要特殊处理。此外,通过 kmap / kunmap 可以临时映射高端内存页,实现对任意物理页的虚拟地址读写。
在进程地址空间层面,读写虚拟地址还涉及 struct mm_struct 与 struct vm_area_struct。内核通过 find_vma 查找虚拟地址所在的VMA,并检查访问权限。如果地址不在任何VMA内,则触发段错误;若页表项不存在但VMA合法,则触发缺页异常,最终由 do_page_fault 处理。对于直接物理页的读写,可使用 follow_page 或 get_user_pages 获取虚拟地址对应的物理页,再通过 kmap 映射后读写。
实现任意虚拟地址的读写(如调试器或进程内存注入)通常需要调用 ptrace 系统调用(例如 process_vm_readv / process_vm_writev),这些接口在内核中基于 get_user_pages 和 access_process_vm 实现。它们允许一个进程以安全方式读取另一个进程的虚拟地址,但会受到权限限制(如 PTRACE_MODE_ATTACH_REALCREDS)。
最后,针对内核模块中直接访问用户指针的常见错误,必须强调:绝对禁止直接解引用用户空间指针。应当使用上述安全API,并在访问前使用 might_fault 或 pagefault_disable 控制上下文。在多线程或并发场景下,还需考虑 mmap_lock(即旧版 mmap_sem)的持有状态,避免死锁。正确的读写流程总结为:验证地址范围 → 获取页表锁 → 复制数据 → 处理异常,这是Linux虚拟内存子系统对外提供服务的基本范式。

查看详情

查看详情