zoukankan      html  css  js  c++  java
  • 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内核架构》

  • 相关阅读:
    众皓网络(T 面试)
    骑芯供应链(T 面试)
    骑芯供应链(W 笔试)
    面试问题_一拉到底
    Java后端学习路线_备战
    docker 容器
    技术展望
    索引 命令
    索引 概念原理
    面试技能更新
  • 原文地址:https://www.cnblogs.com/ck1020/p/6883061.html
Copyright © 2011-2022 走看看