您的位置:首页 > 其它

高端内存永久映射分析

2015-10-14 15:51 260 查看
chipset: MSM8X25Q

Codebase: Android4.1

Kernel: 3.4.0

 

基本概念:

         当你需要将高端页面长期映射到内核空间的时候,就要使用Kmap函数来实现,即高端内存永久映射。这样避免页表和TLB的更新而导致资源的占用。

         使用的时候一般先通过alloc_page(__GFP_HIGHMEM)申请一个page,然后将这个page传给kmap,kmap会建立这个page的页表项,并返回一个虚拟地址供操作。

         Pkmap_count是一个长度为LAST_PKMAP的数组,长度为512,,每个元素对应一个永久映射的页,所以可以映射512*4k = 2M页大小。

[html]
view plaincopy

static int pkmap_count[LAST_PKMAP];  
#define LAST_PKMAP      PTRS_PER_PTE  
#define PTRS_PER_PTE        512  

Pkmap_count元素的值不同时的意义对应如下:

0:相关也还没使用

1:表示页已经被映射,但是由于TLB没被更新而无法使用。

>=2:为2时,表示内核有一处使用该映射页。为n时,表示有n-1处使用该页。

另外,内核使用struct page_address_map来保存页和虚拟地址之前的关系。

[html]
view plaincopy

struct page_address_map {  
    struct page *page;  
    void *virtual;  
    struct list_head list;  
};  

另外,高端内存永久映射是通过page_address_htable这个变量来管理的,结构为:

[html]
view plaincopy

static struct page_address_slot {  
    struct list_head lh;            /* List of page_address_maps */  
    spinlock_t lock;            /* Protect this bucket's list */  
}  

它的管理机制使用了哈希表,对应的函数是page_slot(),对于哈希原理,可自行查资料学习。

[html]
view plaincopy

static struct page_address_slot *page_slot(const struct page *page)  
{  
    return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];  
}  

初始化:

系统开机启动的时候有如下调用:

[html]
view plaincopy

Start_kernel() –> setup_arch() -> paging_init() -> kmap_init()  
static void __init kmap_init(void)  
{  
#ifdef CONFIG_HIGHMEM  
    /*该变量保存了PKMAP_BASE对应的页表项地址,PKMAP_BASE为永久映射的起始地址。*/  
    pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),  
        PKMAP_BASE, _PAGE_KERNEL_TABLE);  
#endif  
}  

创建映射:

         调用是kmap().

[html]
view plaincopy

void *kmap(struct page *page)  
{  
    /*会sleep,所以不能用于中断上下文*/  
    might_sleep();  
    /*如果是低端内存,那么返回page对应虚拟地址*/  
    if (!PageHighMem(page))  
        return page_address(page);  
    /*否则执行高端内存映射*/  
    return kmap_high(page);  
}  
EXPORT_SYMBOL(kmap);  

page_address():

[html]
view plaincopy

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;  
            /*页使用lh链表来管理。*/  
        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;  
}  

kmap_high():

[html]
view plaincopy

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);  
    /*没有就新创建页表项*/  
    if (!vaddr)  
        vaddr = map_new_virtual(page);  
    /*当前页对应的元素值加1.  PKMAP_NR表示相对于PKMAP_BASE的偏移。  
#define PKMAP_NR(virt)      (((virt) - PKMAP_BASE) >> PAGE_SHIFT) */  
    pkmap_count[PKMAP_NR(vaddr)]++;  
    /*<2肯定不正常了,因为1表示创建完成,如果跑到这里,2就表示有模块使用此映射了。*/  
    BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);  
    unlock_kmap();  
    /*返回映射之后的虚拟地址*/  
    return (void*) vaddr;  
}  

map_new_virtual():

[html]
view plaincopy

static inline unsigned long map_new_virtual(struct page *page)  
{  
    unsigned long vaddr;  
    int count;  
  
start:  
    count = LAST_PKMAP;  
    /* Find an empty entry */  
    for (;;) {  
        /* last_pkmap_nr 记录当前映射的页数,也可以认为是最后使用的位置。*/  
        last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;  
        /*为0表示查找完一轮了,这时它会去刷新pkmap_count的值为1的TLB。*/  
        if (!last_pkmap_nr) {  
            flush_all_zero_pkmaps();  
            count = LAST_PKMAP;  
        }  
        /*找到空闲区了*/  
        if (!pkmap_count[last_pkmap_nr])  
            break;  /* Found a usable entry */  
        /*未找到,继续找下一个*/  
        if (--count)  
            continue;  
        /*找了LAST_PKMAP 个之后就睡眠,等待其他模块unmap*/  
        {  
            DECLARE_WAITQUEUE(wait, current);  
            __set_current_state(TASK_UNINTERRUPTIBLE);  
            add_wait_queue(&pkmap_map_wait, &wait);  
            unlock_kmap();  
            schedule();  
            remove_wait_queue(&pkmap_map_wait, &wait);  
            lock_kmap();  
            /*醒来之后看看是不是有其他进程已经做了映射了,如果是,  
就直接返回。*/  
            /* Somebody else might have mapped it while we slept */  
            if (page_address(page))  
                return (unsigned long)page_address(page);  
            /*没有就再去重新新一轮查找*/  
            /* Re-start */  
            goto start;  
        }  
    }  
    /*从这里看到,永久映射的虚拟地址是继续PKMAP_BASE加上一个offset实现的。*/  
    vaddr = PKMAP_ADDR(last_pkmap_nr);  
    /*修改内核页表,将该页与页表进行关联,但还未更新TLB。*/  
    set_pte_at(&init_mm, vaddr,  
           &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));  
    /*表示创建映射页表完成*/  
    pkmap_count[last_pkmap_nr] = 1;  
    /*将vaddr加入到pkmap_page_table 哈希表和struct page_address_map中管理以供后面调用page_address.*/  
    set_page_address(page, (void *)vaddr);  
  
    return vaddr;  
}  

flush_all_zero_pkmaps():

[html]
view plaincopy

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;  
  
/*0的时候还没创建映射,不用管。  
>=2的时候表示还有模块在使用,也不处理。  
1表示已经没有模块使用了,即表示已经unmap了,但是页表还没释放,  
这里就是针对页进行释放。*/  
        if (pkmap_count[i] != 1)  
            continue;  
        pkmap_count[i] = 0;  
        page = pte_page(pkmap_page_table[i]);  
        pte_clear(&init_mm, (unsigned long)page_address(page),  
              &pkmap_page_table[i]);  
  
        set_page_address(page, NULL);  
        need_flush = 1;  
    }  
    /*有改变,刷新永久映射区全部TLB。*/  
    if (need_flush)  
        flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: