1. 程式人生 > >高階記憶體之永久對映

高階記憶體之永久對映

永久對映地址的線性地址

1.1 KMAP_BASE的定義:

#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的大小就是臨時固定對映的低端。

1.2 永久地址範圍

#define PKMAP_ADDR(nr)  (PKMAP_BASE + ((nr) << PAGE_SHIFT))
for (;;) {                                                              
110                 last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;          
136                                                                                 
137                         /* Re-start */                                          
138                         goto start;                                             
139                 }                                                               
140         }                                                                       
141         vaddr = PKMAP_ADDR(last_pkmap_nr);                            

從last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK; 中知道last_pkmap_nr的範圍是1-1024。

所以PKMAP_ADDR(last_pkmap_nr)是PKMAP_BASE+1<<12~PKMAP_BASE+1024<<12,示意圖如下:

 ________
|                |
________  <------------KMAP_BASE+1024
|                |
|                |
 _______  <--------------KMAP_BASE+1
|                |
  ________ <--------------KMAP_BASE
                      

線性地址頁表項

pte_t * pkmap_page_table;
267 void __init permanent_kmaps_init(pgd_t *pgd_base)                               
268 {                                                                               
269         pgd_t *pgd;                                                             
270         pud_t *pud;                                                             
271         pmd_t *pmd;                                                             
272         pte_t *pte;                                                             
273         unsigned long vaddr;                                                    
274                                                                                 
275         vaddr = PKMAP_BASE;                                                                                                                                                                                   
         
277         page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);   
278                                                                                 
279         pgd = swapper_pg_dir + pgd_index(vaddr);                                
280         pud = pud_offset(pgd, vaddr);                                           
281         pmd = pmd_offset(pud, vaddr);                                           
282         pte = pte_offset_kernel(pmd, vaddr);                                    
283         pkmap_page_table = pte;                                                 

285 } 

初始化KMAP_BASE~PAGE_SIZE*LAST_PKMAP線性地址的頁目錄項,分配頁表,初始化頁表項為0,然後把存放頁表的地址賦值給pkmap_page_table。則第N個頁表項的地址是 pkmap_page_table+N(因為pkmap_page_table是pte_t指標,也就是unsigned long型別指標)。

pkmap_count

pkmap_count是一個包含LAST_PKMAP的陣列。

pkmap_count陣列中的元素值代表下面意思:
0:

1:
The corresponding page table entry does not map any high-memory page frame,but it cannot be used because
the corresponding TLB entry has not been flushed since its last use.
翻譯成:
對應的頁表項不對映任何高記憶體實際頁框(頁表項中的值存有實際頁框,只是通過unmap函式解除,邏輯上不存在這樣的對映),TLB相應的快取沒有重新整理。

n:
相應的頁表項對映一個高階記憶體頁框,剛好N-1個核心成分在使用這個頁框。

1.1 pkmap_count值增加機制

函式呼叫關係:
kmap---->kmap_high–>map_new_virtual

邏輯:
在呼叫kmap_high中,先呼叫page_address函式,檢查這個page是否被對映到相應虛擬地址中;如果這個page還沒有被對映,則返回NULL;如果這個page已經被對映到虛擬地址,則返回這個虛擬地址。如果這個page沒有被對映到相應的虛擬地址中,則呼叫map_new_virtual函式,先向這個虛擬地址的頁表項裡寫入分配頁表的地址;然後給pkmap_count的元素賦值為1,返回後在kmap_high再增加pkmap_count的元素的值為2;如果第2次呼叫kmap,顯然page_address會返回這個頁對應的虛擬地址,顯然不需要執行map_new_virtual這個函式,這樣在kmap函式中又加了一次pkmap_count的值,這樣pkmap_count的元素的值為3.

這樣便有了:kmap_count元素的值N對應著N-1次呼叫kmap;也就是N-1個核心成分在使用著這個頁框。

151 void fastcall *kmap_high(struct page *page)                                     
152 {                                                                               
153         unsigned long vaddr;                                                    
154                                                                                 
155         /*                                                                      
156          * For highmem pages, we can't trust "virtual" until                    
157          * after we have the lock.                                              
158          *                                                                      
159          * We cannot call this from interrupts, as it may block                 
160          */                                                                     
161         spin_lock(&kmap_lock);                                                  
162         vaddr = (unsigned long)page_address(page);                              
163         if (!vaddr)                                                             
164                 vaddr = map_new_virtual(page);                                                                                                                                                                
165         pkmap_count[PKMAP_NR(vaddr)]++;                                         
166         if (pkmap_count[PKMAP_NR(vaddr)] < 2)                                   
167                 BUG();                                                          
168         spin_unlock(&kmap_lock);                                                
169         return (void*) vaddr;                                                   
170 } 


