【小镇的技术天梯】vma的基本操作

小镇有一篇博文说了关于linux虚拟内存和物理内存的知识,那篇的知识太浅了,这篇再稍微深入一点。

在32位的系统上,线性地址空间可达到4GB,这4GB一般按照3:1的比例进行分配,也就是说用户进程享有前3GB线性地址空间,而内核独享最后1GB线性地址空间。由于虚拟内存的引入,每个进程都可拥有3GB的虚拟内存,并且用户进程之间的地址空间是互不可见、互不影响的,也就是说即使两个进程对同一个地址进行操作,也不会产生问题。在前面介绍的一些分配内存的途径中,无论是伙伴系统中分配页的函数,还是slab分配器中分配对象的函数,它们都会尽量快速地响应内核的分配请求,将相应的内存提交给内核使用,而内核对待用户空间显然不能如此。用户空间动态申请内存时往往只是获得一块线性地址的使用权,而并没有将这块线性地址区域与实际的物理内存对应上,只有当用户空间真正操作申请的内存时,才会触发一次缺页异常,这时内核才会分配实际的物理内存给用户空间。
【这些在前面的文章已经说过了,我就不再赘述了,大家看看就好】
 
 用户进程的虚拟地址空间包含了若干区域,这些区域的分布方式是特定于体系结构的,不过所有的方式都包含下列成分:
可执行文件的二进制代码,也就是程序的代码段
存储全局变量的数据段
用于保存局部变量和实现函数调用的栈
环境变量和命令行参数
程序使用的动态库的代码
用于映射文件内容的区域


由此可以看到进程的虚拟内存空间会被分成不同的若干区域,每个区域都有其相关的属性和用途,一个合法的地址总是落在某个区域当中的,这些区域也不会重叠。在linux内核中,这样的区域被称之为虚拟内存区域(virtual memory areas),简称vma。一个vma就是一块连续的线性地址空间的抽象,它拥有自身的权限(可读,可写,可执行等等) ,每一个虚拟内存区域都由一个相关的struct vm_area_struct结构来描述
【按照不同职责和用途的区域被称为vma,有相应的struct结构来描述和维护】

进程的若干个vma区域都得按一定的形式组织在一起,这些vma都包含在进程的内存描述符中,也就是struct mm_struct中,这些vma在mm_struct以两种方式进行组织,一种是链表方式,对应于mm_struct中的mmap链表头,一种是红黑树方式,对应于mm_struct中的mm_rb根节点,和内核其他地方一样,链表用于遍历,红黑树用于查找。
【这些vma在 内存描述符中既按照链表的方式也按照红黑树的方式进行着排列,因为链表方便遍历,而红黑树方便查找】
struct vm_area_struct {  
    struct mm_struct * vm_mm;   /* 所属的内存描述符 */  
    unsigned long vm_start;    /* vma的起始地址 */  
    unsigned long vm_end;       /* vma的结束地址 */  
  
    /* 该vma的在一个进程的vma链表中的前驱vma和后驱vma指针,链表中的vma都是按地址来排序的*/  
    struct vm_area_struct *vm_next, *vm_prev;  
  
    pgprot_t vm_page_prot;      /* vma的访问权限 */  
    unsigned long vm_flags;    /* 标识集 */  
  
    struct rb_node vm_rb;      /* 红黑树中对应的节点 */  
  
    /* 
     * For areas with an address space and backing store, 
     * linkage into the address_space->i_mmap prio tree, or 
     * linkage to the list of like vmas hanging off its node, or 
     * linkage of vma in the address_space->i_mmap_nonlinear list. 
     */  
    /* shared联合体用于和address space关联 */  
    union {  
        struct {  
            struct list_head list;/* 用于链入非线性映射的链表 */  
            void *parent;   /* aligns with prio_tree_node parent */  
            struct vm_area_struct *head;  
        } vm_set;  
  
