Linux内存管理有三大块——物理内存、虚拟内存和内存映射。
内存分配的流程是这样的:
一开始申请内存,分配的是虚拟地址空间内存:malloc、mmap等分配虚拟内存;等到寻址发生映射的时候才会由缺页异常来分配物理内存:缺页异常;如果内存不够,分配失败,那就要走回收内存流程:kswapd+LRU/active/inactive/watermark+swap(anno page)/swap cache/swap entry+cache(file-backed page和slab objects);如果回收流程回收不到足够的内存,那么就走OOM流程:oom-killer;所以,一开始的申请内存,是在虚拟地址空间的哪里?
Linux mm_struct中有个属性task_size,他是用户地址空间的size,所以task_size的值就是地址空间中内核态地址空间和用户态地址空间的分界线。
地址:https://elixir.bootlin.com/linux/v5.8.12/source/include/linux/mm_types.h
内核里面是这样定义TASK_SIZE的:
地址:https://elixir.bootlin.com/linux/v4.1/source/arch/x86/include/asm/processor.h#L834
#ifdef CONFIG_X86_32 /* * User space process size: 3GB (default). */ #define TASK_SIZE PAGE_OFFSET #define TASK_SIZE_MAX TASK_SIZE #define STACK_TOP TASK_SIZE #define STACK_TOP_MAX STACK_TOP #else /* * User space process size. 47bits minus one guard page. The guard * page is necessary on Intel CPUs: if a SYSCALL instruction is at * the highest possible canonical userspace address, then that * syscall will enter the kernel with a non-canonical return * address, and SYSRET will explode dangerously. We avoid this * particular problem by preventing anything from being mapped * at the maximum canonical address. */ #define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE) /* This decides where the kernel will search for a free chunk of vm * space during mmap's. */ #define IA32_PAGE_OFFSET ((current->personality & ADDR_LIMIT_3GB) ? \ 0xc0000000 : 0xFFFFe000)内核里面是这样定义PAGE_OFFSET的:
地址:https://elixir.bootlin.com/linux/latest/source/arch/x86/Kconfig#L1454
参考链接:https://stackoverflow.com/questions/15526538/where-page-offset-value-in-linux-kernel-source-defined
arch/x86/include/asm/page_types.h : #define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET) arch/x86/include/asm/page_32_types.h : #define __PAGE_OFFSET _AC(CONFIG_PAGE_OFFSET, UL) arch/x86/Kconfig: config PAGE_OFFSET hex default 0xB0000000 if VMSPLIT_3G_OPT default 0x80000000 if VMSPLIT_2G default 0x78000000 if VMSPLIT_2G_OPT default 0x40000000 if VMSPLIT_1G default 0xC0000000 depends on X86_32 The PAGE_OFFSET in arch/x86/Kconfig is the CONFIG_PAGE_OFFSET in page_32_types.h. Because the prefix 'CONFIG_' is added by make system automatically.所以,page_size的值其实就是task_size的值。在x86_32上,page_offset默认值是0XC000 0000(3GB);而task_size的最大值#define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE)是0X8000 0000 0000(128T)。
所以,在32bit和64bit系统上,用户态地址空间和内核态地址空间的结构是这样的:
这部分主要参考《趣谈Linux操作系统》第二十二节,更多详情,烦请参看专栏内容。
对于用户地址空间,32bit和64bit的VMA结构几乎是一样的(见下图)。malloc分配的是堆,当申请的内存小于128KB的时候,走brk指针碰撞;mmap分配的是内存映射区。
对于内核地址空间而言,32bit和64bit的差别比较大。
32bit:
64bit:
对于内核地址空间的结构,32bit和64bit之所以差别巨大,是因为64bit不需要高端内存,而32bit需要最后的128M地址空间来映射高端内存,详情参看本文补充部分(高端内存zone_highmem)。
Linux在32bit机器上的#page_offset=0xc000 0000,即内核空间和用户空间是1G:3G。这就意味着内核有1G的地址线来映射物理内存,当实际内存大于1G的话(主板上插入的内存条),内核怎么访问多余的内存呢?
此时Linux将物理内存分成zone,物理内存地址的前16M为ZONE_DMA,16M~896M为ZONE_NORMAL,896M以后为ZONE_HIGHMEM。其中zone_dma和zone_normal又称为直接映射区,内核的地址空间可以直接映射这896M内存。这意味着在内核地址空间中用于访问ZONE_HIGEHMEM的地址空间实际上只有128M(内核有1G的地址空间,1G-896M=128M),为了访问多过内核地址空间的物理内存,就需要通过这128M做地址映射。
有三种方式(虚拟地址从低到高):
vmalloc区持久映射区固定映射区这时候内核页表是这么映射的:
对于zome_dma和zone_normal,因为内核有1G的地址线,所以可以直接映射,内核要访问这两个zone,直接虚拟地址减去page_offset;对于zone_highmem的物理内存,内核会用剩下的128M地址先来做映射,根据用途,会选用vmallock、持久映射或固定映射中的一种;Linux在64bit机器上的#page_offset=0x8000 0000 0000(x86_64只用了48bit的地址线),也就是内核空间和用户空间都是128T。内核的128T地址空间完全可以覆盖当前机器插置的物理内存容量,所以不需要再划分出一个ZONE_HIGHMEM来映射访问内核地址线之外的空间。
这是一篇杂凑的随笔,如有错漏,烦请不吝指正!