13.1 父进程分配匿名页面

本文详细解析了Linux内核中匿名页面的分配过程,包括do_anonymous_page()函数如何调用anon_vma_prepare()和page_add_new_anon_rmap()来初始化和添加新的匿名页面。anon_vma_prepare()负责创建或复用anon_vma数据结构,而page_add_new_anon_rmap()则将新页面添加到反向映射系统。此外,还介绍了anon_vma和anon_vma_chain数据结构在匿名页面管理中的作用,以及page->mapping成员的含义和用途。

    父进程为自己的进程地址空间VMA分配物理内存时,通常会产生匿名页面。例如do_anonymous_page()会分配匿名页面,do_wp_page()发生写时复制COW时会生成一个新的匿名页面。以do_anonymous_page()分配一个新的匿名页面为例:

[用户态malloc()分配内存->写入该内存->内核缺页中断->do_anonymous_page()]

static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
        unsigned long address, pte_t *page_table, pmd_t *pmd,
        unsigned int flags)
{
    ......
    /* Allocate our own private page. */
    if (unlikely(anon_vma_prepare(vma)))
        goto oom;
    page = alloc_zeroed_user_highpage_movable(vma, address);
    if (!page)
        goto oom;
    ......
    page_add_new_anon_rmap(page, vma, address);
    ......
}

  在分配匿名页面时,调用RMAP反向映射系统的两个API接口来完成初始化,一个是anon_vma_prepare()函数,另一个page_add_new_anon_rmap()函数,下面查看anon_vma_prepare()函数实现

[do_anonymous_page()->anon_vma_prepare()]

int anon_vma_prepare(struct vm_area_struct *vma)
{
    /*VMA数据结构中有一个成员anon_vma用于指向anon_vma数据结构,如果VMA还没有分配过匿名页面,
    那么vma->anon_vma为NULL,则会分配一个anon_vma数据结构和一个anon_vma_chain数据结构*/
    struct anon_vma *anon_vma = vma->anon_vma;
    struct anon_vma_chain *avc;

    might_sleep();
    if (unlikely(!anon_vma)) {
        struct mm_struct *mm = vma->vm_mm;
        struct anon_vma *allocated;
         /*分配一个struct anon_vma_chain数据结构avc*/
        avc = anon_vma_chain_alloc(GFP_KERNEL);
        if (!avc)
            goto out_enomem;
         /*检查是否可以复用当前vma的前继者near_vma和后继者pre_vma的anon_vma。能复用的判断条件
         比较苛刻,例如两个VMA必须相邻,VMA的内存policy也必须相同,有相同的vm_file等,具体可以看
         anon_vma_compatible()函数。如果相邻的VMA无法复用anon_vma,那么重新分配一个anon_vma
         数据结构*/
        anon_vma = find_mergeable_anon_vma(vma);
        allocated = NULL;
        if (!anon_vma) {
            anon_vma = anon_vma_alloc();
            if (unlikely(!anon_vma))
                goto out_enomem_free_avc;
            allocated = anon_vma;
        }

        anon_vma_lock_write(anon_vma);
        /* page_table_lock to protect against threads */
        spin_lock(&mm->page_table_lock);
        if (likely(!vma->anon_vma)) {
            /*把vma->anon_vma指向到刚才分配的anon_vma*/
            vma->anon_vma = anon_vma;
            /*将刚才分配的avc添加到vma的anon_vma_chian链表中,另外把avc添加到
            anon_vma->rb_root红黑树中。anon_vma数据结构中有一个读写信号量
            rwsem,此操作需要获取写者锁anon_vma_lock_write()。下面查看
            anon_vma_chain_link()函数的实现*/
            anon_vma_chain_link(vma, avc, anon_vma);
            /* vma reference or self-parent link for new root */
            anon_vma->degree++;
            allocated = NULL;
            avc = NULL;
        }
        spin_unlock(&mm->page_table_lock);
        anon_vma_unlock_write(anon_vma);

        if (unlikely(allocated))
            put_anon_vma(allocated);
        if (unlikely(avc))
            anon_vma_chain_free(avc);
    }
    return 0;

 out_enomem_free_avc:
    anon_vma_chain_free(avc);
 out_enomem:
    return -ENOMEM;
}

anon_vma_prepare()函数主要为进程地址空间VMA准备struct anon_vma数据结构和一些管理用的链表。RMAP反向映射系统中有两个重要的数据结构,一个是anon_vma,简称AV;另一个是anon_vma_chain,简称AVC。struct anon_vma数据结构定义如下:

struct anon_vma {
    struct anon_vma *root;      /* 指向anon_vma数据结构中的根节点 */
    struct rw_semaphore rwsem;  /* 保护anon_vma中链表的读写信号量*/
    atomic_t refcount; /*引用计数*/

