持久内核映射 permanent kernel mappings
除了vmalloc,内核提供了其他函数用于把Highmem内存映射到内核地址空间:持久内核映射和临时内核映射。而这些函数和vmalloc无关。网络上对持久内核映射和临时内核映射的理解非常的混乱
持久内核映射允许内核建立高端物理内存帧到内核地址空间的长期映射。和其他kernel地址空间一样,持久内核映射使用kernel一个pte页表来管理持久映射。pkmap_pages_table存储这个页表的地址,LAST_PKMAP定义了页表中表项的数目,一般来说是512或者1024。因此,kernel通过持久内核映射,最多可以同时访问2MB或者4MB的high memory。
pte_t * pkmap_page_table;
保存页表的地址,内核通过这个数据结构来管理映射。
/* * Right now we initialize only a single pte table. It can be extended * easily, subsequent pte tables have to be allocated in one physical * chunk of RAM. */ #ifdef CONFIG_X86_PAE #define LAST_PKMAP 512 #else #define LAST_PKMAP 1024 #endif
定义持久映射页表中的项数
#define PKMAP_BASE ( (FIXADDR_BOOT_START - PAGE_SIZE*(LAST_PKMAP + 1)) & PMD_MASK )
持久映射的起始虚拟地址,这个虚拟地址是架构特定的
static int pkmap_count[LAST_PKMAP];
pkmap_count是一个计数数组, 每一个持久地址映射项都对应一个计数
对于这个计数,我们主要区分三种情况:
1. count= 0, 相应的页表项还没有映射到高端物理内存页框,可以使用它
2. count = 1,相应的页表项没有映射到高端物理内存页框,但是现在不能使用它,因为自从上次使用过后,相应的TLB项还没有刷新。
3. count > 1,相应的页表项已经映射到高端物理内存页框,使用者的数目是n - 1
static unsigned int last_pkmap_nr;pkmap_page_table中最后被使用页表项的偏移量
/* * Hash table bucket */ static struct page_address_slot { struct list_head lh; /* List of page_address_maps */ spinlock_t lock; /* Protect this bucket's list */ } ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
page_address_htable 用来快速查找一个page对应的固定映射虚拟地址。这个和实现关系不大,我们就不详细讨论了
/* * Describes one page->virtual association */ struct page_address_map { struct page *page; void *virtual; struct list_head list; };
描述page和虚拟地址之间的关系,list是用来将page_address_map链接到hash表page_address_htable上。
数据结构之间的关系
page_address返回给定page的线性地址,如果给定的page还没有映射则返回NULL。
262 void *page_address(struct page *page) 263 { 264 unsigned long flags; 265 void *ret; 266 struct page_address_slot *pas; 267 268 if (!PageHighMem(page)) 269 return lowmem_page_address(page); 270 271 pas = page_slot(page); 272 ret = NULL; 273 spin_lock_irqsave(&pas->lock, flags); 274 if (!list_empty(&pas->lh)) { 275 struct page_address_map *pam; 276 277 list_for_each_entry(pam, &pas->lh, list) { 278 if (pam->page == page) { 279 ret = pam->virtual; 280 goto done; 281 } 282 } 283 } 284 done: 285 spin_unlock_irqrestore(&pas->lock, flags); 286 return ret; 287 }
对于给定的物理页面page结构,返回该page已经映射的虚拟地址。如果没有映射,则返回NULL
268~269 如果参数@page不是高端页框,可以通过页框号直接计算出来
否则:
271 计算hash表地址
274 ~ 283 根据hash表项在冲突链中通过page进行比对查找
注意因为固定映射本身就是体系结构特定的,所以映射函数kmap也是架构特定的函数。
4 void *kmap(struct page *page) 5 { 6 might_sleep(); 7 if (!PageHighMem(page)) 8 return page_address(page); 9 return kmap_high(page); 10 }
如果给定的page不是高端物理页面,直接通过page_address返回该页面的虚拟地址
否则调用kmap_high建立高端映射
166 void fastcall *kmap_high(struct page *page) 167 { 168 unsigned long vaddr; 169 170 /* 171 * For highmem pages, we can't trust "virtual" until 172 * after we have the lock. 173 * 174 * We cannot call this from interrupts, as it may block 175 */ 176 spin_lock(&kmap_lock); 177 vaddr = (unsigned long)page_address(page); 178 if (!vaddr) 179 vaddr = map_new_virtual(page); 180 pkmap_count[PKMAP_NR(vaddr)]++; 181 BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2); 182 spin_unlock(&kmap_lock); 183 return (void*) vaddr; 184 } 185 186 EXPORT_SYMBOL(kmap_high);
177 首先检查这个page是否已经被映射了,已经映射过的只需要增加引用计数即可
179 map_new_virtual 执行真正的映射过程,建立page到虚拟地址的映射关系,如果是第一次映射,相应地址项映射计数为1
180 增加映射的计数为
181 由于计数1有特殊含义,所以这里引用计数必然大于等于2
116 static inline unsigned long map_new_virtual(struct page *page) 117 { 118 unsigned long vaddr; 119 int count; 120 121 start: 122 count = LAST_PKMAP; 123 /* Find an empty entry */ 124 for (;;) { 125 last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK; 126 if (!last_pkmap_nr) { 127 flush_all_zero_pkmaps(); 128 count = LAST_PKMAP; 129 } 130 if (!pkmap_count[last_pkmap_nr]) 131 break; /* Found a usable entry */ 132 if (--count) 133 continue; 134 135 /* 136 * Sleep for somebody else to unmap their entries 137 */ 138 { 139 DECLARE_WAITQUEUE(wait, current); 140 141 __set_current_state(TASK_UNINTERRUPTIBLE); 142 add_wait_queue(&pkmap_map_wait, &wait); 143 spin_unlock(&kmap_lock); 144 schedule(); 145 remove_wait_queue(&pkmap_map_wait, &wait); 146 spin_lock(&kmap_lock); 147 148 /* Somebody else might have mapped it while we slept */ 149 if (page_address(page)) 150 return (unsigned long)page_address(page); 151 152 /* Re-start */ 153 goto start; 154 } 155 } 156 vaddr = PKMAP_ADDR(last_pkmap_nr); 157 set_pte_at(&init_mm, vaddr, 158 &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot)); 159 160 pkmap_count[last_pkmap_nr] = 1; 161 set_page_address(page, (void *)vaddr); 162 163 return vaddr; 164 }
124 ~ 155 获得一个空闲虚拟地址项
125 ~ 133 last_pkmap_nr是上一次成功映射的索引值,当last_pkmap_nr达到PKMAP_LAST时,那么从设置last_pkmap_nr为0,此时会调用flush_all_zeros_pkmaps,这个函数会扫描所有的pkmap_count,释放那些pkmap_count为1的地址项
135 ~ 155 如果没有空闲位置,那么进入睡眠状态,直到内核的另一部分执行pkunmap释放出空位
156 last_pkmap_nr是刚刚找到的空闲地址项索引,PKMAP_ADDR根据索引计算出虚拟地址
157 更新页表项,这样就建立了page到虚拟地址之间的映射关系
160 设置引用计数为1
辅助函数flush_all_zeros_pkmaps
69 static void flush_all_zero_pkmaps(void) 70 { 71 int i; 72 73 flush_cache_kmaps(); 74 75 for (i = 0; i < LAST_PKMAP; i++) { 76 struct page *page; 77 78 /* 79 * zero means we don't have anything to do, 80 * >1 means that it is still in use. Only 81 * a count of 1 means that it is free but 82 * needs to be unmapped 83 */ 84 if (pkmap_count[i] != 1) 85 continue; 86 pkmap_count[i] = 0; 87 88 /* sanity check */ 89 BUG_ON(pte_none(pkmap_page_table[i])); 90 91 /* 92 * Don't need an atomic fetch-and-clear op here; 93 * no-one has the page mapped, and cannot get at 94 * its virtual address (and hence PTE) without first 95 * getting the kmap_lock (which is held here). 96 * So no dangers, even with speculative execution. 97 */ 98 page = pte_page(pkmap_page_table[i]); 99 pte_clear(&init_mm, (unsigned long)page_address(page), 100 &pkmap_page_table[i]); 101 102 set_page_address(page, NULL); 103 } 104 flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP)); 105 }
84 注释已经说的很清楚,0 表示我们不需要做任何事,因为没有映射在上面;大于1表示有映射,但是正在使用;而等于1 表示需要我们unmap它
98~102 解除无效的映射。我猜测在此之前,这种映射的地址仍然可以完成访问, 哈哈。
104 因为我们修改了页表,所以需要刷新tlb
Hmmm... 好累,回去吃饭。