Posts Tagged ‘Memory’

do_machine_check() 函数分析

July 20th, 2015

do_machine_check()函数是MCA架构最为核心的函数,在之前的 一篇博文 我们分析了现有MCA 对于现有错误的处理流程,但是没有对于do_machine_check函数进行分析。这里我们会深入分析这个异常处理函数,下面分析的代码是基于Linux-3.14.38,在今天一月份Andi Kleen 提交了最新的补丁,这个会在最后进行说明。

do_machine_check()异常处理函数是由18号异常触发,它执行在NMI上下文,不适用已有的kernel service服务和机制,甚至无法正常打印信息。开头主要是声明一些变量,mca_config 主要用来声明当前系统mca的基本配置,包括是否选择通过mcelog记录CE错误,CMCI中断使能等。

struct mca_config {
        bool dont_log_ce;
        bool cmci_disabled;
        bool ignore_ce;
        bool disabled;
        bool ser;
        bool bios_cmci_threshold;
        u8 banks;
        s8 bootlog;
        int tolerant;
        int monarch_timeout;
        int panic_timeout;
        u32 rip_msr;
};

severity 主要记录错误等级,order主要用于后来进入Monarch’s region的顺序,no_way_out 用来快速检查当前cpu是否需要panic,mce_gather_info(&m, regs);用来收集当前cpu MCA寄存器中的数据,保存到struct mce结构体中。

mces_seen是每个cpu都有存在的struct mce变量,当需要保存该值时,就将其保存在final中,final<=>mces_seen等价。

void do_machine_check(struct pt_regs *regs, long error_code)
{
        struct mca_config *cfg = &mca_cfg;
        struct mce m, *final;
        int i;
        int worst = 0;
        int severity;

        int order;
        int no_way_out = 0; 

        int kill_it = 0;
        DECLARE_BITMAP(toclear, MAX_NR_BANKS);
        DECLARE_BITMAP(valid_banks, MAX_NR_BANKS);
        char *msg = "Unknown";
        atomic_inc(&mce_entry);

        this_cpu_inc(mce_exception_count);

        if (!cfg->banks)
                goto out;

        mce_gather_info(&m, regs);

        final = &__get_cpu_var(mces_seen);
        *final = m;

        memset(valid_banks, 0, sizeof(valid_banks));
        no_way_out = mce_no_way_out(&m, &msg, valid_banks, regs);

        barrier();

检查 mcgstatus 寄存器中的MCG_STATUS_RIPV是否有效,无效,将kill_it置1,后续配合SRAR事件使用。

mce_start()与mce_end()可以使得所有cpu进入Monarch’s region,第一个进入handler的是Monarch,之后的cpu听从Monarch的指挥。之后按照cfg配置信息,扫描当前cpu所有bank,将当前MSR_IA32_MCx_STATUS()信息读取到m.status中,判断这个status是否有效,对于machine_check_poll() 处理的事件跳过处理。

判断当前bank内严重等级,如果severity错误等级是MCE_KEEP_SEVERITY 和 MCE_NO_SEVERITY ,忽略这次扫描,然后mce_read_aux()继续读取ADDR与MISC相关信息到当前struct mce m中。

这时当前severity等级是MCE_AO_SEVERITY 时,kernel会将其保存到ring buffer中,在之后的work queue中进行处理。然后调用mce_log(&m)记录到/dev/mcelog中。如果此次错误等级最高,那么更新 worst ,并struct mce写入到当前cpu。

扫描完毕之后,保存struct mce 信息,清除寄存器内容。再次判断worst 等级,高于MCE_PANIC_SEVERITY的话,稍后会panic。然后mce_end()退出 Monarch。

        if (!(m.mcgstatus & MCG_STATUS_RIPV))
                kill_it = 1;

        order = mce_start(&no_way_out);
        for (i = 0; i < cfg->banks; i++) {
                __clear_bit(i, toclear);
                if (!test_bit(i, valid_banks))
                        continue;
                if (!mce_banks[i].ctl)
                        continue;

                m.misc = 0;
                m.addr = 0;
                m.bank = i;

                m.status = mce_rdmsrl(MSR_IA32_MCx_STATUS(i));
                if ((m.status & MCI_STATUS_VAL) == 0)
                        continue;

                if (!(m.status & (cfg->ser ? MCI_STATUS_S : MCI_STATUS_UC)) &&
                        !no_way_out)
                        continue;

                add_taint(TAINT_MACHINE_CHECK, LOCKDEP_NOW_UNRELIABLE);

                severity = mce_severity(&m, cfg->tolerant, NULL);

                if (severity == MCE_KEEP_SEVERITY && !no_way_out)
                        continue;
                __set_bit(i, toclear);
                if (severity == MCE_NO_SEVERITY) {
                        continue;
                }

                mce_read_aux(&m, i);

                if (severity == MCE_AO_SEVERITY && mce_usable_address(&m))
                        mce_ring_add(m.addr >> PAGE_SHIFT);

                mce_log(&m);

                if (severity > worst) {
                        *final = m;
                        worst = severity;
                }
        }
        m = *final;

        if (!no_way_out)
                mce_clear_state(toclear);

        if (mce_end(order) < 0)
                no_way_out = worst >= MCE_PANIC_SEVERITY;

tolerant 在mca机制中可调, 0最严格,3最宽松。

/*
* Tolerant levels:
* 0: always panic on uncorrected errors, log corrected errors
* 1: panic or SIGBUS on uncorrected errors, log corrected errors
* 2: SIGBUS or log uncorrected errors (if possible), log corr. errors
* 3: never panic or SIGBUS, log all errors (for testing only)
*/

只要小于3,如果no_way_out等于1 ,直接panic,如果不是,worst等级是SRAR,那么标记这个进程,在返回用户态时处理,具体处理的函数就是memory_failure();如果不是SRAR错误,而且RIPV无效,那么只能杀死当前进程。

        if (cfg->tolerant < 3) {
                if (no_way_out)
                        mce_panic("Fatal machine check on current CPU", &m, msg);
                if (worst == MCE_AR_SEVERITY) {
                        /* schedule action before return to userland */
                        mce_save_info(m.addr, m.mcgstatus & MCG_STATUS_RIPV);
                        set_thread_flag(TIF_MCE_NOTIFY);
                } else if (kill_it) {
                        force_sig(SIGBUS, current);
                }
        }

        if (worst > 0)
                mce_report_event(regs);
        mce_wrmsrl(MSR_IA32_MCG_STATUS, 0);
out:
        atomic_dec(&mce_entry);
        sync_core();
}

在最新代码 Linux-4.0.4 中,Andi Kleen 删除了mce_info,mce_save_info(),mce_find_info(),mce_clear_info(),mce_notify_process()和位于do_notify_resume()中的mce_notify_process(),也就是说SRAR不在返回用户态前处理。

x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks
We now switch to the kernel stack when a machine check interrupts
during user mode. This means that we can perform recovery actions
in the tail of do_machine_check()

他改变了SRAR发生在用户空间时,通过设置fiag并调度的方式,直接在do_machine_check()最后加入对于这种错误的处理,并在末位加入memory_failure()的错误恢复,这里指出如果恢复失败,那么直接使用force_sig(SIGBUS, current)。

最后指出:do_machine_check()只处理SRAR、SRAO类型的错误,对于UCNA类型错误由machine_check_poll()处理,下篇博文介绍machine_check_poll()。

 

参考:

http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html

DRAM页映射到BANK中的两种形式

July 4th, 2015

在之前的一篇博文上,我分析了DRAM的物理结构与从bank中存取数据的方式。这里我继续这个话题,并引入一种新式的内存存取数据的方式:Permutation-based Page Interleaving。

当CPU发送物理内存地址给内存的时候需要首先发送给memory controller,然后由memory controller将其翻译成内存地址,也就是以DIMM Rank Chip Bank为单位的地址形式,如下图所示:20150421162433 我们知道CPU对Memory的读写要经过cache,在这里可以认为CPU与cache是一体的,读到了cache也就等于被CPU使用。传统的memory与cache的关系是多路并联、单路并联,也就是说一系列的内存物理地址,被分割成了tag段,每个tag段内的每个内存地址映射到不同cache line中。

cache-related

也就是说不同tag段的相同set index会被映射到同一个cache line中,而最后的block offset正是cache line的大小,也就是说这个物理地址中的内容放到cache line中正是cache line的大小,然后我们将这个位数变化一下,就是传统物理地址对应三个部分,page index,bank index,page offset。而其中bank index的信息是具体的硬件实现(硬件生产商不会对外公布这种参数),一般我们使用软件的方式来测试bank位!

这里的page不是我们常规意义上的物理页,而是第一个图中以bank 与row组成的一行称为page。

下面我们来做一个推定:比如tag值不同,后面set index,block offset两个字段相同的两个物理地址,必定映射同一个cache line。转换到下面的DRAM的页模式,set index相同,那么bank index一定相同,也就是说两个数据处于同一个bank中,又因为tag不同,那么page index不同,意味着不属于同一个row,而每个bank只有一个row buffer,这样必然导致L3(CPU最后一级cache)无法命中,导致 L3 cache conflict miss!

举例:

double x[T], y[T], sum;
for(i = 0; i < T; i++);
     sum += x[i] * y[i];

若x[0] 和y[0] 的距离是L3 cache 大小的倍数,必然会导致读取x[i],y[i]发生L3 cache conflict miss!CPU不得不每次讲L3 cache清空,读取新的数据,这样必然大大增加了数据访问的延迟!

为了避免这种情况工程师提出了一种新的内存page交叉访问方式Permutation-based Page Interleaving Scheme,首先我们看这个算法如何降低了 L3 cache conflict miss。

permutation

 

他在传统物理地址对应的banks存取上,使用tag的一些位与bank index做异或运算,这样就可以大大降低内存页交叉访问带来的L3 conflict miss,不同tag的相同bank index会被映射到不同的bank中!

DRAM

而我们知道每个bank都有一个row buffer,只要我们保证程序局部性存取的数据在不同row buffer中,这样就意味着row buffer的数据可以长时间存在,不同频繁的清空,当需要这些数据,直接从row buffer中读取到memory controller就可以了。而程序的局部性并没有被破坏,仍然存在在一个DRAM page!

DRAM_bank

 

这两种方式长时间存在于DRAM交叉存取bank中,有时候我们在测试Bank位会遇到这两种方式,因此我们必须使用特定工具去检测决定bank位的index,如过检测bank index非常多,就要考虑是否是XOR交叉存取方式了!

 

 

论文:http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=898056

memtest86+与BadRAM使用

June 28th, 2015

一般情况下,DRAM的损坏是永久性的损坏,这个时候我们有三种方式解决这个问题:

  1. 买新的内存条
  2. 在kernel启动时加入mem参数,限制当前mem的使用,比如当前内存2GB,在800M地方,内存存在永久故障区域,那么我们可以在kernel的启动参数加入mem=780M,这样就可以限制当前内核分配800M的内存区域而触发MCE。但是这个也有一个明显的缺点:因为这个坏点导致大部分内存无法使用
  3. 在kernel启动加入badram 0x01000000,0xfffffffc即可。

我们这里使用第三种方式来绕过当前的内存坏块,而达到省钱的目的!badram 第一个参数 是出错的物理基地址,第二个参数是mask,来用标示这个掩码。当我们使用memtest86+测试出当前的出错内存物理地址,然后将这个错误地址加入到kernel 参数中即可。

其中memtest86+是一款离线内存检测工具,可以检测内存中的坏页。

下面我们来实验一下,比如当前系统没有出现内存坏块,那么使用iomem参看当前物理内存地址的分布:

00000000-00000fff : reserved
00001000-0009f3ff : System RAM
0009f400-0009ffff : reserved
000a0000-000bffff : PCI Bus 0000:00
000c0000-000dffff : PCI Bus 0000:00
  000c0000-000c7fff : Video ROM
  000cc000-000cd7ff : Adapter ROM
000e0000-000effff : pnp 00:08
000f0000-000fffff : reserved
  000f0000-000fffff : System ROM
00100000-7f6dffff : System RAM
  01000000-017b9cc4 : Kernel code
  017b9cc5-01d1e8ff : Kernel data
  01e86000-01fc8fff : Kernel bss
....

当我们在grub中加入badram 0x01000000,0xfffffffc参数,也就意味着Kernel代码区的地址出现错误,而且错误大小是2bit,这个时候我们重新启动,再查看当前系统物理内存分布,我们会发现:

01000000-010003ff : RAM buffer
01000400-7f6dffff : System RAM
  02000000-027b9cc4 : Kernel code
  027b9cc5-02d1e8ff : Kernel data

kernel 的代码段避过了问题内存区域,我们查看__pa(x)最终调用__phys_addr_nodebug,而其中phys_base则是在real_mode下面被调用。

static inline unsigned long __phys_addr_nodebug(unsigned long x)
{
    unsigned long y = x - __START_KERNEL_map;

    /* use the carry flag to determine if x was < __START_KERNEL_map */
    x = y + ((x > y) ? phys_base : (__START_KERNEL_map - PAGE_OFFSET));

    return x;
}

22996709_13505212884HWu

Real-mode code 在X+0x8000开始,X就是badram传入的offset!

 

 

参考:

http://ubuntuforums.org/archive/index.php/t-1689890.html

http://ubuntuforums.org/showthread.php?t=2278744

https://help.ubuntu.com/community/BadRAM#BADRAM_setting_in_Grub2

http://blog.chinaunix.net/uid-22996709-id-3376998.html

物理内存管理:vmalloc()的设计实现

May 26th, 2015

对于内核空间,根据不同的映射规则,可以将整个内核空间划分为四大部分:物理内存映射区、vmalloc区、永久内核映射区和固定映射的线性地址区域。这里我们只来谈谈vmalloc()的分配。首先我们来看一下之前的一个图:

phy_addr

vmalloc()区域是从VMALLOC_START开始,一直到VMALLOC_END结束,中间有PAGE_SIZE大小的guard_page作为保护,以防止防止非法的内存访问,内核中使用vm_struct结构来表示每个vmalloc区,也就是说,每次调用vmalloc()函数在内核中申请一段连续的内存后,都对应着一个vm_struct,kernel 中所有的vmalloc区组成一个链表,链表头指针为vmlist

static struct vm_struct *vmlist __initdata;

struct vm_struct {
    struct vm_struct    *next;
    void            *addr;
    unsigned long       size;
    unsigned long       flags;
    struct page     **pages;
    unsigned int        nr_pages;                                                                                                
    phys_addr_t     phys_addr;
    const void      *caller;
};

这里我们只介绍几个重要的字段:
next:所有的vm_struct结构组成一个vmlist链表,该字段指向下一个节点;
addr:代表这段子区域的起始地址;
size:表示区域的大小;
flags:表示该非连续内存区的类型,VM_ALLOC表示由vmalloc()映射的内存区,VM_MAP表示通过vmap()映射的内存区,VM_IOREMAP表示通过ioremap()将硬件设备的内存映射到内核的一段内存区;
pages:指针数组,该数组的成员是struct page*类型的指针,每个成员都关联一个映射到该虚拟内存区的物理页框;
nr_pages:pages数组中page结构的总数;
phys_addr:通常为0,当使用ioremap()映射一个硬件设备的物理内存时才填充此字段;
caller:表示一个返回地址;

 

vmalloc()的实现

vmalloc()内部封装了很多层函数,调用层次就是:vmalloc()-> __vmalloc_node_flags() -> __vmalloc_node() -> __vmalloc_node_range() ,通过这几层调用,就会向最终的__vmalloc_node_range()传入很多参数,GFP_KERNEL|__GFP_HIGHMEM表明内存管理子系统将从高端内存区(ZONE_HIGHMEM)中分配内存空间;NUMA_NO_NODE表示当前不是NUMA架构。

void *vmalloc(unsigned long size)
{
    return __vmalloc_node_flags(size, NUMA_NO_NODE,
                    GFP_KERNEL | __GFP_HIGHMEM);
}
static inline void *__vmalloc_node_flags(unsigned long size,                             
                    int node, gfp_t flags)
{
    return __vmalloc_node(size, 1, flags, PAGE_KERNEL,
                    node, __builtin_return_address(0));
}
static void *__vmalloc_node(unsigned long size, unsigned long align,                    
                gfp_t gfp_mask, pgprot_t prot,
                int node, const void *caller)
{
    return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
                gfp_mask, prot, node, caller);
}

这里我们进入真正分配vmalloc区域的函数__vmalloc_node_range(),我们只要关注第三个第四个参数,就是vm区域的开始地址与结束地址,__vmalloc_node_range()一开始会先修正一下size对齐,PAGE_ALIGN将size的大小修改成页大小的倍数。进行size合法性的检查,如果size为0,或者size所占页框数大于系统当前空闲的页框数(totalram_pages),将返回NULL,申请失败。

