linux 逆向映射机制浅析

2017-05-20

聚会回来一如既往的看了会羽毛球比赛,然后想到前几天和朋友讨论的逆向映射的问题,还是简要总结下,免得以后再忘记了!可是当我添加时间……这就有点尴尬了……520还在写技术博客……


闲话不多说,之前一个问题是想要根据物理页框号得到映射的虚拟地址,一时间不知道如何下手了,在群里和一个朋友讨论了一番,记得之前看swap机制的交换缓存时,记载说系统当要换出一个页面时,可以很容易找到使用该页面的所有进程,然后撤销映射。这一点也就成了我的突破口。经过对源码的一番研究结合相关书籍,便有了今天这篇文章。重点就是逆向映射机制。

顾名思义,有一个虚拟地址经过页面转换得到物理地址的过程为正向映射,那么根据物理地址推导虚拟地址呢?自然成了逆向映射。众所周知,Linux下每个物理页面对应一个page结构,物理页框号可以很容易的转化到page结构,不妨看下内核是怎么转化的。

#define __pfn_to_page(pfn)    (mem_map + ((pfn) - ARCH_PFN_OFFSET))
#define __page_to_pfn(page)    ((unsigned long)((page) - mem_map)+ ARCH_PFN_OFFSET)

这里有点像windows 的pfn数据库了,mem_map是一个page指针,作为pfn数据库(实际上是一个大的数组的起始),ARCH_PFN_OFFSET是物理起始地址的pfn。所以差值实际就是有效pfn。通过page转化成pfn也是同样的思路。那么这和逆向映射什么关系呢?下面要说的就是至关重要的page结构,该结构比较庞大,我们只说和逆向映射有关系的部分。

page结构中有两个字段:

struct page{
        struct address_space *mapping;
        union {
            pgoff_t index;        /* Our offset within mapping. */
            void *freelist;        /* slub/slob first free object */
            bool pfmemalloc;    /* If set by the page allocator,
                         * ALLOC_NO_WATERMARKS was set
                         * and the low watermark was not
                         * met implying that the system
                         * is under some pressure. The
                         * caller should try ensure
                         * this page is only used to
                         * free other pages.
                         */
        };
        struct {

                union {
                    /*
                     * Count of ptes mapped in
                     * mms, to show when page is
                     * mapped & limit reverse map
                     * searches.
                     *
                     * Used also for tail pages
                     * refcounting instead of
                     * _count. Tail pages cannot
                     * be mapped and keeping the
                     * tail page _count zero at
                     * all times guarantees
                     * get_page_unless_zero() will
                     * never succeed on tail
                     * pages.
                     */
                    atomic_t _mapcount;

                    struct { /* SLUB */
                        unsigned inuse:16;
                        unsigned objects:15;
                        unsigned frozen:1;
                    };
                    int units;    /* SLOB */
                };
                atomic_t _count;        /* Usage count, see below. */
            };
        };
    };

}              

其实这里想说的就三个字段,mapping,在映射匿名页面的时候指向一个anon_vma结构,在映射文件页面的时候指向inode节点的address-space;index,表示对应的虚拟页面在vma中的线性索引;_mapcount,共享该页面的进程的数目;注意该值默认是-1,当有一个进程使用时为0,所以其值表明除了当前进程还有多少进程在使用,便于撤销。了解了这三个字段,接下来就好解释多了。通过一个函数page_referenced来解释。

int page_referenced(struct page *page, int is_locked,struct mem_cgroup *memcg, unsigned long *vm_flags)

原版解释如下:Quick test_and_clear_referenced for all mappings to a page,returns the number of ptes which referenced the page.就是快速的检查并清除一个页面的所有引用(不同页表当中),返回引用这个page页面的pte数量。简单走一下流程

int page_referenced(struct page *page,
            int is_locked,
            struct mem_cgroup *memcg,
            unsigned long *vm_flags)
{
    int referenced = 0;
    int we_locked = 0;

    *vm_flags = 0;
    if (page_mapped(page) && page_rmapping(page)) {
        if (!is_locked && (!PageAnon(page) || PageKsm(page))) {
            we_locked = trylock_page(page);
            if (!we_locked) {
                referenced++;
                goto out;
            }
        }
        if (unlikely(PageKsm(page)))
            referenced += page_referenced_ksm(page, memcg,
                                vm_flags);
        else if (PageAnon(page))
            referenced += page_referenced_anon(page, memcg,
                                vm_flags);
        else if (page->mapping)
            referenced += page_referenced_file(page, memcg,
                                vm_flags);
        if (we_locked)
            unlock_page(page);

        if (page_test_and_clear_young(page_to_pfn(page)))
            referenced++;
    }
out:
    return referenced;
}

首先检查正向和逆向映射是否都存在,如果没有锁定该页面并且页面是KSM 页面或者文件映射页面,则需要trylock,如果加锁失败,则直接out.接下来就是对不同情况的处理。如果是KSM页面走page_referenced_ksm。如果是匿名映射页,走page_referenced_anon,如果是文件映射页,走page_referenced_file。KSM是内核页面共享的一种机制,主要用在KVM中,但是其他地方也可以引用,由于其需要计算页面是否相同,所以在重复率不高的场合,大部分选择关掉KSM,关于KSM在另一篇文章已经介绍。

如果是匿名映射页面,进入page_referenced_anonstatic int page_referenced_anon(struct page *page,struct mem_cgroup *memcg,unsigned long *vm_flags)函数

static int page_referenced_anon(struct page *page,
                struct mem_cgroup *memcg,
                unsigned long *vm_flags)
{
    unsigned int mapcount;
    struct anon_vma *anon_vma;
    pgoff_t pgoff;
    struct anon_vma_chain *avc;
    int referenced = 0;

    anon_vma = page_lock_anon_vma_read(page);
    if (!anon_vma)
        return referenced;

    mapcount = page_mapcount(page);
    pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT);
    anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root, pgoff, pgoff) {
        struct vm_area_struct *vma = avc->vma;
        unsigned long address = vma_address(page, vma);
        /*
         * If we are reclaiming on behalf of a cgroup, skip
         * counting on behalf of references from different
         * cgroups
         */
        if (memcg && !mm_match_cgroup(vma->vm_mm, memcg))
            continue;
        referenced += page_referenced_one(page, vma, address,
                          &mapcount, vm_flags);
        if (!mapcount)
            break;
    }

    page_unlock_anon_vma_read(anon_vma);
    return referenced;
}

要查看页面的访问情况,肯定要定位到具体的PTE,而PTE只能根据虚拟地址查找页表获得,所以当务之急还是找到虚拟地址和页表。这里首先获得page对应的anon_vma,前面提到,在匿名映射情况下,page->mapping指向anon_vma结构。然后获取了page的共享计数mapcount,获取page对应的虚拟页框在vma中对应的线性索引index,接下来就开始遍历interval-tree了。每个anon_vma_chain关联一个进程的vma,通过vma_address(page, vma)便可以获取在当前vma对应的进程的虚拟地址。暂且忽略cgroup相关的内容。接下来调用page_referenced_one解除映射。前面已经提到,目前已经有了虚拟地址,有了vma,根据vma可以获取对应的mm_struct,进而获取页基址,OK,流程走通了。该函数就不在列举了,函数中有两种情况,如果是大页面(2M页面),需要获得是pmd;如果是普通页面,需要获取pte;之后检查_PAGE_ACCESSED位。如果被设置,则清除,然后++引用计数器,否则,不变。所以经常访问的页面,引用计数器高,就更容易被定义成活跃页面,常驻活跃LRU链表,就不容易被换出。

回顾下最初的问题,通过物理地址找到虚拟地址,在获取了vma和index后,一个函数就解决问题,但是笔者这里有一个疑问,代码显示这里根据page结构中的index对所有的vma进行索引,这点令我很困惑,理论上将不能保证page映射的虚拟页框在所有的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);

    if (unlikely(is_vm_hugetlb_page(vma)))
        pgoff = page->index << huge_page_order(page_hstate(page));

    return vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
}

代码到这里就不需要多解释了吧,关于anon_vma结构的组织,以后凑空在分析;

感谢主!

参考:

   linux 3.10.1源码

《深入linux内核架构》

原文地址:https://www.cnblogs.com/ck1020/p/6883061.html