Linux内存管理 - 内核页表的建立 (描述)


分类: linux内存管理


总结了高端内存区的固定内核映射区、临时内核映射与永久内核映射。但是对于高端内存中各个区间的布置我们任然不是很清楚,首先我们从整体上看看内核对高端内存的划分情况。

如果内存足够大(比如用户:内核线性空间=3:1,内核就只能访问线性空间的第4GB内容,如果物理内存超过1GB则视为足够大),内核线性空间无法同时映射所有内存。这就需要将内核线性空间分出一段不直接映射物理内存,而是作为窗口分时映射使用到的未映射的内存。

一、非连续内存区布局

Linux内核中对于非连续区间的开始:




[cpp] view plain copy print ?


1. #define VMALLOC_START   ((unsigned long)high_memory + VMALLOC_OFFSET)


[cpp] view plain copy print ?


1. #define VMALLOC_OFFSET  (8 * 1024 * 1024)


对于变量high_memory变量:






[cpp] view plain copy print ?



1. void __init initmem_init(unsigned long
2. long
3. {  
4.     highstart_pfn = highend_pfn = max_pfn;  
5. if
6.         highstart_pfn = max_low_pfn;  
7. ……  
8.     num_physpages = highend_pfn;  
9. /*高端内存开始地址物理*/
10. void
11. ……  
12. }



其中,变量max_low_pfn在highmem_pfn_init()函数中初始化为下面值



[cpp] view plain copy print ?


1. #define MAXMEM  (VMALLOC_END - PAGE_OFFSET - __VMALLOC_RESERVE)

[cpp] view plain copy print ?


1. <p>unsigned int

对于非连续区间的结束定义:






[cpp] view plain copy print ?


1. # define VMALLOC_END    (PKMAP_BASE - 2 * PAGE_SIZE)


由上面的内核代码,画出内存布局细节图如下






由上面的布局可知128M+4M+4M+8K,然而直接映射区和连续内存之间空出来了8M的空间不能用,非连续空间和永久内核映射区之间也有8K的空间不可用,另外,内存顶端空出了4K不可用的。这样,高端内存能用的空间为128M+4M+4M+8K-4K-8M-8K=128M-4K大小的内存。

二、数据结构描述

虚拟内存区描述(对于vmlist链表)



[cpp] view plain copy print ?


1. struct
2. struct
3. void            *addr;/*内存区的第一个内存单元的线性地址*/
4. long
5. long       flags;/*类型*/
6. struct page     **pages;/*指向nr_pages数组的指针,该数组由指向页描述符的指针组成*/
7. int        nr_pages;/*内存区填充的页的个数*/
8. long       phys_addr;/*该字段设为0,除非内存已被创建来映射一个硬件设备的IO共享内存*/
9. void
10. };


虚拟内存区描述(对于红黑树)


[html] view plain copy print ?

1. struct vmap_area {  
2.     unsigned long va_start;  
3.     unsigned long va_end;  
4.     unsigned long flags;  
5.     struct rb_node rb_node;     /* address sorted rbtree */  
6.     struct list_head list;      /* address sorted list */  
7.     struct list_head purge_list;    /* "lazy purge" list */  
8.     void *private;  
9.     struct rcu_head rcu_head;  
10. };


内存区由next字段链接到一起,并且为了查找简单,他们以地址为次序。为了防止溢出,每个区域至少由一个页面隔离开。



三、非连续内存区初始化


非连续内存区的初始化工作在start_kernel()->mm_init()->vmalloc_init()完成


[cpp] view plain copy print ?

1. void __init vmalloc_init(void)  
2. {  
3. struct
4. struct
5. int
6.   
7.     for_each_possible_cpu(i) {  
8. struct
9.   
10.         vbq = &per_cpu(vmap_block_queue, i);  
11.         spin_lock_init(&vbq->lock);  
12.         INIT_LIST_HEAD(&vbq->free);  
13.         INIT_LIST_HEAD(&vbq->dirty);  
14.         vbq->nr_dirty = 0;  
15.     }  
16.   
17. /* Import existing vmlist entries. */
18. for (tmp = vmlist; tmp; tmp = tmp->next) {/*导入vmlist中已经有的数据到红黑树中*/
19. sizeof(struct
20.         va->flags = tmp->flags | VM_VM_AREA;  
21. long)tmp->addr;  
22.         va->va_end = va->va_start + tmp->size;  
23.         __insert_vmap_area(va);  
24.     }  
25.   
26.     vmap_area_pcpu_hole = VMALLOC_END;  
27.   
28. true;/*已经初始化*/
29. }


四、创建非连续内存的线性区

vm_struct结构链接在一个链表中,链表的第一个元素的地址存放在vmlist变量中。当内核需要分配一块新的内存时,函数get_vm_area()分配结构体所需要的空间,然后将其插入到链表中。另外,该版本的内核中增加了红黑树的管理。函数get_vm_area()不仅要将其插入到vmlist链表中,还有将结构体vmap_area插入到vmap_area_root指定根的红黑树中。

get_vm_area()函数会调用__get_vm_area_node()函数

[cpp]       view plain      copy      print      ?     
    
1. static struct vm_struct *__get_vm_area_node(unsigned long
2. long align, unsigned long flags, unsigned long
3. long end, int node, gfp_t gfp_mask, void
4. {  
5. static struct
6. struct
7.   
8.     BUG_ON(in_interrupt());  
9. if
10. int
11.   
12. if
13.             bit = IOREMAP_MAX_ORDER;  
14. else if
15.             bit = PAGE_SHIFT;  
16.   
17.         align = 1ul << bit;  
18.     }  
19.   
20.     size = PAGE_ALIGN(size);  
21. if
22. return
23. /*分配vm_struct结构体内存空间*/
24. sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);  
25. if
26. return
27.   
28. /*
29.      * We always allocate a guard page.
30.      */
31. /*为安全考虑,多一个页面*/
32. /*分配vmap_area结构体,并且将其插入到红黑树中*/
33.     va = alloc_vmap_area(size, align, start, end, node, gfp_mask);  
34. if
35.         kfree(area);  
36. return
37.     }  
38. /*插入vmlist链表*/
39.     insert_vmalloc_vm(area, va, flags, caller);  
40. return
41. }

[cpp] view plain copy print ?

1. /*
2.  * Allocate a region of KVA of the specified size and alignment, within the
3.  * vstart and vend.
4.  */
5. static struct vmap_area *alloc_vmap_area(unsigned long
6. long
7. long vstart, unsigned long
8. int
9. {  
10. struct
11. struct
12. long
13. int
14.   
15.     BUG_ON(!size);  
16.     BUG_ON(size & ~PAGE_MASK);  
17. /*分配vmap_area结构*/
18. sizeof(struct
19.             gfp_mask & GFP_RECLAIM_MASK, node);  
20. if
21. return
22.   
23. retry:  
24.     addr = ALIGN(vstart, align);  
25.   
26.     spin_lock(&vmap_area_lock);  
27. if
28. goto
29.   
30. /* XXX: could have a last_hole cache */
31.     n = vmap_area_root.rb_node;  
32. if
33. struct
34.   
35. do
36. struct
37. struct
38. if
39. if
40.                     first = tmp;  
41.                 n = n->rb_left;  
42. else
43.                 first = tmp;  
44.                 n = n->rb_right;  
45.             }  
46. while
47.   
48. if (!first)/*为最左的孩子,也就是比现有的都小*/
49. goto
50.   
51. if
52.             n = rb_next(&first->rb_node);  
53. if
54. struct
55. else/*next为空*/
56. goto found;/*为找到的节点的下一个,也就是比找到的大*/
57.         }  
58. /*当上面没有满足要求时,重新配置addr,也就是起始
59.         地址*/
60. while
61. /*重新配置起始地址*/
62. if
63. goto
64.   
65.             n = rb_next(&first->rb_node);  
66. if
67. struct
68. else
69. goto found;/*此时应该插入到找到的节点的右边*/
70.         }  
71.     }  
72. found:  
73. if
74. overflow:  
75.         spin_unlock(&vmap_area_lock);  
76. if
77.             purge_vmap_area_lazy();  
78.             purged = 1;  
79. goto
80.         }  
81. if
82.             printk(KERN_WARNING  
83. "vmap allocation for size %lu failed: "
84. "use vmalloc=<size> to increase size.\n", size);  
85.         kfree(va);  
86. return
87.     }  
88.   
89.     BUG_ON(addr & (align-1));  
90. /*初始化va*/
91.     va->va_start = addr;  
92.     va->va_end = addr + size;  
93.     va->flags = 0;  
94. /*插入到红黑树*/
95.     __insert_vmap_area(va);  
96.     spin_unlock(&vmap_area_lock);  
97.   
98. return
99. }

[cpp] view plain copy print ?


1. static void insert_vmalloc_vm(struct vm_struct *vm, struct
2. long flags, void
3. {  
4. struct
5. /*初始化vm*/
6.     vm->flags = flags;  
7. void
8.     vm->size = va->va_end - va->va_start;  
9.     vm->caller = caller;  
10. private
11.     va->flags |= VM_VM_AREA;  
12.   
13.     write_lock(&vmlist_lock);  
14. /*寻找插入位置*/
15. for
16. if
17. break;  
18.     }  
19. /*插入工作*/
20.     vm->next = *p;  
21.     *p = vm;  
22.     write_unlock(&vmlist_lock);  
23. }


