嵌入式Linux中文站

Linux 内核虚拟地址到物理地址转换讨论


首先我们基于平坦型物理内存,单个node,下面是基于64位ARMv8架构得到,其他架构也有类似结论: 首先我们知道在我们成功编译好kernel后会生成一个system.map文件,其给出了内核整个虚拟地址空间情况,比如:

ARM64: 整个内核空间起始地址: ffffffc000080000 T _text

代码段起始地址: ffffffc000080160 T stext

异常向量表地址: ffffffc000083000 T vectors

ffffffc0010890b8 B __bss_start ffffffc0010890b8 D _edata

ffffffc00191ab58 B mem_map ffffffc00191ab60 B max_mapnr

ffffffc001c7dd28 B __bss_stop ffffffc001c7e000 B idmap_pg_dir ffffffc001c80000 B swapper_pg_dir ffffffc001c82000 B _end

ARM:

c0003000 A swapper_pg_dir c0008000 T _text c0008000 T stext c0008090 t __create_page_tables c0008168 t __turn_mmu_on_loc c0008174 T secondary_startup c00081e0 T __secondary_switched c00081ec t __secondary_data c00081f8 t __enable_mmu c0008220 t __vet_atags c0008280 T __exception_text_start c0008280 T _stext c0008280 T asm_do_IRQ c0008284 T do_undefinstr

c0caccb8 b suspend_time c0caccc0 b last_transmit c0caccc8 b activity_lock c0caccd0 b klist_remove_lock c0caccd4 B __bss_stop c0caccd4 B _end

对于ARM来说,32位和64位明显不同。

 

对整个内核空间代码和数据来说,由于他们是直接映射的,我们这里讨论起来非常简单

对于这些地址,内核通过宏__pa()找到这些虚拟地址对应的物理地址。或者通过__va()找到物理地址对应

的虚拟地址。

arch/arm64/include/asm/memory.h #define __pa(x)   __virt_to_phys((unsigned long)(x)) #define __va(x)   ((void *)__phys_to_virt((phys_addr_t)(x))) #define pfn_to_kaddr(pfn) __va((pfn) << PAGE_SHIFT) #define virt_to_pfn(x)      __phys_to_pfn(__virt_to_phys(x))

继续看定义:

include/asm-generic/memory-module.h #define __virt_to_phys(x) (((phys_addr_t)(x) - PAGE_OFFSET + PHYS_OFFSET)) #define __phys_to_virt(x) ((unsigned long)((x) - PHYS_OFFSET + PAGE_OFFSET))

#define __phys_to_pfn(paddr) ((unsigned long)((paddr) >> PAGE_SHIFT)) #define __pfn_to_phys(pfn) ((phys_addr_t)(pfn) << PAGE_SHIFT)

#define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET)) #define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)     

这里需要注意PAGE_OFFSET和PHYS_OFFSET定义,前者是整个内核空间开始的虚拟地址,一般跟体系结构相关,

如经典32为X86和ARM为0xC0000000,即3GB处。对于64位来说,一般为0xFFFFFFC000000000 而PHYS_OFFSET则为物理内存起始地址,一般来说,这个偏移跟芯片设计相关,这个偏移表示了访问DDR最低的地址线。

比如说,如果PHYS_OFFSET为1GB,DDR大小为2GB,那么有效访问DDR空间的地址必须是

0x40000000--0xC0000000之间。

在我们上面给出的system.map中的虚拟地址可以看到, 内核虚拟地址起始地址起始为0xFFFFFFC000000000,这个由内核定义的PAGE_OFFSET给出。 而内核真正开始的使用的地址为0xffffffc000080000这里有个偏移量,大小为128个页(4KB)。总大小为512kB字节。

在本实验上测试,得到PHYS_OFFSET为0x8800000,即136MB开始处。

可以看到,一个内核中的虚拟地址,即0xFFFFFFC000000000以上的地址,如果寻找其物理地址,使用__pa()宏非常简单,

比如上面的mem_map的虚拟地址为0xffffffc00191ab58,其减去起始虚拟地址0xFFFFFFC000000000后为0x0191ab58,

然后根据物理地址偏移情况,得到真正的物理地址:0x0191ab58 + PAGE_OFFSET = 0x0191ab58 + 0x8800000

= 0xA11AB58 可以看到,mem_map的物理地址为0xA11AB58。 那么对于这个物理地址,对应的页帧号为多少呢?即PFN为多少,这时需要使用上面的__phys_to_pfn(). 可以看到,直接进行右移即可。例如对于上面的mem_map来说,其页帧为0xA11A。

那么对于一个PFN,其对应的物理page描述符是多少呢?这时需要使用宏__pfn_to_page(),注意这里使用的 ARCH_PFN_OFFSET,其就是偏移PHYS_OFFSET对应的页帧号,上面为0x8800000则对应页帧为0x8800,则上面mem_map对应的页帧号 为0xA11A -  0x8800 = 0x191A ,然后再根据mem_map数组,即 mem_map[0x191A]为mem_map的页描述符。

本文永久更新链接:http://embeddedlinux.org.cn/emb-linux/entry-level/201911/12-8875.html



分享:

评论