# 常用转换

## 虚拟地址和物理地址

### \_\_pa\_symbol()

获取内核程序中符号的物理地址，也就是nm vmlinux命令能显示出的内容。

比如 nm vmlinux | grep -w \_text，其结果是“ffffffff81000000 T \_text”。

```
#define __START_KERNEL_map	_AC(0xffffffff80000000, UL)


#define __pa_symbol(x) \
	__phys_addr_symbol(__phys_reloc_hide((unsigned long)(x)))


#define __phys_addr_symbol(x) \
	((unsigned long)(x) - __START_KERNEL_map + phys_base)
```

这里我们只看x86\_64的定义。从定义中我们可以看到，符号的物理地址是虚拟地址 减去 \_\_START\_KERNEL\_map 再加上 phys\_base。

当配置了kaslr时，内核加载的物理地址和编译时的加载地址会有个偏移，这个偏移记录在phys\_base。具体解释可见[常用全局变量](https://richardweiyang-2.gitbook.io/kernel-exploring/nei-cun-guan-li/tong-yong/00_global_variable)

正因为phys\_base的偏移是基于\_\_START\_KERNEL\_map计算出来的，所以如此计算后就得到了内核代码中符号的物理地址。

### \_\_pa()

计算出虚拟地址对应的物理地址。

```
static __always_inline unsigned long __phys_addr_nodebug(unsigned long x)
{
	unsigned long y = x - __START_KERNEL_map;

	/* use the carry flag to determine if x was < __START_KERNEL_map */
	x = y + ((x > y) ? phys_base : (__START_KERNEL_map - PAGE_OFFSET));

	return x;
}

#define __phys_addr(x)		__phys_addr_nodebug(x)

#define __pa(x)		__phys_addr((unsigned long)(x))
```

其实这里计算的时候分了两种情况。

* 虚拟地址 > \_\_START\_KERNEL\_map
* 其他虚拟地址

第一种情况，说明虚拟地址在内核代码空间，所以实际上就退化成和\_\_pa\_symbol()一样。 第二种情况，转换过程和\_\_va相反。这说明传入的虚拟地址需要是在内核页表上映射的空间内。

### \_\_va() / phys\_to\_virt()

只能对有内核映射的地址调用该函数，来获得对应地址的虚拟地址。

```
static inline void *phys_to_virt(phys_addr_t address)
{
	return __va(address);
}

#define __PAGE_OFFSET_BASE_L4	_AC(0xffff888000000000, UL)
unsigned long page_offset_base __ro_after_init = __PAGE_OFFSET_BASE_L4;

#define __PAGE_OFFSET           page_offset_base
#define PAGE_OFFSET		((unsigned long)__PAGE_OFFSET)

#define __va(x)			((void *)((unsigned long)(x)+PAGE_OFFSET))
```

再x86\_64架构下，大概率是这么个定义。

也就是一个物理地址（非内核代码范围内的）的虚拟地址 = 虚拟地址 + 0xffff888000000000。

这是因为在映射整个内存空间时，是这么操作的。具体参见[映射完整物理地址](https://richardweiyang-2.gitbook.io/kernel-exploring/nei-cun-guan-li/00-evolution_of_kernel_pagetable/04-map_whole_memory)

## pfn和struct page

这里先列出只有sparsemem的情况

### \_\_pfn\_to\_page(pfn)

```
static inline struct page *__section_mem_map_addr(struct mem_section *section)
{
	unsigned long map = section->section_mem_map;
	map &= SECTION_MAP_MASK;
	return (struct page *)map;
}

#define __pfn_to_page(pfn)				\
({	unsigned long __pfn = (pfn);			\
	struct mem_section *__sec = __pfn_to_section(__pfn);	\
	__section_mem_map_addr(__sec) + __pfn;		\
})
```

先根据pfn找到对应的section，然后对section->section\_mem\_map做一个偏移运算得到struct page的地址。

### \_\_page\_to\_pfn(pg)

```
static inline unsigned long page_to_section(const struct page *page)
{
	return (page->flags >> SECTIONS_PGSHIFT) & SECTIONS_MASK;
}

#define __page_to_pfn(pg)					\
({	const struct page *__pg = (pg);				\
	int __sec = page_to_section(__pg);			\
	(unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec)));	\
})
```

先根据page找到对应的section，注意这里找是因为把sectino number写在了page->flags里。 然后再对section->section\_mem\_map做一个偏移得到pfn。

## 虚拟地址和struct page

有了上面两个转换，自然可以推导出这个转换。也就是形成了地址和struct page之间的关系。

### virt\_to\_page()

```
#define virt_to_page(kaddr)	pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
```

这个转换没有什么太多可以解释的，不过这里有另一个检查的函数值得一看。

```
bool __virt_addr_valid(unsigned long x)
{
	unsigned long y = x - __START_KERNEL_map;

	/* use the carry flag to determine if x was < __START_KERNEL_map */
	if (unlikely(x > y)) {
		x = y + phys_base;

		if (y >= KERNEL_IMAGE_SIZE)
			return false;
	} else {
		x = y + (__START_KERNEL_map - PAGE_OFFSET);

		/* carry flag will be set if starting x was >= PAGE_OFFSET */
		if ((x > y) || !phys_addr_valid(x))
			return false;
	}

	return pfn_valid(x >> PAGE_SHIFT);
}
```

就是这个用来判断虚拟地址是否有效的函数。可以看出在内核中认为有效的虚拟地址空间有两个：

* \_\_START\_KERNEL\_map以上的内核代码空间
* PAGE\_OFFSET以上的direct mapping空间

其余部分都是无效空间。

### page\_to\_virt()

```
#define page_to_virt(x)	__va(PFN_PHYS(page_to_pfn(x)))
```