初步总结了高端内存非连续区的管理框架,后面将总结他的分配和释放工作。



总结了高端内存区的固定内核映射区、临时内核映射与永久内核映射。但是对于高端内存中各个区间的布置我们任然不是很清楚,首先我们从整体上看看内核对高端内存的划分情况。

如果内存足够大(比如用户:内核线性空间=3:1,内核就只能访问线性空间的第4GB内容,如果物理内存超过1GB则视为足够大),内核线性空间无法同时映射所有内存。这就需要将内核线性空间分出一段不直接映射物理内存,而是作为窗口分时映射使用到的未映射的内存。

一、非连续内存区布局

Linux内核中对于非连续区间的开始:



[cpp] view plain copy print ?


1. #define VMALLOC_START   ((unsigned long)high_memory + VMALLOC_OFFSET)


[cpp] view plain copy print ?

1. #define VMALLOC_OFFSET  (8 * 1024 * 1024)


对于变量high_memory变量:




[cpp] view plain copy print ?

1. void __init initmem_init(unsigned long
2. long
3. {  
4.     highstart_pfn = highend_pfn = max_pfn;  
5. if
6.         highstart_pfn = max_low_pfn;  
7. ……  
8.     num_physpages = highend_pfn;  
9. /*高端内存开始地址物理*/
10. void
11. ……  
12. }

其中,变量max_low_pfn在highmem_pfn_init()函数中初始化为下面值


[cpp] view plain copy print ?


1. #define MAXMEM  (VMALLOC_END - PAGE_OFFSET - __VMALLOC_RESERVE)


[cpp] view plain copy print ?

1. <p>unsigned int

对于非连续区间的结束定义:




[cpp] view plain copy print ?


1. # define VMALLOC_END    (PKMAP_BASE - 2 * PAGE_SIZE)


由上面的内核代码,画出内存布局细节图如下




由上面的布局可知128M+4M+4M+8K,然而直接映射区和连续内存之间空出来了8M的空间不能用,非连续空间和永久内核映射区之间也有8K的空间不可用,另外,内存顶端空出了4K不可用的。这样,高端内存能用的空间为128M+4M+4M+8K-4K-8M-8K=128M-4K大小的内存。

二、数据结构描述

虚拟内存区描述(对于vmlist链表)



[cpp] view plain copy print ?

1. struct
2. struct
3. void            *addr;/*内存区的第一个内存单元的线性地址*/
4. long
5. long       flags;/*类型*/
6. struct page     **pages;/*指向nr_pages数组的指针,该数组由指向页描述符的指针组成*/
7. int        nr_pages;/*内存区填充的页的个数*/
8. long       phys_addr;/*该字段设为0,除非内存已被创建来映射一个硬件设备的IO共享内存*/
9. void
10. };

虚拟内存区描述(对于红黑树)


[html] view plain copy print ?

1. struct vmap_area {  
2.     unsigned long va_start;  
3.     unsigned long va_end;  
4.     unsigned long flags;  
5.     struct rb_node rb_node;     /* address sorted rbtree */  
6.     struct list_head list;      /* address sorted list */  
7.     struct list_head purge_list;    /* "lazy purge" list */  
8.     void *private;  
9.     struct rcu_head rcu_head;  
10. };


内存区由next字段链接到一起,并且为了查找简单,他们以地址为次序。为了防止溢出,每个区域至少由一个页面隔离开。



三、非连续内存区初始化


非连续内存区的初始化工作在start_kernel()->mm_init()->vmalloc_init()完成


[cpp] view plain copy print ?

1. void __init vmalloc_init(void)  
2. {  
3. struct
4. struct
5. int
6.   
7.     for_each_possible_cpu(i) {  
8. struct
9.   
10.         vbq = &per_cpu(vmap_block_queue, i);  
11.         spin_lock_init(&vbq->lock);  
12.         INIT_LIST_HEAD(&vbq->free);  
13.         INIT_LIST_HEAD(&vbq->dirty);  
14.         vbq->nr_dirty = 0;  
15.     }  
16.   
17. /* Import existing vmlist entries. */
18. for (tmp = vmlist; tmp; tmp = tmp->next) {/*导入vmlist中已经有的数据到红黑树中*/
19. sizeof(struct
20.         va->flags = tmp->flags | VM_VM_AREA;  
21. long)tmp->addr;  
22.         va->va_end = va->va_start + tmp->size;  
23.         __insert_vmap_area(va);  
24.     }  
25.   
26.     vmap_area_pcpu_hole = VMALLOC_END;  
27.   
28. true;/*已经初始化*/
29. }

四、创建非连续内存的线性区

