kernel中常规内存映射区物理地址与虚拟地址转换

#ifdef CONFIG_ARM_PATCH_PHYS_VIRT

/*
* Constants used to force the right instruction encodings and shifts
* so that all we need to do is modify the 8-bit constant field.
*/
#define __PV_BITS_31_24 0x81000000

extern unsigned long __pv_phys_offset;
#define PHYS_OFFSET __pv_phys_offset

#define __pv_stub(from,to,instr,type) \
__asm__("@ __pv_stub\n" \
"1: " instr " %0, %1, %2\n" \
" .pushsection .pv_table,\"a\"\n" \
" .long 1b\n" \
" .popsection\n" \
: "=r" (to) \
: "r" (from), "I" (type))

static inline unsigned long __virt_to_phys(unsigned long x)
{
unsigned long t;
__pv_stub(x, t, "add", __PV_BITS_31_24);
return t;
}

static inline unsigned long __phys_to_virt(unsigned long x)
{
unsigned long t;
__pv_stub(x, t, "sub", __PV_BITS_31_24);
return t;
}
#else
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
#endif
#endif
#endif /* __ASSEMBLY__ */


这一段就是linux的内核空间中的常规内存映射区虚拟地址与物理地址转换的代码,看到这个大家都会疑惑,不是说常规内存区与物理内存只是一个偏移量的区别吗?之前的理解是有一个PAGE_OFFSET,一般windows都会设为0xC0000000,linux可以设置这个值,但通常也是设为0xC0000000,常规内存映射区只是把物理地址加上这个PAGE_OFFSET就变为了虚拟地址,那为什么这里__phys_to_virt 执行的指令是"sub",而不是"add",__virt_to_phys确执行的是“add”呢?而且后面的__PV_BITS_31_24是什么?为什么会是0x81000000。
以下就来解开这些疑惑:
1、 内核启动过程中会去根据实际物理地址修改这个段中的指令,所以代码中看到的是写成的一个固定的数字(0x81000000),但是实际运行时会去修改指令中的参数域,
由于这个参数域size有特殊的限制,所以内核放置的物理地址必须按照16M对齐
2、下面那三行是代表什麼意思
"   .pushsection .pv_table,\"a\"\n"     \
"   .long   1b\n"               \
"   .popsection\n"
答:

在这个.o文件中插入一个段,段名为.pv_table
段的内容是个long型的,其值是个地址,即前面标号为1处的地址

 

163#define __pv_stub(from,to,instr,type)                   \
164        __asm__("@ __pv_stub\n"                         \
165        "1:     " instr "       %0, %1, %2\n"           \    插入一条指令到当前文件中,

由于__pv_stub这个宏是在代码中被调用的,

所以这一条插入的指令是在当前.o文件的text段,

这里还定义了标号1
166        "       .pushsection .pv_table,\"a\"\n"         \   在当前.o文件的.text里插入一个名为.pv_table的段
167        "       .long   1b\n"                           \            这个段的内容是个地址,这个地址指向的前面的

标号1(1b表示backword 1

,1f表示forword 1,1b就表示在本句之前找到标号1,

假如这里的1b改成1f,

那么就会在本句后面去找标号1)
168        "       .popsection\n"                          \          .pv_table结束,之后的仍然归于.text段
169        : "=r" (to)                                     \
170        : "r" (from), "I" (type))

查看vmlinux.lds.S文件中:

197        .init.pv_table : {
198                __pv_table_begin = .;
199                *(.pv_table)
200                __pv_table_end = .;
201        }
会将所有.o文件中的.pv_table段抓出来放在vmlinux的.init.pv_table段中,这个段中每一个long型都是指向一个地址,位于这些地址处的内容就是各个标号1处的指令
于是在内核启动时就能够通过__pv_table_begin找到所有的这些标号1处的指令,然后就可以修改这些指令的操作数了
把 "1:     " instr "       %0, %1, %2\n"  所在的位址儲存到.init.pv_table section裡面,是为了能够找到这些指令,内核启动时会修改这个指令的参数域,
pa = va - 0xc0000000 + phy_base

phy_base是run时确定的

phy_base的值看:
ARM linux内核启动时几个关键地址  
的ZRELADDR部分

修改指令时,__virt_to_phys中填到参数域中的就是phy_base - 0xc0000000的计算结果
————————————————
版权声明:本文为CSDN博主「dumb_man」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dahailantian1/article/details/78584823

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值