    /*
     * Count of child anon_vmas and VMAs which points to this anon_vma.
     *
     * This counter is used for making decision about reusing anon_vma
     * instead of forking new one. See comments in function anon_vma_clone.
     */
    unsigned degree;

    struct anon_vma *parent;    /* 指向父anon_vma数据结构 */

    /*
     * NOTE: the LSB of the rb_root.rb_node is set by
     * mm_take_all_locks() _after_ taking the above lock. So the
     * rb_root must only be read/written after taking the above lock
     * to be sure to see a valid next pointer. The LSB bit itself
     * is serialized by a system wide lock only visible to
     * mm_take_all_locks() (mm_all_locks_mutex).
     */
    struct rb_root rb_root; /* 红黑树根节点。anon_vma内部有一颗红黑树*/
};

struct anon_vma_chain数据结构是连接父子进程中的枢纽,定义如下:

struct anon_vma_chain {
    struct vm_area_struct *vma; /*指向VMA,可以指向父进程的VMA,也可以指向子进程的VMA,具体情况需要具体分析*/
    struct anon_vma *anon_vma; /*anon_vma数据结构,可以指向父进程的anon_vma数据结构,也可以指向子进程的anon_vma,
    具体情况具体分析*/
    struct list_head same_vma;   /*链表节点,通常把anon_vma_chain添加到vma->anon_vma_chain链表中*/
    struct rb_node rb;          /*红黑树节点,通常把anon_vma_chain添加到anon_vma->rb_root的红黑树 */
    unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
    unsigned long cached_vma_start, cached_vma_last;
#endif
};

回到anon_vma_prepare函数中

anon_vma_chain_link()函数实现:把avc添加到vma的anon_vma_chain链表中,另外把avc添加到anon_vma->rb_root红黑树中。

[mm/rmap.c]

[do_anonymous_page()->anon_vma_prepare()->anon_vma_chain_link()] 

static void anon_vma_chain_link(struct vm_area_struct *vma,
                struct anon_vma_chain *avc,
                struct anon_vma *anon_vma)
{
    avc->vma = vma;
    avc->anon_vma = anon_vma;
    list_add(&avc->same_vma, &vma->anon_vma_chain);
    anon_vma_interval_tree_insert(avc, &anon_vma->rb_root);
}回到anon_vma_prepare()函数

接下来看另外一个重要的API函数:page_add_new_anon_rmap()

do_anonymous_page()->page_add_new_anon_rmap()

/**
 * page_add_new_anon_rmap - add pte mapping to a new anonymous page
 * @page:   the page to add the mapping to
 * @vma:    the vm area in which the mapping is added
 * @address:    the user virtual address mapped
 *
 * Same as page_add_anon_rmap but must only be called on *new* pages.
 * This means the inc-and-test can be bypassed.
 * Page does not have to be locked.
 */
void page_add_new_anon_rmap(struct page *page,
    struct vm_area_struct *vma, unsigned long address)
{
    VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
    /*设置page的标志位PG_SwapBacked,表示这个页面可以swap到磁盘*/
    SetPageSwapBacked(page);
    /*设置page的_mapcount引用计数为0,_mapcount的初始化值为-1.*/
    atomic_set(&page->_mapcount, 0); /* increment count (starts at -1) */
    if (PageTransHuge(page))
        __inc_zone_page_state(page, NR_ANON_TRANSPARENT_HUGEPAGES);
    /*增加页面所在的zone的匿名页面的计数,匿名页面计数类型NR_ANON_PAGES*/
    __mod_zone_page_state(page_zone(page), NR_ANON_PAGES,
            hpage_nr_pages(page));
    /*设置这个页面为匿名映射,下面查看此函数实现*/
    __page_set_anon_rmap(page, vma, address, 1);
}

__page_set_anon_rmap()函数实现:

page_add_new_anon_rmap()->__page_set_anon_rmap()

/**
 * __page_set_anon_rmap - set up new anonymous rmap
 * @page:   Page to add to rmap 
 * @vma:    VM area to add page to.
 * @address:    User virtual address of the mapping 
 * @exclusive:  the page is exclusively owned by the current process
 */
static void __page_set_anon_rmap(struct page *page,
    struct vm_area_struct *vma, unsigned long address, int exclusive)
{
    struct anon_vma *anon_vma = vma->anon_vma;

    BUG_ON(!anon_vma);

    if (PageAnon(page))
        return;

    /*
     * If the page isn't exclusively mapped into this vma,
     * we must use the _oldest_ possible anon_vma for the
     * page mapping!
     */
    if (!exclusive)
        anon_vma = anon_vma->root;
    
    /*将anon_vma的指针的值加上PAGE_MAPPING_ANON,然后把指针赋值给page->mapping。
    struct page数据结构中的mapping成员用于指定页面所在的地址空间。内核中所谓的
    地址空间通常有两个不同的地址空间,一个用于文件映射页面,另一个用于匿名映射。
    mapping指针的最低两位用于判断是否指向匿名映射或KSM页面的地址空间,如果
    mapping指针最低1位不为0,那么mapping指向匿名页面的地址空间数据结构
    struct anon_vma。内核提供一个函数PageAnon()函数,用于判断一个页面是否为
    匿名页面。下面给出*/
    anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON;
    page->mapping = (struct address_space *) anon_vma;
    /*计算当前地址address是在VMA中的第几个页面,然后把offset值赋值到page->index中。
    对于匿名页面来说,vma->vm_pgoff这个成员的值是0或者vm_addr/PAGE_SIZE,比如mmap中
    MAP_SHARED映射时vm_pgoff为0,MAP_PRIVATE映射时为vm_addr/PAGE_SIZE。vm_addr/PAGE_SIZE
    表示匿名页面在整个进程地址空间中的offset。vm_pgoff这个值在匿名页面的生命周期中只有RMAP
    反映射时才用到。对于匿名页面来说把vma->vm_pgoff看成0可能更好的体现出page->index的
    含义来,把page->index看成是一个VMA里面的offset,而不是整个进程地址空间的offset,也许更贴切些*/
    page->index = linear_page_index(vma, address);
}

static inline pgoff_t linear_page_index(struct vm_area_struct *vma,
                    unsigned long address)
{
    pgoff_t pgoff;
    if (unlikely(is_vm_hugetlb_page(vma)))
        return linear_hugepage_index(vma, address);
    pgoff = (address - vma->vm_start) >> PAGE_SHIFT;
    pgoff += vma->vm_pgoff;
    return pgoff >> (PAGE_CACHE_SHIFT - PAGE_SHIFT);
}

补充:

通过struct page中的mapping成员我们可以获得该page映射的相关信息:

(1) 当mapping等于NULL,表示该page frame不再内存中,而是被swap out到磁盘中去了。

(2) 如果不等于NULL,并且least signification bit等于2,表示page frame是KSM页面

(3) 如果不等于NULL,并且least signification bit等于1,表示page frame是匿名映射页面,mapping指向了一个anon_vma的数据结构。

(4) 如果不等于NULL,并且least signification bit等于0,表示page frame是文件映射页面,mapping指向了一个该文件的address_space数据结构。

#define PAGE_MAPPING_ANON   1
#define PAGE_MAPPING_KSM    2
#define PAGE_MAPPING_FLAGS  (PAGE_MAPPING_ANON | PAGE_MAPPING_KSM)

static inline int PageAnon(struct page *page)
{
    return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}

补充:

page->index和vma->vm_pgoff的含义:

(1) 文件映射的情况

对于文件映射,page->index表示的是page映射到文件内的偏移值(单位是Byte),而vma->vm_gpoff表示的是该VMA映射到文件内的偏移(page为单位),因此,通过vma->vm_pgoff和page->index可以得到该page frame在VMA中的地址偏移,再加上vma->vm_start就可以得到该page_frame的虚拟地址。有了虚拟地址和地址空间(vma->vm_mm),我们就可以通过各级页面找到page对应的pte entry。

(2) 匿名映射的情况

其中匿名映射又要分为share anonymous mapping和private anonymous mapping.

  • share anonymous mapping(MAP_SHARED): vma->vm_pgoff等于0,page->index表示将整个虚拟地址空间作为偏移,

  • share anonymous mapping(私有匿名映射):page->index表示作为vma内的地址偏移

  • private anonymous mapping(MAP_PRIVATE): vma->vm_pgoff 等于vma->vm_start >> PAGE_SHIFT, page->index 等于(vma(虚拟地址) - vma->vm_start) >> PAGE_SHIFT + vma->vm_pgoff;所以page->index为整个进程虚拟地址空间的offset 即vm_addr>>PAGE_SHIFT

父进程的匿名页面:

  • 父进程的每个VMA中有一个anon_vma数据结构(下文用AVp来表示),vma->anon_vma指向AVp。

  • 和VMAp相关的物理页面page->mapping都指向AVp。

  • 有一个anon_vma_chain数据结构AVC,其中avc->vma指向VMA,avc->av指向AVp。

  • AVC添加到VMAp->anon_vma_chain

总结:每个vma只有一个anon_vma数据结构,vma中的每个page的mapping都指向anon_vma数据结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byd yes

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值