        struct raw_prio_tree_node prio_tree_node;/*线性映射则链入i_mmap优先树*/  
    } shared;  
  
    /* 
     * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma 
     * list, after a COW of one of the file pages.  A MAP_SHARED vma 
     * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack 
     * or brk vma (with NULL file) can only be in an anon_vma list. 
     */  
    /*anno_vma_node和annon_vma用于管理源自匿名映射的共享页*/  
    struct list_head anon_vma_node; /* Serialized by anon_vma->lock */  
    struct anon_vma *anon_vma;  /* Serialized by page_table_lock */  
  
    /* Function pointers to deal with this struct. */  
    /*该vma上的各种标准操作函数指针集*/  
    const struct vm_operations_struct *vm_ops;  
  
    /* Information about our backing store: */  
    unsigned long vm_pgoff;     /* 映射文件的偏移量,以PAGE_SIZE为单位 */  
    struct file * vm_file;          /* 映射的文件,没有则为NULL */  
    void * vm_private_data;     /* was vm_pte (shared mem) */  
    unsigned long vm_truncate_count;/* truncate_count or restart_addr */  
  
#ifndef CONFIG_MMU  
    struct vm_region *vm_region;    /* NOMMU mapping region */  
#endif  
#ifdef CONFIG_NUMA  
    struct mempolicy *vm_policy;    /* NUMA policy for the VMA */  
#endif  
};  
下面来看几个vma的基本操作函数,这些函数都是后面实现具体功能的基础
find_vma()用来寻找一个针对于指定地址的vma,该vma要么包含了指定的地址,要么位于该地址之后并且离该地址最近,或者说寻找第一个满足addr<vma_end的vma,代码如下:
struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)  
{  
    struct vm_area_struct *vma = NULL;  
  
    if (mm) {  
        /* Check the cache first. */  
        /* (Cache hit rate is typically around 35%.) */  
        vma = mm->mmap_cache; //首先尝试mmap_cache中缓存的vma  
        /*如果不满足下列条件中的任意一个则从红黑树中查找合适的vma 
          1.缓存vma不存在 
          2.缓存vma的结束地址小于给定的地址 
          3.缓存vma的起始地址大于给定的地址*/  
        if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {  
            struct rb_node * rb_node;  
  
            rb_node = mm->mm_rb.rb_node;//获取红黑树根节点  
            vma = NULL;  
  
            while (rb_node) {  
                struct vm_area_struct * vma_tmp;  
  
                vma_tmp = rb_entry(rb_node,   //获取节点对应的vma  
                        struct vm_area_struct, vm_rb);  
  
                /*首先确定vma的结束地址是否大于给定地址,如果是的话,再确定 
                  vma的起始地址是否小于给定地址,也就是优先保证给定的地址是 
                  处于vma的范围之内的,如果无法保证这点,则只能找到一个距离 
                  给定地址最近的vma并且该vma的结束地址要大于给定地址*/  
                if (vma_tmp->vm_end > addr) {  
                    vma = vma_tmp;  
                    if (vma_tmp->vm_start <= addr)  
                        break;  
                    rb_node = rb_node->rb_left;  
                } else  
                    rb_node = rb_node->rb_right;  
            }  
            if (vma)  
                mm->mmap_cache = vma;//将结果保存在缓存中  
        }  
    }  
    return vma;  
}  
【红黑树具体的数据结构大家不需要具体了解,但是我们可以看到红黑树的节点在改变,这里应该是前序遍历】
【然后是合并vma,具体的代码我就不贴出来了,具体的代码放出来大家也不一定看得懂,但是大家要知道为什么要合并vma,因为vma是进程的虚拟内存空间中具有特殊页中断处理规则的任意一部分,这些特殊部分一般是具有一些共有的特性的,那么具有共有特性的的vma就可以合并了。】


你可能感兴趣的:(【小镇的技术天梯】vma的基本操作)