小镇有一篇博文说了关于linux虚拟内存和物理内存的知识,那篇的知识太浅了,这篇再稍微深入一点。
在32位的系统上,线性地址空间可达到4GB,这4GB一般按照3:1的比例进行分配,也就是说用户进程享有前3GB线性地址空间,而内核独享最后1GB线性地址空间。由于虚拟内存的引入,每个进程都可拥有3GB的虚拟内存,并且用户进程之间的地址空间是互不可见、互不影响的,也就是说即使两个进程对同一个地址进行操作,也不会产生问题。在前面介绍的一些分配内存的途径中,无论是伙伴系统中分配页的函数,还是slab分配器中分配对象的函数,它们都会尽量快速地响应内核的分配请求,将相应的内存提交给内核使用,而内核对待用户空间显然不能如此。用户空间动态申请内存时往往只是获得一块线性地址的使用权,而并没有将这块线性地址区域与实际的物理内存对应上,只有当用户空间真正操作申请的内存时,才会触发一次缺页异常,这时内核才会分配实际的物理内存给用户空间。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的基本操作函数,这些函数都是后面实现具体功能的基础
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; }【红黑树具体的数据结构大家不需要具体了解,但是我们可以看到红黑树的节点在改变,这里应该是前序遍历】