linux 内存管理---页框回收(十)

  • 为什么需要页回收?
linux的设计哲学之一:尽可能多的使用内存,比如尽可能的多使用memory cache,disk cache,因为这在系统负载比较小时,能够提升系统性能,但是随着cache越来越多,这迟早会消耗完所有可用的内存,所以只有在系统内存不足时才会触发页回收。

  • 触发页回收的时机
  1. 内存紧缺
grow_buffers(),alloc_page_buffers(),_ _alloc_pages()等函数分配内存失败后,调用try_to_free_pages()函数直接回收页框
  1. suspend-to-disk
  2. 周期性 (vmscan.c)
kswapd内核线程周期性的检查各zone中watermark的情况


  • 哪些页可以被回收?
只有分配给用户态地址空间的页,disk caches和memory caches页才是可以回收的。

  • 页是如何被回收的?
首先通过一定的算法选出要回收的页,然后找到要回收的页映射的所有线性地址,即找到所有的包含要回收也得页表项(这就是逆向映射),然后修改所有的页表项,将要回收的页放入伙伴系统的freelist中。

  • Reverse Mapping (Rmap.c)
逆向映射,或叫反向映射,即如何通过页描述符,找到哪些页表映射了该页,page->_mapcount>0, 说明该页被多个页表所映射,但究竟是哪些页表,该怎么得到呢?

首先对页进行分类:不可回收页,可交换页,可同步页,可丢弃页
映射页:指该页映射了文件的某一部分,比如基于文件内存映射的用户态地址空间中的所有页都是映射页,页高速缓存中的页也都是映射页。映射页差不多都是可同步的,可回收的。
匿名页: 指属于某一进程的某个匿名线性区,匿名线性区是指该线性区没有与之对应的文件,比如用户态的堆和栈都为匿名线性区,为回收页框, 内核必须将页中内容保存到一个专门的磁盘分区或磁盘文件,叫做“交换区“.因此,所有匿名页都是可交换的.

页描述符字段:
page->_mapcount: 页映射的次数,即出现在页表中的次数, -1表示该页没有被映射(可能为高端内存),0表示被映射一次

1.匿名页逆向映射
匿名页描述符中的mapping字段指向anon_vma数据结构,anon_vma形成的双向链表指出映射该页的所有线性区

static inline unsigned long
vma_address(struct page *page, struct vm_area_struct *vma)
{
    pgoff_t pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT);
    unsigned long address;

    address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
    if (unlikely(address < vma->vm_start || address >= vma->vm_end)) {
        /* page should be within any vma from prio_tree_next */
        BUG_ON(!PageAnon(page));
        return -EFAULT;
    }
    return address;
}
通过页描述符可以得到对应线性区的该页的对应线性地址,通过线性地址就很容易得到页表项了。
static int try_to_unmap_anon(struct page *page)
{
    struct anon_vma *anon_vma;
    struct vm_area_struct *vma;
    int ret = SWAP_AGAIN;

    anon_vma = page_lock_anon_vma(page); //page->mapping
    if (!anon_vma)
        return ret;

    list_for_each_entry(vma, &anon_vma->head, anon_vma_node) {   //得到所有的线性区
        ret = try_to_unmap_one(page, vma);
        if (ret == SWAP_FAIL || !page_mapped(page))
            break;
    }
    spin_unlock(&anon_vma->lock);
    return ret;
}

2.文件映射的页逆向映射
文件映射页描述符中的mapping字段指向address_space数据结构
它的思路也和匿名页逆向映射相似,首先指出哪些线性区包含了给定页,然后通过线性区找出对应的页表项。但是和匿名映射不同的是,文件映射的页被共享的进程更多,比如标准c库,几乎被所有进程共享,所以包含给定页的线性区链表就会很长,所以文件映射的逆向映射采用了不同的实现方式:priority search trees, quickly locate all the
memory regions that refer to the same page frame.

每个文件对应个优先搜索树,它存放在address_space 对象的i_mmap 字段中:
struct address_space {
    struct inode        *host;        /* owner: inode, block_device */
    struct radix_tree_root    page_tree;    /* radix tree of all pages */
    ...
    struct prio_tree_root    i_mmap;        /* tree of private and shared mappings */
    ...
}

