虚拟地址空间布局架构
内存管理子系统架构可以分为:用户空间,内核空间以及硬件部分3个层面。
- 用户空间:应用程序使用malloc()申请内存资源/free()释放内存资源
- 内核空间:内核总是驻留在内存中,是操作系统的一部分,内核空间为内核保留,不允许应用程序读写该区域的内容或直接调用内核代码定义的函数。
- 硬件:处理器包含一个内存管理单位( Memory Management Unit ,MMU)的部件,负责把虚拟地址转换为物理地址。
Linux内核只是操作系统当中的一部分,对下管理系统所有硬件设备;对上通过系统调用向Library Routine 或(或者其他应用程序提供的API接口) 用户空间 malloc/free new/delete ptmalloc(glibc) jemalloc(freebsd) tcmalloc(google)
内核空间
sys_brk/sys_mmap/sys_munmap 进行内存管理
虚拟内存管理
页面错误异常处理
页表管理
引导内存分配器包括(页分配器)(块分配器,不连续页分配器,每处理器内存分配器,连续内存分配器)
页回收
内存控制组
内存耗尽
内存碎片整理
硬件
物理内存
中央处理器
缓存
内存管理单元
页表缓存
-
用户空间 相当于应用程序使用malloc()申请内存,通过free()释放内存,通过free()释放内存,malloc()/free()是glibc库的内存分配器ptmalloc提供的接口,ptmalloc 使用的系统调用brk或mmap向内核以页为单位申请内存,然后进行分成很小内存块分配给对应应用程序。
-
内核空间
-
虚拟内存管理 负责从进程的虚拟地址空间分配虚拟页,sys_brk 来扩大或收缩堆,sys_mmap用来在内存映射区域分配虚拟页,sys_munmap用来释放虚拟页。页分配器负责分配物理页,使用分配器是伙伴分配器。
- 内核空间扩展功能,不连续页分配器提供分配内存的接口vmalloc 和释放内存接口vfree ,在内存碎片化的时候,申请连续物理页的成功率比较低,可以申请不连续的物理页,映射到连续的虚拟页,即虚拟地址连续而物理地址不连续。
-
内存控制组用来控制进程占用的内存资源,当内存碎片化的时候,找不到连续的物理页,内存碎片整理通过迁移方式得到连续的物理页,在内存不足的时候,页回收负责回收物理页。
-
硬件 内存管理单元 MMU包含一个页表缓存,保存最近使用过的页表映射,避免每次把虚拟地址转换为物理地址都需要查询内存中的页表。解决处理器执行速度和内存速度不匹配,中间增加一个缓存,一级缓存分为数据缓存和指令缓存。二级作用协调一级缓存和内存之间的工作效率。
系统调用(system call)
用户应用程序 用户调用malloc函数进行内存分配 调用 内核与用户层接口 sbrk()/brk()或mmap()/munmap() 调用 kernel layer 中kmalloc/vmalloc
C++ 语言当中,new/delete他们底层的实现也是malloc/free 实现的,我们在编程过程中使用malloc()/free() 与内核之间的接口(桥梁)就是sbrk()等系统函数...
内存管理详解流程
- 用户层内存管理 STL(内存自动分配自动回收) C语言(malloc()/free()) C++语言(new/delete)
- 系统函数sbrk()/brk()此函数既可以分配也可以收回。
- mmap()分配/munmap()收回释放
内核层内存管理 kmalloc()/vmalloc() get_free_page()
虚拟地址空间布局架构
- 目前应用程序没有那么大的内存需求,所以ARM64处理器不支持完全的64位虚拟地址。
- 在ARM64架构的Linux内核中,内核虚拟地址和用户虚拟地址的宽度相同
- 所有进程共享内核虚拟地址空间,每个进程有独立的用户虚拟空间,同一个线程组的用户线程共享用户虚拟地址空间,内核线程没有用户虚拟地址空间 内核地址空间:0xffff ffff ffff ffff - 0xffff 0000 0000 0000 不规范地址空间(不允许使用) 用户地址空间:0x0000 ffff ffff ffff - 0x0000 0000 0000 0000 ARM 64 内核/用户虚拟地址空间划分
用户虚拟地址空间划分
- 进程的用户虚拟空间的起始地址为0,长度是TASK_SIZE,由每种处理器架构定义自己的宏TASK_SIZE.ARM64 架构定义的宏TASK_SIZE如下: 32位用户空间程序:TASK_SIZE的值是TASK_SIZE_32,即 0x1000000000 等4GB。 64位用户空间程序:TASK_SIZE的值是TASK_SIZE_64,即(UL(1)<<VA_BITS)字节。
//用户虚拟地址空间Linux内核源码
#ifdef CONFIG_COMPAT
#define TASK_SIZE_32 UL(0x100000000)
- VB_BITS 是编译内核的时候选择的虚拟地址位数。 进程的用户虚拟地址空间包含区域: 代码段,数据段,未初始化数据段, 动态库的代码段,数据段和未初始化数据段。 存放动态生成的数据的堆 存放局部变量和实现函数调用的栈。 把文件区间映射到虚拟地址空间的内存映射区域; 存放在栈底部的环境变量和参数字符串。
内核使用内存描述符mm_struct ,描述进程的用户虚拟地址空间
struct mm_struct {
struct vm_area_struct *mmap;//虚拟内存区域链表
struct rb_root mm_rb;//虚拟内存区域红黑树
u32 vmacache_seqnum;
#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; //内存映射区域的起始地址
unsigned long mmap_legacy_base;
#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES
unsigned long mmap_compat_base;
unsigned long mmap_compat_legacy_base;
#endif
unsigned long task_size;// 用户虚拟地址空间的长度
unsigned long highest vm_end;
pgd_t* pgd; //指向页全局目录,即第一级页表
atomic_t mm_users;//共享一个用户虚拟地址空间的进程的数量,也就是线程组包含的进程的数量。
atomic_long_t nr_ptes;
#if CONFIG_PGTABLE_LEVELS >2
atomic_long_t nr_pmds;
#endif
int map_count;
spinlock_t page_table_lock;
struct rw_semaphore mmap_sem;
struct list_head mmlist;
unsigned long hiwater_rss; // 进程所拥有的最大叶框数
unsigned long hiwater_vm; //进程线性区中最大页数
unsigned long total_vm; //进程地址空间的大小(页数)
unsigned long locked_vm; //锁住而不能换出的页的个数
unsigned long pinned_vm;
unsigned long data_vm;
unsigned long exec_vm;
unsigned long stack_vm;
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];
struct mm_rss_stat rss_stat;
struct linux_binfmt *binfmt;
cpumask_var_t cpu_vm_mask_var;
mm_context_t context;//处理器架构特定的内存管理上下文
}
一个进程的虚拟地址空间主要是由两个数据结构进行描述,一个是最高层次的mm_struct,较高层次vm_area_struct。 最高层次mm_struct结构描述一个进程整个虚拟地址空间。较高层次结构描述虚拟地址空间的一个区间(成为虚拟区)。 每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个专门用来指向该进程的结构,mm_struct 结构是对整个用户空间的描述。
task_struct :struct mm_struct *mm; struct mm_struct *active_mm;
mm_struct: struct mm_struct *vm_mm;
vm_area_struct: struct vm_area_struct *vm_next,*vm_prev; //分别vma链表的前后成员链接操作 struct rb_node vm_rb;//如果采用链表组织化,会影响到它搜索速度问题,解决问题采用红黑树。每个进程结构体mm_struct中都创建一个红黑树,将VMA作为一个节点加入到红黑树中,制样可以提升搜索速度。