Linux内核下sparse内存类型,通过虚拟地址转换为page的接口
在Linux内核的稀疏内存(sparse memory)模型中,虚拟地址到struct page
的转换接口与常规模型类似,但底层机制有所不同。以下是关键接口和转换逻辑:
1. 核心转换接口
a. virt_to_page()
1
| struct page *virt_to_page(const void *vaddr);
|
- 作用:将直接映射区域(低端内存)的虚拟地址转换为对应的
struct page
。
- 适用场景:虚拟地址位于线性映射区(如
kmalloc
分配的内存)。
- 稀疏模型调整:通过
vmemmap
按需映射的页表访问struct page
,而非全局mem_map
数组。
b. pfn_to_page()
1
| struct page *pfn_to_page(unsigned long pfn);
|
- 作用:将物理页帧号(PFN)转换为对应的
struct page
。
- 稀疏模型实现:根据PFN定位对应的
vmemmap
区域,计算页结构地址。
- 关键宏:
SECTION_ALIGN_UP(pfn)
确定vmemmap
段,PAGE_SHIFT
计算偏移。
c. vmalloc_to_page()
1
| struct page *vmalloc_to_page(const void *vaddr);
|
- 作用:转换
vmalloc
分配的虚拟地址到struct page
。
- 步骤:通过页表查询物理地址,再调用
pfn_to_page()
。
2. 转换流程
步骤 1:区分虚拟地址类型
- 线性地址(如
kmalloc
):直接使用virt_to_page()
。
- 动态映射地址(如
vmalloc
):需用vmalloc_to_page()
。
- 用户空间地址:需先通过
get_user_pages()
获取struct page
。
步骤 2:获取物理地址/PFN
- 线性地址转PFN:
1
| unsigned long pfn = __pa(vaddr) >> PAGE_SHIFT;
|
- 动态映射地址转PFN:
1
| unsigned long pfn = vmalloc_to_pfn(vaddr);
|
步骤 3:转换为struct page
1
| struct page *page = pfn_to_page(pfn);
|
3. 稀疏内存的底层机制
vmemmap
优化:将struct page
数组按需映射到虚拟地址空间,避免为空洞内存分配结构体。
- 内存段(Section):物理内存按
SECTION_SIZE
(如1GB)划分,每个段独立管理vmemmap
。
- 页表映射:访问
struct page
时,通过vmemmap
的页表动态映射到实际内存。
4. 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <linux/mm.h>
void *linear_vaddr = kmalloc(1024, GFP_KERNEL); struct page *page1 = virt_to_page(linear_vaddr);
void *vmalloc_vaddr = vmalloc(2048); struct page *page2 = vmalloc_to_page(vmalloc_vaddr);
unsigned long pfn = __pa(linear_vaddr) >> PAGE_SHIFT; struct page *page3 = pfn_to_page(pfn);
|
5. 注意事项
- 地址有效性:确保虚拟地址已分配且有效,否则可能触发
oops
。
- 上下文限制:部分接口(如
virt_to_page()
)只能在原子上下文或进程上下文中调用。
- 架构差异:具体实现可能因架构(x86/ARM)有所不同,但接口保持一致。
通过上述接口和逻辑,Linux内核在稀疏内存模型下高效管理虚拟地址到物理页的转换。
如何确定虚拟地址类型
在Linux内核中,虚拟地址的类型可以通过其所属的内存区域来判断。以下是确定虚拟地址类型的关键方法和实现逻辑:
1. 虚拟地址的主要类型
内核虚拟地址分为以下几类:
- 线性映射地址(Low Memory / Direct Mapping):物理内存的直接映射区域,地址范围通常为
PAGE_OFFSET
到 high_memory
。
- vmalloc动态映射地址:通过
vmalloc
分配的地址,范围在 VMALLOC_START
到 VMALLOC_END
。
- 高端内存地址(High Memory):仅在32位系统中有意义,需通过
kmap
动态映射。
- 模块地址空间:内核模块的代码和数据区域(
MODULES_VADDR
到 MODULES_END
)。
- 用户空间地址:用户态进程的地址空间(低于
TASK_SIZE
)。
2. 内核提供的判断接口
a. is_vmalloc_addr()
1 2
| #include <linux/mm.h> bool is_vmalloc_addr(const void *x);
|
- 作用:判断地址是否属于
vmalloc
区域。
- 实现逻辑:检查地址是否在
VMALLOC_START
和 VMALLOC_END
之间。
- 示例:
1 2 3 4
| void *addr = vmalloc(4096); if (is_vmalloc_addr(addr)) { printk("This is a vmalloc address\n"); }
|
b. virt_addr_valid()
1 2
| #include <linux/mm.h> int virt_addr_valid(unsigned long addr);
|
- 作用:判断地址是否为有效的线性映射地址(低端内存)。
- 实现逻辑:
- 检查地址是否在
PAGE_OFFSET
到 high_memory
之间。
- 验证对应的物理页是否存在。
- 示例:
1 2 3 4
| void *addr = kmalloc(1024, GFP_KERNEL); if (virt_addr_valid((unsigned long)addr)) { printk("This is a linear-mapped address\n"); }
|
c. is_kernel_addr()
(部分架构特有)
1 2 3
| #ifdef CONFIG_PPC64 bool is_kernel_addr(unsigned long addr); #endif
|
- 作用:在特定架构(如PowerPC)中判断地址是否属于内核空间。
3. 手动判断地址范围
a. 直接比较地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <asm/pgtable.h>
if (addr >= PAGE_OFFSET && addr < high_memory) { }
if (addr >= VMALLOC_START && addr < VMALLOC_END) { }
if (addr >= MODULES_VADDR && addr < MODULES_END) { }
|
b. 使用内核宏
- **
__is_lm_address(addr)
**:判断是否为线性映射地址。
- **
__is_kernel_text(addr)
**:判断是否在内核代码段。
4. 地址类型判断流程图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| +---------------------+ | 虚拟地址 (void *) | +---------------------+ | v +-----------------------------+ | addr >= MODULES_VADDR | | && addr < MODULES_END? | +-----------------------------+ | +----------------+----------------+ | 是 | 否 v v +----------------------+ +-----------------------------+ | 模块地址空间 | | addr >= VMALLOC_START | +----------------------+ | && addr < VMALLOC_END? | +-----------------------------+ | +-----------------+-----------------+ | 是 | 否 v v +----------------------+ +-----------------------------+ | vmalloc地址 | | addr >= PAGE_OFFSET | +----------------------+ | && addr < high_memory? | +-----------------------------+ | +-------------+-------------+ | 是 | 否 v v +----------------------+ +----------------------+ | 线性映射地址 | | 其他(如高端内存、用户空间)| +----------------------+ +----------------------+
|
5. 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <linux/mm.h> #include <linux/vmalloc.h>
void check_address_type(void *addr) { if (is_vmalloc_addr(addr)) { printk("Address %p is a vmalloc address\n", addr); } else if (virt_addr_valid((unsigned long)addr)) { printk("Address %p is a linear-mapped address\n", addr); } else if (addr >= MODULES_VADDR && addr < MODULES_END) { printk("Address %p is in kernel module space\n", addr); } else if (addr < (void *)TASK_SIZE) { printk("Address %p is a userspace address\n", addr); } else { printk("Address %p is of unknown type (possibly highmem)\n", addr); } }
|
6. 注意事项
- 架构差异:地址范围定义(如
PAGE_OFFSET
、VMALLOC_START
)可能因架构(x86/ARM/PowerPC)而不同。
- 高端内存(High Memory):仅在32位系统中需要特殊处理,64位系统通常无需考虑。
- 性能:直接使用内核接口(如
is_vmalloc_addr
)比手动检查更高效且可移植。
- 用户空间地址:在内核中访问用户空间地址需先使用
copy_from_user
或 get_user_pages
。
通过上述方法,可以准确判断虚拟地址的类型,从而选择正确的内存操作接口(如 virt_to_page
或 vmalloc_to_page
)。