高階記憶體之永久對映
永久對映地址的線性地址
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_init之後,假設分配的頁實體地址是0x44b000,屬性是0x67,因為KMAP_BASE=0xff800000,修改頁目錄第0x3fe項。
主要做了如下工作:
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.
- 通過函式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.
臨時固定對映,先選中一個線性地址,然後對映一個物理頁,然後使用線性地址操作。