[Linux][内核学习笔记]--内存管理

本文深入探讨Linux内存管理,包括内存管理概述、物理内存的初始化、zone管理、zonelist、fallbacks、伙伴系统、页面高速缓存、64位页表映射、物理页面的分配与释放。讲解了内存的分区、水位值、迁移类型、分配策略以及内核如何通过地址映射、页表、页高速缓存等机制高效管理内存。
摘要由CSDN通过智能技术生成

1.内存管理概览

在这里插入图片描述

  • MMU:虚拟地址到物理地址的转换机制。
  • TLB:简称快表,页表的硬件缓存单元,用于加速物理地址的寻址。
  • cache:为了与CPU的访问速度匹配,设计cache,有的平台支持L1 cache 、L2 cache,可以通过lscpu指令查看。
  • DDR:内存,程序必须加载到内存中才能执行。
  • 页表管理:地址映射机制。
  • 伙伴系统/页面分配器:以页为单位管理和分配空闲页面。
  • slab:分配比页小的内存。
  • 页面回收机制:当页面使用完毕后,需要归还给伙伴系统或slab。
  • page_cache:有文件背景的页面,用于缓存磁盘上数据。读过一遍的数据,进程下次再读的时候就直接从page cache里去拿,提升系统的整体性能。
  • 匿名页面:没有文件背景的页面,如堆,栈,数据段等,不是以文件形式存在,因此无法和磁盘文件交换,但可以通过硬盘上划分额外的swap交换分区或使用交换文件进行交换。
  • 缺页中断:也称请页机制,当执行进程时,若发现需要访问的页不在内存中,则触发请页机制。
  • VMA管理:用户地址空间的区间管理,比如栈、堆、mmap、bss、data、text等,通过双向链表和红黑树管理。
  • 反向映射:通过物理地址找到哪些虚拟地址使用它,在页面回收的时候会用到。
  • KSM:基于写时复制机制COW,也就是将内容相同的页面合并成一个只读页面,从而释放出空闲物理页面。
  • 页迁移:分配新页面,将旧页面内容拷贝到新页面。

2.物理内存管理

2.1 物理内存初始化流程

在这里插入图片描述

2.2 zone

由于硬件存在缺陷而引起了如下内存选址问题:

  • 一些硬件只能用某些特定的内存地址来执行DMA。
  • 一些体系结构的物理内存寻址范围比虚拟地址寻址范围大得多,会导致一些内存不能永久地映射到内核空间上。

所以,Linux内核主要将内存分为以下几个区,每个区由一个zone结构管理,在<linux/mmzone.h>中定义:

  • ZONE_DMA: 用来执行DMA操作。
  • ZONE_DMA32:只能被32位的设备用来DMA操作。
  • ZONE_NORMAL:能正常映射的页。
  • ZONE_HIGHMEM:高端内存页,这些页不能永久地映射到内核地址空间。
struct zone {
	/* Read-mostly fields */

	/* zone watermarks, access with *_wmark_pages(zone) macros */
	unsigned long watermark[NR_WMARK];//每个zone在系统启动时会计算出三个水位值,分别是WMARK_MIN、WMARK_LOW、WMARK_HIGH,在页面分配器和kswapd页面回收中会用到。
	unsigned long nr_reserved_highatomic;//为某些场景预留的内存
	long lowmem_reserve[MAX_NR_ZONES];//zone中预留的内存

#ifdef CONFIG_NUMA//非同一内存访问,即便硬件上是一整块连续内存的UMA,Linux也可将其划分为若干的node。同样,即便硬件上是物理内存不连续的NUMA,Linux也可将其视作UMA。所以在Linux系统中,可以基于一个UMA的平台测试NUMA上的应用特性。从另一个角度,UMA就是只有一个node的特殊NUMA,所以两者可以统一用NUMA模型表示。
	int node;
#endif
	struct pglist_data	*zone_pgdat;//指向内存结点
	struct per_cpu_pageset __percpu *pageset;//用于维护Per_CPU上的一系列页面,以减少自旋锁的争用。
#ifndef CONFIG_SPARSEMEM//稀疏内存模型,为管理struct page而出现。
	unsigned long		*pageblock_flags;
#endif /* CONFIG_SPARSEMEM */

	/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
	unsigned long		zone_start_pfn;//zone中开始页面的叶帧号

	unsigned long		managed_pages;//zone中被伙伴系统管理的页面数量
	unsigned long		spanned_pages;//zone中包含的页面数量
	unsigned long		present_pages;//zone里实际管理的页面数量,对一些体系结构来说,其值和spanned_pages一样

	const char		*name;//是一个以NULL结束的字符串表示这个区的名字。内核启动期间初始化你这个值,其代码位于mm/page_alloc.c中。三个区的名字分别为DMA、Normal、HighMem

#ifdef CONFIG_MEMORY_ISOLATION//内存隔离
	unsigned long		nr_isolate_pageblock;
#endif

#ifdef CONFIG_MEMORY_HOTPLUG//内存热拔插
	/* see spanned/present_pages for more description */
	seqlock_t		span_seqlock;
#endif

	int initialized;

	/* Write-intensive fields used from the page allocator */
	ZONE_PADDING(_pad1_)//可以让zone->lock 和 zone->lru_lock 这两个锁分布在不同的cache line中,为了性能而浪费空间

	/* free areas of different sizes */
	struct free_area	free_area[MAX_ORDER];//管理空闲区域的数组,包含管理链表等

	/* zone flags, see below */
	unsigned long		flags;

	/* Primarily protects free_area */
	spinlock_t		lock;//并行访问时,用于对zone保护的自旋锁

	/* Write-intensive fields used by compaction and vmstats. */
	ZONE_PADDING(_pad2_)
	
	unsigned long percpu_drift_mark;

#if defined CONFIG_COMPACTION || defined CONFIG_CMA 
	/* pfn where compaction free scanner should start */
	unsigned long		compact_cached_free_pfn;
	/* pfn where async and sync compaction migration scanner should start */
	unsigned long		compact_cached_migrate_pfn[2];
#endif

#ifdef CONFIG_COMPACTION //内核里的紧致内存机制,类似于磁盘碎片整理:把碎的页移动整合到连续的一段空间,就留出一段连续的内存了。在内核中打开CONFIG_COMPACTION就可以使用compaction功能了,要注意它只能整理可移动的页面。
	unsigned int		compact_considered;
	unsigned int		compact_defer_shift;
	int			compact_order_failed;
#endif

#if defined CONFIG_COMPACTION || defined CONFIG_CMA
	/* Set to true when the PG_migrate_skip bits should be cleared */
	bool			compact_blockskip_flush;
#endif

	bool			contiguous;

	ZONE_PADDING(_pad3_)
	/* Zone statistics */
	atomic_long_t		vm_stat[NR_VM_ZONE_STAT_ITEMS];//zone计数
	atomic_long_t		vm_numa_stat[NR_VM_NUMA_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;//因为zone会经常被访问到,因此用这个数据结构要求以L1 cache对齐
  • watermark(水位),每个zone都有三个水位值:
enum zone_watermarks {
	WMARK_MIN,// 最低水位,代表内存显然已经不够用了
	WMARK_LOW,//低水位,代表内存已经开始吃紧,需要启动回收页内核线性kswapped去回收内存
	WMARK_HIGH,//高水位,代表内存还是足够的
	NR_WMARK
};
  • lowmem_reserve : 这个zone区域保留的内存,当系统内存出现不足的时候,系统就会使用这些保留的内存来做一些操作,比如使用保留的内存进程用来可以释放更多的内存。
  • free_area:用于维护空闲的页,其中数组的下标对应页的order数。最大order目前是11。free_are的结构体:
struct free_area {
	struct list_head	free_list[MIGRATE_TYPES];//用于将各个order的free page链接在一起,而每一个order中又根据迁移类型分成了几组
	unsigned long		nr_free;//代表这个order中还有多个空闲page
};
enum migratetype {
	MIGRATE_UNMOVABLE,//不可移动的页,核心内核分配的大多数内存属于该类别
	MIGRATE_MOVABLE,//可以移动的页,当出现内存碎片的时候,就可以移动此页,腾出更多连续的空间。属于用户空间应用程序的页属于该类别. 它们是通过页表映射的, 如果它们复制到新位置,页表项可以相应地更新,应用程序不会注意到任何事
	MIGRATE_RECLAIMABLE,//可以回收的页, 例如,映射自文件的数据属于该类别,kswapd守护进程会根据可回收页访问的频繁程度,周期性释放此类内存. 页面回收本身就是一个复杂的过程. 内核会在可回收页占据了太多内存时进行回收, 在内存短缺(即分配失败)时也可以发起页面回收.
	MIGRATE_PCPTYPES,//用来表示每CPU页框高速缓存的数据结构中的链表的迁移类型数目
	MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
	MIGRATE_CMA,//一块连续的物理内存
#endif
#ifdef CONFIG_MEMORY_ISOLATION
	MIGRATE_ISOLATE,//不能从这个链表分配页,因为这个链表专门用于NUMA结点移动物理内存页,将物理内存页内容移动到使用这个页最频繁的CPU
#endif
	MIGRATE_TYPES
};

通过 cat /proc/zoneinfo 可以看到每个zone区的详细信息:

potato@ubuntu:~$ cat /proc/zoneinfo
Node 0, zone      DMA
  per-node stats
      nr_inactive_anon 3712
      nr_active_anon 217352
      nr_inactive_file 192440
      nr_active_file 198710
      nr_unevictable 4
      nr_slab_reclaimable 42640
      nr_slab_unreclaimable 19617
      nr_isolated_anon 0
      nr_isolated_file 0
      workingset_nodes 0
      workingset_refault 0
      workingset_activate 0
      workingset_restore 0
      workingset_nodereclaim 0
      nr_anon_pages 219892
      nr_mapped    76009
      nr_file_pages 392313
      nr_dirty     296
      nr_writeback 0
      nr_writeback_temp 0
      nr_shmem     4055
      nr_shmem_hugepages 0
      nr_shmem_pmdmapped 0
      nr_file_hugepages 0
      nr_file_pmdmapped 0
      nr_anon_transparent_hugepages 0
      nr_unstable  0
      nr_vmscan_write 0
      nr_vmscan_immediate_reclaim 0
      nr_dirtied   292233
      nr_written   273663
      nr_kernel_misc_reclaimable 0
  pages free     3968
        min      68
        low      85
        high     102
        spanned  4095
        present  3997
        managed  3976
        protection: (0, 2914, 3836, 3836, 3836)
      nr_free_pages 3968
      nr_zone_inactive_anon 0
      nr_zone_active_anon 0
      nr_zone_inactive_file 0
      nr_zone_active_file 0
      nr_zone_unevictable 0
      nr_zone_write_pending 0
      nr_mlock     0
      nr_page_table_pages 0
      nr_kernel_stack 0
      nr_bounce    0
      nr_zspages   0
      nr_free_cma  0
      numa_hit     1
      numa_miss    0
      numa_foreign 0
      numa_interleave 0
      numa_local   1
      numa_other   0
  pagesets
    cpu: 0
              count: 0
              high:  0
              batch: 1
  vm stats threshold: 4
    cpu: 1
              count: 0
              high:  0
              batch: 1
  vm stats threshold: 4
  node_unreclaimable:  0
  start_pfn:           1
Node 0, zone    DMA32
  pages free     219272
        min      12782
        low      15977
        high     19172
        spanned  1044480
        present  782288
        managed  758575
        protection: (0, 0, 922, 922, 922)
      nr_free_pages 219272
      nr_zone_inactive_anon 3595
      nr_zone_active_anon 148063
      nr_zone_inactive_file 138458
      nr_zone_active_file 156085
      nr_zone_unevictable 4
      nr_zone_write_pending 255
      nr_mlock     4
      nr_page_table_pages 6215
      nr_kernel_stack 4372
      nr_bounce    0
      nr_zspages   0
      nr_free_cma  0
      numa_hit     1026744
      numa_miss    0
      numa_foreign 0
      numa_interleave 1
      numa_local   1026744
      numa_other   0
  pagesets
    cpu: 0
              count: 149
              high:  378
              batch: 63
  vm stats threshold: 24
    cpu: 1
              count: 235
              high:  378
              batch: 63
  vm stats threshold: 24
  node_unreclaimable:  0
  start_pfn:           4096
Node 0, zone   Normal
  pages free     5010
        min      4045
        low      5056
        high     6067
        spanned  262144
        present  262144
        managed  236120
        protection: (0, 0, 0, 0, 0)
      nr_free_pages 5010
      nr_zone_inactive_anon 117
      nr_zone_active_anon 69289
      nr_zone_inactive_file 53982
      nr_zone_active_file 42625
      nr_zone_unevictable 0
      nr_zone_write_pending 41
      nr_mlock     0
      nr_page_table_pages 4508
      nr_kernel_stack 7884
      nr_bounce    0
      nr_zspages   0
      nr_free_cma  0
      numa_hit     495142
      numa_miss    0
      numa_foreign 0
      numa_interleave 32381
      numa_local   495142
      numa_other   0
  pagesets
    cpu: 0
              count: 362
              high:  378
              batch: 63
  vm stats threshold: 16
    cpu: 1
              count: 348
              high:  378
              batch: 63
  vm stats threshold: 16
  node_unreclaimable:  0
  start_pfn:           1048576
Node 0, zone  Movable
  pages free     0
        min      0
        low      0
        high     0
        spanned  0
        present  0
        managed  0
        protection: (0, 0, 0, 0, 0)
Node 0, zone   Device
  pages free     0
        min      0
        low      0
        high     0
        spanned  0
        present  0
        managed  0
        protection: (0, 0, 0, 0, 0)
potato@ubuntu:~$

可以看到,当前系统将内存分为DMA、DMA32、Normal、Moveable四个zone区,以及每个区的水位值。

2.3 zonelist

 当指定的节点Node无法满足分配请求时,需要从其他节点Node中分配, 因此内核在内存的结点pg_data_t中提供了一个zonelists,它是两个大小的数组,意味着一个是由本Node的zones组成,另一个是由从本node分配不到内存时可选的备用zones组成,相当于是选择了一个退路:

typedef struct pglist_data {
	struct zone node_zones[MAX_NR_ZONES];
	struct zonelist node_zonelists[MAX_ZONELISTS];
	...
}

struct zonelist {
	struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
};

struct zoneref {
	struct zone *zone;	/* Pointer to actual zone */
	int zone_idx;		/* zone_idx(zoneref->zone) */
};

2.4 fallbacks

 当一种特定的 MIGRATE_TYPES 类型的页面不够分配时,需要从其他类型页面中分配来满足需求。从其他类型页面分配有一定的次序,由备用列表 fallbacks 来管理:

static int fallbacks[MIGRATE_TYPES][4] = {
	//分配不可移动页面失败时的备用列表
	[MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES },
	//分配可回收页面失败时的备用列表
	[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_TYPES },
	//分配可移动页面失败时的备用列表
	[MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
#ifdef CONFIG_CMA
	[MIGRATE_CMA]         = { MIGRATE_TYPES }, /* Never used */
#endif
#ifdef CONFIG_MEMORY_ISOLATION
	[MIGRATE_ISOLATE]     = { MIGRATE_TYPES }, /* Never used */
#endif
};

例如:每一行对应一个类型的分配次序, 在内核想要分配不可移动页MIGRATE_UNMOVABLE时, 如果对应链表为空, 则遍历fallbacks[MIGRATE_UNMOVABLE], 首先后退到可回收页链表MIGRATE_RECLAIMABLE, 接下来到可移动页链表MIGRATE_MOVABLE, 最后到紧急分配链表MIGRATE_TYPES。

2.5 伙伴系统

 每个zone里面空闲页面由伙伴系统管理,可将伙伴系统中的 free_area 结构 和 page 结构表示为下图:
在这里插入图片描述
 如图所示,内核采用伙伴系统以页为单位动态管理空闲页面,在用户提出申请时,分配一块大小合适的内存块给用户,用户用完后,归还给伙伴系统。在伙伴系统中,内存块是2的order次幂,order的最大值是11,也就是把所有的空闲页面组成11个内存块链表。每个内存块包括1,2,4,8,16,32,···,1024个连续的页面,1024对应着4MB大小的连续物理内存。
 通过 cat /proc/buddyinfo 可以查看伙伴系统中剩余页面的情况:
在这里插入图片描述
也可以通过 cat /proc/pagetypeinfo 查看更加详细的页面剩余情况:
在这里插入图片描述
Free pages count per migrate type at order :不同order 按照migrate type的空闲page数量。
Number of blocks:连续内存块数量

Page block order: 9 //分配阶数
Pages per block:  512 //对应的页面数

Page block表示每个迁移链表应该有适当的页面。由内核中的两个全局变量pageblock_order 和 pageblock_nr_pages提供的. 第一个表示一个分配阶, pageblock_nr_pages则表示该分配阶对应的页面数:

#ifdef CONFIG_HUGETLB_PAGE
#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
/* Huge page sizes are variable */
extern unsigned int pageblock_order;
#else /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */
/* Huge pages are a constant size */
#define pageblock_order		HUGETLB_PAGE_ORDER
#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */
#else /* CONFIG_HUGETLB_PAGE */
/* If huge pages are not used, group by MAX_ORDER_NR_PAGES */
#define pageblock_order		(MAX_ORDER-1)

2.6 pg_data_t

  一个Node中的所有zone由一个pg_data_t 来管理,pg_data_t又由一个全局数组node_data来管理。node_data里面的每个数组项代表一个Node,在NUMA结构下,每个Node有自己的CPU、总线、内存,NUMA结构模型如下:
在这里插入图片描述
(图片来源于:https://blog.csdn.net/ustc_dylan/article/details/45667227)

因此可以用下图来表示各数据结构的关系:
在这里插入图片描述

3. 虚拟空间管理

3.1 进程虚拟地址空间

在这里插入图片描述
 在32位系统中,进程虚拟地址空间由3G的用户空间和1G的内核空间组成,如上图所示。用户空间中的各个进程之间互不干扰,即使用到相同的虚拟地址,也不会引起地址冲突。但是内核空间是被各个进程所共享的,内核空间的地址冲突极可能会引起整个系统崩溃。

3.2 虚拟地址空间布局

在这里插入图片描述
 虚拟地址空间的大致分布如图所示,不同的平台,虚拟地址空间划分可能存在差异。每个字段的说明如下:

3.2.1 内核空间
  • 临时映射区:当所安装的物理内存超过1G时,内核空间无法触及整个物理空间,所以增加临时映射区完成剩余物理内存的临时映射。而在64位的系统上就不存在这样的问题了,因为可用的线性地址空间远大于可安装的内存。
  • vmalloc区:内核空间调用vmalloc()可分配的空间,特点是虚拟地址连续,物理地址不连续。
  • 永久内存映射区:内核专门留出的一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。
  • 固定内存区:内核保留了一些线性空间用于特殊需求。其特点是①每个CPU 占用一块空间,②其内部又分为多个小空间,每个小空间大小是1 个 page,每个小空间用于一个目的,这些目的定义在kmap_types.h 中的km_type 中。
  • normal memroy:能够正常映射页的地址空间。
  • kernel image:image相关的数据段。
  • swapper_pg_dir:存放全局页目录的基地址。
3.2.2 用户空间
  • Environment variables:环境变量空间。
  • Command-line argument:保存命令行参数空间。
  • stack:栈,为函数内部的局部变量提供存储空间。
  • Memory mmap:映射文件相关的空间。
  • heap:堆,malloc()分配区。
  • BSS:存储未初始化或初始化为0的全局变量、静态变量。bss段与data段的区别是,编译时需为data段分配空间,而bss段不用。BSS段仅为未初始化的静态分配变量预留位置,在目标文件中并不占据空间,这样可减少目标文件体积。
  • data:存储经过初始化的全局和静态变量
  • text:文本段,也叫代码段,存储可执行文件的指令;也有可能包含一些只读的常数变量,例如字符串常量等。

3.2 描述用户空间的数据结构

在这里插入图片描述
 如图所示。用户空间被划分为几段不同的区间,每段区间由一个vm_area_struct结构管理,每个区间来源不同,可能来源于可执行映像、共享库、动态分配等,并且进程对每个区间的访问权限也不相同。当vm_area_struct结构在内核中数量不多时,内核会采用双向链表来管里所有的vm_area_struct,由mm_struct中mmap域指向。当数量较多时,内核会采用红黑树来管理vm_area_struct结构,由mm_struct中的mm_rb指向

 整个进程是由task_struct来管理,管理着与进程相关的所有信息,比如页目录、调度器、进程地址空间等,其中的mm_struct、vm_area_struc字段则用来管理用户空间。

4. 内存管理机制

 内核是如何管理内存的,大致可分为以下几个机制:
在这里插入图片描述
 Linux内核对虚拟地址的管理实现了五大机制,分别是地址映射机制、请页机制、内存分配与回收机制、交换机制、缓存与刷新机制。它们之间相互协作,如上图所示:

  • 内核通过地址映射机制将进程从磁盘映射到虚拟地址空间中。
  • ①当执行进程时,若发现需要访问的页不在内存中,则触发请页机制。
  • ②如果系统有空闲内存可供分配,则请求分配内存。
  • ③并且把正在使用的页记录在缓存中(最近访问的页,下一次很可能还会被访问到)。
  • ④/⑤当系统中没有空闲内存分配时,则会触发交换机制腾出内存空间。
  • ⑥交换机制中也会用到缓存机制。
  • ⑦物理页交换到交换文件后,需要通过页表来修改映射后的文件地址。
  • ⑧在地址映射中,会用到TLB来加速物理内存的寻找。

4.1 地址映射(页表映射)

在这里插入图片描述
 32位虚拟地址的高12位作为访问一级页表的索引值,找到对应的表项,每个表项指向一个二级页表,以虚拟地址的次8位作为访问二级页表的索引值,得到对应的页表项,从这个页表项中找到20位的物理页面地址(页帧)。最后页帧和页面偏移组合成物理地址。在ARM32中,这个过程由硬件MMU来完成。

4.2 页面高速缓存

 页表保存在内存中,每次访存都需要经过页表转换,降低了访问速度。为了提高访问速度,有一个页面高速缓存硬件诞生——TLB(转换旁路缓冲器),也称快表。当CPU访问某个地址空间时,先检查对应的页帧是否在TLB中,如果在(命中),就不需要经过页表转换了,如果不在(未命中),需要经过页表转换,此时硬件也会自动将未命中的页帧更新到TLB中,也便下一次访问。有时TLB的更新也需要软件更新,比如进程切换、更改内核页表等。

4.3 64位机器的页表映射

 虽然64 位机器支持48位的选址空间,但其页表转换的模型与32几乎一致,不过是增加了两级页表,采用4级页表结构:
PGD:page Global directory(47-39), 页全局目录
PUD:Page Upper Directory(38-30),页上级目录
PMD:page middle directory(29-21),页中间目录
PTE:page table entry(20-12),页表项

5. 物理页面的分配

 内核中常用以下函数来分配一个或多个连续的页面,有利于缓解系统内存的碎片化:

函数描述
alloc_page(gfp_mask)只分配一页,返回指向页结构的指针
alloc_pages(gfp_mask,order)分配2order个页,返回指向第一页页结构的指针
__get_free_page(gfp_mask)只分配一页,返回指向其逻辑地址的指针
__get_free_pages(gfp_mask,order)分配2order个页,返回指向第一页逻辑地址的指针
get_zeroed_page(gfp_mask)只分配一页,让其填充为0,返回指向逻辑地址的指针

参数 gfp_mask 是分配掩码,决定从哪一个zone分配页面,页面分配单位是1页。参数order是分配阶,决定从哪一个free_list中分配页面,即分配多少页面。

5.1 gfp_mask标志

gfp_mask标志可分为三类:行为修饰符、区修饰符及类型修饰符。

行为修饰符表示内核应该如何分配所需的内存,在某些特定的情况下,只能使用某些特定的方法分配内存,例如,中断处理程序要求内核在分配内存过程中不能睡眠(因为中断处理程序不能被重新调度)。

区修饰符表示从哪里分配内存,内核把物理内存分为多个区(zone),每个区具有不同的用途。

类型修饰符是行为修饰符和区修饰符的组合,简化了修饰符的使用。所有这些修饰符都在< linux/gfp.h >中:

区修饰符:
#define __GFP_DMA	((__force gfp_t)___GFP_DMA) //从ZONE_DMA分配
#define __GFP_HIGHMEM	((__force gfp_t)___GFP_HIGHMEM)//从ZONE_HIGHMEM或ZONE_NORMAL分配
#define __GFP_DMA32	((__force gfp_t)___GFP_DMA32)//从ZONE_DMA32分配
#define __GFP_MOVABLE	((__force gfp_t)___GFP_MOVABLE)  /* ZONE_MOVABLE allowed */
#define GFP_ZONEMASK	(__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
行为修饰符:
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE)//slab分配器中可回收页面
#define __GFP_WRITE	((__force gfp_t)___GFP_WRITE)//表示调用者打算写物理页。在可能的情况下,尽量把该类型的页分布到本地节点的 所有区域,避免所有 脏页 在一个 内存区域
#define __GFP_HARDWALL   ((__force gfp_t)___GFP_HARDWALL)//强制cpuset内存分配策略
#define __GFP_THISNODE	((__force gfp_t)___GFP_THISNODE)//强制从本地节点分配页
#define __GFP_ACCOUNT	((__force gfp_t)___GFP_ACCOUNT)//把分配的也记录在 内核内存控制组

#define __GFP_ATOMIC	((__force gfp_t)___GFP_ATOMIC)//指明调用者为 高优先级,不能回收页或者进入睡眠
#define __GFP_HIGH	((__force gfp_t)___GFP_HIGH)//指明调用者为 高优先级,系统必须通过请求
#define __GFP_MEMALLOC	((__force gfp_t)___GFP_MEMALLOC)//允许访问所有内存
#define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC)//进程访问 紧急保留内存

#define __GFP_IO	((__force gfp_t)___GFP_IO)//允许读写存储设备
#define __GFP_FS	((__force gfp_t)___GFP_FS)//允许向下调用到 底层文件系统。当 文件系统 申请页时,如果内存 严重不足,直接回收页,把 脏页 会写到 存储设备。为了避免调用 文件系统函数 可能会导致的 死锁,文件系统申请页的时候应该 清除 该标志位。
#define __GFP_DIRECT_RECLAIM	((__force gfp_t)___GFP_DIRECT_RECLAIM)//调用者可以 直接回收页
#define __GFP_KSWAPD_RECLAIM	((__force gfp_t)___GFP_KSWAPD_RECLAIM)//当 空闲页数 达到 低水线 时,调用者想要唤醒 页回收线程kswapd(即 异步回收页)
#define __GFP_RECLAIM ((__force gfp_t)(___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM))//允许 直接回收页 和 异步回收页
#define __GFP_RETRY_MAYFAIL	((__force gfp_t)___GFP_RETRY_MAYFAIL)//允许重试,直到多次以后放弃,分配可能 失败
#define __GFP_NOFAIL	((__force gfp_t)___GFP_NOFAIL)//必须无限次重试,因为调用者 不能处理分配失败
#define __GFP_NORETRY	((__force gfp_t)___GFP_NORETRY)//不要重试,当 直接回收页 和 内存碎片整理 不能使得分配成功的时候,应该放弃。

#define __GFP_NOWARN	((__force gfp_t)___GFP_NOWARN)//如果分配失败,不要打印告警信息
#define __GFP_COMP	((__force gfp_t)___GFP_COMP)//把分配的页组成 复合页(compound page)
#define __GFP_ZERO	((__force gfp_t)___GFP_ZERO)//把 页 使用 0 进行初始化

[参考]:https://www.jianshu.com/p/78a978ff48f2

类型修饰符(组合):
#define GFP_ATOMIC	(__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)//原子分配 内核使用的页,不能睡眠。调用者为 高优先级,允许 异步回收页
#define GFP_KERNEL	(__GFP_RECLAIM | __GFP_IO | __GFP_FS)//分配 内核 使用的页,可能睡眠。从 低端内存区域 分配,允许 直接回收页 和 异步回收页,允许 读写存储设备,允许 调用到底层文件系统。
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)//
#define GFP_NOWAIT	(__GFP_KSWAPD_RECLAIM)//分配 内核 使用的页,不用等待。允许 异步回收页,不允许 直接回收页,不允许 读写存储设备,不允许 调用到底层文件系统。
#define GFP_NOIO	(__GFP_RECLAIM)//不允许 读写存储设备,允许 直接回收页 和 异步回收页。
#define GFP_NOFS	(__GFP_RECLAIM | __GFP_IO)//不允许 调用到底层文件系统,允许 读写存储设备,允许 直接回收页 和 异步回收页。
#define GFP_USER	(__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)//分配 用户空间 使用的页,内核或硬件 也可以直接访问,从 普通区域(线性映射区) 分配分配,允许 调用到底层文件系统,允许 读写存储设备,允许 直接回收页 和 异步回收页,允许 实施cpuset内存分配策略。
#define GFP_DMA		__GFP_DMA
#define GFP_DMA32	__GFP_DMA32
#define GFP_HIGHUSER	(GFP_USER | __GFP_HIGHMEM)//分配 用户空间 使用的页,内核或硬件 不直接访问,从 高端区域 分配分配,物理页使用过程中 不可以移动
#define GFP_HIGHUSER_MOVABLE	(GFP_HIGHUSER | __GFP_MOVABLE)//分配 用户空间 使用的页,内核或硬件 不直接访问,从 高端区域 分配分配,物理页可以通过 页回收 或 页迁移技术移动
#define GFP_TRANSHUGE_LIGHT	((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
			 __GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM) //分配 用户空间 使用的 巨型页,把分配的页块组成 复合页,禁止使用 紧急保留内存,禁止 打印告警信息,不允许 直接回收页 和 异步回收页
#define GFP_TRANSHUGE	(GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)//和 GFP_TRANSHUGE_LIGHT 类似,不同之处在于允许 直接回收页

[参考]:https://www.jianshu.com/p/78a978ff48f2

5.2 常见的gfp_mask使用时机

情形相应标志
进程上下文,可以睡眠使用GFP_KERNEL
进程上下文,不可以睡眠使用GFP_ATOMIC,在你睡眠之前或之后以GFP_KERNEL执行内存分配
中断处理程序使用GFP_ATOMIC
软中断使用GFP_ATOMIC
tasklet使用GFP_ATOMIC
需要用于DMA的内存,可以睡眠使用(GFP_DMA | GFP_KERNEL)
需要用于DMA的内存,不可以睡眠使用(GFP_DMA | GFP_ATOMIC),或在你睡眠之前执行内存分配

5.3 物理内存分配流程