101 static inline unsigned long map_new_virtual(struct page *page)                  
102 {   
142         set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));   
143                                                                                 
144         pkmap_count[last_pkmap_nr] = 1;
}

1.2 pkmap_count值減少機制

函式呼叫流程:
kunmap---------->kunmap_high

邏輯:
不想使用這個頁對映,則會呼叫kunmap函式,kunmap_high中使用語句switch (–pkmap_count[nr]) {,減少pkmap_count的元素值。

191         switch (--pkmap_count[nr]) {                                                                     
192         case 0:                                                                                          
193                 BUG();                                                                                   
194         case 1:                                                                                          
195                 /*                                                                                       
196                  * Avoid an unnecessary wake_up() function call.                
197                  * The common case is pkmap_count[] == 1, but                   
198                  * no waiters.                                                                           
199                  * The tasks queued in the wait-queue are guarded               
200                  * by both the lock in the wait-queue-head and by               
201                  * the kmap_lock.  As the kmap_lock is held here,               
202                  * no need for the wait-queue-head's lock.  Simply              
203                  * test if the queue is empty.                                                                                                   
204                  */                                                                                      
205                 need_wakeup = waitqueue_active(&pkmap_map_wait);                
206         }   

說明: 如果kmap_count的值為1,則喚醒工作佇列pkmap_map_wait,具體動作後面細究。

1.3 pkmap_count清0操作

 62 static void flush_all_zero_pkmaps(void)                                         
 63 {                                                                               
 64         int i;                                                                  
 65                                                                                 
 66         flush_cache_kmaps();                                                    
 67                                                                                 
 68         printk(KERN_ERR "%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);          
 69         for (i = 0; i < LAST_PKMAP; i++) {                                      
 70                 struct page *page;                                              
 71                                                                                 
 72                 /*                                                              
 73                  * zero means we don't have anything to do,                     
 74                  * >1 means that it is still in use. Only                       
 75                  * a count of 1 means that it is free but                       
 76                  * needs to be unmapped                                         
 77                  */                                                             
 78                 if (pkmap_count[i] != 1)                                        
 79                         continue;                                               
 80                 pkmap_count[i] = 0;                                             
 81                                                                                 
 82                 /* sanity check */                                              
 83                 if (pte_none(pkmap_page_table[i]))                              
 84                         BUG();                                                  
 85                                                                                 
 86                 /*                                                              
 87                  * Don't need an atomic fetch-and-clear op here;                
 88                  * no-one has the page mapped, and cannot get at                
 89                  * its virtual address (and hence PTE) without first            
 90                  * getting the kmap_lock (which is held here).                  
 91                  * So no dangers, even with speculative execution.              
 92                  */                                                             
 93                 page = pte_page(pkmap_page_table[i]);                           
 94                 pte_clear(&pkmap_page_table[i]);                                
 95                                                                                 
 96                 set_page_address(page, NULL);                                   
 97         }                                                                       
 98         flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));          
 99 } 

機制:在判斷pkmap_count元素值為1的是胡,然後pte_clear(&pkmap_page_table[i]); set_page_address(page, NULL); flush_tlb_kernel_range進行下面操作:
1)清空頁表項內容
2)設定頁表描述符為空
3)更新tlb快取

page_address_maps

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

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


說明:page_address_maps是一個數組,這個陣列元素包含著存page的地址和虛擬地址,把這個陣列存放到page_address_pool連結串列佇列中,在散列表page_address_htable需要使用陣列元素時候,就從page_address_pool中取出一個元素;不需要的時候就把這個陣列元素從page_address_htable刪除,然後新增到page_address_pool中。
LAST_PKMAP是1024。

page_address_htable散列表

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個元素。

先把頁表描述符的地址page轉化為0-255的數字索引,然後通過page_slot函式獲得索引對應陣列page_address_htable的元素地址。

在set_page_address函式中,從page_address_pool中取出一個節點,填充數值,填充到page_address_hstable陣列元素的連結串列中。
程式碼如下:

549 void set_page_address(struct page *page, void *virtual)                         
550 {                                                                               
551         unsigned long flags;                                                    
552         struct page_address_slot *pas;                                          
553         struct page_address_map *pam;                                           
554                                                                                 
555         BUG_ON(!PageHighMem(page));                                             
556                                                                                 
557         pas = page_slot(page);                                                  
558         if (virtual) {          /* Add */                                                                                                                                                                     
559                 BUG_ON(list_empty(&page_address_pool));                         
560                                                                                 
561                 printk(KERN_ERR "%s %s %d %x\n",__FILE__,__FUNCTION__,__LINE__,page_address_pool.next);
562                 spin_lock_irqsave(&pool_lock, flags);                           
563                 pam = list_entry(page_address_pool.next,                        
564                                 struct page_address_map, list);                 
565                 list_del(&pam->list);                                           
566                 spin_unlock_irqrestore(&pool_lock, flags);                      
567                                                                                 
568                 pam->page = page;                                               
569                 pam->virtual = virtual;                                         
570                                                                                 
571                 spin_lock_irqsave(&pas->lock, flags);                           
572                 list_add_tail(&pam->list, &pas->lh);  
}

呼叫流程

2.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 }  

首先檢查這個page是不是低端記憶體,如果是則進入page_address,在page_address中檢測到低端地址,因為低端地址的頁表已經對映好,直接回對應低端記憶體的線性地址。如果是高階記憶體,則執行kmap_high函式。
2.2 kmap_high函式

151 void fastcall *kmap_high(struct page *page)                                     
152 {                                                                               
153         unsigned long vaddr;                                                    
154                                                                                 
155         /*                                                                      
156          * For highmem pages, we can't trust "virtual" until                    
157          * after we have the lock.                                              
158          *                                                                      
159          * We cannot call this from interrupts, as it may block                 
160          */                                                                     
161         spin_lock(&kmap_lock);                                                  
162         vaddr = (unsigned long)page_address(page);                              
163         if (!vaddr)                                                             
164                 vaddr = map_new_virtual(page);                                  
165         pkmap_count[PKMAP_NR(vaddr)]++;                                         
166         if (pkmap_count[PKMAP_NR(vaddr)] < 2)                                   
167                 BUG();                                                          
168         spin_unlock(&kmap_lock);                                                                                                                                                                              
169         return (void*) vaddr;                                                   
170 }  

首先進入page_address函式,如果查詢的page已經在page_address_htable的一個元素連結串列的節點資料中,則返回相應節點資料中的vddr,如果不存在,則返回NULL。如果是NULL,則執行map_new_virtual。

520 void *page_address(struct page *page)                                           
521 {                                                                               
522         unsigned long flags;                                                    
523         void *ret;                                                              
524         struct page_address_slot *pas;                                          
525                                                                                 
526         if (!PageHighMem(page))                                                 
527                 return lowmem_page_address(page);                               
528                                                                                 
529         pas = page_slot(page);                                                  
530         ret = NULL;                                                             
531         spin_lock_irqsave(&pas->lock, flags);                                   
532         if (!list_empty(&pas->lh)) {                                            
533                 struct page_address_map *pam;                                   
534                                                                                 
535                 list_for_each_entry(pam, &pas->lh, list) {                      
536                         if (pam->page == page) {                                
537                                 ret = pam->virtual;                             
538                                 goto done;                                      
539                         }                                                       
540                 }                                                               
541         }                                                                                                                                                                                                     
542 done:                                                                           
543         spin_unlock_irqrestore(&pas->lock, flags);                              
544         return ret;                                                             
545 }  

呼叫函式page_slot(page),得到page對應的page_address_hstable的元素,就是一個存放虛擬地址和page的連結串列。

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_slot函式返回後,遍歷整個連結串列,看這個連結串列節點,存在不存在查詢的page,如果存在,則返回對應page的虛擬地址,如果不存在,則返回NULL。

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

說明:主要工作,找到一個空餘的地址,vddr是永久地址的一個地址,然後找到這個線性地址,對應的頁表項中寫入page對應的實體地址,並且把page和vddr寫入到page對應的page_address_htable陣列元素(元素是一個連結串列)。

圖解過程

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陣列元素的連結串列中。

永久對映呼叫

直接呼叫kmap(page),然後找到空閒線性地址,對映到請求的page.
臨時固定對映,先選中一個線性地址,然後對映一個物理頁,然後使用線性地址操作。