内核源码分析之进程地址空间(基于3.16-rc4)

时间:2022-12-01 17:36:53

所谓进程的地址空间,指的就是进程的虚拟地址空间。当创建一个进程时,内核会为该进程分配一个线性的地址空间(虚拟地址空间),有了虚拟地址空间后,内核就可以通过页表将进程的物理地址地址空间映射到其虚拟地址空间中,程序员所能看到的其实都是虚拟地址,物理地址对程序员而言是透明的。当程序运行时,MMU硬件机制会将程序中的虚拟地址转换成物理地址,然后在内存中找到指令和数据,来执行进程的代码。下面我们就来分析和进程的地址空间相关的各种数据结构和操作。

用到的数据结构:

1.内存描述符struct mm_struct (include/linux/mm_types.h)

 struct mm_struct {
struct vm_area_struct *mmap; /* list of VMAs */
struct rb_root mm_rb;
u32 vmacache_seqnum; /* per-thread vmacache */
#ifdef CONFIG_MMU
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
#endif
unsigned long mmap_base; /* base of mmap area */
unsigned long mmap_legacy_base; /* base of mmap area in bottom-up allocations */
unsigned long task_size; /* size of task vm space */
unsigned long highest_vm_end; /* highest vma end address */
pgd_t * pgd;
atomic_t mm_users; /* How many users with user space? */
atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */
atomic_long_t nr_ptes; /* Page table pages */
int map_count; /* number of VMAs */ spinlock_t page_table_lock; /* Protects page tables and some counters */
struct rw_semaphore mmap_sem; struct list_head mmlist; /* List of maybe swapped mm's. These are globally strung
* together off init_mm.mmlist, and are protected
* by mmlist_lock
*/ unsigned long hiwater_rss; /* High-watermark of RSS usage */
unsigned long hiwater_vm; /* High-water virtual memory usage */ unsigned long total_vm; /* Total pages mapped */
unsigned long locked_vm; /* Pages that have PG_mlocked set */
unsigned long pinned_vm; /* Refcount permanently increased */
unsigned long shared_vm; /* Shared pages (files) */
unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE */
unsigned long stack_vm; /* VM_GROWSUP/DOWN */
unsigned long def_flags;
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end; unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */ /*
* Special counters, in some configurations protected by the
* page_table_lock, in other configurations by being atomic.
*/
struct mm_rss_stat rss_stat; struct linux_binfmt *binfmt; cpumask_var_t cpu_vm_mask_var; /* Architecture-specific MM context */
mm_context_t context; unsigned long flags; /* Must use atomic bitops to access the bits */ struct core_state *core_state; /* coredumping support */
#ifdef CONFIG_AIO
spinlock_t ioctx_lock;
struct kioctx_table __rcu *ioctx_table;
#endif
#ifdef CONFIG_MEMCG
/*
* "owner" points to a task that is regarded as the canonical
* user/owner of this mm. All of the following must be true in
* order for it to be changed:
*
* current == mm->owner
* current->mm != mm
* new_owner->mm == mm
* new_owner->alloc_lock is held
*/
struct task_struct __rcu *owner;
#endif /* store ref to file /proc/<pid>/exe symlink points to */
struct file *exe_file;
#ifdef CONFIG_MMU_NOTIFIER
struct mmu_notifier_mm *mmu_notifier_mm;
#endif
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
#ifdef CONFIG_CPUMASK_OFFSTACK
struct cpumask cpumask_allocation;
#endif
#ifdef CONFIG_NUMA_BALANCING
/*
* numa_next_scan is the next time that the PTEs will be marked
* pte_numa. NUMA hinting faults will gather statistics and migrate
* pages to new nodes if necessary.
*/
unsigned long numa_next_scan; /* Restart point for scanning and setting pte_numa */
unsigned long numa_scan_offset; /* numa_scan_seq prevents two threads setting pte_numa */
int numa_scan_seq;
#endif
#if defined(CONFIG_NUMA_BALANCING) || defined(CONFIG_COMPACTION)
/*
* An operation with batched TLB flushing is going on. Anything that
* can move process memory needs to flush the TLB when moving a
* PROT_NONE or PROT_NUMA mapped page.
*/
bool tlb_flush_pending;
#endif
struct uprobes_state uprobes_state;
};

struct mm_struct

每个进程描述符中都包含一个内存描述符,用来存放进程的虚拟地址空间。该结构体中的成员比较多,我们简要的介绍几个,以后用到时再具体分析。第2行的mmap指向了该虚拟地址空间中的第一个线性区,下边有介绍。内核将该虚拟地址空间中的所有线性区组织成一棵红黑数,第3行的mm_rb指向了红黑树的树根。第10行保存了虚拟地址空间的开始地址,第12行保存了该虚拟地址空间的大小,第13行保存了虚拟地址空间的结束地址。第14行pgd保存了进程的页全局目录表的地址。第18行保存了该虚拟地址空间的线性区个数。内核将所有进程的内存描述符(每个进程有一个)通过第23行的mmlist组织成双向链表。第39行分别代表虚拟地址空间中的代码段的起始和结束地址,数据段的起始和结束地址。第40行分别代表堆区的起始和结束地址,堆栈的起始地址。第41行分别代表了进程的命令行参数起始和结束地址,环境变量的起始和结束地址。对于内核线程而言,没有线性区。

2.线性区struct vm_area_struct(include/linux/mm_types.h)

 struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */ unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address
within vm_mm. */ /* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev; struct rb_node vm_rb; /*
* Largest free memory gap in bytes to the left of this VMA.
* Either between this VMA and vma->vm_prev, or between one of the
* VMAs below us in the VMA rbtree and its ->vm_prev. This helps
* get_unmapped_area find a free area of the right size.
*/
unsigned long rb_subtree_gap; /* Second cache line starts here. */ struct mm_struct *vm_mm; /* The address space we belong to. */
pgprot_t vm_page_prot; /* Access permissions of this VMA. */
unsigned long vm_flags; /* Flags, see mm.h. */ /*
* For areas with an address space and backing store,
* linkage into the address_space->i_mmap interval tree, or
* linkage of vma in the address_space->i_mmap_nonlinear list.
*/
union {
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} linear;
struct list_head nonlinear;
} 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.
*/
struct list_head anon_vma_chain; /* Serialized by mmap_sem &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */ /* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops; /* Information about our backing store: */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
units, *not* PAGE_CACHE_SIZE */
struct file * vm_file; /* File we map to (can be NULL). */
void * vm_private_data; /* was vm_pte (shared mem) */ #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
};

