高端内存永久映射分析

来源:互联网 发布:手机qq防盗软件 编辑:程序博客网 时间:2024/04/30 00:30

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
  1. static int pkmap_count[LAST_PKMAP];  
  2. #define LAST_PKMAP      PTRS_PER_PTE  
  3. #define PTRS_PER_PTE        512  

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

0:相关也还没使用

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

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

另外,内核使用struct page_address_map来保存页和虚拟地址之前的关系。
[html] view plaincopy
  1. struct page_address_map {  
  2.     struct page *page;  
  3.     void *virtual;  
  4.     struct list_head list;  
  5. };  

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

[html] view plaincopy
  1. static struct page_address_slot {  
  2.     struct list_head lh;            /* List of page_address_maps */  
  3.     spinlock_t lock;            /* Protect this bucket's list */  
  4. }  

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

[html] view plaincopy
  1. static struct page_address_slot *page_slot(const struct page *page)  
  2. {  
  3.     return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];  
  4. }  

初始化:

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

[html] view plaincopy
  1. Start_kernel() –> setup_arch() -> paging_init() -> kmap_init()  
  2. static void __init kmap_init(void)  
  3. {  
  4. #ifdef CONFIG_HIGHMEM  
  5.     /*该变量保存了PKMAP_BASE对应的页表项地址,PKMAP_BASE为永久映射的起始地址。*/  
  6.     pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),  
  7.         PKMAP_BASE, _PAGE_KERNEL_TABLE);  
  8. #endif  
  9. }  

创建映射:

         调用是kmap().

[html] view plaincopy
  1. void *kmap(struct page *page)  
  2. {  
  3.     /*会sleep,所以不能用于中断上下文*/  
  4.     might_sleep();  
  5.     /*如果是低端内存,那么返回page对应虚拟地址*/  
  6.     if (!PageHighMem(page))  
  7.         return page_address(page);  
  8.     /*否则执行高端内存映射*/  
  9.     return kmap_high(page);  
  10. }  
  11. EXPORT_SYMBOL(kmap);  

page_address():

[html] view plaincopy
  1. void *page_address(const struct page *page)  
  2. {  
  3.     unsigned long flags;  
  4.     void *ret;  
  5.     struct page_address_slot *pas;  
  6.     /*又一次判断是低端还是高端内存,因为此函数开放给调用者调用。*/  
  7.     if (!PageHighMem(page))  
  8.     /*低端内存就按固定偏移返回虚拟地址*/  
  9.         return lowmem_page_address(page);  
  10.     /*高端内存就从哈希表中查找返回。*/  
  11.     pas = page_slot(page);  
  12.     ret = NULL;  
  13.     spin_lock_irqsave(&pas->lock, flags);  
  14.     if (!list_empty(&pas->lh)) {  
  15.         struct page_address_map *pam;  
  16.             /*页使用lh链表来管理。*/  
  17.         list_for_each_entry(pam, &pas->lh, list) {  
  18.             if (pam->page == page) {  
  19.                 ret = pam->virtual;  
  20.                 goto done;  
  21.             }  
  22.         }  
  23.     }  
  24. done:  
  25.     spin_unlock_irqrestore(&pas->lock, flags);  
  26.     return ret;  
  27. }  

kmap_high():

[html] view plaincopy
  1. void *kmap_high(struct page *page)  
  2. {  
  3.     unsigned long vaddr;  
  4.   
  5.     /*  
  6.      * For highmem pages, we can't trust "virtual" until  
  7.      * after we have the lock.  
  8.      */  
  9.     lock_kmap();  
  10.     /*先判断是否已经被映射过了*/  
  11.     vaddr = (unsigned long)page_address(page);  
  12.     /*没有就新创建页表项*/  
  13.     if (!vaddr)  
  14.         vaddr = map_new_virtual(page);  
  15.     /*当前页对应的元素值加1.  PKMAP_NR表示相对于PKMAP_BASE的偏移。  
  16. #define PKMAP_NR(virt)      (((virt) - PKMAP_BASE) >> PAGE_SHIFT) */  
  17.     pkmap_count[PKMAP_NR(vaddr)]++;  
  18.     /*<2肯定不正常了,因为1表示创建完成,如果跑到这里,2就表示有模块使用此映射了。*/  
  19.     BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);  
  20.     unlock_kmap();  
  21.     /*返回映射之后的虚拟地址*/  
  22.     return (void*) vaddr;  
  23. }  

