当前位置: 首页 > news >正文

Linux ARM64 将内核虚拟地址转化为物理地址

文章目录

  • 前言
  • 一、通用方案
    • 1.1 kern_addr_valid
    • 1.2 __pa
  • 二、ARM64架构
    • 2.1 AT S1E1R
    • 2.2 is_kernel_addr_vaild
    • 2.3 va2pa_helper
  • 三、demo演示
  • 参考资料

前言

本文介绍一种通用的将内核虚拟地址转化为物理地址的方案以及一种适用于ARM64 将内核虚拟地址转化为物理地址的方案,两种方案都可以。

一、通用方案

1.1 kern_addr_valid

int kern_addr_valid(unsigned long addr)
{pgd_t *pgdp;pud_t *pudp, pud;pmd_t *pmdp, pmd;pte_t *ptep, pte;if ((((long)addr) >> VA_BITS) != -1UL)return 0;pgdp = pgd_offset_k(addr);if (pgd_none(READ_ONCE(*pgdp)))return 0;pudp = pud_offset(pgdp, addr);pud = READ_ONCE(*pudp);if (pud_none(pud))return 0;if (pud_sect(pud))return pfn_valid(pud_pfn(pud));pmdp = pmd_offset(pudp, addr);pmd = READ_ONCE(*pmdp);if (pmd_none(pmd))return 0;if (pmd_sect(pmd))return pfn_valid(pmd_pfn(pmd));ptep = pte_offset_kernel(pmdp, addr);pte = READ_ONCE(*ptep);if (pte_none(pte))return 0;return pfn_valid(pte_pfn(pte));
}

kern_addr_valid用于检测内核虚拟地址是否有效,适用于x86架构和arm64架构。

1.2 __pa

__pa用于将内核虚拟地址转化为物理地址,适用于x86架构和arm64架构。

对于arm64架构:

// v5.4/source/arch/arm64/include/asm/memory.h#define __pa(x)			__virt_to_phys((unsigned long)(x))
// v5.4/source/arch/arm64/include/asm/memory.h#define __virt_to_phys(x)	__virt_to_phys_nodebug(x)
// v5.4/source/arch/arm64/include/asm/memory.h#define __virt_to_phys_nodebug(x) ({					\phys_addr_t __x = (phys_addr_t)(__tag_reset(x));		\__is_lm_address(__x) ? __lm_to_phys(__x) : __kimg_to_phys(__x);	\
})

二、ARM64架构

2.1 AT S1E1R

ARM64架构将内核虚拟地址转化为物理地址主要是采用ARM64架构的 at s1e1r 指令。
在这里插入图片描述
在这里插入图片描述

at s1e1r

AT S1E1R 是 ARM64 架构中的一条系统指令,用于执行 Stage 1 地址翻译,并模拟从 EL1(Exception Level 1,通常是操作系统内核)读取给定虚拟地址的权限。

执行 Stage 1 地址翻译,并根据翻译结果更新 PAR_EL1(Physical Address Register)寄存器。

指令类型:
64 位系统指令。

操作数:
一个 64 位虚拟地址。

结果存储:
翻译结果(物理地址和状态信息)存储到 PAR_EL1 寄存器中。
PAR_EL1 寄存器:
在这里插入图片描述
bit0:Fault 标志位(F bit)。如果为 1,表示翻译失败;如果为 0,表示翻译成功。

2.2 is_kernel_addr_vaild

static bool is_kernel_addr_vaild(unsigned long addr)
{unsigned long flags;u64 par;local_irq_save(flags);asm volatile("at s1e1r, %0" :: "r" (addr));isb();par = read_sysreg(par_el1);local_irq_restore(flags);/** If we now have a valid translation, treat the translation fault as* spurious.*/if (par & SYS_PAR_EL1_F)return false;return true;
}

该函数用于检查给定的内核虚拟地址是否有效。它通过 ARM64 的 AT(Address Translate)指令和 PAR_EL1(Physical Address Register)寄存器来完成地址翻译,并根据翻译结果判断地址的有效性。

代码说明:
(1)保存当前中断状态并禁用中断,确保接下来的操作不会被中断打断。
(2)使用 AT 指令将虚拟地址 addr 翻译为物理地址,并将结果存储到 PAR_EL1 寄存器中。
s1e1r:表示在 EL1 阶段使用 Stage 1 翻译表进行读取操作。
%0:表示输入操作数 addr(虚拟地址)。
(3)插入指令屏障
插入一条指令屏障(Instruction Synchronization Barrier),确保 AT 指令的结果在读取 PAR_EL1 之前完成。
(4)读取 PAR_EL1 寄存器的值
读取 PAR_EL1 寄存器的值,该寄存器保存了 AT 指令的结果。
(5) 恢复中断状态
(6) 检查翻译结果
SYS_PAR_EL1_F:表示 PAR_EL1 寄存器中的 Fault 标志位。如果该位为 1,表示地址翻译失败(如页表项无效或权限不足)。

2.3 va2pa_helper

typedef uintptr_t vaddr_t;
typedef uint64_t paddr_t;#define BIT32(nr)		((uint64_t)1 << (nr))  // 生成 32 位掩码
#define BIT64(nr)		((uint64_t)1 << (nr))  // 生成 64 位掩码#define PAR_F			    BIT32(0) // PAR 寄存器的 Fault 标志位,表示 PAR 寄存器中的 Fault 标志位(第 0 位)。如果该位为 1,表示地址翻译失败。#define PAR_PA_SHIFT		UL(12) // 物理地址在 PAR 寄存器中的偏移量
#define PAR_PA_MASK		    (BIT64(36) - 1) // 物理地址的掩码(36 位),用于从 PAR 寄存器中提取物理地址。#define DEFINE_REG_READ_FUNC_(reg, type, asmreg)    \
static inline type read_##reg(void)            \
{                            \type val;                    \\asm volatile("mrs %0, " #asmreg : "=r" (val));    \return val;                    \
}#define DEFINE_U64_REG_READ_FUNC(reg) \DEFINE_REG_READ_FUNC_(reg, uint64_t, reg)DEFINE_U64_REG_READ_FUNC(par_el1)// 使用 AT S1E1R 指令将虚拟地址 va 翻译为物理地址,并将结果存储到 PAR_EL1 寄存器中。
static inline void write_at_s1e1r(uint64_t va)
{asm volatile ("at	S1E1R, %0" : : "r" (va));
}//通过 ARM64 的 AT(Address Translate)指令和 PAR_EL1(Physical Address Register)寄存器来完成地址翻译。
bool va2pa_helper(void *va, paddr_t *pa)
{paddr_t par = 0;paddr_t par_pa_mask = 0;bool ret = false;write_at_s1e1r((vaddr_t)va);  // 使用 AT 指令翻译虚拟地址isb();  // 插入指令屏障,确保 AT 指令完成par = read_par_el1();  // 读取 PAR_EL1 寄存器的值par_pa_mask = PAR_PA_MASK;if (par & PAR_F)  // 检查 Fault 标志位goto out;// 提取物理地址并合并低 12 位*pa = (par & (par_pa_mask << PAR_PA_SHIFT)) |((vaddr_t)va & (BIT64(PAR_PA_SHIFT) - 1));ret = true;out:return ret;
}

函数逻辑:
(1)使用 AT 指令翻译虚拟地址:
调用 write_at_s1e1r,将虚拟地址 va 翻译为物理地址,并将结果存储到 PAR_EL1 寄存器中。
(2)插入指令屏障:
使用 isb() 确保 AT 指令的结果在读取 PAR_EL1 之前完成。
(3)读取 PAR_EL1 寄存器的值:
调用 read_par_el1,获取地址翻译的结果。
(4)检查 Fault 标志位:
如果 PAR_F 标志位为 1,表示地址翻译失败,直接返回 false。
(5)提取物理地址:
从 PAR_EL1 中提取物理地址的高 36 位(par & (par_pa_mask << PAR_PA_SHIFT))。
将虚拟地址的低 12 位(页内偏移)合并到物理地址中。
(6)返回结果:
如果地址翻译成功,返回 true,并将物理地址存储到 *pa。
如果地址翻译失败,返回 false。

三、demo演示

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mm_types.h>
#include <linux/mm.h>
#include <linux/gfp.h>#include <asm/esr.h>
#include <asm/sysreg.h>
#include <asm/system_misc.h>typedef uintptr_t vaddr_t;
typedef uint64_t paddr_t;#define BIT32(nr)		((uint64_t)1 << (nr))  // 生成 32 位掩码
#define BIT64(nr)		((uint64_t)1 << (nr))  // 生成 64 位掩码#define PAR_F			    BIT32(0) // PAR 寄存器的 Fault 标志位,表示 PAR 寄存器中的 Fault 标志位(第 0 位)。如果该位为 1,表示地址翻译失败。#define PAR_PA_SHIFT		UL(12) // 物理地址在 PAR 寄存器中的偏移量
#define PAR_PA_MASK		    (BIT64(36) - 1) // 物理地址的掩码(36 位),用于从 PAR 寄存器中提取物理地址。#define DEFINE_REG_READ_FUNC_(reg, type, asmreg)    \
static inline type read_##reg(void)            \
{                            \type val;                    \\asm volatile("mrs %0, " #asmreg : "=r" (val));    \return val;                    \
}#define DEFINE_U64_REG_READ_FUNC(reg) \DEFINE_REG_READ_FUNC_(reg, uint64_t, reg)DEFINE_U64_REG_READ_FUNC(par_el1)// 使用 AT S1E1R 指令将虚拟地址 va 翻译为物理地址,并将结果存储到 PAR_EL1 寄存器中。
static inline void write_at_s1e1r(uint64_t va)
{asm volatile ("at	S1E1R, %0" : : "r" (va));
}//通过 ARM64 的 AT(Address Translate)指令和 PAR_EL1(Physical Address Register)寄存器来完成地址翻译。
bool va2pa_helper(void *va, paddr_t *pa)
{paddr_t par = 0;paddr_t par_pa_mask = 0;bool ret = false;write_at_s1e1r((vaddr_t)va);  // 使用 AT 指令翻译虚拟地址isb();  // 插入指令屏障,确保 AT 指令完成par = read_par_el1();  // 读取 PAR_EL1 寄存器的值par_pa_mask = PAR_PA_MASK;if (par & PAR_F)  // 检查 Fault 标志位goto out;// 提取物理地址并合并低 12 位*pa = (par & (par_pa_mask << PAR_PA_SHIFT)) |((vaddr_t)va & (BIT64(PAR_PA_SHIFT) - 1));ret = true;out:return ret;
}static bool is_kernel_addr_vaild(unsigned long addr)
{unsigned long flags;u64 par;local_irq_save(flags);asm volatile("at s1e1r, %0" :: "r" (addr));isb();par = read_sysreg(par_el1);local_irq_restore(flags);/** If we now have a valid translation, treat the translation fault as* spurious.*/if (par & SYS_PAR_EL1_F)return false;return true;
}//内核模块初始化函数
static int __init lkm_init(void)
{unsigned long virt_address, phys_address, phys_address2;//调用伙伴系统接口分配 2^2 = 4 个连续的物理页,返回其内核虚拟起始地址virt_address = __get_free_pages(GFP_KERNEL, 2);phys_address = __pa(virt_address);if(is_kernel_addr_vaild(virt_address)){printk("kernel address 0x%lx is valid\n", virt_address);}else{printk("kernel address 0x%lx is not invalid\n", virt_address);goto out;}printk("virtual addr = 0x%lx, phys address = 0x%lx\n", virt_address, phys_address);if(va2pa_helper((void *)virt_address, (paddr_t *)&phys_address2)){printk("virtual address 0x%lx -> physical address 0x%lx\n", virt_address, phys_address2);}else {printk("Failed to translate virtual address to physical address\n");} out:free_pages(virt_address, 2);return 0;
}//内核模块退出函数
static void __exit lkm_exit(void)
{printk("Goodbye\n");
}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");
# insmod va2pa.ko
# dmesg -c
[7816428.962021] kernel address 0xffffff9fe7bd8000 is valid
[7816428.962024] virtual addr = 0xffffff9fe7bd8000, phys address = 0x2067bd8000
[7816428.962025] virtual address 0xffffff9fe7bd8000 -> physical address 0x2067bd8000

可以看到使用 __pa 和 使用 AT 指令将虚拟地址 转化为物理地址结果相同。

参考资料

https://www.cnblogs.com/smilingsusu/p/14600585.html

http://www.lryc.cn/news/535737.html

相关文章:

  • 使用 Visual Studio Code (VS Code) 开发 Python 图形界面程序
  • 图像处理篇---基本OpenMV图像处理
  • 一文讲清springboot所有注解
  • pytest测试专题 - 1.1 运行pytest
  • Java多线程——线程池的使用
  • NO.15十六届蓝桥杯备战|while循环|六道练习(C++)
  • DeepSeek 从入门到精通学习指南,2025清华大学《DeepSeek从入门到精通》正式发布104页pdf版超全解析
  • 2025年SEO自动优化工具
  • KEPServerEX 的接口类型与连接方式的详细说明
  • AGI时代的认知重塑:人类文明的范式转移与思维革命
  • OmniManip:以目标为中心的交互基元作为空间约束实现通用机器人操作
  • 论文第二次阅读笔记
  • 【Android开发AI实战】选择目标跟踪基于opencv实现——运动跟踪
  • 系统漏洞扫描服务:安全风险识别与防护指南
  • 2.Excel:滨海市重点中学的物理统考考试情况❗(15)
  • 使用 React 16+Webpack 和 pdfjs-dist 或 react-pdf 实现 PDF 文件显示、定位和高亮
  • 驱动开发系列35 - Linux Graphics GEM Buffer Object 介绍
  • Java常见的异常类有哪些?
  • 清华大学新闻与传播学院沈阳团队出品的《DeepSeek:从入门到精通》104页PDF
  • 增量hdfs数据追平
  • Linux高并发服务器开发 第十七天(管道缓存区查询大小 管道的优劣 命名管道mkfifo 建立释放映射区mmap/munmap 匿名映射 进程间的通信)
  • C语言常见概念
  • AI代码生成器如何重塑前端开发的工作环境
  • 设计模式-结构型-外观模式
  • 8.flask+websocket
  • ARM Cortex-M3/M4 权威指南 笔记【二】架构
  • HCIA项目实践--静态路由的拓展配置
  • STL中list的模拟实现
  • 计算机网络知识速记:HTTP1.0和HTTP1.1
  • Apache Kafka 中的认证、鉴权原理与应用