struct vm_area_struct

从上边可以看到,每个内存描述符中包含若干线性区,并且组织成红黑树的形式。内存描述符代表了进程的整个虚拟地址空间,包括了数据段,代码段,堆栈段等等,而每个线性区则用来表示一个段(区),比如代码段和数据段等。因此内存描述符中必然会有多个线性区。对于大型进程而言(比如数据库),它的线性区非常多,这就给搜索线性区带来很大挑战,于是将线性区组织成红黑树的形式,便于快速搜索。下面简单介绍下该结构体中的成员。第4-5行保存了该线性区的起始和结束虚拟地址。内存描述符中的所有线性区会用两种数据结构进行组织,一个是双向链表,另一个就是红黑树,从第9和11行可看出。使用两种数据结构来组织,更加方便于管理,当线性区数量不太多的时候,使用链表来搜索方便又快捷;当线性区数量很庞大时,链表就捉襟见肘了,此时红黑树就能派上用场。二者相互补充相互配合。第23行指向了该线性区所属的内存描述符。第24行存放该线性区的访问权限。第25行存放该线性区所涉及页的相关标志(这些标志表明页是可读的可写的还是可执行的等等)。这些标志定义在include/linux/mm.h中。线性区中的这些访问权限和标志用来设置页表的权限和标志。

3.红黑树节点(include/linux/rbtree.h)

 struct rb_node {
unsigned long __rb_parent_color;
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

第2行__rb_parent_color隐含的指向了其父节点,其中最低的1bit保存了该节点的颜色(红或黑)。对任何体系结构而言,指针都要求至少要四字节对齐,因此最低的两个bit位无用,可以保存颜色信息。在使用父指针时,要屏蔽掉最低两位,使用rb_parent函数提取父指针。第3-4行分别指向节点的左,右孩子。

线性区的处理函数:

1.find_vma函数(mm/mmap.c)

 struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
{
struct rb_node *rb_node;
struct vm_area_struct *vma; /* Check the cache first. */
vma = vmacache_find(mm, addr);
if (likely(vma))
return vma; rb_node = mm->mm_rb.rb_node;
vma = NULL; while (rb_node) {
struct vm_area_struct *tmp; tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb); if (tmp->vm_end > addr) {
vma = tmp;
if (tmp->vm_start <= addr)
break;
rb_node = rb_node->rb_left;
} else
rb_node = rb_node->rb_right;
} if (vma)
vmacache_update(addr, vma);
return vma;
}

该函数用来查找addr地址所在的线性区,返回找到的线性区指针。该函数接收两个参数,一个是内核描述符,另一个是虚拟地址,所找到的线性区要么包含addr地址,要么在addr地址的右侧(不包含addr地址)。进程描述符中的vmacache[i]域中装有若干刚使用过的线性区,那么按照程序执行的局部性原理,新给出的虚拟地址在这些线性区中的概率非常大,因此第7行使用vmacache_find函数现在vmacache数组中查找,若找到直接返回。否则,就在红黑树中找。第11行将红黑树的树根保存到rb_node中,第14-26行,遍历整棵红黑树,第17行获取红黑树节点rb_node所在的线性区描述符指针保存到tmp中,第19行如果该线性区的vm_end>addr,那么addr一定在该线性区或者该线性区之前的线性区,则遍历红黑树的左子树,否则遍历右子树,第21行如果addr>=某线性区的vm_start,无疑addr处于该线性区中。(线性区既然组织成红黑树的形式,那么根的左子树所有vm_end一定小于根,根的右子树所有vm_end一定大于根),第29行将找到的线性区更新至vmacache数组中,第30行返回找到的线性区指针,如果vma红黑树为空(没有任何线性区),返回null。

2.find_vma_intersection函数(include/linux/mm.h)

 static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
{
struct vm_area_struct * vma = find_vma(mm,start_addr); if (vma && end_addr <= vma->vm_start)
vma = NULL;
return vma;
}

该进程查找一个和以start_addr地址开始,以end_addr结束的线性区之后的第一个线性区(可以相连)。

3.get_unmapped_area函数(mm/mmap.c)

 unsigned long
get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags)
{
unsigned long (*get_area)(struct file *, unsigned long,
unsigned long, unsigned long, unsigned long); unsigned long error = arch_mmap_check(addr, len, flags);
if (error)
return error; /* Careful about overflows.. */
if (len > TASK_SIZE)
return -ENOMEM; get_area = current->mm->get_unmapped_area;
if (file && file->f_op->get_unmapped_area)
get_area = file->f_op->get_unmapped_area;
addr = get_area(file, addr, len, pgoff, flags);
if (IS_ERR_VALUE(addr))
return addr; if (addr > TASK_SIZE - len)
return -ENOMEM;
if (addr & ~PAGE_MASK)
return -EINVAL; addr = arch_rebalance_pgtables(addr, len);
error = security_mmap_addr(addr);
return error ? error : addr;
}

该函数用来查找起始地址为addr,长度为len的空闲区域(从线性区和线性区之间的空隙查找)。如果是为了文件的内核映射而查找,那么参数file不为空,第18行,get_area指针指向file结构体中的get_unmapped_area函数;如果是为了匿名映射查找,第16行get_area指针指向内存描述符中的get_unmapped_area函数。第19行执行get_area函数。file结构体中的get_unmapped_area函数我们不讨论,只讨论内存描述符中的该函数。内存描述符中的该函数有两个版本,分别是arch_get_unmapped_area和arch_get_unmapped_area_topdown,前者从线性地址的低地址向高端地址方向查找,后者从用户态堆栈开始向低地址方向查找,根据需求选择不同的函数。下面分析下arch_get_unmapped_area函数的代码(mm/mmap.c):

 unsigned long