map_new_virtual():

[html] view plaincopy
  1. static inline unsigned long map_new_virtual(struct page *page)  
  2. {  
  3.     unsigned long vaddr;  
  4.     int count;  
  5.   
  6. start:  
  7.     count = LAST_PKMAP;  
  8.     /* Find an empty entry */  
  9.     for (;;) {  
  10.         /* last_pkmap_nr 记录当前映射的页数,也可以认为是最后使用的位置。*/  
  11.         last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;  
  12.         /*为0表示查找完一轮了,这时它会去刷新pkmap_count的值为1的TLB。*/  
  13.         if (!last_pkmap_nr) {  
  14.             flush_all_zero_pkmaps();  
  15.             count = LAST_PKMAP;  
  16.         }  
  17.         /*找到空闲区了*/  
  18.         if (!pkmap_count[last_pkmap_nr])  
  19.             break;  /* Found a usable entry */  
  20.         /*未找到,继续找下一个*/  
  21.         if (--count)  
  22.             continue;  
  23.         /*找了LAST_PKMAP 个之后就睡眠,等待其他模块unmap*/  
  24.         {  
  25.             DECLARE_WAITQUEUE(wait, current);  
  26.             __set_current_state(TASK_UNINTERRUPTIBLE);  
  27.             add_wait_queue(&pkmap_map_wait, &wait);  
  28.             unlock_kmap();  
  29.             schedule();  
  30.             remove_wait_queue(&pkmap_map_wait, &wait);  
  31.             lock_kmap();  
  32.             /*醒来之后看看是不是有其他进程已经做了映射了,如果是,  
  33. 就直接返回。*/  
  34.             /* Somebody else might have mapped it while we slept */  
  35.             if (page_address(page))  
  36.                 return (unsigned long)page_address(page);  
  37.             /*没有就再去重新新一轮查找*/  
  38.             /* Re-start */  
  39.             goto start;  
  40.         }  
  41.     }  
  42.     /*从这里看到,永久映射的虚拟地址是继续PKMAP_BASE加上一个offset实现的。*/  
  43.     vaddr = PKMAP_ADDR(last_pkmap_nr);  
  44.     /*修改内核页表,将该页与页表进行关联,但还未更新TLB。*/  
  45.     set_pte_at(&init_mm, vaddr,  
  46.            &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));  
  47.     /*表示创建映射页表完成*/  
  48.     pkmap_count[last_pkmap_nr] = 1;  
  49.     /*将vaddr加入到pkmap_page_table 哈希表和struct page_address_map中管理以供后面调用page_address.*/  
  50.     set_page_address(page, (void *)vaddr);  
  51.   
  52.     return vaddr;  
  53. }  

flush_all_zero_pkmaps():

[html] view plaincopy
  1. static void flush_all_zero_pkmaps(void)  
  2. {  
  3.     int i;  
  4.     int need_flush = 0;  
  5.   
  6.     flush_cache_kmaps();  
  7.   
  8.     for (i = 0; i < LAST_PKMAP; i++) {  
  9.         struct page *page;  
  10.   
  11. /*0的时候还没创建映射,不用管。  
  12. >=2的时候表示还有模块在使用,也不处理。  
  13. 1表示已经没有模块使用了,即表示已经unmap了,但是页表还没释放,  
  14. 这里就是针对页进行释放。*/  
  15.         if (pkmap_count[i] != 1)  
  16.             continue;  
  17.         pkmap_count[i] = 0;  
  18.         page = pte_page(pkmap_page_table[i]);  
  19.         pte_clear(&init_mm, (unsigned long)page_address(page),  
  20.               &pkmap_page_table[i]);  
  21.   
  22.         set_page_address(page, NULL);  
  23.         need_flush = 1;  
  24.     }  
  25.     /*有改变,刷新永久映射区全部TLB。*/  
  26.     if (need_flush)  
  27.         flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));  

0 0
原创粉丝点击