上半部分分析了kmap_atomic里的low memory page地址的获取,那如何获取high memory page的virtual address呢。

kmap_atomic分析

入口就是pagefault_disable,查看定义:

/*
* These routines enable/disable the pagefault handler in that
* it will not take any locks and go straight to the fixup table.
*
* They have great resemblance to the preempt_disable/enable calls
* and in fact they are identical; this is because currently there is
* no other way to make the pagefault handlers do this. So we do
* disable preemption but we don't necessarily care about that.
*/
static inline void pagefault_disable(void)
{
preempt_count_inc();
/*
* make sure to have issued the store before a pagefault
* can hit.
*/
barrier();
}

看来也就是禁止抢占了,应该和后面用到smp_processor_id有关,是否对kmap_high_get有必要?接着看:

	kmap = kmap_high_get(page);
if (kmap)
return kmap;

kmap_high_get和架构特性有关:

#ifdef ARCH_NEEDS_KMAP_HIGH_GET
extern void *kmap_high_get(struct page *page);
#else
static inline void *kmap_high_get(struct page *page)
{
return NULL;
}
#endif

先看看支持的:

void *kmap_high_get(struct page *page)
{
unsigned long vaddr, flags;

lock_kmap_any(flags);
vaddr = (unsigned long)page_address(page);
if (vaddr) {
BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 1);
pkmap_count[PKMAP_NR(vaddr)]++;
}
unlock_kmap_any(flags);
return (void*) vaddr;
}

代码里这样注释:

/*
* The reason for kmap_high_get() is to ensure that the currently kmap'd
* page usage count does not decrease to zero while we're using its
* existing virtual mapping in an atomic context.

lock应该是保护pkmap_count,BUG_ON就是确保原子操作的count不会为0。

如何获取high memory page地址:

/**
* page_address - get the mapped virtual address of a page
* @page: &struct page to get the virtual address of
*
* Returns the page's virtual address.
*/
void *page_address(const struct page *page)
{
unsigned long flags;
void *ret;
struct page_address_slot *pas;

if (!PageHighMem(page))
return lowmem_page_address(page);

pas = page_slot(page);
ret = NULL;
spin_lock_irqsave(&pas->lock, flags);
if (!list_empty(&pas->lh)) {
struct page_address_map *pam;

list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done;
}
}
}
done:
spin_unlock_irqrestore(&pas->lock, flags);
return ret;
}

从代码看是在遍历某个链表,涉及到两个结构体:page_address_mappage_address_slot

#if defined(HASHED_PAGE_VIRTUAL)

#define PA_HASH_ORDER 7

/*
* Describes one page->virtual association
*/
struct page_address_map {
struct page *page;
void *virtual;
struct list_head list;
};

static struct page_address_map page_address_maps[LAST_PKMAP];