如果分配的内存区大小合法,__get_vm_area_node()中的alloc_vmap_area()将在整个非连续内存区中查找一个size大小的子内存区。该函数先遍历整个vmlist链表,依次比对每个vmalloc区,直到找到满足要求的子内存区结束。在这里面还做好了内核页表的映射等操作。

建立好了vm_struct 结构,下面就要分配使用__vmalloc_area_node()为这个vmalloc内存区分配真正的物理页。

void *__vmalloc_node_range(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, int node, const void *caller)
{
    struct vm_struct *area;
    void *addr;
    unsigned long real_size = size;
                                                                                                                                 
    size = PAGE_ALIGN(size);
    if (!size || (size >> PAGE_SHIFT) > totalram_pages)
        goto fail;
    
    area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED,
                  start, end, node, gfp_mask, caller);
    if (!area)
        goto fail;
    
    addr = __vmalloc_area_node(area, gfp_mask, prot, node);
    if (!addr) 
        return NULL;
...    
    return addr;
....

__vmalloc_area_node()的实现

首先根据传入的vm_struct *area 获取要分配的物理页大小,这里的大小包括了guard_page,所以要在get_vm_area_size()减去这个PAGE_SIZE,翻译过来就是nr_pages = (area->size – PAGE_SIZE) >> PAGE_SHIFT;抹去低位,也就是要分配的page数,然后nr_pages * sizeof(struct page *)也就是真正的物理page大小。

根据这个array_size大小,我们可以判断要分配的是大于PAGE_SIZE还是小于PAGE_SIZE,大于的话就递归分配pages数组,小于的话,通过kmalloc_node()为pages数组分配一段连续的空间,这段空间位于内核空间的物理内存线性映射区。然后将分配好的page加入area,更新area中的pages。

static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
                 pgprot_t prot, int node)
{
    const int order = 0;
    struct page **pages;
    unsigned int nr_pages, array_size, i;
    const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
    const gfp_t alloc_mask = gfp_mask | __GFP_NOWARN;                                                                            

    nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
    array_size = (nr_pages * sizeof(struct page *));

    area->nr_pages = nr_pages;
    /* Please note that the recursion is strictly bounded. */
    if (array_size > PAGE_SIZE) {
        pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
                PAGE_KERNEL, node, area->caller);
        area->flags |= VM_VPAGES;
    } else {
        pages = kmalloc_node(array_size, nested_gfp, node);
    }
    area->pages = pages;

接下来通过一个循环为pages数组中的每个页面描述符分配真正的物理页框。page结构并不是代表一个具体的物理页框,只是用来描述物理页框的数据结构而已。如果node未指定物理内存所在节点,那么alloc_page()分配一个页框,并将该页框对应的页描述符指针赋值给page临时变量;否则通过alloc_pages_node()在指定的节点上分配物理页框。接着将刚刚分配的物理页框对应的页描述符赋值给pages数组的第i个元素。这里一旦某个物理页框分配失败则直接返回NULL,表示本次vmalloc()操作失败。

for (i = 0; i < area->nr_pages; i++) {
        struct page *page;

        if (node == NUMA_NO_NODE)
            page = alloc_page(alloc_mask);
        else
            page = alloc_pages_node(node, alloc_mask, order);

        if (unlikely(!page)) {
            /* Successfully allocated i pages, free them in __vunmap() */
            area->nr_pages = i;
            goto fail;
        }
        area->pages[i] = page;
        if (gfp_mask & __GFP_WAIT)
            cond_resched();
    }

    if (map_vm_area(area, prot, pages))
        goto fail;
    return area->addr;

fail:
    warn_alloc_failed(gfp_mask, order,
              "vmalloc: allocation failure, allocated %ld of %ld bytes\n",
              (area->nr_pages*PAGE_SIZE), area->size);
    vfree(area->addr);
    return NULL;
}

这里当kernel分配完page,并将信息输入到area中后,这些分散的页框并没有映射到area所代表的那个连续vmalloc区中。使用map_vm_area()将完成映射,它依次修改内核页表,将pages数组中的每个页框分别映射到连续的vmalloc区中。

 

map_vm_area()工作原理会在下一篇文章中说明!

[1] http://lxr.free-electrons.com/source/mm/vmalloc.c#L1557

物理内存管理:Page的逆向映射

May 18th, 2015

kernel中包含着庞大的元数据struct page,一个struct page管理一个4k大小的物理内存(默认),之前我们进行的是从task_struct向vm_area_struct一直到最后的struct page的寻找过程。

