linux kernel 内存管理概述【转】
转自:https://www.jianshu.com/p/d6b7fe874a13
1 内存管理概述
内存管理子系统可分为用户空间,内核空间和硬件3个层面。
![](http://upload-images.jianshu.io/upload_images/26492154-e334489ac74d832e.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
1.1 用户空间
应用程序使用malloc申请内存,free释放内存;malloc和free是glibc库的内存分配器ptmalloc提供的接口,ptmalloc使用系统调用brk或mmap向内核以页为单位申请内存,然后划分为小内存块分配给应用程序。
1.2 内核空间
1.2.1 内核空间的基本功能
虚拟内存管理负责从进程的虚拟地址空间分配虚拟页,sys_brk用来扩大或收缩堆,sys_mmap用来在内存映射区分配虚拟页,sys_munmap用来释放虚拟页。
内核使用延迟分配物理内存的策略,进程第一次访问虚拟页的时候,触发页错误异常,页错误异常处理程序从页分配器申请物理页,在进程的页表中把虚拟页映射到物理页。
页分配器负责分配物理页,当前使用的页分配器是伙伴分配器。
内核空间提供了把页划分成小内存块分配的块分配器,提供分配内存的接口kmalloc和kfree;支持三种块分配器:SLAB分配器,SLUB分配器和SLOB分配器。
1.2.2 内核空间的扩展功能
不连续页分配器提供了分配内存的接口vmalloc和vfree;在内存碎片化的时候,申请连续物理页的成功率很低,可以申请不连续的物理页,映射到连续的虚拟页,即虚拟地址连续而物理地址不连续。
每处理器内存分配器用来为每处理器变量分配内存。
连续内存分配(CMA)用来给驱动程序预留一段连续的内存,当驱动程序不用的时候,可以给进程使用;当驱动程序需要的时候,把进程占用的内存通过回收或迁移的方式让出来,给驱动程序使用。
内存控制组用来控制进程占用的内存资源。
在内存不足的时候,页回收负责回收物理页,对于没有后备存储设备支持的匿名页把数据换出到交换区,然后释放物理页;对于有后备存储设备支持的文件,把数据写回到存储设备,然后释放物理页。如果页回收失败,使用最后一招,out-of-memory killer,选择进程杀掉。
1.3 硬件层面
处理器包含一个称为内存管理单元MMU的部件,负责把虚拟地址转换成物理地址。
内存管理单元包含一个称为TLB的部件,保存最近使用过的页表映射,避免每次把虚拟地址转换成物理地址都需要查询内存中的页表。
为了解决处理器的执行速度和内存的访问速度不匹配的问题,在处理器和内存之间增加了缓存cache。缓存通常分为l1,l2,l3,为了支持并行地取指令和数据,l1 cache分为数据缓存和指令缓存。
2 虚拟地址空间布局
2.1 虚拟地址空间划分
实际ARM64不支持完全的64位虚拟地址:
(1)虚拟地址的最大宽度是48位,内核虚拟地址在64位地址空间的顶部,高16位全为1,范围是0xFFFF 0000 0000 0000到0xFFFF FFFF FFFF FFFF,用户态的虚拟地址在64位空间的底部,高16位全为0,范围是0x0000 0000 0000 0000到0x0000 FFFF FFFF FFFF。
(2)如果处理器实现了ARM8.2标准的大虚拟地址(LVA)支持,并且页长度是64KB,那么虚拟地址的最大宽度是52位。
(3) 可以为虚拟地址配置比最大宽度小的宽度,并且可以为内核虚拟地址和用户虚拟地址配置不同的宽度。转换控制器TCR_EL1的字段T0SZ定义了必须是全0的最高位的数量,字段T1SZ定义了必须是全1的最高位的数量,用户虚拟地址的宽度是(64 - TCR_EL1.T0SZ),内核虚拟地址的宽度是(64 - TCR_EL1.T1SZ)。
在ARM64架构的Linux内核中,内核虚拟地址和用户虚拟地址空间的宽度相同。
所有进程共享内核虚拟地址空间,每个进程有独立的用户虚拟空间,同一个线程组的用户线程共享用户虚拟空间,内核线程没有用户虚拟空间。
2.2 用户虚拟地址空间布局
2.2.1 TASK_SIZE
进程的用户虚拟地址空间的起始地址是0,长度是TASK_SIZE,由每种处理器架构定义自己的宏TASK_SIZE。ARM64架构定义的宏TASK_SIZE如下所示。
(1)32位用户空间程序:TASK_SIZE的值是TASK_SIZE_32,即0x100000000,等于4GB。
(2)64位用户空间程序:TASK_SIZE的值是TASK_SIZE_64,即2的VA_BITS次方字节,VA_BITS在内核编译时选择的虚拟地址位数。
#ifdef CONFIG_COMPAT
#define TASK_SIZE_32 UL(0x100000000)
#define TASK_SIZE (test_thread_flag(TIF_32BIT) ? \
TASK_SIZE_32 : TASK_SIZE_64)
#define TASK_SIZE_OF(tsk) (test_tsk_thread_flag(tsk, TIF_32BIT) ? \
TASK_SIZE_32 : TASK_SIZE_64)
#else
2.2.2 mm_struct
进程的用户虚拟地址空间包含以下区域:
(1)代码段,数据段和未初始化的数据段。
(2)动态库的代码段,数据段,和未初始化的数据段。
(3)存放动态生成的数据的堆。
(4)存放局部变量和实现函数调用的栈。
(5)存放在栈底部的环境变量和参数字符串。
(6)把文件区间映射到虚拟地址空间的内存映射区域。
内核使用内存描述符mm_struct描述进程的用户虚拟地址空间,成员如下:
struct mm_struct {
struct vm_area_struct *mmap; /* list of VMAs */
struct rb_root mm_rb;
u64 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; /* PTE page table pages */
#if CONFIG_PGTABLE_LEVELS > 2
atomic_long_t nr_pmds; /* PMD page table pages */
#endif
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;
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 */
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
struct task_struct __rcu *owner;
#endif
struct user_namespace *user_ns;
/* store ref to file /proc/<pid>/exe symlink points to */
struct file __rcu *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
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)
bool tlb_flush_pending;
#endif
#ifdef CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
/* See flush_tlb_batched_pending() */
bool tlb_flush_batched;
#endif
struct uprobes_state uprobes_state;
#ifdef CONFIG_X86_INTEL_MPX
/* address of the bounds directory */
void __user *bd_addr;
#endif