/*
* Hash table bucket
*/
static struct page_address_slot {
struct list_head lh; /* List of page_address_maps */
spinlock_t lock; /* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];

高端内存用page_address_map里的virtual保存page的虚拟地址,用page_address_slot的lh list来管理这些highmem pages。也就是先找到slot,再找到slot里的page,整个过程通过hash table完成。这点从set_page_address也能看出来:

/**
* set_page_address - set a page's virtual address
* @page: &struct page to set
* @virtual: virtual address to use
*/
void set_page_address(struct page *page, void *virtual)
{
unsigned long flags;
struct page_address_slot *pas;
struct page_address_map *pam;

BUG_ON(!PageHighMem(page));

pas = page_slot(page); //先定下slot
if (virtual) { /* Add */
pam = &page_address_maps[PKMAP_NR((unsigned long)virtual)];
pam->page = page;
pam->virtual = virtual;

spin_lock_irqsave(&pas->lock, flags);
list_add_tail(&pam->list, &pas->lh); //把该page增加到slot里

那如果不支持ARCH_NEEDS_KMAP_HIGH_GET特性:

void *kmap_atomic(struct page *page)
{
...
// 以下不支持ARCH_NEEDS_KMAP_HIGH_GET处理
type = kmap_atomic_idx_push();

idx = FIX_KMAP_BEGIN + type + KM_TYPE_NR * smp_processor_id();
vaddr = __fix_to_virt(idx);
#ifdef CONFIG_DEBUG_HIGHMEM
/*
* With debugging enabled, kunmap_atomic forces that entry to 0.
* Make sure it was indeed properly unmapped.
*/
BUG_ON(!pte_none(get_fixmap_pte(vaddr)));
#endif
/*
* When debugging is off, kunmap_atomic leaves the previous mapping
* in place, so the contained TLB flush ensures the TLB is updated
* with the new mapping.
*/
set_fixmap_pte(idx, mk_pte(page, kmap_prot));

return (void *)vaddr;
}

因为是SMP架构内存共享,kmap_high_get用了lock的方式防止临界访问,这里用的方法:

type = kmap_atomic_idx_push(); //每映射一次,idx++
idx = FIX_KMAP_BEGIN + type + KM_TYPE_NR * smp_processor_id();

我理解如下图:

cpu0 ----+-----------+-------> FIX_KMAP_BEGIN
| |
| highmem0 +-------> type0 ---- pageN
| | |
cpu1 ----+-----------+ +---> KM_TYPE_NR * smp_processor_id()
| | |
| highmem1 +-------> type1 ---- pageN
| |
+-----------+
...

也就是每个cpu有自己的kmap映射空间。FIX_KMAP_BEGIN在arch/arm/include/asm/fixmap.h里:

#define FIXADDR_START           0xffc00000UL
#define FIXADDR_END 0xfff00000UL

enum fixed_addresses {
FIX_EARLYCON_MEM_BASE,
FIX_SMP_MEM_BASE,
__end_of_permanent_fixed_addresses,
FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,

FIX_KMAP_BEGIN = __end_of_permanent_fixed_addresses,
FIX_KMAP_END = FIX_KMAP_BEGIN + (KM_TYPE_NR * NR_CPUS) - 1,
/* Support writing RO kernel text via kprobes, jump labels, etc. */
FIX_TEXT_POKE0,
FIX_TEXT_POKE1,

__end_of_fixed_addresses = (FIXADDR_END - FIXADDR_START) >> PAGE_SHIFT,
};

从定义来看,

       FIX_KMAP_BEGIN
|
permanent | KM_TYPE_NR * NR_CPUS |
|
FIX_KMAP_END

用__fix_to_virt(idx)得到虚拟地址:

#define FIXADDR_END		0xfff00000UL
#define FIXADDR_TOP (FIXADDR_END - PAGE_SIZE)

#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
#define __virt_to_fix(x) ((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT)

最后把page和idx建立对应关系,用set_fixmap_pte实现。

kunmap_atomic分析

kunmap相对kmap简单许多,代码如下:

void __kunmap_atomic(void *kvaddr)
{
if (kvaddr >= (void *)FIXADDR_START) {
type = kmap_atomic_idx();
idx = FIX_KMAP_BEGIN + type + KM_TYPE_NR * smp_processor_id();

if (cache_is_vivt())
__cpuc_flush_dcache_area((void *)vaddr, PAGE_SIZE);
#ifdef CONFIG_DEBUG_HIGHMEM
BUG_ON(vaddr != __fix_to_virt(idx));
set_fixmap_pte(idx, __pte(0));
#else
(void) idx; /* to kill a warning */
#endif
kmap_atomic_idx_pop(); //idx--
} else if (vaddr >= PKMAP_ADDR(0) && vaddr < PKMAP_ADDR(LAST_PKMAP)) {
/* this address was obtained through kmap_high_get() */
kunmap_high(pte_page(pkmap_page_table[PKMAP_NR(vaddr)]));
}
pagefault_enable(); //放开抢占,对应kmap
}

主要是看下分支条件, 涉及到FIXADDR_START, PKMAP_ADDR(0),PKMAP_ADDR(LAST_PKMAP)。

kmap_atomic的地址获取通过kmap_high_get or kmap_atomic_idx_push, 用kmap_atomic_idx_push时是在FIXADDR范围内。

kmap_atomic里的kmap_high_get是通过page_address_htable获取地址的,pkmap_page_table是用在kmap_high里的啊,rt?

完。