上半部分分析了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?

完。