 以 alloc_pages为例,简要梳理一下物理页面的分配逻辑:

#define alloc_pages(gfp_mask, order) \
		alloc_pages_node(numa_node_id(), gfp_mask, order)

alloc_pages是一个宏,实际分配页面的函数是__alloc_pages_nodemask():
[alloc_pages()->alloc_pages_node()->__alloc_pages()->__alloc_pages_nodemask()]

struct page *__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,nodemask_t *nodemask)
  • 根据gfp_mask计算出相关参数,比如计算出zone id,以便后续判断应该从哪一个zone开始分配内存,以及计算出页面迁移类型等。并将内容填充到 struct alloc_context结构中。
  • 尝试分配页面。首先会判断应该从哪个zone分配。根据上一步得到的zone id ,从zone id 代表的zone 开始扫描内存节点中的zonelist去查找适合分配内存的zone。
  • 找到合适的zone后,会先检查水位。如果低于水位(LOW),则会调zone_reclaim()函数回收页面。
  • 调用buffer_rmqueue()函数从伙伴系统中分配物理页面。

6.物理页面的释放

 释放内存页面的核心是把页面添加到伙伴系统适当的free_area链表中。其原理是:在释放内存页面时,首先会检查相邻的页面是否空闲,如果空闲,则会合并成一个大的内存页面,放置到高一阶的空闲链表free_area中。如果还能继续合并相邻的内存块,那么就会继续合并,转移到更高一阶的空闲链表中,这个过程会一直重复,直到所有可合并的页面都已合并。

 当需要释放页时,可以使用以下函数释放页:

void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值