但是从RAS角度,如果一个page发生问题,那么如何从struct page向上找到对应的page table呢(找到page table也就找到了pid、task_struct….)?这时候就要使用逆向映射

为了尽可能减小strut page的大小,Andrea复用了page的struct address_space *mapping数据结构来表示不同类型的页,主要分为三个类型swap、page cache、anonymous page。这里我们只说明page cache 与 anonymous page的逆向映射。

0.什么是匿名页?

想说明匿名页,必须首先说明什么是映射页。在用户态下打开一个文件,kernel会使用map() 映射文件的某个部分,这个部分在用户态地址空间存在地址,这种页要被回收的时候,会检查是否是dirty,如果为dirty,则需要写回相应的磁盘文件。

而匿名页没有对应了打开的磁盘文件,比如进程的用户态堆和stack可以称为匿名页,当匿名页过长时间驻留内存时,kernel可以要把它保存到一个特定的磁盘分区,这就是swap分区!

首先我们看一下struct page的结构:

struct page {
          /* First double word block */
          unsigned long flags;            /* Atomic flags, some possibly
                                           * updated asynchronously */
          union {
                  struct address_space *mapping;  /* If low bit clear, points to
                                                   * inode address_space, or NULL.
                                                   * If page mapped as anonymous
                                                   * memory, low bit is set, and
                                                   * it points to anon_vma object:
                                                   * see PAGE_MAPPING_ANON below.
                                                   */
                  void *s_mem;                    /* slab first object */
          };

          /* Second double word */
          struct {
                  union {
                          pgoff_t index;          /* Our offset within mapping. */
 ....

struct address_space *mapping是确定页是映射还是匿名

  • mapping为空表示该页属于交换高速缓存swap;
  • mapping非空,且最低位是1,表示该页为匿名页,同时mapping字段中存放的是指向anon_vma描述符的指针;
  • mapping非空,且最低位是0,表示该页为映射页;同时mapping字段指向对应文件的address_space对象。

而第二部分的pgoff_t index是用来指明偏移量的,在映射页中是以页为单位偏移。

1.当我们判断这个页是映射页时,每个page的mapping都指向一个对应的address_space,这个结构是page cache的核心,可以通过这个找到具体的inode!

2.当我们判断这个页是匿名页时,那么这个page指向struct anon_vma

但是这里kernel在2010年提交过一个补丁,为了解决大量fork出来的子进程占用大量anon_vma结构,他们的结构完全相同,而且这种结构导致匿名映射寻找也是O(N)的复杂度。所以引入了struct anon_vma_chain

假设系统只有一个task_struct时,组织结构如下:

anon_map

struct anon_vma_chain {
         struct vm_area_struct *vma;
         struct anon_vma *anon_vma;
         struct list_head same_vma;   /* locked by mmap_sem & page_table_lock */
         struct rb_node rb;                      /* locked by anon_vma->rwsem */
         unsigned long rb_subtree_last;
 #ifdef CONFIG_DEBUG_VM_RB
         unsigned long cached_vma_start, cached_vma_last;
 #endif
};

这样子就形成了anon_vma与vm_area_struct N对N的组织形式,当父进程fork出一个子进程,那么这个子进程拥有一个vm_area_struct,当然也就拥有一个anon_vma,除非发生COW,否则父子进程共享vm_area_struct。

this patch changes the way anon_vmas and VMAs are linked, which allows us to associate multiple anon_vmas with a VMA. At fork time, each child process gets its own anon_vmas, in which its COWed pages will be instantiated. The parents’ anon_vma is also linked to the VMA, because non-COWed pages could be present in any of the children.

3.刚才说的是正向的一个匿名页查找,现在我们反过来,一直一个struct page,找到这个匿名页的page table。结合正向的结构图与相关struct成员,我们可以画出相应的结构图,这里page指向anon_vma就是通过mapping做到的:

 

anonvma2

 

而anon_vma (AV),anon_vma_chain entry (AVC),vm_area_struct(VMA)三者的关系如下

avchain1

当father process fork()出来子进程后,会产生新的struct anon_vma ,这个结构就会分裂,创建VMA->创建AVC->创建AV。

avchain4

 

 

 

参考:

http://lwn.net/Articles/335768/

http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=5beb49305251e5669852ed541e8e2f2f7696c53e