0
点赞
收藏
分享

微信扫一扫

linux内核那些事之mempolicy(1)

mempolicy为内核分配内存策略管理模块,主要是在numa系统中用于根据指定的内存分配策略决定分配的内存所处的NUMA节点中:

由于NUMA系统中有多个内存控制器,每个控制器并不是与每个cpu距离相等的,如下图所示:

每个节点有自己专属内存控制器,所管理的内存称为本地内存,本地内存距离自己节点最近因此访问速度最快。

而不属于本节点内存称为远端内存remote memory,每个节点访问远端内存速度比本地内存要慢的多,具体慢多少与具体硬件架构有关系。

因此NUMA节点中分配的内存所处的节点,是否与程序运行节点一致,对性能会产生严重影响,下图是内核中alloc_pages接口申请物理内存逻辑:

  •  NUMA系统中在分配物理内存之前首先需要经过mempolicy内核子系统根据合适的内存配置策略决定分配内存所处节点,之后buddy根据分配的节点分配内存。
  • UMA系统中由于只有一个节点,因此不需要经过mempolicy子系统,而是直接从唯一的一个节点中通过buddy分配物理内存。

通常在一个NUMA系统中,分配内存策略会对程序性能影响非常大,合适的内存分配策略对程序性能会有一个显著提升。最好的内存申请策略就是将内存分配到程序运行的节点中,但是在一些大型程序中将内存全部分布到运行的节点中会受限当前节点内存带宽,有时为了分担这种节点内存带宽需要定制内存分配策略分配到其他节点中。

struct mempolicy 

内核将内存管理策略抽象成struct mempolicy 结构方便对内存策略进行管理:

struct mempolicy {
	atomic_t refcnt;
	unsigned short mode; 	/* See MPOL_* above */
	unsigned short flags;	/* See set_mempolicy() MPOL_F_* above */
	union {
		short 		 preferred_node; /* preferred */
		nodemask_t	 nodes;		/* interleave/bind */
		/* undefined for default */
	} v;
	union {
		nodemask_t cpuset_mems_allowed;	/* relative to these nodes */
		nodemask_t user_nodemask;	/* nodemask passed by user */
	} w;
};

 结构成员:

  • atomic_t refcnt: 该策略引用计数。
  • unsigned short mode:策略管理模式支持:MPOL_DEFAULT、MPOL_PREFERRED、MPOL_BIND、MPOL_INTERLEAVE、MPOL_LOCAL。
  • unsigned short flags: 设置内存策略管理标记为支持:MPOL_F_STATIC_NODES和MPOL_F_RELATIVE_NODES 标记位,后续在5.12内核版本中还增加了MPOL_F_NUMA_BALANCIN标记位。
  • union v:该结构体指明内存策略时优先使用的内存节点,preferred_node为优先使用的节点,nodes为interleave间隔轮询方式使用的内存节点。
  • union w:与系统cpuset 和指定用户支持的node,cpuset_mems_allowed为cpuset设置的节点mask,user_nodemask为用户通过配置传递nodemask配置。

mempolicy mode

mempolicy mode用于决定内存策略采用何种模式,主要支持以下几种:

/*
 * Both the MPOL_* mempolicy mode and the MPOL_F_* optional mode flags are
 * passed by the user to either set_mempolicy() or mbind() in an 'int' actual.
 * The MPOL_MODE_FLAGS macro determines the legal set of optional mode flags.
 */

/* Policies */
enum {
	MPOL_DEFAULT,
	MPOL_PREFERRED,
	MPOL_BIND,
	MPOL_INTERLEAVE,
	MPOL_LOCAL,
	MPOL_MAX,	/* always last member of enum */
};
  •  MPOL_DEFAULT: 默认配置策略,系统起来之后会配置一个默认配置策略(系统默认策略:分配内存时采用本地内存)。
  • MPOL_PREFERRED:建议优先从指定的node 节点中申请内存,当指定的node节点分配内存失败时,会采用fallback从zonelist中其他节点内存中申请内存。
  • MPOL_BIND:强制绑定内存策略,必须从指定的node节点中申请内存,如果指定内存节点申请内存失败,不会采用fallback策略从其他节点中获取内存。
  • MPOL_INTERLEAVE:间隔轮询策略,有时为了避免在一个节点申请过多内存造成带宽不够,需要将申请的内存分配到其他节点中进行带宽分担。采用该分配策略,申请内存时,如果时匿名页或者共享内存页,会通过虚拟地址对应vma中page offset计算出申请的内存节点。
  • MPOL_LOCAL:从3.8版本中开始引入,从本地内存中申请内存。

