标签:linux kernel 内存管理
前面讲过,针对于内核地址空间中后面的128MB空间,Kernel提供了三种机制来映射物理内存。之前讲过了两种,即持久内核映射和临时内核映射。这两种机制的目的都是一样的:使Kernel能够访问到高端内存。
今天讲一下第三种机制:非连续内存分配,也就是vmalloc。这个机制同样可以使Kernel能够访问到高端内存,不过这不是该机制的主要目的。该机制的主要目的是:把物理上不连续的页面映射到连续的内核线性地址空间中。
非连续内存区域管理
既然是映射,肯定会涉及到三个元素:集合L,集合P,映射M。
集合L:线性地址空间
vmalloc映射到的线性地址空间,位于内核地址空间后面128MB中的开始部分,范围是 VMALLOC_START ~ VMALLOC_END。
该空间的开始,与物理内存直接映射的空间的结尾(即3GB+896MB)之间,有一个8MB的安全间隔。
为了管理这段空间,Kernel必须知道其中哪些区域已经使用了,哪些还未使用。为此,Kernel提供了一个数据结构,来描述已经使用了的非连续内存区域。
25 struct vm_struct {
26 /* keep next,addr,size together to speedup lookups */
27 struct vm_struct *next;
28 void *addr;
29 unsigned long size;
30 unsigned long flags;
31 struct page **pages;
32 unsigned int nr_pages;
33 unsigned long phys_addr;
34 };addr:该区域的起始地址;
size:该区域的大小,加上4096(区域之间的安全间隔)。
flags:该区域的标志。
VM_ALLOC: 该区域是由 vmalloc 创建的。
VM_MAP: 该区域是由 vmap 创建的。
VM_IOREMAP: 由 ioremap 使用。
pages:指向一个page指针数组的指针。每一项元素都表示一个映射到该区域的物理页面。
nr_pages:该区域映射的物理页面的个数。
phys_addr:由ioremap使用。
所有的 vm_struct 结构体都链接在一个链表上,链表的头为vmlist。该链表由锁vmlist_lock保护。
24 DEFINE_RWLOCK(vmlist_lock); 25 struct vm_struct *vmlist;
Kernel提供了函数 get_vm_area() 来寻找一块空闲区域,并创建一个vm_struct结构体。该函数最终调用函数 __get_vm_area_node() 来完成工作。
169 static struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long flags,
170 unsigned long start, unsigned long end,
171 int node, gfp_t gfp_mask)
172 {
173 struct vm_struct **p, *tmp, *area;
174 unsigned long align = 1;
175 unsigned long addr;
...
188 addr = ALIGN(start, align);
189 size = PAGE_ALIGN(size);
190 if (unlikely(!size))
191 return NULL;
192
193 area = kmalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
194
195 if (unlikely(!area))
196 return NULL;
197
198 /*
199 * We always allocate a guard page.
200 */
201 size += PAGE_SIZE;
202
203 write_lock(&vmlist_lock);
204 for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {
205 if ((unsigned long)tmp->addr < addr) {
206 if((unsigned long)tmp->addr + tmp->size >= addr)
207 addr = ALIGN(tmp->size +
208 (unsigned long)tmp->addr, align);
209 continue;
210 }
211 if ((size + addr) < addr)
212 goto out;
213 if (size + addr <= (unsigned long)tmp->addr)
214 goto found;
215 addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);
216 if (addr > end - size)
217 goto out;
218 }
219
220 found:
221 area->next = *p;
222 *p = area;
223
224 area->flags = flags;
225 area->addr = (void *)addr;
226 area->size = size;
227 area->pages = NULL;
228 area->nr_pages = 0;
229 area->phys_addr = 0;
230 write_unlock(&vmlist_lock);
231
232 return area;
233
234 out:
235 write_unlock(&vmlist_lock);
236 kfree(area);
237 if (printk_ratelimit())
238 printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc=<siz e> to increase size.\n");
239 return NULL;
240 }参数 start 和 end 分别设为 VMALLOC_START 和 VMALLOC_END。
193行调用kmalloc分配一个vm_struct结构体。
201行把size增加一页大小。增加的这一页大小用来作为区域之间的安全间隔。
204 ~ 218 遍历链表 vmlist,寻找一个大小至少为 size+PAGE_SIZE 的空闲区域。如果没找到,则释放前面分配的vm_struct结构体,返回NULL。
220 ~ 232 初始化找到的空闲区域,并把该区域的vm_struct结构体添加到链表vmlist中,并返回该结构体的地址。
集合P:不连续的物理页面
vmalloc要映射的物理页面,当然也是从buddy system中分配出来的。该工作由函数__vmalloc_area_node()来完成。
426 void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
427 pgprot_t prot, int node)
428 {
429 struct page **pages;
430 unsigned int nr_pages, array_size, i;
431
432 nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
433 array_size = (nr_pages * sizeof(struct page *));
434
435 area->nr_pages = nr_pages;
436 /* Please note that the recursion is strictly bounded. */
437 if (array_size > PAGE_SIZE) {
438 pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,
439 PAGE_KERNEL, node);
440 area->flags |= VM_VPAGES;
441 } else {
442 pages = kmalloc_node(array_size,
443 (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,
444 node);
445 }
446 area->pages = pages;
447 if (!area->pages) {
448 remove_vm_area(area->addr);
449 kfree(area);
450 return NULL;
451 }
452
453 for (i = 0; i < area->nr_pages; i++) {
454 if (node < 0)
455 area->pages[i] = alloc_page(gfp_mask);
456 else
457 area->pages[i] = alloc_pages_node(node, gfp_mask, 0);
458 if (unlikely(!area->pages[i])) {
459 /* Successfully allocated i pages, free them in __vunmap() */
460 area->nr_pages = i;
461 goto fail;
462 }
463 }
464
465 if (map_vm_area(area, prot, &pages))
466 goto fail;
467 return area->addr;
468
469 fail:
470 vfree(area->addr);
471 return NULL;
472 }432行确定要映射的物理页面的个数。
433 ~ 451 分配页面指针数组pages。
453 ~ 463 从buddy system中申请物理页面。这里需要注意的一点是,物理页面是按单个页面,逐次分配出来的。这正是vmalloc的一个核心思想所在,正因如此,vmalloc才能够把物理上不连续的页面映射到连续的线性地址空间中。
映射M:kernel page tables
至此,我们有了一块连续的线性地址空间,也有了足够的物理页面,那接下来的工作就是把两者映射起来。该工作是由函数map_vm_area()完成的。
148 int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)
149 {
150 pgd_t *pgd;
151 unsigned long next;
152 unsigned long addr = (unsigned long) area->addr;
153 unsigned long end = addr + area->size - PAGE_SIZE;
154 int err;
155
156 BUG_ON(addr >= end);
157 pgd = pgd_offset_k(addr);
158 do {
159 next = pgd_addr_end(addr, end);
160 err = vmap_pud_range(pgd, addr, next, prot, pages);
161 if (err)
162 break;
163 } while (pgd++, addr = next, addr != end);
164 flush_cache_vmap((unsigned long) area->addr, end);
165 return err;
166 }该函数通过修改页表来完成具体的映射操作。这里需要注意的一点是,该函数只是修改了kernel page tables, 而当前进程的页表并没有改变。
好了,把以上三个元素组装起来,就是函数vmalloc()的实现了。该函数最终通过函数__vmalloc_node()来完成工作。
490 static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,
491 int node)
492 {
493 struct vm_struct *area;
494
495 size = PAGE_ALIGN(size);
496 if (!size || (size >> PAGE_SHIFT) > num_physpages)
497 return NULL;
498
499 area = get_vm_area_node(size, VM_ALLOC, node, gfp_mask);
500 if (!area)
501 return NULL;
502
503 return __vmalloc_area_node(area, gfp_mask, prot, node);
504 }释放函数:vfree
函数vfree()用来释放一个vmalloc区域。它最终调用函数__vunmap()来完成工作。
322 static void __vunmap(void *addr, int deallocate_pages)
323 {
324 struct vm_struct *area;
325
326 if (!addr)
327 return;
328
329 if ((PAGE_SIZE-1) & (unsigned long)addr) {
330 printk(KERN_ERR "Trying to vfree() bad address (%p)\n", addr);
331 WARN_ON(1);
332 return;
333 }
334
335 area = remove_vm_area(addr);
336 if (unlikely(!area)) {
337 printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",
338 addr);
339 WARN_ON(1);
340 return;
341 }
342
343 debug_check_no_locks_freed(addr, area->size);
344
345 if (deallocate_pages) {
346 int i;
347
348 for (i = 0; i < area->nr_pages; i++) {
349 BUG_ON(!area->pages[i]);
350 __free_page(area->pages[i]);
351 }
352
353 if (area->flags & VM_VPAGES)
354 vfree(area->pages);
355 else
356 kfree(area->pages);
357 }
358
359 kfree(area);
360 return;
361 }它的具体实现也是由三步来完成,正好与vmalloc()一一对应。
第一步:找到要释放区域的描述符,并解除映射。该操作是由335行上的函数remove_vm_area()完成的。
284 static struct vm_struct *__remove_vm_area(void *addr)
285 {
286 struct vm_struct **p, *tmp;
287
288 for (p = &vmlist ; (tmp = *p) != NULL ;p = &tmp->next) {
289 if (tmp->addr == addr)
290 goto found;
291 }
292 return NULL;
293
294 found:
295 unmap_vm_area(tmp);
296 *p = tmp->next;
297
298 /*
299 * Remove the guard page.
300 */
301 tmp->size -= PAGE_SIZE;
302 return tmp;
303 }解除映射由函数unmap_vm_area()完成。正如vmalloc(),这里只会修改kernel page tables,而当前进程的页表不会改变。
第二步:把该区域所映射的物理页面释放回buddy system,并释放页表指针数组pages。345 ~ 357
第三步:释放vm_struct结构体所占用的内存。359行。
后记
刚才我们强调过,函数vmalloc()只是修改了kernel page tables,当前进程的page tables并没有改变。因此,当一个处于内核态的进程P访问vmalloc区域时,就会产生一个page fault,因为在进程P的页表中,该vmalloc区域所对应的页表项为空。然而,page fault handler会检查master kernel Page Tables里面对应于出错线性地址的页表项。如果该页表项不为空,那它就会把该页表项的内容copy到进程P的页表对应的页表项中(实际上,copy的是Page Middle Directory中的页表项)。这样,page fault handler返回后,进程P就可以继续执行并正常访问该vmalloc区域了。
正如函数vmalloc(),函数vfree()也是只修改了kernel page tables,而并没有修改进程的页表。那它是怎么工作的呢?
假设,一个处于内核态的进程P,访问一个vmalloc区域。按照我们上面讲的,page fault handler会把kernel page tables中对应的页表项copy到进程P的页表对应的页表项中,从而使得进程P可以正常访问该vmalloc区域。也就是说,针对于该vmalloc区域中的地址,进程P的Page Global Directory和master kernel Page Global Directory最终指向相同的的page tables。如果此时,vfree()释放了该区域,清除了这些page tables里面对应的内容。进程P再访问这个vmalloc区域时就会产生一个page fault。而page fault handler发现kernel page tables中也不包含出错地址所对应的页表项,因此就会把这次访问看作为bug。
我们前面讲过,kernel page tables不会被任何用户态或内核态进程直接使用,而是为进程的页表提供了一个参考模型。说的就是这个意思。
本文出自 “内核部落格” 博客,请务必保留此出处http://richardguo.blog.51cto.com/9343720/1684142
Kernel那些事儿之内存管理(13) --- 内核映射(下)
标签:linux kernel 内存管理
原文地址:http://richardguo.blog.51cto.com/9343720/1684142