vmalloc能完成高端内存到内核虚拟空间的映射,但是内核更期望一种能专门正对高端内存的映射关系,持久映射 便是将高端内存长久映射到内存虚拟地址空间。
持久映射原理
类同vmalloc需要建立物理页到虚拟地址的映射关系,持久映射显然要简单的多,所涉及数据结构如下:
struct page_address_map {
struct page *page; //物理页
void *virtual; //虚拟地址,该地址必须在持久映射区(PKMAP_BASE→ FIXMAP_START)
struct list_head list;//系统标准链表
};
为了便于组织,系统使用了散列表来保存page_address_map实例。映射关系是示意图如下:
图中vitual address space里面的每一个格子的空间大小为4kB,及一个页的大小,该空间及虚拟空间。pkmap_count所指代的数组的每个单位大小是4B,及int类型,该数组主要是为了对vitual address space 中的虚拟地址被映射多少次的计数
持久映射功能函数
// 该函数根据page指针找到与之映射的虚拟地址
void *kmap_high(struct page *page)
{
unsigned long vaddr;
/*
* For highmem pages, we can't trust "virtual" until
* after we have the lock.
*/
lock_kmap();
vaddr = (unsigned long)page_address(page); // page_address的实现上节已经提及
if (!vaddr)
vaddr = map_new_virtual(page); // 如果虚拟地址不存在,需要建立新的映射
pkmap_count[PKMAP_NR(vaddr)]++; // 引用计数加1
BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
unlock_kmap();
return (void*) vaddr;
}
void kunmap_high(struct page *page); // 解除映射关系
该函数的主要任务对pkmap_count数组中的对应位置减1
临时内核映射原理
上文描述的持久映射因为不能用于中断处理程序,所以系统还需要一种原子执行的函数来完成任务,逻辑上称为kmap_atomic,他的主要优点是执行速度快,但是不能用于进入睡眠的代码。
但在描述临时映射之前,阐明固定映射是必要的,毕竟它是基于固定映射的。固定映射在3.4节中有所提及。
在内核虚拟内存的划分中,最后一段FIXMAPS即为固定映射所处的虚拟地址段。
首先创建一个枚举类型,这里我略去了与本次主题无关的变量
enum fixed_addresses {
。。。。。。
VSYSCALL_LAST_PAGE,
VSYSCALL_FIRST_PAGE = VSYSCALL_LAST_PAGE
+ ((VSYSCALL_END-VSYSCALL_START) >> PAGE_SHIFT) - 1,
VSYSCALL_HPET,
FIX_DBGP_BASE,
FIX_EARLYCON_MEM_BASE,
#ifdef CONFIG_X86_32
FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */
/* 保存到页表中,用于建立内核临时映射*/
FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
#ifdef CONFIG_PCI_MMCONFIG
FIX_PCIE_MCFG,
#endif
#endif
。。。。。。
__end_of_fixed_addresses
};
unsigned long __FIXADDR_TOP = 0xfffff000; // 注意这里的并不是oxffffffff
#define FIXADDR_TOP ((unsigned long)__FIXADDR_TOP)
//通过枚举变量计算虚拟地址
#define__fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
#define__virt_to_fix(x) ((FIXADDR_TOP - ((x)&PAGE_MASK)) >>PAGE_SHIFT)
其次因为__fix_to_virt(x)已经能将枚举变量转换为虚拟地址了,那么下一步,只需要将枚举变量于物理页关联便可以了。系统通过函数set_fixmap(idx,phys)实现。该函数是通过页表来建立关联关系。
最后来讨论我们的临时映射了,临时映射也会建立一个枚举类型
enum km_type {
KM_BOUNCE_READ,
KM_SKB_SUNRPC_DATA,
KM_SKB_DATA_SOFTIRQ,
KM_USER0,
KM_USER1,
KM_BIO_SRC_IRQ,
KM_BIO_DST_IRQ,
KM_PTE0,
KM_PTE1,
KM_PTE2,
KM_IRQ0,
KM_IRQ1,
KM_SOFTIRQ0,
KM_SOFTIRQ1,
KM_TYPE_NR
};
虚拟地址的计算过程:
idx= type + KM_TYPE * smp_processor_id();
vaddr= __fix_to_virt(FIX_KMAP_BEGIN + idx);
FIX_KMAP_BEGIN来自固定映射的枚举类型
从上面的计算公式我们知道每一个临时内核映射对于不同的cpu是分开的。如下图: