Linux--内存管理

每个Linux进程都有一个地址空间,逻辑上有三段组成:代码、数据和堆栈段。其中堆的生长方式向上,栈与之相反,这样能保证最大限度的利用地址空间,提高空间的利用率。除了动态分配内存外,Linux的进程可以通过内存映射文件来访问文件数据,即将文件映射到内存空间的一部分从而使文件可以像位于内存中的字节数组一样被读写,举个不太恰当的例子来说明,相当于将指向文件的指针传入内存空间,进程可以通过这个指针找到这个文件,本质上就是将值传递改成了地址传递。采取这种方式可以使多个进程同时映射相同的文件。

Linux的内存有三部分组成。前两部分是内核和内存映射,被“钉”在内存中(从不换出),其它部分被划分成页框。Linux维护一个页描述数组,称为mem_map,其中页描述符是page类型的,而且每个物理页框都有一个页描述符,每个页描述符的大小是32字节,对于4kb的页框而言实际上只消耗不到1%的物理内存。每个描述符都有一个指针,在页面非空闲时时指向它所属的地址空间,在空闲时则与其它空闲页面的空闲页面组成双向链表,以此来记录所有的空闲页框和一些其它的域。

物理内存被分为区域,同时为每个区域维护一个区域描述符。区域描述符包含了本区域的详细信息,包括活动和非活动的数目和一些与页面置换算法有关的高低水位。同时区域描述符也包含一个空闲区数组,第i个元素标记了2^i个空闲页的第一个块的第一个页描述符,有上述可知,页描述符的指针将这些空闲页面链接起来,可以很方便的访问一个页面的空闲区。

最后,Linux可以移植到NUMA体系结构(不同的内存地址有不同的访问时间),为了区分不同节点上的物理内存,使用一个节点描述符,每个节点描述符包含了具体信息。

为了使分页机制在32位和64位体系结构中高效工作,linux采用了四级分页机制,分别由全局目录,上级目录,中间目录和页面组成。

Linux的内存分配机制则使用了伙伴算法。举例来讲,假如有64个页面,如果有一个5页面的需求到来,那么整个内存块被一分为二,成为两个32个页面的内存块,32个页面还是太大了,于是其中一个32页面的内存块再次一分为二,但还是过大,于是再分,变成8个页面的内存块,于是将其分配给请求者。但这种算法会导致大量的内存碎片,为了缓解这个问题(只是缓解,并不能解决),linux有一个内存分配器,slab分配器。它使用伙伴算法获得内存块,但是之后从中切出slab(更小的单元)并且分别进行管理。因为内存频繁的创建和销毁一定类型的对象,它使用了对象缓存,而这些对象缓存由指向一个或多个slab的指针组成。例如当内存需要分配一个新的进程描述符时,会试图寻找一个部分满的slab,如果没有,则找空闲的。如有必要,会分配一个新的slab。

linux的虚拟地址空间被分割成同构连续页面对齐的区域,即每个区域由一系列 连续的 具有相同保护和分页属性组成。代码段和映射文件就是区的例子。区与区之间可以有空隙,这些空隙不可引用。每个区则由vm_area_struct项描述,该项列出了该区的属性,包括保护模式,生长方向等,同时也记录了该区是私有的还是共享的,当创建一个子进程后,linux为子进程复制一份区链表,但页表是同样的,区被标记为可读可写,但页是只读的,当任一进程试图写页时,会产生保护故障,如果逻辑上是可写的,便复制一个页面给该进程同时将该页面的副本标记为可读可写,这个机制便是写时复制。一个进程的所有vm_area_struct用一个链表链接在一起,并按照地址顺序排序,当链表过长时,则创建树加速搜索。

linux的分页机制的基本思想则是:进程运行并不需要完全在内存中,只需要用户结构和页表在内存中即可,就可以调度运行,页面只有在需要的时候才在分页设备或者分区上被分配。页面置换算法则是linux先保存一些空闲页面,在需要的时候直接分配。PFRA(页框回收算法)为每个页面维护两个标记:活动/非活动和是否被引用。这两个标记构成四种状态。PFRA则根据这四种状态进行分配

猜你喜欢

转载自blog.csdn.net/u012021393/article/details/71915711