Linux物理内存的管理

Linux物理页框的描述
为了管理和描述物理页框,Linux在文件/include/linux/mm.h中定义了page结构:

struct page {
    page_flags_t flags;        /* 页的一些状态标志*/
    atomic_t _count;        /* 计数器,记录了访问本页框的程序数 */
        atomic_t _mapcount;        /* mm映射的的pte计数 */    
    unsigned long private;        /* 映射私有数据 */
    struct address_space *mapping;    /* 以树节点表示的地址空间 */
    pgoff_t index;        /* 页面在文件中的序号 */
    void *freelist;        /* SLUB: freelist req. slab lock */
    struct list_head lru;    
    
#if defined(WANT_PAGE_VIRTUAL)
    void *virtual;            /* 页框的虚拟地址(若没有映射,为空) */
#endif 
};
在Linux中,每一个物理页框都对应一个page类型数据,所以这个page类型数据可以看作是物理页框的控制块(身份证)。Linux把系统中所有物理页框的page类型数据组织成一个数组mem_map[],其下标为物理页框码,这个页框码就把物理页框和它的page数据关联了起来。

页表中的页框码与物理页框和mem_map[]数组元素之间的关系如下图所示:

也就可以看出,页表中的页框码有两个作用:

通过页框码可以找到物理页框的page数据,从而可以了解该对应页框的相关属性;
页码框在低位补上0x000之后就是物理页框的指针,进而可以找到物理页框。
为了补上0x000呢?

我们之前说过不考虑页表结构的话,虚拟地址和物理地址都可以分成两个部分:一个是页码(页框码);另一个是偏移量。两者的映射关系:页码可以经过快表--页表找到页框码,而偏移量两者是一样的。偏移量的范围是由页框的大小定的,4KB也就是12位,化作16进制就是从000-FFF。也就是说物理页框的指针为0x000,页框末尾的指针为0xFFF。

物理页框的分配与回收
虚拟内存中的代码和数据必须复制到物理内存中才能被执行,物理内存才是映射到虚拟内存的程序真正的活动舞台。也就是说,虚拟内存体现的是程序对内存资源的需求,而物理内存是对该请求的供应。

程序的虚拟内存空间一般比物理内存空间大得多。在这样的情况下,在物理内存的管理方面就要使用所谓的“交换”技术,把程序不再使用或暂时不用的页面“换出”到辅存,以腾出必要的物理内存空间来“换入”急需的页面。所以,换页的质量对系统的总体性能影响极大。

从一个物理页面框换出页面的做法叫做物理内存的回收;从辅存中把一个虚存页面换入物理页框的叫做分配。

物理页框的分配
在计算机技术中,物理内存中未被占用的物理页框叫做空闲页框,或者叫做空闲页。

空闲页的管理在物理页框的管理中处于一个至关重要的地位,对这项管理工作主要的要求就是能否尽量少地出现零散、不连续的空闲页。系统运行一段时间经过频繁的页交换之后,使物理内存中空闲页面框的分布变得很分散、零碎,即出现大量的“片外碎片”。这些碎片的总容量可能足够大,但在空间上不连续。因此,尽管碎片的总容量足够大,但在系统需要一个若干页框连续的空闲空间时就会得不到满足。

解决上述问题的办法之一就是,在分配页框之初就减少分配的盲目性,尽可能按照所需页的数目大小分配给它相匹配数量的页框。这样,就需要系统在进行页框分配之前,对内存中具有不同长度的连续空闲页框进行统计和记录。Linux系统采用了著名的“伙伴算法”。

伙伴算法的思想是:把内存中连续的空闲页框空间看成是空闲页框块,并按照它们的大小(连续页框的数目)分成10组:

第0组:每个元素都代表一个由2^0个页框组成的空闲页框块;
第1组:每个元素都代表一个由2^1个页框组成的空闲页框块;
第2组:每个元素都代表一个由2^2个页框组成的空闲页框块;
第3组:每个元素都代表一个由2^3个页框组成的空闲页框块;
……
第9组:每个元素都代表一个由2^9个页框组成的空闲页框块.
每组中的空闲页框块用其自身的page结构链接为双向链表;然后使结构free_area_struct的next和prev分别作为各个链表的头、尾指针;最后把各组的free_area_struct结构以组的序号为下标保存在数组free_area[]中。

结构free_area_struct定义如下:

struct free_area_struct {
    struct page *next;
    struct page *prev;
    unsigned int *map;
};
该组织的结构图如下图所示:

free_area_struct类型的数组free_area[]的每一个元素都有三个指针:next和prev指向空闲页框块的链表;map指向记录页框块分配情况的位图。图中,空闲页框块链表中的数字表示页框块的起始页框码。

物理内存中的空闲页框用伙伴结构管理起来之后,再分配页框时就可以在上述10个组中选择与页请求最匹配的空闲页框了,从而提高了内存空间的分配效率。

内核函数__get_free_pages()用于物理页框的分配,其原型如下:

unsigned long __get_free_pages(int gfp_mask, unsigned long order)
{
    struct page * page;
    page = alloc_pages(gfp_mask, order);
    if (!page)
        return 0;
    return (unsigned long) page_address(page);
}
当有页框请求出现时,分配算法首先按最佳匹配到对应的组去找合适的连续空闲页框。如果没有这样大小的连续空闲页框,则它到两倍于请求大小的连续空闲页框组中去搜索。这个过程一直将持续到free_area被搜索完或找到满足要求的连续空闲页框为止。如果找到的连续空闲页框比请求的大,则按请求的大小为其分配页框,而剩余的空闲页框则在对其进行分割之后链入低一级的连续空闲页框的链表。由于块大小都是2的次幂,所以分割起来十分简单。

物理页框的回收
系统经过一段页框分配,尽管有伙伴算法的帮助,但分配就是化整为零的过程,还是会产生碎片。因此,系统必须有一个把零碎的页框聚拢成大片的机制,Linux仍然是采用伙伴算法来实现这个机制,也就是说,是用伙伴算法的逆运算来进行物理页框的回收工作的。

用于页框回收工作的函数是free_pages(),其原型如下:

void free_pages(unsigned long addr, unsigned long order)
{
    if (addr != 0) {
        VM_BUG_ON(!virt_addr_valid((void *)addr));
        __free_pages(virt_to_page((void *)addr), order);
    }
}
将大的页面块打碎进行分配将增加系统中零碎空闲块的数目。页面回收代码在适当的时机下要将这些页面结合起来形成单一大页面块。事实上,页面块大小决定了页面重新组合的难易程度。

当页面块被释放时,代码将检查是否有相同大小的相邻或者buddy内存块存在。如果有,则将它们结合起来形成一个大小为原来两倍的新空闲块。每次结合完之后,代码还要检查是否可以继续合并成更大的页面。最佳情况是系统的空闲页面块将与允许分配的最大内存一样大。

守护程序
Linux在内核中还设置了一个“守护进程”kswapd,用它来定期检查并预先将若干页面换出,以腾出必要的物理内存空间来避免在处理器忙碌时,临时搜寻物理页框进行换入/换出。

其实,从原理上来看,kswapd实质上是一个程序,它有自己的程序控制快,同其他程序一样受内核的调度,所以可以让它在系统相对空闲时来运行。

发布了14 篇原创文章 · 获赞 21 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/wuyongmao/article/details/102986208