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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:55:01