深入Linux内核架构笔记 - 内存管理(NUMA模型中的内存组织)

概述

  • 内存通过节点(pg_data_t)来管理,每个节点关联到系统中的一个处理器
  • 节点又进一步划分为内存域,是对内存的进一步细分,一个节点最多有3个内存域。
  • 各个内存域关联了一个数组,用来组织属于该内存域的物理内存页(页帧)。对各个页帧,都分配了一个struct page实例以及所需的管理数据。
  • 除了节点自己的内存域,每个节点还提供了一个备用列表(struct zonelist),该列表包含了其他节点,可用于代替当前节点分配内存。
    在这里插入图片描述

数据结构

1. 内存域类型

```
enum zone_type {
	#ifdef CONFIG_ZONE_DMA
	ZONE_DMA,
	#endif
	#ifdef CONFIG_ZONE_DMA32
	ZONE_DMA32,
	#endif
	ZONE_NORMAL,
	#ifdef CONFIG_HIGHMEM
	ZONE_HIGHMEM,
	#endif
	ZONE_MOVABLE,
	MAX_NR_ZONES
};
```
- ZONE_DMA:适合DMA的内存区域,长度依赖于处理器类型
- ZONE_DMA32:使用了32位地址字可寻址,适合DMA的区域,只有在64位系统上有意义
- ZONE_NORMAL:可以直接映射到内核段的普通内存域,所以系统结构上都会存在的
	唯一内存域,但无法保证该地址范围对应了实际的物理内存
	(AMD64位系统有2G内存,那么所有内存都属于ZONE_DMA32范围,ZONE_NORMAL为空)
- ZONE_HIGHMEM:标记了超出内核段的物理内存
- ZONE_MOVABLE:伪内存域,防止物理内存碎片机制需要该内存域
- MAX_NR_ZONES:结束标记

2. 节点管理

typedef struct pglist_data {
	struct zone node_zones[MAX_NR_ZONES];
	struct zonelist node_zonelists[MAX_ZONELISTS];
	int nr_zones;
	struct page *node_mem_map;
	struct bootmem_data *bdata;
	
	unsigned long node_start_pfn;
	unsigned long node_present_pages; /* total number of physical pages */
	unsigned long node_spanned_pages; /* total size of physical page
					     range, including holes */
	int node_id;
	wait_queue_head_t kswapd_wait;
	struct task_struct *kswapd;
	int kswapd_max_order;
} pg_data_t;
  • node_zones:包含了节点内各内存域的数据结构.
  • node_zonelists:指定了备用节点及其内存域的列表,如果当前节点没有可用空间时,可在备用节点内分配内存
  • nr_zones:节点内内存域的个数
  • node_mem_map:包含节点内的所有物理内存页
  • bdata:指向自举内存分配器数据结构的实例
  • node_start_pfn:该节点内第一个页帧的逻辑编号,系统内所有页帧是依次编号的,每个页帧的号码是全局唯一的
  • node_present_pages:节点中页帧的个数
  • node_spanned_pages:节点中包含空洞的页帧个数
  • node_id:全局节点ID
  • pgdat_next:连接到下一个内存节点,系统中的所有内存节点都通过单链表连接起来.
  • kswapd_wait:交换守护进程的等待队列.
enum node_states {
	N_POSSIBLE,		/* The node could become online at some point */
	N_ONLINE,		/* The node is online */
	N_NORMAL_MEMORY,	/* The node has regular memory */
#ifdef CONFIG_HIGHMEM
	N_HIGH_MEMORY,		/* The node has regular or high memory */
#else
	N_HIGH_MEMORY = N_NORMAL_MEMORY,
#endif
	N_CPU,		/* The node has one or more cpus */
	NR_NODE_STATES
};
  • 节点状态管理数据结构
  • static inline void node_set_state(int node, enum node_states state)
  • static inline void node_clear_state(int node, enum node_states state)

3. 内存域

struct zone {
	/* Fields commonly accessed by the page allocator */
	unsigned long		pages_min, pages_low, pages_high;

	unsigned long		lowmem_reserve[MAX_NR_ZONES];

	struct per_cpu_pageset	*pageset[NR_CPUS];

	spinlock_t		lock;

	struct free_area	free_area[MAX_ORDER];

	ZONE_PADDING(_pad1_)

	/* Fields commonly accessed by the page reclaim scanner */
	spinlock_t		lru_lock;	
	struct list_head	active_list;
	struct list_head	inactive_list;
	unsigned long		nr_scan_active;
	unsigned long		nr_scan_inactive;
	unsigned long		pages_scanned;	   /* since last reclaim */
	unsigned long		flags;		   /* zone flags, see below */

	/* Zone statistics */
	atomic_long_t		vm_stat[NR_VM_ZONE_STAT_ITEMS];

	int prev_priority;


	ZONE_PADDING(_pad2_)
	/* Rarely used or read-mostly fields */

	wait_queue_head_t	* wait_table;
	unsigned long		wait_table_hash_nr_entries;
	unsigned long		wait_table_bits;

	/*
	 * Discontig memory support fields.
	 */
	struct pglist_data	*zone_pgdat;
	/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
	unsigned long		zone_start_pfn;
	
	unsigned long		spanned_pages;	/* total size, including holes */
	unsigned long		present_pages;	/* amount of memory (excluding holes) */

	/*
	 * rarely used fields:
	 */
	const char		*name;
} ____cacheline_internodealigned_in_smp;
  • ZONE_PADDING:生成填充字段,使常用数据结构处于自身的缓冲行中
  • pages_min, pages_high,pages_low:页换出时使用的内存水印,如果内存不足,内核可以将页写到硬盘
    • 空闲页多于pages_high:内存域是理想状态
    • 空闲页低于pages_low:内核开始将页换出到硬盘
    • 空闲页低于page_min:页回收工作压力大
  • lowmem_reserve:用于一些无论如何都不能失败的关键性内存分配,分别为各个内存域指定了若干页
  • pageset:用于实现每个CPU的热/冷页帧列表,用于保存可用于满足分配的新鲜页,仍然在高速缓存中的称为热页
  • free_area:用于实现伙伴系统,每个数组元素表示固定长度的一些连续内存区
  • active_list:活动页的集合
  • inactive_list:不活动页的集合
  • nr_scan_active和nr_scan_inactive:内存回收时需要扫描的活动和不活动页的数据
  • pages_scanned:从上次换出一页以来,有多少页未能成功扫描
  • flags:内存域的当前状态
    typedef enum {
       ZONE_ALL_UNRECLAIMABLE, 
       ZONE_RECLAIM_LOCKED,
       ZONE_OOM_LOCKED,
    }
    
    • ZONE_ALL_UNRECLAIMABLE:页无法回收
    • ZONE_RECLAIM_LOCKED:CPU回收内存域时设置,阻止其他CPU进行同样操作
    • ZONE_OOM_LOCKED:内核试图杀死消耗进程最多的进程时设置该标志位
  • vm_stat:维护该内存域的统计信息
  • prev_priority:上一次扫描该内存域的优先级
  • wait_table, wait_table_bits和wait_table_hash_nr_entries实现了一个等待队列,可用于等待某一页表变为可用的进程
  • zone_pgdat:指向父节点

4. 内存域水印

  • 在计算内存水印前,内核首先要确定为关键性分配保留的内存空间的最小值,该值随可用内存大小而非线性增长,并保存在全局变量min_free_kbytes中,一个不变的约束是不能少于128k也不能多于64MB。
  • 数据结构中水印值的填充由init_per_zone_pages_min处理,该函数在内核启动期间调用,主要调用如下两个函数:
    • setup_per_zone_pages_min
    • setup_per_zone_lowmem_reserve
  • setup_per_zone_pages_min负责设置struct zone中的pages_min,pages_low和pages_high成员,在计算出高端内存区域之外的页面总数后(保存在lowmem_pages),内核迭代系统中的所有内存域并执行相关计算
  • lowmem_reserve的计算由setup_per_zone_lowmem_reserve完成,内核迭代系统中所有节点,对每个节点的各个内存域分别计算预留内存最小值

5. 冷热页

  • struct zone的pageset成员用于实现冷热分配器,热页是指那些加载到CPU高速缓存的页,其数据访问速度比不在高速缓存中的冷页快
    struct per_cpu_pageset {
    struct per_cpu_pages pcp[2]; //0:hot, 1:code
    } ____cacheline_aligned_in_smp;
    
    struct per_cpu_pages {
    int count;		/* number of pages in the list */
    int high;		/* high watermark, emptying needed */
    int batch;		/* chunk size for buddy add/remove */
    struct list_head list;	/* the list of pages */
    }
    
    • cout:该列表相关的页的数目
    • high:如果count > high则表明列表中的页太多了
    • list:双链表,保存了当前CPU的冷页或者热页

