高端内存之永久映射

定义

永久内核映射允许内核建立高端页框到内核地址空间的长期映射。

内核线性地址空间的最后128MB中的一部分专门用于映射高端内存页框,通过重复使用线性地址,使得整个高端内存能够在不同的时间被访问。

设计

需求:
从分配的页框中,从一组线性地址范围中,得到一个线性地址,以便后续操作。

  1. 提供一组可供选择的线性地址。

  2. 需要一个向量数组,每个数组元素的值标记着每个页表项的使用情况。

  3. 需要一个结构体包含线性地址和物理页地址组成一个节点,然后挂在一个链表中。
    这个情景中有1024项,如果在一个链表中遍历1024个项,这比较耗时。可以把1024项目分成256个链表或者128个链表,这样每个链表只有4个项或者8个项,这样遍历时间比较短,可以通过hasb把物理页地址转化为索引,然后通过索引到相应的链表中进行遍历。

  4. 分配页的时候,先查询下是否存在物理页对应的线性地址,如果存在则返回线性地址,同时设置变量的引用。

. 如果不存在对应的线性地址。
1)首先找一个空闲的页表项
2)把物理页的物理地址和权限写进页表项
3) 把线性地址和物理地址组成的节点挂在相应的链表中。
4) 同时更新页表项的使用情况,设置为1.

  1. 对外接口
    1)kmap_high
    2)kunmap_high

1. 永久映射线性地址PKMAP_BASE

1.1 范围``````

#define PKMAP_BASE ( (FIXADDR_BOOT_START - PAGE_SIZE*(LAST_PKMAP + 1)) & PMD_MASK )
 #define FIXADDR_BOOT_START      (FIXADDR_TOP - __FIXADDR_BOOT_SIZE)
 #define __FIXADDR_BOOT_SIZE     (__end_of_fixed_addresses << PAGE_SHIFT)   

在本机的调试环境中:

__FIXADDR_BOOT_SIZE=0x2e000
FIXADDR_BOOT_START=0xfffd1000 
PKMAP_BASE=ff800000 
PAGE_SHIFT=4K
FIXADDR_TOP=0xffff f000
PMD_MASK = 00 0000 0000 0000 0000 0000

说明:
1) __FIXADDR_BOOT_SIZE的大小就是固定线性地址映射fixed_addresses的枚举变量值。
2) FIXADDR_BOOT_START就是固定线性映射的最低地址。
3) PKMAP_BASE就是从固定映射的前4M开始(也就是跳过固定映射的前一个页目录项)。
PKMAP_BASE地址是0xff80 0000

1.2 接口

#define PKMAP_ADDR(nr)  (PKMAP_BASE + ((nr) << PAGE_SHIFT))

通过参数nr,指向第nr个页表项。

2 页表项的使用情况

2.1 pkmap_count

pkmap_count数组包含LAST_PKMAP个计数器,pkmap_page_table指向的页表中的每一项都有一个。pkmap_count数组元素的值有下面三种情况:

0: 对应的页表项没有映射任何高端内存页框,并且是可用的。
1:
逻辑上线性地址和物理地址没有映射(但是页表项中的内容有物理地址),但是它不能使用,页表项中有物理映射因为相应的TLB还未刷新。

如果线性地址和物理地址没有映射,在调用kmap_high设置pkmap_count为2(kmap_high中调用map_new_virtual设置为1,然后再设为2),下一次对 同样的page调用kmap_high,则给pkmap_count相应元素加1,则设置为3.
如果对这个页调用两次kunmap_high,则pkmap_count对应的数值为1。就会出现这样的情况。

n:
相应的页表映射一个高端内存页框,这意味着正好有n-1个内核成分在使用这个页框。

2.2 pkmap_count值的增加

  1. kmap_high中调用map_new_virtual设置pkmap_count的值为1,在kmap_high里又增加了pkmap_count的值。
  2. 再次对同样的页描述符调用kmap_high,则再pkmap_count的值增加1.

2.3 pkmap_count值的减少

  1. 调用kunmap_high函数pkmap_count相应的值减少1
  2. 调用flush_all_zero_pkmaps函数把pkmap_count的值从1减少为0.

2.4 pkmap_count值清零

在函数flush_all_zero_pkmaps(void)中pkmap_count值清0

保存已映射的地址的链表

3.1 设计

这个情景中有1024项,如果在一个链表中遍历1024个项,这比较耗时。可以把1024项目分成256个链表或者128个链表,这样每个链表只有4个项或者8个项,这样遍历时间比较短,可以通过hasb把物理页地址转化为索引,然后通过索引到相应的链表中进行遍历。也就是hash桶。

3.2 实现

507 /*                                                                              
508  * Hash table bucket                                                            
509  */                                                                             
510 static struct page_address_slot {                                               
511         struct list_head lh;                    /* List of page_address_maps */ 
512         spinlock_t lock;                        /* Protect this bucket's list */
513 } ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];           
514                                                                                 
515 static struct page_address_slot *page_slot(struct page *page)                                                                                                                                                 
516 {                                                                               
517         return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];             
518 } 

定义了page_address_htable,因为PA_HASH_ORDER=7,所以数组page_address_htable个数是256个,永久映射的页表项数是1024,如果页表项都映射的话,则page_address_htable每个元素不一定挂4个元素。

3.3 节点

挂在链表上的节点的数据结构,至少包含,虚拟地址和物理页地址,链表。定义的结构如下:

493 struct page_address_map {                                                                                                         
494         struct page *page;                                                      
495         void *virtual;                                                          
496         struct list_head list;                                                  
497 }; 

3.4 预分配节点链表

为了方便节点使用,可以把LAST_KMAP个数据节点,然后加入到可用链表中,需要使用时,取出一个节点,不需要使用时候,把这个节点返回可用链表。相当于一个节点池中。
具体实现:
:page_address_maps是一个数组,这个数组元素包含着存page的地址和虚拟地址,把这个数组存放到page_address_pool链表队列中,在散列表page_address_htable需要使用数组元素时候,就从page_address_pool中取出一个元素;不需要的时候就把这个数组元素从page_address_htable删除,然后添加到page_address_pool中。
LAST_PKMAP是1024。

static struct page_address_map page_address_maps[LAST_PKMAP]; 
static struct list_head page_address_pool;      /* freelist */ 

         for (i = 0; i < ARRAY_SIZE(page_address_maps); i++)                     
                 list_add(&page_address_maps[i].list, &page_address_pool); 

4.物理页与线性地址映射

4.1 物理页映射查询

void *page_address(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;                                                             
}  
  1. 先判断是低端页,还是高端页,如果是低端页,直接通过函数lowmem_page_address返回线性地址。
  2. 如果是高端页,通过对page进行hash,然后确定如果存在,应该在那个链表中。
  3. 然后在链表中,查询是否存在这个页映射,如果存在则返回相应的线性地址,如果不存在则返回NULL。

4.2 创建页映射函数

100 static inline unsigned long map_new_virtual(struct page *page)                                                                    
101 {                                                                               
102         unsigned long vaddr;                                                    
103         int count;                                                              
104                                                                                 
105 start:                                                                          
106         count = LAST_PKMAP;                                                     
107         /* Find an empty entry */                                               
108         for (;;) {                                                              
109                 last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;          
110                 if (!last_pkmap_nr) {                                           
111                         flush_all_zero_pkmaps();                                
112                         count = LAST_PKMAP;                                     
113                 }                                                               
114                 if (!pkmap_count[last_pkmap_nr])                                
115                         break;  /* Found a usable entry */                      
116                 if (--count)                                                    
117                         continue;                                               
118                                                                                 
119                 /*                                                              
120                  * Sleep for somebody else to unmap their entries               
121                  */                                                                   
122                 {                                                               
123                         DECLARE_WAITQUEUE(wait, current);                       
124                                                                                 
125                         __set_current_state(TASK_UNINTERRUPTIBLE);              
126                         add_wait_queue(&pkmap_map_wait, &wait);                 
127                         spin_unlock(&kmap_lock);                                
128                         schedule();                                             
129                         remove_wait_queue(&pkmap_map_wait, &wait);              
130                         spin_lock(&kmap_lock);                                  
131                                                                                 
132                         /* Somebody else might have mapped it while we slept */ 
133                         if (page_address(page))                                 
134                                 return (unsigned long)page_address(page);       
135                                                                                 
136                         /* Re-start */                                          
137                         goto start;                                             
138                 }                                                               
139         }                                                                       
140         vaddr = PKMAP_ADDR(last_pkmap_nr);                                      
141         set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));   
142                                                                                                                                   
143         pkmap_count[last_pkmap_nr] = 1;                                         
144         set_page_address(page, (void *)vaddr);                                  
145                                                                                 
146         return vaddr;                                                           
147 }  
 

