Linux内存管理分析报告
分析内容: linux 内存管理之sys_brk 实现分析
目 录
1 概述... 2
1.1 报告题目... 2
2 系统调用功能概述... 2
3 数据结构分析... 4
3.1 数据结构... 5
3.2 vm_area_struct结构体... 6
3.3 mm_struct结构体... 8
4 sbrk()系统调用代码分析... 10
4.1 用户空间的收缩... 12
4.1.1 do_munmap. 12
4.2 用户空间的伸展... 28
4.2.1 find_vma_intersection. 28
4.2.2 do_brk. 29
4.3 流程图... 34
4.4 新旧版本对比分析... 35
5 心得体会... 37
6 附录... 39
6.1 参考文献... 39
6.2 相关工具... 39
1 概述
1.1 报告题目
linux 内存管理之sys_brk 实现分析:系统调用brk()的流程,涉及到的主要数据结构,代码分析结果,并画出流程图来表示相关函数之间的相互调用关系。
2 系统调用功能概述
brk和sbrk主要的工作是实现虚拟内存到内存的映射。系统调用sbrk用来调整数据段的上限。进程的brk值是一个位于进程堆空间和它的转折点。Linux进程空间中,1--3G是用户空间,4G则是内核的。用户只能访问用户空间,内核也只能访问内核空间,它们受到MMU的严格控制。如果内核要访问用户空间,也只能通过put_user和get_user这两个宏或类似的宏才可以。
内存分配是这样的:每个进程可访问的虚拟内存空间为3G,但在程序编译时,不可能也没必要为程序分配这么大的空间,只分配并不大的数据段空间,程序中动态分配的空间就是从这一块分配的。如果这块空间不够,malloc函数族(realloc,calloc等)就调用sbrk函数将数据段的下界移动,sbrk函数在内核的管理下将虚拟地址空间映射到内存,供malloc函数使用。(参见linux内核情景分析)。
在Linux系统上,程序被载入内存时,内核为用户进程地址空间建立了代码段、数据段和堆栈段,在数据段与堆栈段之间的空闲区域用于动态内存分配。
下图简要描述了进程内存区域的分布:
下图反映了进程地址空间的管理模型:
进程的地址空间对应的描述结构是 “内存描述符结构”,它表示进程的全部地址空间,——包含了和进程地址空间有关的全部信息,其中当然包含进程的内存区域。
数据段中包括了所有静态分配的数据空间,包括全局变量和说明为static的局部变量。这些空间是进程所必须的基本要求,所以内核在建立一个进程的余兴映象时就分配好这些空间,包括虚存地址区间和页面,并建立好二者间的映射。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的。所不同的是,堆栈空间安置在虚存空间的顶部,运行时由顶向下延伸;代码段和数据段则在底部,在运行时并不向上伸展。而从数据段的顶部end_data到堆栈段地址的下咽这个中间区域则是一个巨大的空洞,这就是可以在余兴时动态分配的空间。最初,这个动态分配空间是从进程的end_data开始的,这个地址为内核和进程所共知。以后,每次动态分配一块“内存”,这个边界就往上推进一段距离,同时内核和进程都要记下当前的边界在哪里。在进程这一边由malloc()或类似的库函数管理,而在内核中则将当前的边界记录在进程的mm_struct结构中。具体的说,mm_struct结构中有一个成分brk,表示动态分配区当前的底部。当一个进程需要分配内存时,将要求的大小与其当前的动态分配区底部边界相加,所得的就是所要求的新边界,也就是sbrk()调用时的参数brk。当内核能满足要求时,系统调用sbrk()返回0,此后新旧两个边界之间的虚存地址就都可以使用了。当内核发现无法满足要求(例如无力空间已经分配完),或者发现新的边界已经过于逼近设于等部的堆栈时,就拒绝分配而返回-1。
3 数据结构分析
一个进程的虚拟地址空间主要由两个数据结来描述。一个是最高层次的:mm_struct,一个是较高层次的:vm_area_structs。最高层次的mm_struct结构描述了一个进程的整个虚拟地址空间。较高层次的结构vm_area_truct描述了虚拟地址空间的一个区间(简称虚拟区)。它们都定义在include\linux\mm_types.h 文件中。
内核数据结构mm_struct中的成员变量start_code和end_code是进程代码段的起始和终止地址,start_data和 end_data是进程数据段的起始和终止地址,start_stack是进程堆栈段起始地址,start_brk是进程动态内存分配起始地址(堆的起始地址),还有一个 brk(堆的当前最后地址),就是动态内存分配当前的终止地址。
3.1 数据结构
在 Linux 内核中对应进程内存区域的数据结构是:vm_area_struct, 内核将每个内存区域作为一个单独的内存对象管理,相应的操作也都一致。采用面向对象方法使 VMA 结构体可以代表多种类型的内存区域--比如内存映射文件或进程的用户空间栈等,对这些区域的操作也都不尽相同。
vm_area_strcut 结构比较复杂,vm_area_struct 是描述进程地址空间的基本管理单元, 它是以链表形式链接,不过为了方便查找,内核又以红黑树(以前的内核使用平衡树)的形式组织内存区域,以便降低搜索耗时。并存两种组织形式,并非冗余:链表用于需要遍历全部节点的时候用,而红黑树适用于在地址空间中定位特定内存区域的时候。内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构。
3.2 vm_area_struct结构体
在这个结构中,vm_start和vm_end记录了这个进程当前使用的虚拟地址空间。假设一个进程的某段合法的起始虚拟地址为A,大小为2个物理页面,就需要一个vm_area_struct来描述这段虚拟地址区域。一个进程有多个虚拟地址区域,这些vm_area_struct根据虚拟地址被组织成一颗红黑树,这主要是为了加速查找的速度。
结构体原型如下:
/*
* This struct defines a memory VMM memory area. There is one of these
* per VM-area/task. A VM area is any part of the process virtual memory
* space that has a special rule for the page-fault handlers (ie a shared
* library, the executable area etc).
*/
struct vm_area_struct {
struct mm_struct * vm_mm; /* 指针指向进程的mm_struct结构体 */
unsigned long vm_start; /* 虚拟区域的开始地址 */
unsigned long vm_end; /* 虚拟区域的终止地址*/
/* 构成线性链表的指针,按虚存区基址从小到大排列*/
struct vm_area_struct *vm_next, *vm_prev;
pgprot_t vm_page_prot; /*虚存区域的页面的保护特性,存取权限*/
unsigned long vm_flags; /*虚拟区间的标志*/
struct rb_node vm_rb; /*指向red_black树*/
/*
* 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.
*/
union { /* 或者是关联于address_space->i_mmap字段,或者是关联于i_mmap_nonlinear字段 */
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;
} 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; /* 匿名的VMA对象 */
/* 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 映射的文件vm_file的页偏移量*/
struct file * vm_file; /* File we map to (can be NULL). 映射的文件指针*/
void * vm_private_data; /* 私有数据 */
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
};
3.3 mm_struct结构体
结构体原型如下:
struct mm_struct {
struct vm_area_struct * mmap; /* 指向虚拟区间(VMA)链表 */
struct rb_root mm_rb; /*指向red_black树*/
struct vm_area_struct * mmap_cache; /* 最后使用内存区域 */
#ifdef CONFIG_MMU
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
#endif
unsigned long mmap_base; /* base of mmap area */
unsigned long task_size; /* size of task vm space */
unsigned long cached_hole_size; /* if non-zero, the largest hole below free_area_cache */
unsigned long free_area_cache; /* first hole of size cached_hole_size or larger */
pgd_t * pgd; /* 页全局目录 */
atomic_t mm_users; /* 该地址空间用户 */
atomic_t mm_count; /* 主使用记数 */
int map_count; /* 内存区域数目 */
struct rw_semaphore mmap_sem; /* 内存区域信号量 */
spinlock_t page_table_lock; /* 页表锁 */
struct list_head mmlist; /*包含全部mm_structs的链表/
unsigned long hiwater_rss; /* High-watermark of RSS usage */
unsigned long hiwater_vm; /* High-water virtual memory usage */
unsigned long total_vm, locked_vm, shared_vm, exec_vm;
unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
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_t cpu_vm_mask; /* 懒惰(lazy)TLB交换掩码 */
/* Architecture-specific MM context */
mm_context_t context; /* 体系结构特殊数据 */
/* Swap token stuff */
/*
* Last value of global fault stamp as seen by this process.
* In other words, this value gives an indication of how long
* it has been since this task got the token.
* Look at mm/thrash.c
*/
unsigned int faultstamp;
unsigned int token_priority;
unsigned int last_interval;
unsigned long flags; /* Must use atomic bitops to access the bits */
struct core_state *core_state; /* core开始完成 core结束完成*/
#ifdef CONFIG_AIO
spinlock_t ioctx_lock; /* AIO I/O链表锁 */
struct hlist_head ioctx_list; /* AIO I/O链表*/
#endif
#ifdef CONFIG_MM_OWNER
/*
* "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 *owner;
#endif
#ifdef CONFIG_PROC_FS
/* store ref to file /proc/<pid>/exe symlink points to */
struct file *exe_file;
unsigned long num_exe_file_vmas;
#endif
#ifdef CONFIG_MMU_NOTIFIER
struct mmu_notifier_mm *mmu_notifier_mm;
#endif
};