每日一贴,今天的内容关键字为映射内存
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);
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; }
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; }
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; }
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)); }
20130422
文章结束给大家分享下程序员的一些笑话语录: 雅虎最擅长的不是开通新业务,是关闭旧业务。