arch_get_unmapped_area(struct file *filp, unsigned long addr,
unsigned long len, unsigned long pgoff, unsigned long flags)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
struct vm_unmapped_area_info info; if (len > TASK_SIZE - mmap_min_addr)
return -ENOMEM; if (flags & MAP_FIXED)
return addr; if (addr) {
addr = PAGE_ALIGN(addr);
vma = find_vma(mm, addr);
if (TASK_SIZE - len >= addr && addr >= mmap_min_addr &&
(!vma || addr + len <= vma->vm_start))
return addr;
} info.flags = ;
info.length = len;
info.low_limit = mm->mmap_base;
info.high_limit = TASK_SIZE;
info.align_mask = ;
return vm_unmapped_area(&info);
}

第15行将addr地址按照页面地址向上对齐(调整为4K的整数倍),第17行可看出该函数是通过调用find_vma实现查找,找到的vma中可能包含addr(这种情况下就说明以addr地址开始没有可用的vma),也可能在addr之后,还可能为null,后两者才表示存在起始地址为addr,长度为len的空闲区域。第18行如果0<=找到的地址+len<=用户虚拟地址空间大小TASK_SIZE(4TB),并且要么vma为空(说明vma红黑树为空,没有地址被线性区占用,所有地址都空闲),要么vma在addr之后,同时addr+len不能延伸到vma中,返回addr地址。否则的话,说明没有满足起始地址为addr,长度为len的空闲区域,就执行第28行的函数,从整个虚拟地址空间中找满足长度len的空闲区域。下面看下vm_unmapped_area函数(include/linux/mm.h)。

 static inline unsigned long
vm_unmapped_area(struct vm_unmapped_area_info *info)
{
if (!(info->flags & VM_UNMAPPED_AREA_TOPDOWN))
return unmapped_area(info);
else
return unmapped_area_topdown(info);
}

该函数调用unmapped_area或者unmapped_area_topdown来查找空闲的区域。我们分析下前者,后者和前者类似。代码如下:

 unsigned long unmapped_area(struct vm_unmapped_area_info *info)
{
/*
* We implement the search by looking for an rbtree node that
* immediately follows a suitable gap. That is,
* - gap_start = vma->vm_prev->vm_end <= info->high_limit - length;
* - gap_end = vma->vm_start >= info->low_limit + length;
* - gap_end - gap_start >= length
*/ struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long length, low_limit, high_limit, gap_start, gap_end; /* Adjust search length to account for worst case alignment overhead */
length = info->length + info->align_mask;
if (length < info->length)
return -ENOMEM; /* Adjust search limits by the desired length */
if (info->high_limit < length)
return -ENOMEM;
high_limit = info->high_limit - length; if (info->low_limit > high_limit)
return -ENOMEM;
low_limit = info->low_limit + length; /* Check if rbtree root looks promising */
if (RB_EMPTY_ROOT(&mm->mm_rb))
goto check_highest;
vma = rb_entry(mm->mm_rb.rb_node, struct vm_area_struct, vm_rb);
if (vma->rb_subtree_gap < length)
goto check_highest; while (true) {
/* Visit left subtree if it looks promising */
gap_end = vma->vm_start;
if (gap_end >= low_limit && vma->vm_rb.rb_left) {
struct vm_area_struct *left =
rb_entry(vma->vm_rb.rb_left,
struct vm_area_struct, vm_rb);
if (left->rb_subtree_gap >= length) {
vma = left;
continue;
}
} gap_start = vma->vm_prev ? vma->vm_prev->vm_end : ;
check_current:
/* Check if current node has a suitable gap */
if (gap_start > high_limit)
return -ENOMEM;
if (gap_end >= low_limit && gap_end - gap_start >= length)
goto found; /* Visit right subtree if it looks promising */
if (vma->vm_rb.rb_right) {
struct vm_area_struct *right =
rb_entry(vma->vm_rb.rb_right,
struct vm_area_struct, vm_rb);
if (right->rb_subtree_gap >= length) {
vma = right;
continue;
}
} /* Go back up the rbtree to find next candidate node */
while (true) {
struct rb_node *prev = &vma->vm_rb;
if (!rb_parent(prev))
goto check_highest;
vma = rb_entry(rb_parent(prev),
struct vm_area_struct, vm_rb);
if (prev == vma->vm_rb.rb_left) {
gap_start = vma->vm_prev->vm_end;
gap_end = vma->vm_start;
goto check_current;
}
}
} check_highest:
/* Check highest gap, which does not precede any rbtree node */
gap_start = mm->highest_vm_end;
gap_end = ULONG_MAX; /* Only for VM_BUG_ON below */
if (gap_start > high_limit)
return -ENOMEM; found:
/* We found a suitable gap. Clip it with the original low_limit. */
if (gap_start < info->low_limit)
gap_start = info->low_limit; /* Adjust gap address to the desired alignment */
gap_start += (info->align_offset - gap_start) & info->align_mask; VM_BUG_ON(gap_start + info->length > info->high_limit);
VM_BUG_ON(gap_start + info->length > gap_end);
return gap_start;
}

unmapped_area

如果前边找不到合适的空闲区域,那么就通过该函数查找,该函数只查找红黑树根的左侧是否有满足大小的空闲区域,有的话分配,没有的话在高端地址上分配(高端地址的话和红黑树就没有关系了)。所谓的左侧,是因为线性区除了以红黑树的形式组织外,还以链表进行组织,二者都可以访问所有的线性区节点,而节点在链表中是按照地址升序进行排列的,那么一个节点的左侧就是指小于该线性区地址的区域。第32行将红黑树根所对应的线性区指针赋给vma,第33-34判断根节点的左侧是否有满足大小的空闲区域(vma的rb_subtree_gap成员保存了该vma左侧最大的空闲区的大小),如果没有的话,跳转到check_highest处。否则,说明根节点的左侧有足够的空闲,执行第36行的大循环,第38行根节点的起始地址暂时当作空闲区域的结束地址(因为要遍历左子树,左子树的线性地址小于根,因此根的地址作为结束地址),第39-42左子树非空的话,left指向左子树的根,第43行判断left节点的左侧是否有足够的空间,有的话,vma指向左子树的根,进入下一次循环,开始对左子树遍历。如果某个根节点左子树为空或者其左侧没有足够大空闲区域,第49行将该根节点的前一个节点(如果存在的话),或者0暂时作为空闲区域的起始地址,第54行该空闲区域的大小满足需求,跳到found处标志找到空闲区域了。否则进入第58行,如果该根节点的右子树不为空并且右子树根节点的左侧有满足大小的空闲区域,则对右子树进行遍历,进入下一次循环,整个过程类似于递归。如果该根节点的右子树为空或者右子树根节点的左侧没有满足大小的空闲区域,进入第69行,使prev指向该根节点,使vma指向根节点在红黑树中的父节点。第75行如果prev节点是vma的左孩子,则使vma节点的vm_prev(vma节点在链表中的前驱节点)的结束地址作为空闲区域的开始地址,vma的开始地址作为空闲区域的结束地址,跳到check_current处进行比较;否则继续第69行小循环,直到找出满足prev是vma的左孩子这样的条件。第83行,如果在红黑树中找不到合适区域,就会挑战到这里,在高端地址分配空闲区域,内存描述符mm的highest_vm_end域保存了线性区的结束地址,同时也是高端区域的起始地址(高端区域不用红黑树来管理),第85行将该起始地址作为空闲区域的起始地址,第87行如果空闲区域起始地址超出了最大限制,返回错误码,否则意味着找到了,第92-93如果起始地址小于最低限制,则将其调整为最低限制的地址,第96行对起始地址按照预设的对齐方式对齐后,第100行返回找到的空闲区域起始地址。分析了这么多,肯定很难理解吧,笔者给大家一个建议,画一棵红黑树,然后中序遍历这棵红黑树就得到了红黑树节点在链表中的顺序,接着可以假设链表中不同的相邻节点之间存在满足大小的空闲区域,进而按照该函数的算法去遍历一下红黑树,看是否能找到你所假设的那些满足大小的区域,该算法复杂又精妙~。

4.insert_vm_struct(mm/mmap.c)

 int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)
{
struct vm_area_struct *prev;
struct rb_node **rb_link, *rb_parent; /*
* The vm_pgoff of a purely anonymous vma should be irrelevant
* until its first write fault, when page's anon_vma and index
* are set. But now set the vm_pgoff it will almost certainly
* end up with (unless mremap moves it elsewhere before that
* first wfault), so /proc/pid/maps tells a consistent story.
*
* By setting it to reflect the virtual start address of the
* vma, merges and splits can happen in a seamless way, just
* using the existing file pgoff checks and manipulations.
* Similarly in do_mmap_pgoff and in do_brk.
*/
if (!vma->vm_file) {
BUG_ON(vma->anon_vma);
vma->vm_pgoff = vma->vm_start >> PAGE_SHIFT;
}
if (find_vma_links(mm, vma->vm_start, vma->vm_end,
&prev, &rb_link, &rb_parent))
return -ENOMEM;
if ((vma->vm_flags & ) &&
security_vm_enough_memory_mm(mm, vma_pages(vma)))
return -ENOMEM; vma_link(mm, vma, prev, rb_link, rb_parent);
return ;
}

该函数向线性区红黑树以及线性区链表中插入一个线性区节点。参数mm是红黑树所在的内存描述符地址,vma是要插入的线性区。第18-20行,如果线性区的不是文件映射的话,设置它的vm_pgoff域。第22行查找要插入的位置,后边分析find_vma_links函数。第25-27行如果该线性区用作IPC共享区,那么检查是否还有足够的地址空间供插入,没有的话返回错误码。第29行将vma插入红黑树中,随后分析。下面我们先分析find_vma_links函数代码(mm/mmap.c):

 static int find_vma_links(struct mm_struct *mm, unsigned long addr,
unsigned long end, struct vm_area_struct **pprev,
struct rb_node ***rb_link, struct rb_node **rb_parent)
{
struct rb_node **__rb_link, *__rb_parent, *rb_prev; __rb_link = &mm->mm_rb.rb_node;
rb_prev = __rb_parent = NULL; while (*__rb_link) {
struct vm_area_struct *vma_tmp; __rb_parent = *__rb_link;
vma_tmp = rb_entry(__rb_parent, struct vm_area_struct, vm_rb); if (vma_tmp->vm_end > addr) {
/* Fail if an existing vma overlaps the area */
if (vma_tmp->vm_start < end)
return -ENOMEM;
__rb_link = &__rb_parent->rb_left;
} else {
rb_prev = __rb_parent;
__rb_link = &__rb_parent->rb_right;
}
} *pprev = NULL;
if (rb_prev)
*pprev = rb_entry(rb_prev, struct vm_area_struct, vm_rb);
*rb_link = __rb_link;
*rb_parent = __rb_parent;
return ;
}

该函数为新的线性区寻找适当的插入位置。第7行使__rb_link指向红黑树的根节点。第10-25行对整棵树进行遍历来寻找插入位置。第13行使__rb_parent也指向当前根节点,第14行vma_tmp保存根节点所在的线性区指针。第16-20行,如果addr地址小于当前线性区的结束地址,说明要查入的位置在当前线性区的左子树上,第18行需要保证要插入的线性区的结束地址没有延伸到当前线性区内,如果覆盖到了当前线性区则返回错误码,否则遍历当前节点的左子树,继续寻找插入位置。第21-23行说明addr地址大于当前根节点的结束地址,则要查入的位置在当前线性区的右子树上,那么遍历右子树查找。一直遍历到某个节点,该节点要么是叶子节点,要么没有左孩子或者右孩子,这时循环退出循环(保证至少一个孩子为空才能打破循环条件),该节点就是待插入节点,新节点将成为该节点的左孩子或者右孩子。第29行将待插入节点所在的线性区指针保存到*pprev中传递回调用函数。第30行将待插入叶子节点的左孩子或者右孩子的指针(二级指针)保存到*rb_link中传递回调用函数,在这里二级指针值非空,但是指向的值一定为NULL。第31行将待插入节点的指针存入*rb_parent中传递回调用函数,该指针不同于*pprev。下面分析vma_link函数(mm/mmap.c):

 static void vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
struct vm_area_struct *prev, struct rb_node **rb_link,
struct rb_node *rb_parent)
{
struct address_space *mapping = NULL; if (vma->vm_file) {
mapping = vma->vm_file->f_mapping;
mutex_lock(&mapping->i_mmap_mutex);
} __vma_link(mm, vma, prev, rb_link, rb_parent);
__vma_link_file(vma); if (mapping)
mutex_unlock(&mapping->i_mmap_mutex); mm->map_count++;
validate_mm(mm);
}

该函数通过第12行的__vma_link函数将vma插入到红黑树和链表中。第18行增加线性区的数量。接着看下__vma_link函数代码(mm/mmap.c):

 static void
__vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
struct vm_area_struct *prev, struct rb_node **rb_link,
struct rb_node *rb_parent)
{
__vma_link_list(mm, vma, prev, rb_parent);
__vma_link_rb(mm, vma, rb_link, rb_parent);
}

可以看到,第6-7行分别将vma插入到了链表和红黑树中。第一个函数我们就不分析了,很简单的双向链表插入~,直接来看__vma_link_rb函数(mm/mmap.c):

 void __vma_link_rb(struct mm_struct *mm, struct vm_area_struct *vma,
struct rb_node **rb_link, struct rb_node *rb_parent)
{
/* Update tracking information for the gap following the new vma. */
if (vma->vm_next)
vma_gap_update(vma->vm_next);
else
mm->highest_vm_end = vma->vm_end; /*
* vma->vm_prev wasn't known when we followed the rbtree to find the
* correct insertion point for that vma. As a result, we could not
* update the vma vm_rb parents rb_subtree_gap values on the way down.
* So, we first insert the vma with a zero rb_subtree_gap value
* (to be consistent with what we did on the way down), and then
* immediately update the gap to the correct value. Finally we
* rebalance the rbtree after all augmented values have been set.
*/
rb_link_node(&vma->vm_rb, rb_parent, rb_link);
vma->rb_subtree_gap = ;
vma_gap_update(vma);
vma_rb_insert(vma, &mm->mm_rb);
}

第5-6行如果新插入的线性区的在链表中的下一个线性区存在,则更新下一个线性区的rb_subtree_gap域,否则第8行将新插入线性区的结束地址保存到内存描述符的highest_vm_end域(如果新线性区的vm_next域为空,本身就说明新线性区是最后一个线性区,其结束地址理应作为所作为mm的highest_vm_end值)。第19行将新节点插入到待插节点上(只是插入红黑树的一个步骤,后边还要对红黑树进行着色和旋转等操作),并对新节点初始化。第20行初始化新线性区的rb_subtree_gap,第21行进一步更新新线性区的rb_subtree_gap域。我们最关心的是第22行的vma_rb_insert函数,该函数进一步完善了新线性区在红黑树上的插入操作。代码如下(mm/mmap.c):

 static inline void vma_rb_insert(struct vm_area_struct *vma,
struct rb_root *root)
{
/* All rb_subtree_gap values must be consistent prior to insertion */
validate_mm_rb(root, NULL); rb_insert_augmented(&vma->vm_rb, root, &vma_gap_callbacks);
}

第5行的函数遍历整棵红黑树,对所有节点进行错误检查,有错误的话,内核就会发出oops~。第7行的函数调用了__rb_insert_augmented-----> __rb_insert,所以我们直接看 __rb_insert函数,代码如下(lib/rbtree.c):

 static __always_inline void
__rb_insert(struct rb_node *node, struct rb_root *root,
void (*augment_rotate)(struct rb_node *old, struct rb_node *new))
{
struct rb_node *parent = rb_red_parent(node), *gparent, *tmp; while (true) {
/*
* Loop invariant: node is red
*
* If there is a black parent, we are done.
* Otherwise, take some corrective action as we don't
* want a red root or two consecutive red nodes.
*/
if (!parent) {
rb_set_parent_color(node, NULL, RB_BLACK);
break;
} else if (rb_is_black(parent))
break; gparent = rb_red_parent(parent); tmp = gparent->rb_right;
if (parent != tmp) { /* parent == gparent->rb_left */
if (tmp && rb_is_red(tmp)) {
/*
* Case 1 - color flips
*
* G g
* / \ / \
* p u --> P U
* / /
* n N
*
* However, since g's parent might be red, and
* 4) does not allow this, we need to recurse
* at g.
*/
rb_set_parent_color(tmp, gparent, RB_BLACK);
rb_set_parent_color(parent, gparent, RB_BLACK);
node = gparent;
parent = rb_parent(node);
rb_set_parent_color(node, parent, RB_RED);
continue;
} tmp = parent->rb_right;
if (node == tmp) {
/*
* Case 2 - left rotate at parent
*
* G G
* / \ / \
* p U --> n U
* \ /
* n p
*
* This still leaves us in violation of 4), the
* continuation into Case 3 will fix that.
*/
parent->rb_right = tmp = node->rb_left;
node->rb_left = parent;
if (tmp)
rb_set_parent_color(tmp, parent,
RB_BLACK);
rb_set_parent_color(parent, node, RB_RED);
augment_rotate(parent, node);
parent = node;
tmp = node->rb_right;
} /*
* Case 3 - right rotate at gparent
*
* G P
* / \ / \
* p U --> n g
* / \
* n U
*/
gparent->rb_left = tmp; /* == parent->rb_right */
parent->rb_right = gparent;
if (tmp)
rb_set_parent_color(tmp, gparent, RB_BLACK);
__rb_rotate_set_parents(gparent, parent, root, RB_RED);
augment_rotate(gparent, parent);
break;
} else {
tmp = gparent->rb_left;
if (tmp && rb_is_red(tmp)) {
/* Case 1 - color flips */
rb_set_parent_color(tmp, gparent, RB_BLACK);
rb_set_parent_color(parent, gparent, RB_BLACK);
node = gparent;
parent = rb_parent(node);
rb_set_parent_color(node, parent, RB_RED);
continue;
} tmp = parent->rb_left;
if (node == tmp) {
/* Case 2 - right rotate at parent */
parent->rb_left = tmp = node->rb_right;
node->rb_right = parent;
if (tmp)
rb_set_parent_color(tmp, parent,
RB_BLACK);
rb_set_parent_color(parent, node, RB_RED);
augment_rotate(parent, node);
parent = node;
tmp = node->rb_left;
} /* Case 3 - left rotate at gparent */
gparent->rb_right = tmp; /* == parent->rb_left */
parent->rb_left = gparent;
if (tmp)
rb_set_parent_color(tmp, gparent, RB_BLACK);
__rb_rotate_set_parents(gparent, parent, root, RB_RED);
augment_rotate(gparent, parent);
break;
}
}
}

__rb_insert

该函数是进行红黑树插入操作的内核函数,只要用到红黑树的地方,最终都会调用该函数完成节点插入。因此该函数是我们分析的重点。第5行将node节点的父指针(完整的父指针,包括最低bit位的颜色信息)赋给parent变量。第15-17行如果parent为空,说明当前node节点就是红黑树根,对其着黑色,然后退出循环,完成操作。否则第18-19行,parent存在并且颜色是黑色,则不需要任何操作,退出循环。否则,说明parent存在但是颜色为红色,执行21行,将parent节点的父指针(node节点的祖父)赋给gparent变量。第23行,tmp指向了node爷爷的右孩子(可能是node的父亲也可能是node的叔叔),第24行如果tmp是node的叔叔的话,执行if体。(case1颜色翻转)第25-45行,如果node的叔叔tmp是红色的(node的父亲已经是红色的),则进行颜色翻转:第39-49行将node的叔叔tmp和父亲parent着为黑色,第43行将node的祖父gparent着为红色,完成翻转,进入下一次循环;如果node没有叔叔或者其叔叔为黑色,则不进行case1的翻转,第47行使tmp指向node父亲的右孩子(可能是node本身或者node的兄弟),(case2父节点左旋)如果node是其父亲节点的右孩子,对node的父亲左旋,使node和其父亲角色互换,并使其父亲成为node的左孩子,同时使node原先的左孩子成为其原先父亲的右孩子。就代码而言,第61行使node的左孩子成为其父亲的右孩子,同时也使tmp指向node的左孩子,第62行使node的父亲成为node的左孩子,第63-64行,如果node原先存在左孩子,则对该左孩子(已经成为node原先父亲的右孩子了)着黑色,第66行对node原先父亲着红色,第68行使parent变量指向node节点,第69行使tmp指向node的右孩子,剩下的部分将在case3中完成;(case3祖父节点右旋)只要node的父亲是其祖父的左孩子,就要执行第81-87行,对node的祖父节点右旋(case2的剩下部分也是这么处理):使node的父亲成为树根,node的祖父成为其父亲的右孩子,node的父亲的右孩子成为其祖父的左孩子,就代码而言,第81行使node父亲的右孩子tmp成为其祖父的左孩子,第82行使node的祖父成为其父亲的右孩子,第83-87行,如果node父亲原先的右孩子tmp不为空,将tmp着为黑色。第85行函数将node节点原先的祖父的颜色着给node原先的父亲(也是现在的父亲),然后给node节点原先的祖父着红色,再使node原先祖父的父节点成为其现在父亲的父节点,完成右旋。如果第24行node的父亲节点是其祖父节点的右孩子(那么tmp就指向了node的父亲,而不是叔叔),则执行第88行以后的部分,也分为三种旋转。(case1‘颜色翻转)第89行使tmp指向node祖父的左孩子,也就是node的叔叔,第90-97行如果node的叔叔存在并且是红色的,就要进行case1‘翻转,第92-93给node的父亲和叔叔着黑色,第96行给node的祖父着红色,完成case1‘翻转,进入下一次循环。(case2‘父节点右旋)否则node的叔叔不存在或者存在但是颜色为黑色,就要对node父亲右旋,第100行使tmp指向其父亲的左孩子(可能是node本身或者node的兄弟),第101行node是其父亲的左孩子,则进行case2‘旋转,第103行使node的右孩子成为其父亲的左孩子,同时使tmp指向node的右孩子,第104行使node的父亲成为node的右孩子,第105-106如果node原先存在右孩子,则给该右孩子着黑色,第108行给node原先的节点着红色。第110-111行使parent变量指向node节点,使tmp指向node的左孩子,剩下的部分将在case3‘中完成;(case3‘祖父节点左旋)只要node的父亲是其祖父的左孩子,就要执行第115-121行,对node的祖父节点左旋(case2‘的剩下部分也是这么处理):使node的父亲成为树根,node的祖父成为其父亲的左孩子,node的父亲的左孩子成为其祖父的右孩子,就代码而言,第115行使node父亲的左孩子tmp成为其祖父的右孩子,第116行使node的祖父成为其父亲的左孩子,第117-118行,如果node父亲原先的左孩子tmp不为空,将tmp着为黑色。第119行函数将node节点原先的祖父的颜色着给node原先的父亲(也是现在的父亲),然后给node节点原先的祖父着红色,再使node原先祖父的父节点成为其现在父亲的父节点,完成左旋。可以看到,其实case1’-3‘和case1-3是完全对称的操作过程,笔者在这里不厌其烦,都详细的写出来,希望大家结合红黑树操作几遍就知道翻转和旋转的算法是怎么回事了~

5.do_mmap_pgoff函数(mm/mmap.c)

 unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flags, unsigned long pgoff,
unsigned long *populate)
{
struct mm_struct * mm = current->mm;
vm_flags_t vm_flags; *populate = ; /*
* Does the application expect PROT_READ to imply PROT_EXEC?
*
* (the exception is when the underlying filesystem is noexec
* mounted, in which case we dont add PROT_EXEC.)
*/
if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
if (!(file && (file->f_path.mnt->mnt_flags & MNT_NOEXEC)))
prot |= PROT_EXEC; if (!len)
return -EINVAL; if (!(flags & MAP_FIXED))
addr = round_hint_to_min(addr); /* Careful about overflows.. */
len = PAGE_ALIGN(len);
if (!len)
return -ENOMEM; /* offset overflow? */
if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)
return -EOVERFLOW; /* Too many mappings? */
if (mm->map_count > sysctl_max_map_count)
return -ENOMEM; /* Obtain the address to map to. we verify (or select) it and ensure
* that it represents a valid section of the address space.
*/
addr = get_unmapped_area(file, addr, len, pgoff, flags);
if (addr & ~PAGE_MASK)
return addr; /* Do simple checking here so the lower-level routines won't have
* to. we assume access permissions have been handled by the open
* of the memory object, so we don't do any here.
*/
vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |
mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; if (flags & MAP_LOCKED)
if (!can_do_mlock())
return -EPERM; if (mlock_future_check(mm, vm_flags, len))
return -EAGAIN; if (file) {
struct inode *inode = file_inode(file); switch (flags & MAP_TYPE) {
case MAP_SHARED:
if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))
return -EACCES; /*
* Make sure we don't allow writing to an append-only
* file..
*/
if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE))
return -EACCES; /*
* Make sure there are no mandatory locks on the file.
*/
if (locks_verify_locked(file))
return -EAGAIN; vm_flags |= VM_SHARED | VM_MAYSHARE;
if (!(file->f_mode & FMODE_WRITE))
vm_flags &= ~(VM_MAYWRITE | VM_SHARED); /* fall through */
case MAP_PRIVATE:
if (!(file->f_mode & FMODE_READ))
return -EACCES;
if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) {
if (vm_flags & VM_EXEC)
return -EPERM;
vm_flags &= ~VM_MAYEXEC;
} if (!file->f_op->mmap)
return -ENODEV;
if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
return -EINVAL;
break; default:
return -EINVAL;
}
} else {
switch (flags & MAP_TYPE) {
case MAP_SHARED:
if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
return -EINVAL;
/*
* Ignore pgoff.
*/
pgoff = ;
vm_flags |= VM_SHARED | VM_MAYSHARE;
break;
case MAP_PRIVATE:
/*
* Set pgoff according to addr for anon_vma.
*/
pgoff = addr >> PAGE_SHIFT;
break;
default:
return -EINVAL;
}
} /*
* Set 'VM_NORESERVE' if we should not account for the
* memory use of this mapping.
*/
if (flags & MAP_NORESERVE) {
/* We honor MAP_NORESERVE if allowed to overcommit */
if (sysctl_overcommit_memory != OVERCOMMIT_NEVER)
vm_flags |= VM_NORESERVE; /* hugetlb applies strict overcommit unless MAP_NORESERVE */
if (file && is_file_hugepages(file))
vm_flags |= VM_NORESERVE;
} addr = mmap_region(file, addr, len, vm_flags, pgoff);
if (!IS_ERR_VALUE(addr) &&
((vm_flags & VM_LOCKED) ||
(flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE))
*populate = len;
return addr;
}

do_mmap_pgoff

该函数用来分配线性区并且创建映射。另外,该函数所在文件中还有另外一个函数do_brk,和本函数类似,不过是用来做匿名映射。第17-19行判断新创建线性区的可读属性是否暗含了可执行属性,是的话为新线性区添加可执行属性。第33-34行检查len是否造成地址空间(页面)的溢出,造成溢出的话返回错误码。第37-38检查线性区数量是否超出限制。第43行获取一个空闲的线性区,此函数上边已分析过。第51-52为新线性区添加各种标志(读写执行等),第61-125行,当file结构体指针为空或者非空时,根据新线性区是共享的还是私有的,分别设置不同的标志(VM_SHARED, VM_MAYSHARE)。第141行mmap_region函数分配新的线性区结构体,并检查是否可以与其它线性区进行合并,待会分析该函数。第146行返回新线性区的起始地址。下面看下mmap_region函数(mm/mmap.c):

 unsigned long mmap_region(struct file *file, unsigned long addr,
unsigned long len, vm_flags_t vm_flags, unsigned long pgoff)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma, *prev;
int error;
struct rb_node **rb_link, *rb_parent;
unsigned long charged = ; /* Check against address space limit. */
if (!may_expand_vm(mm, len >> PAGE_SHIFT)) {
unsigned long nr_pages; /*
* MAP_FIXED may remove pages of mappings that intersects with
* requested mapping. Account for the pages it would unmap.
*/
if (!(vm_flags & MAP_FIXED))
return -ENOMEM; nr_pages = count_vma_pages_range(mm, addr, addr + len); if (!may_expand_vm(mm, (len >> PAGE_SHIFT) - nr_pages))
return -ENOMEM;
} /* Clear old maps */
error = -ENOMEM;
munmap_back:
if (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent)) {
if (do_munmap(mm, addr, len))
return -ENOMEM;
goto munmap_back;
} /*
* Private writable mapping: check memory availability
*/
if (accountable_mapping(file, vm_flags)) {
charged = len >> PAGE_SHIFT;
if (security_vm_enough_memory_mm(mm, charged))
return -ENOMEM;
vm_flags |= VM_ACCOUNT;
} /*
* Can we just expand an old mapping?
*/
vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff, NULL);
if (vma)
goto out; /*
* Determine the object being mapped and call the appropriate
* specific mapper. the address has already been validated, but
* not unmapped, but the maps are removed from the list.
*/
vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
if (!vma) {
error = -ENOMEM;
goto unacct_error;
} vma->vm_mm = mm;
vma->vm_start = addr;
vma->vm_end = addr + len;
vma->vm_flags = vm_flags;
vma->vm_page_prot = vm_get_page_prot(vm_flags);
vma->vm_pgoff = pgoff;
INIT_LIST_HEAD(&vma->anon_vma_chain); if (file) {
if (vm_flags & VM_DENYWRITE) {
error = deny_write_access(file);
if (error)
goto free_vma;
}
vma->vm_file = get_file(file);
error = file->f_op->mmap(file, vma);
if (error)
goto unmap_and_free_vma; /* Can addr have changed??
*
* Answer: Yes, several device drivers can do it in their
* f_op->mmap method. -DaveM
* Bug: If addr is changed, prev, rb_link, rb_parent should
* be updated for vma_link()
*/
WARN_ON_ONCE(addr != vma->vm_start); addr = vma->vm_start;
vm_flags = vma->vm_flags;
} else if (vm_flags & VM_SHARED) {
error = shmem_zero_setup(vma);
if (error)
goto free_vma;
} if (vma_wants_writenotify(vma)) {
pgprot_t pprot = vma->vm_page_prot; /* Can vma->vm_page_prot have changed??
*
* Answer: Yes, drivers may have changed it in their
* f_op->mmap method.
*
* Ensures that vmas marked as uncached stay that way.
*/
vma->vm_page_prot = vm_get_page_prot(vm_flags & ~VM_SHARED);
if (pgprot_val(pprot) == pgprot_val(pgprot_noncached(pprot)))
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
} vma_link(mm, vma, prev, rb_link, rb_parent);
/* Once vma denies write, undo our temporary denial count */
if (vm_flags & VM_DENYWRITE)
allow_write_access(file);
file = vma->vm_file;
out:
perf_event_mmap(vma); vm_stat_account(mm, vm_flags, file, len >> PAGE_SHIFT);
if (vm_flags & VM_LOCKED) {
if (!((vm_flags & VM_SPECIAL) || is_vm_hugetlb_page(vma) ||
vma == get_gate_vma(current->mm)))
mm->locked_vm += (len >> PAGE_SHIFT);
else
vma->vm_flags &= ~VM_LOCKED;
} if (file)
uprobe_mmap(vma); /*
* New (or expanded) vma always get soft dirty status.
* Otherwise user-space soft-dirty page tracker won't
* be able to distinguish situation when vma area unmapped,
* then new mapped in-place (which must be aimed as
* a completely new data area).
*/
vma->vm_flags |= VM_SOFTDIRTY; return addr; unmap_and_free_vma:
if (vm_flags & VM_DENYWRITE)
allow_write_access(file);
vma->vm_file = NULL;
fput(file); /* Undo any partial mapping done by a device driver. */
unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end);
charged = ;
free_vma:
kmem_cache_free(vm_area_cachep, vma);
unacct_error:
if (charged)
vm_unacct_memory(charged);
return error;
}

mmap_region

第30行的find_vma_links函数为新线性区查找插入位置,上边已经分析过该函数,如果没找到插入点,说明新线性区可能延伸到了相邻线性区,则执行if体,撤消新线性区。第49行将新线性区和它前后的线性区进行合并(它们的vm_flags标志必须完全相同),第50-51行,如果合并成功,跳转到第120行。否则第58行,申请新的线性区结构体来管理新分配的地址空间。第64-70行对新线性区结构体的各个成员初始化。第72-98行当file结构体指针非空或者新线性区是匿名共享区时,分别对新线性区结构体进行相应初始化。第115行将新线性区插入红黑树和链表中。第124-130行如果新线性区被锁定,则将线性区的页数累加到vma的locked_vm域中。第142行给新线性区中设置脏标志,第144行返回新线性区的起始虚拟地址。

6.do_munmap函数(mm/mmap.c)

 int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
{
unsigned long end;
struct vm_area_struct *vma, *prev, *last; if ((start & ~PAGE_MASK) || start > TASK_SIZE || len > TASK_SIZE-start)
return -EINVAL; if ((len = PAGE_ALIGN(len)) == )
return -EINVAL; /* Find the first overlapping VMA */
vma = find_vma(mm, start);
if (!vma)
return ;
prev = vma->vm_prev;
/* we have start < vma->vm_end */ /* if it doesn't overlap, we have nothing.. */
end = start + len;
if (vma->vm_start >= end)
return ; /*
* If we need to split any vma, do it now to save pain later.
*
* Note: mremap's move_vma VM_ACCOUNT handling assumes a partially
* unmapped vm_area_struct will remain in use: so lower split_vma
* places tmp vma above, and higher split_vma places tmp vma below.
*/
if (start > vma->vm_start) {
int error; /*
* Make sure that map_count on return from munmap() will
* not exceed its limit; but let map_count go just above
* its limit temporarily, to help free resources as expected.
*/
if (end < vma->vm_end && mm->map_count >= sysctl_max_map_count)
return -ENOMEM; error = __split_vma(mm, vma, start, );
if (error)
return error;
prev = vma;
} /* Does it split the last one? */
last = find_vma(mm, end);
if (last && end > last->vm_start) {
int error = __split_vma(mm, last, end, );
if (error)
return error;
}
vma = prev? prev->vm_next: mm->mmap; /*
* unlock any mlock()ed ranges before detaching vmas
*/
if (mm->locked_vm) {
struct vm_area_struct *tmp = vma;
while (tmp && tmp->vm_start < end) {
if (tmp->vm_flags & VM_LOCKED) {
mm->locked_vm -= vma_pages(tmp);
munlock_vma_pages_all(tmp);
}
tmp = tmp->vm_next;
}
} /*
* Remove the vma's, and unmap the actual pages
*/
detach_vmas_to_be_unmapped(mm, vma, prev, end);
unmap_region(mm, vma, prev, start, end); /* Fix up all other VM information */
remove_vma_list(mm, vma); return ;
}

do_munmap

该函数用来删除一个线性区。第13行查找start地址所在的线性区,此函数上边分析过了。第20-22行如果start----start+len的区域位于找到的vma的左侧,则直接返回,什么都不用操作(这也说明要删除的地址空间是空闲的,未非配给线性区)。第31行如果start起始地址位于找到的vma中,执行if体,第39行如果start+len<vma->vm_end(说明要删除的地址空间全部位于所找到的vma中,也说明删除后将产生两个不连续的线性区),同时当前mm中的线性区数量已经达到最大值(此时如果坚持删除的话,所产生的线性区总数必定要超过最大值),则返回错误码。第42行的__split_vma函数,将所找到的vma线性区以start地址为界,划分为两个部分。第49行找到end地址所在的线性区,第50-54行如果end地址处于所找到的last线性区中,则以end地址为界,将last划分为两个部分。第55行使vma指向包含start的线性区,该线性区可能包含start,也可能处于start的右侧。第60-69行,如果待删除的线性区被锁定,则全部解锁。