逻辑:

  1. 在121行,通过判断pkmap_count[last_pkmap_nr]的值是否为0,判断是否找到空闲线性地址入口。
  2. 如果找到,直接到140行,主要做了如下工作:
    1) 根据索引值last_pkmap_nr转换为线性地址
    2) 根据线性地址,把物理地址和属性写进线性地址对应的页表中。
    3) 修改索引对应pkmap_count元素的值为1.
    4) 调用set_page_address函数。
  3. 如果未找到空闲地址,则在128行通过schedule调度出去,直到其他线性地址释放,则会带来一个申请线性地址,申请不到,则会阻塞问题。
    释放永久地址是在k
172 void fastcall kunmap_high(struct page *page)                                    
173 {                                                                               
174         unsigned long vaddr;                                                    
175         unsigned long nr;                                                                                                         
176         int need_wakeup;                                                        
177                                                                                 
178         spin_lock(&kmap_lock);                                                  
179         vaddr = (unsigned long)page_address(page);                              
180         if (!vaddr)                                                             
181                 BUG();                                                          
182         nr = PKMAP_NR(vaddr);                                                   
183                                                                                 
184         /*                                                                      
185          * A count must never go down to zero                                   
186          * without a TLB flush!                                                 
187          */                                                                     
188         need_wakeup = 0;                                                        
189         switch (--pkmap_count[nr]) {   
          188         need_wakeup = 0;                                                        
189         switch (--pkmap_count[nr]) {                                            
190         case 0:                                                                 
191                 BUG();                                                          
192         case 1:                                                                 
193                 /*                                                              
194                  * Avoid an unnecessary wake_up() function call.                
195                  * The common case is pkmap_count[] == 1, but                   
196                  * no waiters.                                                  
197                  * The tasks queued in the wait-queue are guarded               
198                  * by both the lock in the wait-queue-head and by               
199                  * the kmap_lock.  As the kmap_lock is held here,               
200                  * no need for the wait-queue-head's lock.  Simply              
201                  * test if the queue is empty.                                  
202                  */                                                             
203                 need_wakeup = waitqueue_active(&pkmap_map_wait);                
204         }                                                                       
205         spin_unlock(&kmap_lock);                                                
206                                                                                 
207         /* do wake-up, if needed, race-free outside of the spin lock */                                                           
208         if (need_wakeup)                                                        
209                 wake_up(&pkmap_map_wait);                                       
210 }   
               

如果pkmap_count的值为2,减少1,则为1,在203行neee_wakeup=1,则在209行wake_up变量pkmap_map_wait,则函数map_new_virtual从129重新执行。

4.3 设置page的虚拟地址

547 void set_page_address(struct page *page, void *virtual)                         
548 {                                                                               
549         unsigned long flags;                                                    
550         struct page_address_slot *pas;                                                                                            
551         struct page_address_map *pam;                                           
552                                                                                 
553         BUG_ON(!PageHighMem(page));                                             
554                                                                                 
555         pas = page_slot(page);                                                  
556         if (virtual) {          /* Add */                                       
557                 BUG_ON(list_empty(&page_address_pool));                         
558                                                                                 
559                 spin_lock_irqsave(&pool_lock, flags);                           
560                 pam = list_entry(page_address_pool.next,                        
561                                 struct page_address_map, list);                 
562                 list_del(&pam->list);                                           
563                 spin_unlock_irqrestore(&pool_lock, flags);                      
564                                                                                 
565                 pam->page = page;                                               
566                 pam->virtual = virtual;                                         
567                                                                                 
568                 spin_lock_irqsave(&pas->lock, flags);                           
569                 list_add_tail(&pam->list, &pas->lh);                            
570                 spin_unlock_irqrestore(&pas->lock, flags);                      
571         } else {                /* Remove */                                             
572                 spin_lock_irqsave(&pas->lock, flags);                           
573                 list_for_each_entry(pam, &pas->lh, list) {                      
574                         if (pam->page == page) {                                
575                                 list_del(&pam->list);                           
576                                 spin_unlock_irqrestore(&pas->lock, flags);      
577                                 spin_lock_irqsave(&pool_lock, flags);           
578                                 list_add_tail(&pam->list, &page_address_pool);  
579                                 spin_unlock_irqrestore(&pool_lock, flags);      
580                                 goto done;                                      
581                         }                                                       
582                 }                                                               
583                 spin_unlock_irqrestore(&pas->lock, flags);                      
584         }                                                                                                                         
585 done:                                                                           
586         return;                                                                 
587 }             

这个函数提供两个流程,如果vaddr=NULL,则删除从hash链表中删除,有其对应page的节点。
如果vddr不是NULL,则page_address_pool取出一个可用节点,填充page和vaddr数值,然后添加到hash桶链表中。

5 对外接口

5.1 kmap函数

  3 void *kmap(struct page *page)                                                                            
  4 {                                                                                                        
  5         might_sleep();                                                                                   
  6         if (!PageHighMem(page))                                                                          
  7                 return page_address(page);                                      
  8         return kmap_high(page);                                                                          
  9 }                                                                                                                                 
 10                                                                                                          

5.2 kunmap

 11 void kunmap(struct page *page)                                                                           
 12 {                                                                                                        
 13         if (in_interrupt())                                                                              
 14                 BUG();                                                                                   
 15         if (!PageHighMem(page))                                                                          
 16                 return;                                                                                  
 17         kunmap_high(page);                                                                               
 18 }   

6. 图解过程

kmap_init

假设,KMAP_BASE=0xff800000,没执行kmap_init,页目录如下图:
kmap-1
执行kmap_init之后,假设分配的页物理地址是0x44b000,属性是0x67,因为KMAP_BASE=0xff800000,修改页目录第0x3fe项。
kmap-2
主要做了如下工作:
1)给KMAP_BASE对应页目录项分配页表,然后把页表物理地址保存到页目录项中。
2)pkmap_page_table指向页表的基地址。
说明:浅绿色框存放的是物理地址,红色框中存的地址是虚拟地址。

使用情景:

kmap(page)

1.在函数page_address中查询映射的page是否有虚拟地址映射。

具体操作:对page=0xc1f67f20做hash值得到0x3f,然后得到链表page_address_htable[0x3f],遍历这个page_address_htable[0x3f]链表,找不到节点page是0xc1f67f20的节点。

2.通过map_new_virutal函数映射虚拟地址
2.1 循环永久高端内存的1024个页表项,循环索引是last_nr整数,通过kmap_count[last_nr]是否为0,判断整个虚 拟地址是否空闲,假设kmap_count[1]=2,kmap_count[2]=3,kmap_count[2]=0,则last_nr=3。
2.2 对应的虚拟地址vaddr=PKMAP_BASE+3<<12=0xff803000可用。
2.3 虚拟地址vaddr对应的页表项地址是pkmap_page_table+3*4=0xc044b000+0xc=0xc044b00c。
2.4 物理页描述数组mem_map的地址是0xc1000000,page描述符对应的地址是0xc1f67f20,page对应物理页框序号是(0xc1f67f20-=0x7b3ed),页表项的属性是0x163,则写入相应页表项的值是:0x7b3ed<<12+0x163=0x7b3ed163.

  1. 通过函数set_page_address把page=0xc1f67f20,和vaddr=0xff803000写入到相应page_address_htable[0x3f]链表,找不到节点page是0xc1f67f20的节点。
    3.1 对page=0xc1f67f20做hash值得到0x3f,然后得到链表page_address_htable[0x3f]。
    3.2 从page_address_pool链表去掉一个数据节点,填充vaddr,page,然后把这个节点添加到链表page_address_htable[0x3f]中。
    流程图如下,红色框就是对应着上面的流程的序号。

流程图
从上面图中可以看出,两个主要工作:
4. 从永久映射的线性地址,找出个没有映射的线性地址,然后在页表项中写入page对应的物理页框地址(包括页属性)。
5. 把页表page和vddr加入page_address_hstable数组元素的链表中。

7. 其他

tlb: 页表缓存,至于什么虚拟地址和物理地址映射是否被加入到tlb,完成由硬件完成,没有软件接口把地址映射加入到tlb中。在函数map_new_virtual中,就是创建映射关系后,默认映射加入到tlb中。

接触映射在函数中:

 62 static void flush_all_zero_pkmaps(void)                                         
 63 {                                                                               
 64         int i;                                                                  
 65                                                                                 
 66         flush_cache_kmaps();                                                                                                      
 67                                                                                 
 68         for (i = 0; i < LAST_PKMAP; i++) {                                      
 69                 struct page *page;                                              
 70                                                                                 
 71                 /*                                                              
 72                  * zero means we don't have anything to do,                     
 73                  * >1 means that it is still in use. Only                       
 74                  * a count of 1 means that it is free but                       
 75                  * needs to be unmapped                                         
 76                  */                                                             
 77                 if (pkmap_count[i] != 1)                                        
 78                         continue;                                               
 79                 pkmap_count[i] = 0;                                             
 80                                                                                 
 81                 /* sanity check */                                              
 82                 if (pte_none(pkmap_page_table[i]))                              
 83                         BUG();              
 .......
 }                                    

在66行中,清除tlb缓存,同时把pkmap_count的元素等于1的,重新赋值为0.意思说pkmap_count的元素没有了,可以使用了。

想到一个问题

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值