6. 页帧

  • 页帧是系统内存的最小单位,对内存中的每一个页都会创建struct page的一个实例

  • 页的广泛使用,增加了struct page的复杂性(内核不同部分对相同的数据有不同的解释)和保持结构长度的难度

    struct page {
    	unsigned long flags;		/* Atomic flags, some possibly
    					             * updated asynchronously */
    	atomic_t _count;		/* Usage count, see below. */
    	union {
    		atomic_t _mapcount;	/* Count of ptes mapped in mms, */
    		unsigned int inuse;	/* SLUB: Nr of objects */
    	};
    	union {
    	    struct {
    		unsigned long private;		/* Mapping-private opaque data:
    								 	 * usually used for buffer_heads
    									 * if PagePrivate set; used for
    									 * swp_entry_t if PageSwapCache;
    									 * indicates order in the buddy
    									 * system if PG_buddy is set.
    									 */
    		struct address_space *mapping;	/* If low bit clear, points to
    										 * inode address_space, or NULL.
    										 * If page mapped as anonymous
    										 * memory, low bit is set, and
    										 * it points to anon_vma object:
    										 * see PAGE_MAPPING_ANON below.
    										 */
    	    };
    	    struct kmem_cache *slab;	/* SLUB: Pointer to slab */
    	    struct page *first_page;	/* Compound tail pages */
    	};
    	union {
    		pgoff_t index;		/* Our offset within mapping. */
    		void *freelist;		/* SLUB: freelist req. slab lock */
    	};
    	struct list_head lru;		/* Pageout list, eg. active_list
    								 * protected by zone->lru_lock !
    								 */
    	/*
    	 * On machines where all RAM is mapped into kernel address space,
    	 * we can simply calculate the virtual address. On machines with
    	 * highmem some memory is mapped into kernel virtual memory
    	 * dynamically, so we need a place to store that address.
    	 * Note that this field could be 16 bits on x86 ... ;)
    	 *
    	 * Architectures with slow multiplication can define
    	 * WANT_PAGE_VIRTUAL in asm/page.h
    	 */
    	void *virtual;			/* Kernel virtual address (NULL if
    					  	      not kmapped, ie. highmem) */
    };
    
    • slab,freelist和inuse用于slub分配器
    • flags存储了和体系结构无关的标志,用于描述页的属性
    • _count是一个使用计数,表示内核中引用该页的次数
    • _mapcount表示在页表中有多少项指向该页
    • lru是一个表头,用于在各种链表上维护该页,以便将页按照不同类别分组,最重要的类别是活动和不活动页
    • 内核可以将多个毗连的页合并为较大的复合页,第一个页称作首页,而所有其余各页叫做尾页,所有尾页对应的page实例,都将first_page设置为指向首页
    • mapping指定了页帧所在的地址空间,index是页帧在映射内部的偏移量,地址空间是一个非常一般的概念,例如,可以用在向内存读取文件时,用于将文件的内容与装载数据的内存区关联起来。通过一个小技巧,mapping不仅能保存一个指针,还能包含一些额外的信息,用于判断页是否属于某个未关联到地址空间某个匿名内存区:如果将mapping最低位置1,该指针指向另外一个数据结构(anon_vma),这个结构对实现匿名映射的逆向映射很重要
    • private指向私有数据的指针,根据页的用途,可以用不同方式使用该指针
    • virtual用于高端内存区域中的页
  • 体系结构无关的标志

    • PG_locked:锁定页面不允许内核的其他部分访问.
    • PG_error:涉及该页的I/O操作期间发生错误.
    • PG_referenced, PG_active:控制系统中使用该页的活跃程度.
    • PG_uptodate:页的数据已经从块设备读取,其间没有出错
    • PG_dirty:与硬盘上的数据相比,页的内容发生改变.
    • PG_lru:有助于实现页面回收和切换,内核使用两个最近最少使用链表来区分活动页和不活动页,如果页在其中一个链表中,该位置位,如果在活动链表中,PG_active会置位
    • PG_highmem:页在高端内存中
    • PG_private:表明page的private成员非空
    • PG_writeback:页的内容处于向块设备回写过程中
    • PG_slab:页是slab分配器的一部分
    • PG_swapcache:页处于交换缓存中
    • PG_reclaim:内核决定回收特定页时设置
    • PG_compound:该页属于一个更大复合页
    • PG_buddy:表明页空闲且包含在伙伴系统的列表中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值