mempolicy flags

mempolicy flags主要作用是根据指定的mode模式之后进行选择node节点时,flags指定选择node节点原则,

flag主要分为两个类型外部flag和内存flag

外部flag

外部flag可以通过系统调用修改指定主要支持两个:

/* Flags for set_mempolicy */
#define MPOL_F_STATIC_NODES	(1 << 15)
#define MPOL_F_RELATIVE_NODES	(1 << 14)
  •  MPOL_F_STATIC_NODES从指定的nodesmask选择(由于可以指定多个node节点因此内核使用位即nodemask代表做配置的节点。当nodesmask修改时,并不会修改已经分配的内存所使用的节点
  • MPOL_F_RELATIVE_NODES从指定node mask中轮询选择进行分担
  • MPOL_F_NUMA_BALANCING为内核5.12版本中因此,主要时当mode 为MPOL_BIND模式是,启动内核NUMA 内存功能

内部flag

内部flag主要是kernel内部处理使用 ,主要有以下:

#define MPOL_F_SHARED  (1 << 0)	/* identify shared policies */
#define MPOL_F_LOCAL   (1 << 1)	/* preferred local allocation */
#define MPOL_F_MOF	(1 << 3) /* this policy wants migrate on fault */
#define MPOL_F_MORON	(1 << 4) /* Migrate On protnone Reference On Node */
  • MPOL_F_SHARED: 该策略是共享的,被多个VMA或者线程使用。
  • MPOL_F_LOCAL: 使用本地preferred节点,一般为程序运行的节点。
  • MPOL_F_MOF:在page fault是可以或者即将进行迁移。
  • MPOL_F_MORON:将页面迁移到使用该内存的节点上。

Scope of Memory Policies

内核将mempolicy按照使用的范围及作用域,划分成了几个不同的memory policy。

System Default Policy

系统默认配置策略,也称为“hard code",在系统起来之后就构建初始化完成并且不能通过系统调用进行修改,默认策略就是使用申请内存时使用本地内存(local allocation)即程序运行所处的NUMA节点与内存分配节点一致。特别要注意的是在内核启动过程中分配的内存并不是按照此策略,为了防止启动过程中申请的内存全部累积到一个节点,启动完成中造成其他程序在该节点没有足够内存,所以在内存启动过程中采用轮询分配策略,将内核启动过程申请的内存分布到各个节点中。

 针对全局内存配置策略有preferred_node_policy和default_policy两个。

default_policy

default_policy全局默认配置策略定义如下:

/*
 * run-time system-wide default policy => local allocation
 */
static struct mempolicy default_policy = {
	.refcnt = ATOMIC_INIT(1), /* never free it */
	.mode = MPOL_PREFERRED,
	.flags = MPOL_F_LOCAL,
};

default_policy为其他策略都全部没有情况下,使用默认配置策略,mode为MPOL_PREFERRED即优先指定node, flags为MPOL_F_LOCAL为优先使用本地节点内存。

preferred_node_policy

preferred_node_policy为每个NUMA节点都定义了一个默认配置策略,preferred_node_policy定义如下:

static struct mempolicy preferred_node_policy[MAX_NUMNODES];

系统启动之后numa_policy_init()函数为每个节点都配置默认配置策略:

/* assumes fs == KERNEL_DS */
void __init numa_policy_init(void)
{
    ... ...
	for_each_node(nid) {
		preferred_node_policy[nid] = (struct mempolicy) {
			.refcnt = ATOMIC_INIT(1),
			.mode = MPOL_PREFERRED,
			.flags = MPOL_F_MOF | MPOL_F_MORON,
			.v = { .preferred_node = nid, },
		};
	}

	... ...
}
  •  每个节点都初始化相应的配置策略为MPOL_PREFERRED,指定的preferred_node为本地节点,flag可以做迁移。

Task/Process Policy

  • taks/process policy为进程/线程配置策略,每个线程或进程都可以指定自己专属配置策略。该配置可以通过系统调用set_policy进行修改或者添加,如果进程/线程没有指定配置策略,将会fall back到系统配置策略。
  • 该配置策略将会对整个task地址空间其作用,并且可以被继承。如fork()之后子进程继承父进程mempolicy。如果是一个线程则只在当前线程其作用,如果在设定该策略之后创建的线程将继承当前线程的配置策略。
  • task/process policy配置策略只会影响还未分配的内存,如何内存是指定配置策略之前申请的将不受影响。

 taks/process policy数据管理结构关系如下:

  •  struct mempolicy结构挂载到struct task_struct结构中,每个线程/进程有自己专属mempolicy。 

mpol_set_nodemask()

mpol_set_nodemask函数用于为一个进程或者线程设置一个新的mempolicy:

static int mpol_set_nodemask(struct mempolicy *pol,
		     const nodemask_t *nodes, struct nodemask_scratch *nsc)
{
	int ret;

	/* if mode is MPOL_DEFAULT, pol is NULL. This is right. */
	if (pol == NULL)
		return 0;
	/* Check N_MEMORY */
	nodes_and(nsc->mask1,
		  cpuset_current_mems_allowed, node_states[N_MEMORY]);

	VM_BUG_ON(!nodes);
	if (pol->mode == MPOL_PREFERRED && nodes_empty(*nodes))
		nodes = NULL;	/* explicit local allocation */
	else {
		if (pol->flags & MPOL_F_RELATIVE_NODES)
			mpol_relative_nodemask(&nsc->mask2, nodes, &nsc->mask1);
		else
			nodes_and(nsc->mask2, *nodes, nsc->mask1);

		if (mpol_store_user_nodemask(pol))
			pol->w.user_nodemask = *nodes;
		else
			pol->w.cpuset_mems_allowed =
						cpuset_current_mems_allowed;
	}

	if (nodes)
		ret = mpol_ops[pol->mode].create(pol, &nsc->mask2);
	else
		ret = mpol_ops[pol->mode].create(pol, NULL);
	return ret;
}
  • mpol_new:申请一个新的mempolicy。
  • task_lock(current):将当前运行的task进行加锁
  • mpol_set_nodemask: 设置node mask.
  • old = current->mempolicy:获取旧mempolicy
  • current->mempolicy = new:将新的mempolicy设置到当前task中。
  • 如果mempolicy为MPOL_INTERLEAVE,即为交替节点申请内存,设置il_prev字段。
  • task_unlock(current):释放task锁
  • mpol_put(old):将old mempolicy计数减1,如果为0则将其占用内存释放。

VMA Policy

VMA policy是颗粒度更小的内存配置管理策略,支持指定特定的内存空间配置策略。该内存配置策略可以通过mbind系统调用指定。

  • VMA policy仅作用在匿名页空间,包括栈空间、堆空间以及通过mmap使用MAP_ANONYMOUS标记为申请的地址空间。如果是使用MAP_SHARED标记的文件映射mmap,vma policy将会被忽略。如果是MAP_PRIVATE标记的文件映射mmap,该策略只有在使用匿名页申请到的内存在COW(copy on write)才起作用。
  • 当一个虚拟内存被所有进程/线程共享时,该虚拟内存 vma policy将会共享,且可以通过fork进行继承。 
  • 如果一个旧的VMA 中有部分VMA policy不同 将会被分裂成两个,即同一个vma必须 vma policy相同。
  • VMA策略只有在申请内存时才会有效,即如果一个vma已经申请物理内存,之后再修改该vma策略 已经分配的物理内存还是按照旧的策略使用。

vma policy管理结构如下图所示:

  • vma policy内存管理数据挂载到struct vm_area_struct结构中。
  • 除了上述,vma policy还支持通过vm_ops自定义set_policy配置策略。再部分驱动程序可能需要使用该策略进行单独配置驱动特有的配置策略。 

vma_replace_policy

vma_replace_policy用于设置vma 基本mempolicy:


/*
 * Apply policy to a single VMA
 * This must be called with the mmap_lock held for writing.
 */
static int vma_replace_policy(struct vm_area_struct *vma,
						struct mempolicy *pol)
{
	int err;
	struct mempolicy *old;
	struct mempolicy *new;

	pr_debug("vma %lx-%lx/%lx vm_ops %p vm_file %p set_policy %p\n",
		 vma->vm_start, vma->vm_end, vma->vm_pgoff,
		 vma->vm_ops, vma->vm_file,
		 vma->vm_ops ? vma->vm_ops->set_policy : NULL);

	new = mpol_dup(pol);
	if (IS_ERR(new))
		return PTR_ERR(new);

	if (vma->vm_ops && vma->vm_ops->set_policy) {
		err = vma->vm_ops->set_policy(vma, new);
		if (err)
			goto err_out;
	}

	old = vma->vm_policy;
	vma->vm_policy = new; /* protected by mmap_lock */
	mpol_put(old);

	return 0;
 err_out:
	mpol_put(new);
	return err;
}
  • struct vm_area_struct *vma:所需要设置的vma结构。
  • struct mempolicy *pol:vma将要设置的mempolicy。
  • new = mpol_dup(pol):将pol复制生成一个新的mempolicy。
  • vma->vm_ops->set_policy:如果vma->vm_ops->set_policy有设置,则直接调用进行单独处理
  • old = vma->vm_policy:获取vma旧的mempolicy
  • vma->vm_policy:将新的mempolicy 设置到vma->vm_policy中。
  • mpol_put(old): 将旧的old mempolicy计数减一,如果计数为0则将旧的mempolicy释放。

mpol_new()

mpol_new用于从slab中申请一个mempolicy内存,用于管理内存策略:


/*
 * This function just creates a new policy, does some check and simple
 * initialization. You must invoke mpol_set_nodemask() to set nodes.
 */
static struct mempolicy *mpol_new(unsigned short mode, unsigned short flags,
				  nodemask_t *nodes)
{
	struct mempolicy *policy;

	pr_debug("setting mode %d flags %d nodes[0] %lx\n",
		 mode, flags, nodes ? nodes_addr(*nodes)[0] : NUMA_NO_NODE);

	if (mode == MPOL_DEFAULT) {
		if (nodes && !nodes_empty(*nodes))
			return ERR_PTR(-EINVAL);
		return NULL;
	}
	VM_BUG_ON(!nodes);

	/*
	 * MPOL_PREFERRED cannot be used with MPOL_F_STATIC_NODES or
	 * MPOL_F_RELATIVE_NODES if the nodemask is empty (local allocation).
	 * All other modes require a valid pointer to a non-empty nodemask.
	 */
	if (mode == MPOL_PREFERRED) {
		if (nodes_empty(*nodes)) {
			if (((flags & MPOL_F_STATIC_NODES) ||
			     (flags & MPOL_F_RELATIVE_NODES)))
				return ERR_PTR(-EINVAL);
		}
	} else if (mode == MPOL_LOCAL) {
		if (!nodes_empty(*nodes) ||
		    (flags & MPOL_F_STATIC_NODES) ||
		    (flags & MPOL_F_RELATIVE_NODES))
			return ERR_PTR(-EINVAL);
		mode = MPOL_PREFERRED;
	} else if (nodes_empty(*nodes))
		return ERR_PTR(-EINVAL);
	policy = kmem_cache_alloc(policy_cache, GFP_KERNEL);
	if (!policy)
		return ERR_PTR(-ENOMEM);
	atomic_set(&policy->refcnt, 1);
	policy->mode = mode;
	policy->flags = flags;

	return policy;
}
  • 根据mode 和flag 对参数进行判断,如果 mode MPOL_PREFERRED或者MPOL_LOCAL且flag设置MPOL_F_STATIC_NODES和MPOL_F_RELATIVE_NODES,必须要配置至少一个node节点,否则返回NULL。
  • kmem_cache_alloc:用于从名称为numa_policy的slab中申请一个内存。
  • 将申请到的mempolicy refcnt计数为1。
  • 设置mempolicy设置其中的mode和flag。

mpol_put()

mpol_put用于将mempolicy中的 refcnt计数减1,如果最后计数为0,将占用的内存释放掉,该函数最终会调用__mpol_put:

/* Slow path of a mpol destructor. */
void __mpol_put(struct mempolicy *p)
{
	if (!atomic_dec_and_test(&p->refcnt))
		return;
	kmem_cache_free(policy_cache, p);
}
  •  将mempolicy 中refcnt计数减一 并检查结果是否为0
  • 如果为0,调用kmem_cache_free 将mempolicy内存释放到slab中。

nodemask_t节点mask

nodemask_t为最多支持的numa节点bit为数结构,定义为:

typedef struct { DECLARE_BITMAP(bits, MAX_NUMNODES); } nodemask_t;

 DECLARE_BITMAP宏为unsigned long 类型的数组,其大小由其大小等于(MAX_NUMNODES/sizeof(unsigned long)),每个节点占用一个bit位,DECLARE_BITMAP定义(include\linux\types.h):

#define DECLARE_BITMAP(name,bits) \
	unsigned long name[BITS_TO_LONGS(bits)]

 get_nodes()

get_nodes()函数用于将系统调用传递的nmask 参数转换成nodemask_t:

int get_nodes(nodemask_t *nodes, const unsigned long __user *nmask, unsigned long maxnode)

参数:

  • nodemask_t *nodes:转换后的结果。
  • unsigned long __user *nmask:传递的node 节点,采用bitmap形式,unsigned long类型的数组
  • unsigned long maxnode:最多node节点数目。

get_nodes源码如下:


/*
 * User space interface with variable sized bitmaps for nodelists.
 */

/* Copy a node mask from user space. */
static int get_nodes(nodemask_t *nodes, const unsigned long __user *nmask,
		     unsigned long maxnode)
{
	unsigned long k;
	unsigned long t;
	unsigned long nlongs;
	unsigned long endmask;

	--maxnode;
	nodes_clear(*nodes);
	if (maxnode == 0 || !nmask)
		return 0;
	if (maxnode > PAGE_SIZE*BITS_PER_BYTE)
		return -EINVAL;

	nlongs = BITS_TO_LONGS(maxnode);
	if ((maxnode % BITS_PER_LONG) == 0)
		endmask = ~0UL;
	else
		endmask = (1UL << (maxnode % BITS_PER_LONG)) - 1;

	/*
	 * When the user specified more nodes than supported just check
	 * if the non supported part is all zero.
	 *
	 * If maxnode have more longs than MAX_NUMNODES, check
	 * the bits in that area first. And then go through to
	 * check the rest bits which equal or bigger than MAX_NUMNODES.
	 * Otherwise, just check bits [MAX_NUMNODES, maxnode).
	 */
	if (nlongs > BITS_TO_LONGS(MAX_NUMNODES)) {
		for (k = BITS_TO_LONGS(MAX_NUMNODES); k < nlongs; k++) {
			if (get_user(t, nmask + k))
				return -EFAULT;
			if (k == nlongs - 1) {
				if (t & endmask)
					return -EINVAL;
			} else if (t)
				return -EINVAL;
		}
		nlongs = BITS_TO_LONGS(MAX_NUMNODES);
		endmask = ~0UL;
	}

	if (maxnode > MAX_NUMNODES && MAX_NUMNODES % BITS_PER_LONG != 0) {
		unsigned long valid_mask = endmask;

		valid_mask &= ~((1UL << (MAX_NUMNODES % BITS_PER_LONG)) - 1);
		if (get_user(t, nmask + nlongs - 1))
			return -EFAULT;
		if (t & valid_mask)
			return -EINVAL;
	}

	if (copy_from_user(nodes_addr(*nodes), nmask, nlongs*sizeof(unsigned long)))
		return -EFAULT;
	nodes_addr(*nodes)[nlongs-1] &= endmask;
	return 0;
}
  • 节点id从0开始,因此maxnode节点要减一--maxnode;
  • nodes_clear(*nodes):先将输出的nodemastk_t清零 防止传递的参数没有初始化。
  • maxnode 参数进行判断,不能超过一个PAGE_SIZE大小。
  • nlongs = BITS_TO_LONGS(maxnode):根据maxnode 计算出unsigned long  *数组大小,采用bitmap形式,每个node节点占1位,maxnode即为多少个bit数目
  • endmask:求出在结束mask在unsigned long中偏移。
  • nlongs 大于系统可支持MAX_NUMNODES节点,检查超过部分是否为0,如果超过部分不为0则返回错误。
  • copy_from_user:将系统调用用户层内存copy到nodemask_t中。
  • nodes_addr(*nodes)[nlongs-1] &= endmask:将未设置部分节点都清零。

参考资料

NUMA policy and memory types [LWN.net]

[PATCH v6 0/6] Introduce multi-preference mempolicy [LWN.net]

NUMA Memory Policy — The Linux Kernel documentation

举报

相关推荐

0 条评论