vm_struct结构链接在一个链表中,链表的第一个元素的地址存放在vmlist变量中。当内核需要分配一块新的内存时,函数get_vm_area()分配结构体所需要的空间,然后将其插入到链表中。另外,该版本的内核中增加了红黑树的管理。函数get_vm_area()不仅要将其插入到vmlist链表中,还有将结构体vmap_area插入到vmap_area_root指定根的红黑树中。

get_vm_area()函数会调用__get_vm_area_node()函数


[cpp] view plain copy print ?

1. static struct vm_struct *__get_vm_area_node(unsigned long
2. long align, unsigned long flags, unsigned long
3. long end, int node, gfp_t gfp_mask, void
4. {  
5. static struct
6. struct
7.   
8.     BUG_ON(in_interrupt());  
9. if
10. int
11.   
12. if
13.             bit = IOREMAP_MAX_ORDER;  
14. else if
15.             bit = PAGE_SHIFT;  
16.   
17.         align = 1ul << bit;  
18.     }  
19.   
20.     size = PAGE_ALIGN(size);  
21. if
22. return
23. /*分配vm_struct结构体内存空间*/
24. sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);  
25. if
26. return
27.   
28. /*
29.      * We always allocate a guard page.
30.      */
31. /*为安全考虑,多一个页面*/
32. /*分配vmap_area结构体,并且将其插入到红黑树中*/
33.     va = alloc_vmap_area(size, align, start, end, node, gfp_mask);  
34. if
35.         kfree(area);  
36. return
37.     }  
38. /*插入vmlist链表*/
39.     insert_vmalloc_vm(area, va, flags, caller);  
40. return
41. }

[cpp] view plain copy print ?

1. /*
2.  * Allocate a region of KVA of the specified size and alignment, within the
3.  * vstart and vend.
4.  */
5. static struct vmap_area *alloc_vmap_area(unsigned long
6. long
7. long vstart, unsigned long
8. int
9. {  
10. struct
11. struct
12. long
13. int
14.   
15.     BUG_ON(!size);  
16.     BUG_ON(size & ~PAGE_MASK);  
17. /*分配vmap_area结构*/
18. sizeof(struct
19.             gfp_mask & GFP_RECLAIM_MASK, node);  
20. if
21. return
22.   
23. retry:  
24.     addr = ALIGN(vstart, align);  
25.   
26.     spin_lock(&vmap_area_lock);  
27. if
28. goto
29.   
30. /* XXX: could have a last_hole cache */
31.     n = vmap_area_root.rb_node;  
32. if
33. struct
34.   
35. do
36. struct
37. struct
38. if
39. if
40.                     first = tmp;  
41.                 n = n->rb_left;  
42. else
43.                 first = tmp;  
44.                 n = n->rb_right;  
45.             }  
46. while
47.   
48. if (!first)/*为最左的孩子,也就是比现有的都小*/
49. goto
50.   
51. if
52.             n = rb_next(&first->rb_node);  
53. if
54. struct
55. else/*next为空*/
56. goto found;/*为找到的节点的下一个,也就是比找到的大*/
57.         }  
58. /*当上面没有满足要求时,重新配置addr,也就是起始
59.         地址*/
60. while
61. /*重新配置起始地址*/
62. if
63. goto
64.   
65.             n = rb_next(&first->rb_node);  
66. if
67. struct
68. else
69. goto found;/*此时应该插入到找到的节点的右边*/
70.         }  
71.     }  
72. found:  
73. if
74. overflow:  
75.         spin_unlock(&vmap_area_lock);  
76. if
77.             purge_vmap_area_lazy();  
78.             purged = 1;  
79. goto
80.         }  
81. if
82.             printk(KERN_WARNING  
83. "vmap allocation for size %lu failed: "
84. "use vmalloc=<size> to increase size.\n", size);  
85.         kfree(va);  
86. return
87.     }  
88.   
89.     BUG_ON(addr & (align-1));  
90. /*初始化va*/
91.     va->va_start = addr;  
92.     va->va_end = addr + size;  
93.     va->flags = 0;  
94. /*插入到红黑树*/
95.     __insert_vmap_area(va);  
96.     spin_unlock(&vmap_area_lock);  
97.   
98. return
99. }

[cpp] view plain copy print ?

1. static void insert_vmalloc_vm(struct vm_struct *vm, struct
2. long flags, void
3. {  
4. struct
5. /*初始化vm*/
6.     vm->flags = flags;  
7. void
8.     vm->size = va->va_end - va->va_start;  
9.     vm->caller = caller;  
10. private
11.     va->flags |= VM_VM_AREA;  
12.   
13.     write_lock(&vmlist_lock);  
14. /*寻找插入位置*/
15. for
16. if
17. break;  
18.     }  
19. /*插入工作*/
20.     vm->next = *p;  
21.     *p = vm;  
22.     write_unlock(&vmlist_lock);  
23. }

初步总结了高端内存非连续区的管理框架,后面将总结他的分配和释放工作。