内核映射 - 持久内核映射

持久内核映射 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... 好累,回去吃饭。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值