如何在ARM64架构Linux(M1 MacBook)下从逻辑地址获取物理地址?
在ARM64架构Linux(M1 MacBook)中从虚拟地址获取物理地址的实现方法
原代码的问题分析
你提供的代码存在几个关键错误:
- 错误使用
pud_offset_phys、pmd_offset_phys这类函数:它们接收的是物理地址形式的页表项指针,但你通过pgd_offset得到的是虚拟地址形式的pgd_t*,应该使用虚拟地址版本的pud_offset、pmd_offset、pte_offset_kernel函数。 - 缺少页表项有效性检查:每个层级的页表项都需要验证是否存在、是否合法,否则会触发内核错误。
- 权限限制:用户态程序无法直接访问内核页表结构体,这类操作必须在内核态(内核模块、内核程序)中执行,或通过用户态合法接口实现。
方法一:内核态实现(内核模块)
以下是ARM64架构下内核态的正确实现,需编译为内核模块运行:
#include <linux/mm.h> #include <linux/sched.h> #include <linux/module.h> unsigned long virt_to_phys_kernel(unsigned long virt_addr) { struct mm_struct *mm = current->mm; // 当前进程的内存描述符 pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; unsigned long pfn; // 区分用户虚拟地址与内核虚拟地址 if (virt_addr >= TASK_SIZE) { mm = &init_mm; } pgd = pgd_offset(mm, virt_addr); if (pgd_none(*pgd) || pgd_bad(*pgd)) { return 0; // 无效页表项 } pud = pud_offset(pgd, virt_addr); if (pud_none(*pud) || pud_bad(*pud)) { return 0; } pmd = pmd_offset(pud, virt_addr); if (pmd_none(*pmd) || pmd_bad(*pmd)) { return 0; } pte = pte_offset_kernel(pmd, virt_addr); if (!pte || pte_none(*pte) || !pte_present(*pte)) { return 0; } pfn = pte_pfn(*pte); // 计算物理地址:页帧号左移页大小偏移 + 页内偏移 return (pfn << PAGE_SHIFT) | (virt_addr & (PAGE_SIZE - 1)); } // 模块测试函数(示例) static int __init virt2phys_init(void) { unsigned long virt_addr = (unsigned long)__initdata_start; unsigned long phys_addr = virt_to_phys_kernel(virt_addr); printk(KERN_INFO "Virtual address: 0x%lx | Physical address: 0x%lx\n", virt_addr, phys_addr); return 0; } static void __exit virt2phys_exit(void) { printk(KERN_INFO "Module exited\n"); } module_init(virt2phys_init); module_exit(virt2phys_exit); MODULE_LICENSE("GPL");
方法二:用户态实现(通过/proc文件系统)
用户态可通过/proc/[pid]/pagemap文件获取页帧号(PFN),进而计算物理地址,需root权限运行:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <stdint.h> #include <sys/mman.h> #define PAGE_SIZE 4096 #define PAGEMAP_ENTRY_SIZE 8 #define PFN_MASK ((1ULL << 55) - 1) #define PAGEMAP_PRESENT (1ULL << 63) unsigned long virt_to_phys_user(unsigned long virt_addr) { int fd = open("/proc/self/pagemap", O_RDONLY); if (fd < 0) { perror("open pagemap failed"); return 0; } // 计算目标页在pagemap中的偏移位置 off_t offset = (virt_addr / PAGE_SIZE) * PAGEMAP_ENTRY_SIZE; if (lseek(fd, offset, SEEK_SET) != offset) { perror("lseek failed"); close(fd); return 0; } uint64_t entry; if (read(fd, &entry, sizeof(entry)) != sizeof(entry)) { perror("read pagemap failed"); close(fd); return 0; } close(fd); // 检查页是否存在于物理内存 if (!(entry & PAGEMAP_PRESENT)) { fprintf(stderr, "Page not present in physical memory\n"); return 0; } uint64_t pfn = entry & PFN_MASK; return (pfn << PAGE_SHIFT) | (virt_addr & (PAGE_SIZE - 1)); } int main() { // 分配内存并触发物理页分配(写操作) int *test_ptr = malloc(PAGE_SIZE); if (!test_ptr) { perror("malloc failed"); return 1; } *test_ptr = 0x1234; unsigned long virt_addr = (unsigned long)test_ptr; unsigned long phys_addr = virt_to_phys_user(virt_addr); if (phys_addr) { printf("Virtual address: 0x%lx\nPhysical address: 0x%lx\n", virt_addr, phys_addr); } free(test_ptr); return 0; }
编译运行说明:
gcc -o virt2phys_user virt2phys_user.c sudo ./virt2phys_user
内容的提问来源于stack exchange,提问作者iceprins
相关产品推荐
相关产品推荐

