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页大小。
- 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来保存页和虚拟地址之前的关系。- struct page_address_map {
- struct page *page;
- void *virtual;
- struct list_head list;
- };
另外,高端内存永久映射是通过page_address_htable这个变量来管理的,结构为:
- static struct page_address_slot {
- struct list_head lh; /* List of page_address_maps */
- spinlock_t lock; /* Protect this bucket's list */
- }
它的管理机制使用了哈希表,对应的函数是page_slot(),对于哈希原理,可自行查资料学习。
- static struct page_address_slot *page_slot(const struct page *page)
- {
- return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
- }
初始化:
系统开机启动的时候有如下调用:
- 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().
- 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():
- 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():
- 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():
- 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():
- 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));
- }