prio_tree_node 数据结构表示的个PST 节点,该数据结构在每个线符的shared.prio_tree_node字段中
static int try_to_unmap_file(struct page *page)
{
    struct address_space *mapping = page->mapping;
    pgoff_t pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT);
    struct vm_area_struct *vma;
    struct prio_tree_iter iter;
    int ret = SWAP_AGAIN;
    unsigned long cursor;
    unsigned long max_nl_cursor = 0;
    unsigned long max_nl_size = 0;
    unsigned int mapcount;

    spin_lock(&mapping->i_mmap_lock);
    vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff) {   //page->mapping->i_mmap存放搜索树的根,得到树上的每个节点,然后container of()得到包含节点的线性区
        ret = try_to_unmap_one(page, vma);
        if (ret == SWAP_FAIL || !page_mapped(page))
            goto out;
    }
    ...
}

  • 页回收算法
linux采用LRU,即least recently used算法获得要回收的页,如果页属LRU 链表,则设置页描述符中的PG_lru标志.此外,如果页属于活动链表,则设置PG__active 标志.而如果页属于非活动链表,则清PG_act ive标志.页描述符的lru 字段存放指向LRU 链表中下个元素和前一个元素的指针,active_list和inactive_list统称为lru list.当从zone中分配页时,只有可回收的页才会放在zone描述符的active_list和inactive_list链表中:
  1. 当按需装入进程的匿名页时,do_anonymous_page ( )函数执行
  2. 按需装入内存映射文件的个页时,由filemap_nopage ()函数执
  3. 当按需装入IPC 共享内存区的一个页时shme凡nopage () 函数执行
  4. 从文件读取数据页时,由do_generic_file_read () 函数执行
  5. 当换入个页时,do_swap_page ( 函数执
  6. 当在页高速缓存中搜索一个缓冲区页时, __f ind_get_block {) 函数

在上述这些函数中将新分配到的页通过调用lru_cache_add ( )加入inactive_list或调用lru_cache_add_active()加入active_list中。

也就是说要回收的页都是从active_list和inactive_list链表这两个链表中得到的。

页在inactive_list和active_list之间的移动:
根据 least recently used,页在active_list和inactive_list之间进行移动, 在页描述符中的PG_referenced标志用来把一个页从非活动链表移到活动链表所需的访问次数加倍,也把一个页从活动链表移到非活动链表所需的“丢失访问”次数加倍.例如,假定在非活动链表中的一个页其PG_re ferenced 标志置为0. 第次访问把这个标志置为I ,但是这一页仍然留在非活动链表中.第二次对该页访问时发现这一标志被设置, 才把页移到活动链表。但是,如果第次访问之后在给定的时间间隔内第二次访问没有发生,那么页框回收算法就可能重置PG__referenced 标志。

当内核必须把一个页标记为访问过时 就调用mark_page_accessed()数。

PFRA 扫描一页调用一次page_referenced()函数,如果PG_referenced标志或页表项中的某些Accessed标志位置位.则该函数返回1,否则返回0。该函数首先检查页描述符的PG_referenced标志。如果标志置位则清0。

从活动链表到非活动链表移动页不是由page_referenced()函数,而是由refill_inactive_zone()函数实施的。
refill_inactive_zone() 函数由shrink_zone()调用.而shrink_zone ()函数对页高速缓存和用户态地址空间进行页回收,refill_inactive_zone() 函数的工作至关重要,因为,从活动链表将页移到非活动链
表就意味着页迟早要被PFRA 捕获。如果函数的掠夺性过强,就会有过多的页从活动链表被移动到非活动链表.因此, PFRA 就会回收大量的页框,系统性能会受到影响.反过来,如果函数太懒惰 就没有足够的采用页来补充非活动链表, PFRA 就不能回收内存。为此,该函数可以调整自己的行为:开始时.对每次调用,扫描非活动链表中少量的页,但是PFRA 很难回收内存时,refill_inactive_zone()在每次调用时就逐渐增加扫描的活动页数, scan_control 数据结构中priority字段的值控制该函数的行(低值表示更紧迫的优先级)。

LRU 链表中有两类页:属于用户态地址空间的页、不属于任何用户态进程且在页高速缓存中的页.PFRA 倾向于压缩页高速缓存,而将用户态进程的页留RAM 中.然而,每一种策略中都没有个固定的黄金法则保证在每个场景中系统的高性能,所以refill_inactive_zone() 函数使用交换倾向( swap tendency 经验值,由它确定函数是移动所有的页还是只移动不属于用户态地址空间的页。函数按如下公式计算交换倾向值:
                        交换倾向值=映射比率/2 +负荷值+交换值
映射比率( mapped ratio ) 是用户态地址空间所有内存管理区的页(sc->nr_mapped)占所有可分配页框数的百分比。mapped_ratio 的值大表示动态内存大部分用于用户态进程.而值小则表示大部分用于页高速缓存.
                        mapped_ratio = (sc->nr_mapped * 100) / total_memory;
负荷值( distress ) 用于表示PFRA 在管理区中回收页框的效率, 其依据是前次PFRA运行时管理区的扫描优先级,这个优先级存放在管理区描述符的prev_priority 字段
                        distress = 100 >> zone->prev_priority;
最后,交换值( swappiness ) 是个用户定义常数.默认值为60. 系统管理员可以在/proc/sys/vm/swappiness 文件内修改这个值,或用相应的sysctl ()系统调用调整这个值。

只有当管理区交换倾向值大于等于1 00 时.页才从进程地址空间回收.那么当系统管理员将交换值设为0 时, PFRA 就不会从用户态地址空间回收页,除非管理区的前一次先级为0 (这不大可能发生).如果系统管理员将交换值设为1 00 ,那么PFRA 每次调用该函数时都会从用户态地址空间回收页.
if (swap_tendency >= 100)
        reclaim_mapped = 1;

    while (!list_empty(&l_hold)) {
        cond_resched();
        page = lru_to_page(&l_hold);
        list_del(&page->lru);
        if (page_mapped(page)) {
            if (!reclaim_mapped ||
                (total_swap_pages == 0 && PageAnon(page)) ||
                page_referenced(page, 0, sc->priority <= 0)) {
                list_add(&page->lru, &l_active);
                continue;
            }
        }
        list_add(&page->lru, &l_inactive);
    }

try_to_free_pages() shrink_cache( ),这些函数目的是找到要回收的页, shrink_list() 函数才是真正执行页回收,将page list放入伙伴系统中。

shrink_slab()函数用于释放obj cache到slab分配器中,比如dentry cache, inode cache
cache_reap()函数用于释放slab分配器中空闲的slab,即归还slab占据的页到伙伴系统中


  • OOM(out of memory)
当空闲内存十分紧缺且PFRA 又无法成功回收任何页时,__ alloc_pages()调用out_of_memory()函数删除程序,该过程通过select_bad_process()选择系统中的一个进程,然后调用oom_kill_process()强行删除它并释放占用的页框


  • Swapping
Swapping has been introduced to offer a backup on disk for unmapped pages. 交换是页框回收的个最高级特性。如果我们要确保进程的所有页框都能被PFRA随意回收,而不仅仅是回收有磁盘映像的页,那么就必须使用交换。
• Pages that belong to an anonymous memory region of a process (User Mode
stack or heap)
• Dirty pages that belong to a private memory mapping of a process
• Pages that belong to an IPC shared memory region

我们知道每个页表项包含个Present轩u志。内核利用这个标志来通知属于某个进程地址空间的页已披换出。在这个标志之外, Linux还利用页表中的其他位存放换出页标识符(swapped-out page identifier ) 。该标识符用于编码换出页在磁盘上的位置.当缺页异常发生时,相应的异常处理程序可以检测到该页不在RAM 中,然后调用函数从磁盘换入该缺页。

交换区
从内存中换出的页存放在交换区( swap area ) 中.交换的实现可以使用自己的磁盘分区,也可以使用包含在大型分区中的文件.可以定义几种不同的交换区,最大个数由MAX_ SWAPFILES 宏( 通常被设置成32 )确定.

每个交换区都由组页槽( page slot ) 组成.也就是说.由组4096 字节大小的块组成,每块中包含个换出的页.交换区的第一个页槽用来永久存放有关交换区的信息,其格式由swap_header 联合体(由两个结构info和magic 组成)来描述. magic 结构提供了个字符串,用来把磁盘某部分明确地标记成交换区,它只含有个字段magic.magic ,这个字段含有一个10 字符的 mag i c ”字符串. magic 结构从根本上允许内核明确地把个文件或分区标记成交换区.这个字符串的内容就是“SWAP-SPACE ”orSWAPSPACE2”. 该字段通常位于第一个页懵的末尾。
union swap_header {
    struct {
        char reserved[PAGE_SIZE - 10];
        char magic[10];            /* SWAP-SPACE or SWAPSPACE2 */
    } magic;
    struct {
        char         bootbits[1024];    /* Space for disklabel etc. */
        unsigned int version;
        unsigned int last_page;
        unsigned int nr_badpages;
        unsigned int padding[125];
        unsigned int badpages[1];
    } info;
};

交换区描述符
每个活动的交换区在内存中都有自己的swap_info_struct描述符:
struct swap_info_struct {
    unsigned int flags;                //交换区标志
    spinlock_t sdev_lock;
    struct file *swap_file;            //指针.指向存放交挨区的普通文件或设备文件的文件对象
    struct block_device *bdev;   //存放交换区的块设备描述符
    struct list_head extent_list;   //组成交换区的子区链袤的头部
    int nr_extents;                      //组成交焕区的子区数量
    struct swap_extent *curr_swap_extent;       //指向最近使用的子区描述符的指针
    unsigned old_block_size;                 //存放交换区的磁盘分区自然块大小
    unsigned short * swap_map;   //指向计数器数组的指针,交换区的每个页槽对应个数组元素
    unsigned int lowest_bit;       //在搜索个空闲页槽时要扫描的第一个页槽
    unsigned int highest_bit;
    unsigned int cluster_next;
    unsigned int cluster_nr;
    int prio;            /* swap priority */          // 交换区优先级
    int pages;
    unsigned long max;
    unsigned long inuse_pages;
    int next;            /* next entry on swap list */
}
flags 字段:
SWP USED:如果交换区是活动的,该值就是1,如果交换区不是活动的,该值就是0.
SWP_WRITEOK:如果可以写入交换区.该值就是1; 如果交换区只读,该值就是0 (可以是活动的或不是活动的)。
SWP_ACTIVE:这个两位的字段实际上是SWP_USED 和SWP_WRITEOK 的组合。如果前面两个标志置位,那么SWP_ACTIVE标志置位.

swap_map字段指向个计数器数组,交换区的每个页槽对应一个元素.如果计数器值等于0 ,那么这个页情就是空闲的;如果计数器为正数.那么换出页就填充了这个页槽.实际上,页槽计数器的值就表示共享换出页的进程数.

prio字段是一个有符号的整数,表示交换子系统依据这个值考虑每个交换区的次序。

swap_info数组包括MAX _SWAPFI LES 个交换区描述符.只有那些设置了SW P_ USED的交换区才被使用,因为它们是活动区域.


换出页标识符
可以很简单地而又唯地标识个换出页.这是通过在swap_info数组中指定交换区的索引和在交换区内指定页槽的索引实现的.
换出页标识符的格式如下:

swp_entry ( type,offfset)宏负责从交换区索引type 和页槽索引offset中构造换出页标识符。swp_typeswp_offset 宏正好相反,它们分别从换出页标识符中提取出交换区索引和页槽索引.

页被换出时,其标识符就作为页的表项插入页表中,这样在需要时就可以再找到这个页。要注意这种标识符的最低位与Present 标志对应,通常被清除来说明该页目前不在RAM 中。

激活和禁用交换区
旦交换区被初始化,超级用户(或者更确切地说是任何具有CAP_ SYS_ADM I N权能的用户 就可以分别使用swapon 和swapoff 程序激活和禁用交换区 这两个程序分别使用了swapon()
swapoff () 系统调用

swap cache
Transferring pages to and from a swap area is an activity that can induce many race conditions. 换入换出页到交换区会引起很多条件竞争

The swap cache is implemented by the page cache data structures and procedures, a single swapper_space address space is used for all pages in the swap cache,
so a single radix tree pointed to by swapper_space.page_tree addresses the pages in the swap cache. The nrpages field of the swapper_space address space stores the number of pages contained in the swap cache.

swap cache实际上就是一种特殊的page cache

zRAM
嵌入式系统受flash限制,很少使用swap分区,一般都swapoff。所以嵌入式系统引入zRAM技术。
zRAM直接把一块内存模拟成一个硬盘分区,当作swap分区使用,此分区自带透明压缩功能,当匿名页向zRAM分区写时,Linux内核使CPU自动对匿名页进行压缩。接下来,当应用程序又执行到刚才的匿名页时,由于此页已经被swap到zRAM中,内存中没有命中,页表也没有命中,所以此时再去访问这块内存时再次发生page fault,Linux就从zRAM分区中将匿名页透明的解压出来还到内存中。
zRAM的特点是用内存来做swap分区,透明压(两页匿名页有可能被压缩成一页),透明解(一页解压成两页),这样其实相当于扩大了内存,但会多损耗一些CPU。

参考资料:
《understanding the linux kernel 3rd edition》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值