目录
内核映射
尽管
vmalloc
函数族可用于从高端内存域向内核映射页帧(这些在内核空间中通常是无法直接看
到的),但这并不是这些函数的实际用途。重要的是强调以下事实:内核提供了其他函数用于将
ZONE_HIGHMEM
页帧显式映射到内核空间,这些函数与
vmalloc
机制无关
1,持久内核映射
如果需要将高端页帧长期映射(作为持久映射
)到内核地址空间中,必须使用
kmap
函数。需要映射的页用指向page
的指针指定,作为该函数的参数。
如果没有启用高端支持,该函数的任务就比较简单。在这种情况下,所有页都可以
直接访问
,因此只需要返回页的地址,无需显式创建一个映射。
如果确实存在
高端页
,情况会比较复杂。类似于vmalloc
,内核首先必须
建立高端页和所映射到的地址
之间的关联。还必须在虚拟地址空间中分配一个区域以映射页帧,最后,内核必须记录该虚拟区域的哪些部分在使用中,哪些仍然是空闲的。
数据结构:page_address_map
建立物理内存的page实例与其在虚拟内存区中位置之间的关联。
struct page_address_map {
struct page *page;
void *virtual;
struct list_head list;
};
- 建立page→virtual的映射
- page是一个指向全局mem_map数组中的page实例的指针;
- virtual指定了该页在内核虚拟地址空间中分配的位置。
查找地址
page_address首先检查传递进来的
page
实例在普通内存还是在高端内存。如果是前者,页地址可以根据page
在
mem_map
数组中的位置计算。对于后者,可通过上述散列表查找虚拟地址。
创建映射
为通过
page
指针建立映射,必须使用
kmap
函数。
①
它只是一个前端,用于确认指定的页是否确实
在高端内存域中。否则,结果返回
page_address
得到的地址。如果确实在高端内存中,则内核将工作委托给kmap_high
,该函数定义如下:
void *kmap_high(struct page *page)
{
unsigned long vaddr;
lock_kmap();
vaddr = (unsigned long)page_address(page);
if (!vaddr)
vaddr = map_new_virtual(page);
pkmap_count[PKMAP_NR(vaddr)]++;
BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
unlock_kmap();
return (void*) vaddr;
}
- page_address函数首先检查该页是否已经映射。如果它不对应到有效地址,则必须使用map_new_virtual映射该页。
- 从最后使用的位置(保存在全局变量last_pkmap_nr中)开始,反向扫描pkmap_count数组,直至找到一个空闲位置。如果没有空闲位置,该函数进入睡眠状态,直至内核的另一部分执行解除映射操作腾出空位。
解除映射
用kmap
映射的页,如果不再需要,必须用
kunmap
解除映射
关键函数:
flush_all_zero_pkmaps
static void flush_all_zero_pkmaps(void)
{
int i;
int need_flush = 0;
flush_cache_kmaps();
for (i = 0; i < LAST_PKMAP; i++) {
struct page *page;
if (pkmap_count[i] != 1)
continue;
pkmap_count[i] = 0;
BUG_ON(pte_none(pkmap_page_table[i]));
page = pte_page(pkmap_page_table[i]);
pte_clear(&init_mm, PKMAP_ADDR(i), &pkmap_page_table[i]);
set_page_address(page, NULL);
need_flush = 1;
}
if (need_flush)
flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
}
- flush_cache_kmaps在内核映射上执行刷出,因为内核的全局页表已经修改;
- 扫描整个pkmap_count数组。计数器值为1的项设置为0,从页表删除相关的项,最后删除该 映射;
- 最后,使用flush_tlb_kernel_range函数刷出所有与PKMAP区域相关的TLB项。
2,临时内存映射
kmap_atomic,其执行是原子的。该函数的一个主要优点是它比普通的kmap快速。但它不能用于可能进入睡眠的代码。因此,它对于很快就需要一个临时页的简短代码,是非常理想的。
void *kmap_atomic(struct page *page, enum km_type type)
3,没有高端内存
使用通用版本的kmap返回页的地址,且不修改虚拟内存。不能用于中断处理程序,如果pkmap数组中没有空闲位置,该函数会进入睡眠状态,直至情形有所改善。
void *kmap_high(struct page *page);
void kunmap_high(struct page *page);
static inline void *kmap(struct page *page)
{
BUG_ON(in_interrupt());
if (!PageHighMem(page))
return page_address(page);
return kmap_high(page);
}
static inline void kunmap(struct page *page)
{
BUG_ON(in_interrupt());
if (!PageHighMem(page))
return;
kunmap_high(page);
}
void *kmap_atomic(struct page *page);
void __kunmap_atomic(void *kvaddr);
参考
《深入Linux内核架构》