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

embedded/2025/2/12 20:21:14/

文章目录

  • 前言
  • 一、通用方案
    • 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.ppmy.cn/embedded/161682.html

相关文章

Ollama命令使用指南

Ollama 命令使用指南 Ollama 命令使用指南1. Ollama 命令概览2. Ollama 命令详解2.1 启动 Ollama2.2 创建模型2.3 查看模型信息2.4 运行模型2.5 停止运行的模型2.6 从注册表拉取模型2.7 推送模型到注册表2.8 列出本地模型2.9 查看正在运行的模型2.10 复制模型2.11 删除模型 3. …

索引为什么是B+树结构,MySQL有哪些引擎,有什么区别?

目录 为什么索引使用 B+ 树结构? 1. 适合磁盘存储 2. 高效的查询性能 3. 适合大数据量 4. 与 B 树的区别 MySQL 的存储引擎及区别 1. InnoDB 2. MyISAM 3. Memory 4. Archive 5. CSV 6. Blackhole 存储引擎的选择建议 总结 为什么索引使用 B+ 树结构? B+ 树是…

Unity状态机相关

Unity状态机&#xff08;Animator&#xff09;详解 一、基本概念 1. 什么是状态机 用于管理动画状态的系统控制角色/物体的不同状态切换基于条件驱动的状态转换可视化的动画编程工具 2. Animator组件 Unity内置的动画控制器组件连接状态机和游戏对象管理动画权重和过渡支持…

vue 134~152

认识Vue3 1. Vue2 选项式 API vs Vue3 组合式API <script> export default {data(){return {count:0}},methods:{addCount(){this.count}} } </script><script setup> import { ref } from vue const count ref(0) const addCount ()> count.value &l…

Java 读取 PDF 模板文档并替换内容重新生成 PDF

朋友们&#xff01;在实际开发里&#xff0c;经常会遇到需要根据 PDF 模板文档生成特定 PDF 的需求&#xff0c;比如合同、证书等。咱们可以借助 iText 库来实现读取 PDF 模板文档、替换指定内容&#xff0c;最后重新生成新 PDF 的功能。下面我就详细给大家讲讲具体怎么做。 1.…

Failed to build mysqlclient

​​​​​​​Links for mysqlclient 在上述链接下载对应版本即可。 Installing build dependencies ... done Getting requirements to build wheel ... done Preparing metadata (pyproject.toml) ... done Building wheels for collected packages: mysqlclient Bui…

制药行业 BI 可视化数据分析方案

一、行业背景 随着医药行业数字化转型的深入&#xff0c;企业积累了海量的数据&#xff0c;包括销售数据、生产数据、研发数据、市场数据等。如何利用这些数据&#xff0c;挖掘其价值&#xff0c;为企业决策提供支持&#xff0c;成为医药企业面临的重大挑战。在当今竞争激烈的…

阿里 Java 岗个人面经分享(技术三面 + 技术 HR 面):Java 基础 +Spring+JVM+ 并发编程 + 算法 + 缓存

技术一面 20 分钟 1、自我介绍 说了很多遍了&#xff0c;很流畅捡重点介绍完。 2、问我数据结构算法好不好 挺好的&#xff08;其实心还是有点虚&#xff0c;不过最近刷了很多题也只能壮着胆子充胖子了&#xff09; 3、找到单链表的三等分点&#xff0c;如果单链表是有环的…