在 Linux 内核中,内核空间直接映射区(Direct Mapping Region)是指一段虚拟地址空间,包括 ZONE_DMA 和 ZONE_NORMAL 区域。它与物理地址之间存在固定的线性偏移关系。这种区域的设计使得内核可以快速地通过简单的地址运算将虚拟地址转换为物理地址,而不需要查找复杂的页表。以下是关于内核空间直接映射区的详细解析:
直接映射区的特点
线性映射: 虚拟地址与物理地址之间通过一个固定的偏移直接映射。
如果物理地址是 0x1000,对应的虚拟地址是:
{Physical Address} = {Virtual Address} - {PAGE_OFFSET}
{Virtual Address} = {Physical Address} + {PAGE_OFFSET}
地址范围: 直接映射区涵盖了大部分低端物理内存,通常是 0 到 max_low_pfn * PAGE_SIZE
的范围(由硬件架构和内存大小决定)。
性能高: 由于不需要多级页表查找,地址转换速度非常快。
用途: 主要用于分配小块内存(如通过 kmalloc
)或直接操作物理页。
直接映射区的虚拟地址范围
在 x86_64 架构中:
PAGE_OFFSET
是直接映射区的基地址,通常定义为 0xffff888000000000
。
内核直接映射区从 PAGE_OFFSET
开始,覆盖整个物理内存的范围。
物理地址和虚拟地址之间的固定偏移为 PAGE_OFFSET
。
如果物理地址是 0x1000
,对应的虚拟地址是:
Virtual Address = 0x1000 + 0xffff888000000000
即 0xffff888000001000
。
在 32 位架构中:
PAGE_OFFSET
通常是 0xc0000000
。
直接映射区覆盖低端物理内存(通常小于 896 MB
的范围)。
直接映射区相关操作函数
虚拟地址到物理地址
使用 virt_to_phys
函数。
定义
phys_addr_t virt_to_phys(const volatile void *address);
输入: 内核空间的虚拟地址。 输出: 对应的物理地址。
示例
void example_virt_to_phys(void)
{
void *buffer = kmalloc(1024, GFP_KERNEL); // 分配 1 KB 内存
if (!buffer) {
printk(KERN_ERR "kmalloc failed\n");
return;
}
phys_addr_t phys = virt_to_phys(buffer);
printk(KERN_INFO "Virtual address: %p, Physical address: %pa\n", buffer, &phys);
kfree(buffer); // 释放内存
}
物理地址到虚拟地址
使用 phys_to_virt
函数。
定义
void *phys_to_virt(phys_addr_t address);
输入: 物理地址。 输出: 内核直接映射区中的虚拟地址。
示例
void example_phys_to_virt(void)
{
phys_addr_t phys = 0x1000; // 示例物理地址
void *virt = phys_to_virt(phys);
printk(KERN_INFO "Physical address: %pa, Virtual address: %p\n", &phys, virt);
}
虚拟地址到 struct page
使用 virt_to_page
函数。
定义
struct page *virt_to_page(const void *kaddr);
输入: 内核直接映射区的虚拟地址。
输出: 对应的 struct page
结构。
示例
void example_virt_to_page(void)
{
void *buffer = kmalloc(4096, GFP_KERNEL); // 分配一个页大小的内存
if (!buffer) {
printk(KERN_ERR "kmalloc failed\n");
return;
}
struct page *page = virt_to_page(buffer);
printk(KERN_INFO "Page frame number: %lu\n", page_to_pfn(page));
kfree(buffer); // 释放内存
}
直接映射区的限制
仅适用于直接映射范围
virt_to_phys
和 phys_to_virt
仅在直接映射区内有效。
vmalloc
分配的虚拟地址不在直接映射区中,不能直接使用这些函数。
地址范围有限:
直接映射区通常只覆盖低端物理内存(例如,896 MB
以下的内存)。
超出范围的物理地址需要特殊处理(如高端内存区通过映射函数访问)。
使用场景
小块内存分配: 使用 kmalloc
分配的小块内存位于直接映射区,可以通过 virt_to_phys
或 virt_to_page
快速获取物理地址或页信息。
直接操作硬件设备: 通过直接映射区的虚拟地址访问设备缓冲区或 DMA 区域。
高性能数据处理: 内存直接映射的线性地址转换方式提供极高的性能,适合需要快速地址转换的场景。
示例:直接映射区的典型用法
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
void direct_mapping_example(void)
{
// 分配一个内存块
void *buffer = kmalloc(4096, GFP_KERNEL); // 分配一个页
if (!buffer) {
printk(KERN_ERR "kmalloc failed\n");
return;
}
// 获取物理地址
phys_addr_t phys = virt_to_phys(buffer);
printk(KERN_INFO "Virtual address: %p, Physical address: %pa\n", buffer, &phys);
// 获取 struct page
struct page *page = virt_to_page(buffer);
printk(KERN_INFO "Page frame number: %lu\n", page_to_pfn(page));
// 从物理地址反向获取虚拟地址
void *virt = phys_to_virt(phys);
printk(KERN_INFO "Reversed virtual address: %p\n", virt);
kfree(buffer); // 释放内存
}
通过直接映射区,内核可以快速、高效地在虚拟地址和物理地址之间进行转换,同时避免了复杂的页表查找,非常适合性能敏感的内存管理和硬件访问操作。