免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4491 | 回复: 8
打印 上一主题 下一主题

[内存管理] 参考牛图阐述自己对内存管理的理解,请指点 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-07-21 18:17 |只看该作者 |倒序浏览
本帖最后由 harvey_perfect 于 2012-07-21 18:52 编辑

Aryang画的内存管理的图片太棒了,思路清晰,色彩搭配还非常漂亮。
下面根据Aryang 的第三版内存管理图阐述一下自己的理解,请各位达人给与指点。
Aryang 画的第三版内存管理图连接:http://bbs.chinaunix.net/thread-2018659-2-1.html
本想接在Aryang的帖子后面,只是帖子太长了,为了大家指点方便,在此重新开贴,抛砖引玉。



1、处理器硬件辅助的虚实地址转换(参考图中的A区)
在x86中虚实地址转换分为段式转换和页转换。段转换过程是由逻辑地址(或称为虚拟地址)转换为线性地址;页转换过程则是将线性地址转换为物理地址。在linux中只使用了4个全局描述符表,内核空间和用户空间分别两个gdt,分别对应各自的代码段和数据段。也可以认为在linux中变相地disable了x86的段式转换功能。
页转换过程请参考图中A区部分。
在32位系统中,处理器能访问的地址空间为4Gbyte。Linux系统中把4G的空间划分为两部分,0—3G为进程的用户空间,每个进程的用户空间都是独立的,参见A区进程页目录项中的嫩绿色部分;3G—4G的空间为内核空间,为所有进程共享,参见A区进程页目录项中的淡紫色部分。内核区域对应的页表项U/S(用户/超级用户标志)被设置为0,即只有超级用户可以访问,即只有在内核态才可以访问页表项。从而防止用户态访问3—4G的空间。
在linux中x86 的cr3寄存器(页表基地址寄存器)保存在进程的上下文中,在进程切换时会保存或回复该寄存器的内容,这样每个进程都有自己的转换页表,从而保证了每个进程有自己的虚拟空间。

2、内存的zone分区(见图中B区)
内存被分为多个块,称为zones,它表示内存中一段区域。一个zone用struct zone结构描述,zone的类型主要有ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。ZONE_DMA位于低端的内存空间,用于某些旧的ISA设备。ZONE_NORMAL的内存直接映射到Linux内核线性地址空间的部分,所以称为直接映射区,ZONE_HIGHMEM位于物理地址高于896MB的区域。

内核空间只有1GB线性地址,如果使用大于1GB的物理内存就没法直接映射到内核线性空间了。实际上,为了映射高端内存,从1GB中留出了128MB空间,所以当系统中的内存大于896MB时,把内核线性空间分为两部分,内核中低于896MB线性地址空间直接映射到低896MB的物理地址空间;高于896MB的128MB内核线性空间用于动态映射ZONE_HIGHMEM内存区域(即物理地址高于896MB的物理空间)。



3、Buddy伙伴系统(见图中B区)
每个zone区域都采用伙伴系统(buddy system)来管理空闲内存页面。把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续的页框。链表编号分别为0,1,2,3,… k… 10,即连续页面数为2^k个。
从buddy system中申请页面过程:
1、        根据申请存储区域大小查找对应的编号为K的块链表。
2、        如果编号K的链表为空,则向编号为k+1的链表申请一个存储区域。如果编号为k+1链表不为空,系统从编号为k+1的链表上拆下一个区域,并将拆下的区域分为两个2^k的区域,一个返还给申请者,另一个则挂到编号为k的链表。
3、        如果编号为k+1的链表也为空,编号为k+2的链表不为空。则从k+2的链表中拆下一个区域变为两个2^(k+1)区域,一个挂到编号为k+1的链表上,把另一个拆为两个2^k的区域,一个返还给申请者,把另一个挂到编号为k的链表上。
4、        如果k+2的链表也为空,则一直向上迭代,直到编号为10的链表为止,如果编号为10的链表还为空,则申请失败。
向buddy system中释放页面过程:
在向buddy system 释放页面时,总会检测释放的页面和链表中其他页面是否可以组成一个更大一级的页面,如果可以组成,则把这两个区域组成一个并挂到更高一级的链表中。这个过程是迭代的,释放过程会一层层向上找伙伴,然后合并成更大的,再向上找伙伴,实在找不到了就停止了!
疑问:按照上面的说法,是否会出现这种情况,在释放某个页面导致所有页面都组成了标号为10的连续页面了。等到再需要分配1个页面时,又要一级一级地拆分。这样的话效率是否很低??
是否在buddy system 每个链表结构中设一个门限值会更好?释放时标记一下可以组成buddy的两个连续区域,只有该级空闲的区域个数超过门限后才组成buddy并挂到上一级链表上。当然,这个门限值可以由内核根据目前总的空闲页面数量进行动态调整。
4、Slab系统(见图中B区)
引入buddy系统是为了减少页外碎片的,而引入slab系统是为了减少业内碎片的。
可以把slab看做批发商,slab从buddy中一次批发n个页面,并把每个页面划分成若干小的对象object。内核模块以object为单位从slab中申请或释放小块内存。
在内存初始化过程中会创建一个slab的 cache_chain,这是一个 slab 缓存的链接列表。可以用来查找最适合所需要的分配大小的缓存。cache_chain 的每个元素都是一个 kmem_cache 结构的引用。一个kmem_cache中的所有object大小都相同。

5、内核空间分类(见图中C区)
内核空间分为直接映射区和高端映射区,其中高端映射区又分为动态映射区、KMAP区和固定映射区,如下表所示。
直接映射区        动态映射区        KMAP区        固定映射区
896(max)        120(min)        4M        4M
调用kmalloc()函数可以从直接映射区申请指定大小的内存区域,对应的释放内存函数为kfree()。如果申请的空间较小,会根据申请空间的大小从slab中获取;如果申请的空间较大,如超过一个页面,会直接从buddy系统中获取。调用kmalloc()成功申请到多个页面的空间时,这些页面的物理地址是连续的。
        调用vmalloc()函数可以从动态映射区申请指定大小的内存区域,即其虚拟地址处于动态映射区,对应的释放内存函数为vfree()。另外,调用vmalloc()函数通常从高端内存区申请页面,并且用vmalloc()申请的内存区域的物理地址可能是不连续的。
        用函数alloc_pages(_GFP_HIGHMEM)申请的若干高端内存页面,再用函数kmap()映射到内核空间(即分配内核空间虚拟地址),就会映射到KMAP区,KMAP区只有4MB,所以不用时需要及时释放。用kmap()函数分配的内核虚拟地址并非一定会落在KMAP区,跟踪kmap()函数的实现就可以发现,只用从高端内存区获取的内存才会分配KMAP区的虚拟地址,否则会返回页面对应的直接映射区的虚拟地址。

注意:
1、在调用alloc_pages()函数申请页面时,如果注明_GFP_HIGHMEM,即从高端内存区申请。则实际是优先从高端内存申请,顺序为(分配顺序是HIGH, NORMAL, DMA )。
2、高端映射区是指内核空间的最高128M的虚拟地址空间;高端内存区是指系统中物理地址大于896MB的所有物理内存。




6、单个进程的内存管理(见图中D区)
每个进程的task_struct中都有一个active_mm成员,类型为struct mm_struct,内核就是利用该成员管理进程虚拟空间的。参见数据结构task_struct,为了方便阅读,删除了该结构中无关的成员变量。
struct task_struct{
        struct mm_struct *mm, *active_mm;
        }

参考下面的数据结构定义。数据结构struct mm_struct 中的成员mm_rb指向了一棵红黑树的根,该进程的所有申请的虚拟空间都以起始虚拟地址为红黑树的key值挂到了这棵红黑树上。mm_struct 中的成员map_count指示该进程拥有的虚拟空间的个数,pgd指向该进程的页转换表。

struct mm_struct{
        struct vm_area_struct * mmap; /* list of VMAs 指向若干个VMA组成的链表 */
        struct rb_root mm_rb;               指向一棵红黑树       
        struct vm_area_struct * mmap_cache;        指向最近找到的虚拟存储区域
        int map_count;                                /* number of VMAs */  虚拟区间的个数
        pgd_t * pgd;             指向页转换表
}

数据结构struct vm_area_struct定义了一个连续的虚拟地址空间,包括起始地址和结束地址,以及红黑树节点vm_rb。内核就是以vm_start为key值把vm_rb挂到进程内存红黑树上的。
struct vm_area_struct{
        struct mm_struct * vm_mm;        /* The address space we belong to. */
        unsigned long vm_start;                /* Our start address within vm_mm. */
        unsigned long vm_end;         /* The first byte after our end address within vm_mm. */
        struct rb_node vm_rb;             这个虚拟区域对应的红黑树的节点
}
内核在给进程分配了一块虚拟地址内存块后,就将该区域挂接到进程的红黑树上,此时内核尚未给该进程分配实际的内存。在进程访问该区域时则产生缺页中断,在中断中检查访问的区域已经分配给进程后,则分配实际内存页面,并更新该进程的页转换查找表。中断返回,进程重新执行触发中断的指令,并继续运行。
当进程释放一块内存区域后,内核会立即收回分配给该区域的物理内存页面。


我对page数据结构中的虚拟地址有下面的疑问,请各位给指点,谢谢先
问题1、直接映射区域应该是在初始化阶段就配置了相关的页转换表的吗?
问题2、每个物理页面对应一个page结构。(如果问题1的答案是肯定的话)在初始化过程中会把所有的直接映射区对应的物理页page结构中虚拟地址virtual填写为(PAGE_OFFSET + phy_address ),而高端物理内存页对应page中的虚拟地址应该是null的。那么在后续的运行中page中的虚拟地址会如何改变?比如,直接映射区的页面分配给用户空间,page中的virtual会被修改吗?高端内存被分配给内核空间和用户空间时,其virtual会如何修改?如果页面用作多个进程间的共享内存时,其virtual的数值应该如何修改?










论坛徽章:
2
CU大牛徽章
日期:2013-04-17 11:46:28CU大牛徽章
日期:2013-04-17 11:46:39
2 [报告]
发表于 2012-07-21 18:28 |只看该作者
学习了,谢谢共享!

论坛徽章:
0
3 [报告]
发表于 2012-07-23 21:15 |只看该作者
回复 1# harvey_perfect
问题1、直接映射区域应该是在初始化阶段就配置了相关的页转换表的吗?
问题2、每个物理页面对应一个page结构。(如果问题1的答案是肯定的话)在初始化过程中会把所有的直接映射区对应的物理页page结构中虚拟地址virtual填写为(PAGE_OFFSET + phy_address ),而高端物理内存页对应page中的虚拟地址应该是null的。那么在后续的运行中page中的虚拟地址会如何改变?比如,直接映射区的页面分配给用户空间,page中的virtual会被修改吗?高端内存被分配给内核空间和用户空间时,其virtual会如何修改?如果页面用作多个进程间的共享内存时,其virtual的数值应该如何修改?

哪位能给指点一下?谢谢先




   

论坛徽章:
0
4 [报告]
发表于 2012-07-24 10:51 |只看该作者
说一下我的理解, 不对之处请指正。
从代码看, virtual 这一项只有在定义了 WANT_PAGE_VIRTUAL时才会被编译, 而这个宏在绝大多数体系结构下都是未定义的。所以virtual这个变量的重要性不大。
注释说它是“内核虚拟地址”:   /* Kernel virtual address (NULL if not kmapped, ie. highmem) */
但是实际编程中都是用 page_address() 来获得内核虚拟地址。

直接映射区的页面分配给用户空间,page中的virtual会被修改吗?
不会, 仍然是内核空间的虚拟地址。

高端内存被分配给内核空间和用户空间时,其virtual会如何修改?
会被修改为映射后的内核空间虚拟地址, 跟用户空间地址无关。

如果页面用作多个进程间的共享内存时,其virtual的数值应该如何修改?
不变, 仍然是内核空间的虚拟地址。
  1. /*
  2. 139         * On machines where all RAM is mapped into kernel address space,
  3. 140         * we can simply calculate the virtual address. On machines with
  4. 141         * highmem some memory is mapped into kernel virtual memory
  5. 142         * dynamically, so we need a place to store that address.
  6. 143         * Note that this field could be 16 bits on x86 ... ;)
  7. 144         *
  8. 145         * Architectures with slow multiplication can define
  9. 146         * WANT_PAGE_VIRTUAL in asm/page.h
  10. 147         */
  11. 148#if defined(WANT_PAGE_VIRTUAL)
  12. 149        void *virtual;                  /* Kernel virtual address (NULL if
  13. 150                                           not kmapped, ie. highmem) */
  14. 151#endif /* WANT_PAGE_VIRTUAL */
复制代码
另:
在宏WANT_PAGE_VIRTUAL有定义时,page_address()直接返回virtual的值:
  1. #if defined(WANT_PAGE_VIRTUAL)
  2. #define page_address(page) ((page)->virtual)
复制代码
而通常情况WANT_PAGE_VIRTUAL未定义,page_address()需要计算, 见highmem.c里面的page_address()函数。


   

论坛徽章:
0
5 [报告]
发表于 2012-07-24 20:59 |只看该作者
wwxbei 发表于 2012-07-24 10:51
说一下我的理解, 不对之处请指正。
从代码看, virtual 这一项只有在定义了 WANT_PAGE_VIRTUAL时才会被编 ...



非常感谢指点!
之前在sourceinsight 下看代码时,WANT_PAGE_VIRTUAL一直是红色的,我以为对各个处理器平台该宏定义都定义了。谢谢指点。
另外,再请教一个问题,
如果系统内存只有512MB,在初始化过程中将512MB映射到了3G--3.5G虚拟空间。
在给进程分配了一段空间后,这段内存空间在3G--3.5G虚拟空间之内的虚实映射会被取消吗?还是继续保留?
谢谢

论坛徽章:
0
6 [报告]
发表于 2012-07-25 11:33 |只看该作者
回复 5# harvey_perfect


    不会取消。一段物理内存地址可以被映射为N个虚拟地址

论坛徽章:
0
7 [报告]
发表于 2012-07-25 20:01 |只看该作者
omycle 发表于 2012-07-25 11:33
回复 5# harvey_perfect

谢谢指点!

论坛徽章:
0
8 [报告]
发表于 2012-07-25 21:14 |只看该作者
写这么多 niulegebiliti

论坛徽章:
0
9 [报告]
发表于 2012-07-26 19:30 |只看该作者
kgn28 发表于 2012-07-25 21:14
写这么多 niulegebiliti

谢谢支持:wink:
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP