【Linux Kernel-task_struct专题】task_struct内存分配

在Linux 内核中task_struct的内存是一个kmem_cache结构体对象,kmem_cache 是内核对象缓存(Slab 分配器)的一个结构体,用于管理内核对象的内存分配和释放。kmem_cache 结构体通常用于创建一个缓存,以便快速分配和释放特定类型的内核对象。

kmem_cache


源码定义kernel/fork.c Linux kernel -v6.11

static struct kmem_cache *task_struct_cachep;

static inline struct task_struct *alloc_task_struct_node(int node)
{
	return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node);
}

上面👆,static struct kmem_cache *task_struct_cachep; 这行代码定义了一个静态的指针变量 task_struct_cachep,它指向一个 kmem_cache 结构体。这个指针通常用于指向一个特定类型的内核对象的缓存,例如进程描述符 task_struct 的缓存。

这里的 static 关键字表示这个变量具有静态存储期,它只能在定义它的文件内部访问,且在程序的整个运行期间都存在。

那么,kmem-cache到底是什么呢?我们先看下它的定义,具体的详细剖析,在下一个专题中会讲到。

/*
 * Slab cache management.
 */
struct kmem_cache {
#ifndef CONFIG_SLUB_TINY
	struct kmem_cache_cpu __percpu *cpu_slab;
#endif
	/* Used for retrieving partial slabs, etc. */
	slab_flags_t flags;
	unsigned long min_partial;
	unsigned int size;		/* Object size including metadata */
	unsigned int object_size;	/* Object size without metadata */
	struct reciprocal_value reciprocal_size;
	unsigned int offset;		/* Free pointer offset */
#ifdef CONFIG_SLUB_CPU_PARTIAL
	/* Number of per cpu partial objects to keep around */
	unsigned int cpu_partial;
	/* Number of per cpu partial slabs to keep around */
	unsigned int cpu_partial_slabs;
#endif
	struct kmem_cache_order_objects oo;

	/* Allocation and freeing of slabs */
	struct kmem_cache_order_objects min;
	gfp_t allocflags;		/* gfp flags to use on each alloc */
	int refcount;			/* Refcount for slab cache destroy */
	void (*ctor)(void *object);	/* Object constructor */
	unsigned int inuse;		/* Offset to metadata */
	unsigned int align;		/* Alignment */
	unsigned int red_left_pad;	/* Left redzone padding size */
	const char *name;		/* Name (only for display!) */
	struct list_head list;		/* List of slab caches */
#ifdef CONFIG_SYSFS
	struct kobject kobj;		/* For sysfs */
#endif
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	unsigned long random;
#endif

#ifdef CONFIG_NUMA
	/*
	 * Defragmentation by allocating from a remote node.
	 */
	unsigned int remote_node_defrag_ratio;
#endif

#ifdef CONFIG_SLAB_FREELIST_RANDOM
	unsigned int *random_seq;
#endif

#ifdef CONFIG_KASAN_GENERIC
	struct kasan_cache kasan_info;
#endif

#ifdef CONFIG_HARDENED_USERCOPY
	unsigned int useroffset;	/* Usercopy region offset */
	unsigned int usersize;		/* Usercopy region size */
#endif

	struct kmem_cache_node *node[MAX_NUMNODES];
};

alloc_task_struct_node

alloc_task_struct_node(int node) 函数是在 Linux 内核中用于分配一个task_struct结构体的函数。

在多核系统中,内存是分区的,每个区(通常是一个NUMA节点)都有自己的内存。alloc_task_struct_node 函数的目的是在指定的NUMA节点上分配 task_struct,这样可以减少跨节点的内存访问,提高性能。参数 node 指定了要分配 task_struct 的NUMA节点。

这个函数通常会使用 Slab 分配器来分配内存。Slab 分配器是 Linux 内核中用于管理内核对象(如进程描述符、文件对象等)内存分配的机制。它通过缓存这些对象的内存,可以快速地分配和释放这些对象,减少内存碎片,并提高分配效率。

在内核初始化时,会创建一个专门用于 task_struct 的 Slab 缓存,通常命名为 “task_struct”。当需要创建一个新进程时,就会从这个缓存中分配一个 task_struct 结构体。这个过程通常发生在进程创建(如通过 fork 系统调用)或者在加载内核模块时。

这个函数只传入一个参数,这个参数node其实是一个int类型,它表示NUMA节点的编号。


kmem_cache_alloc_node

kmem_cache_alloc_node 函数用于从指定的 NUMA 节点分配内存。这个函数是 Slab 分配器的一部分,用于管理内核对象的缓存。

void *kmem_cache_alloc_node_noprof(struct kmem_cache *s, gfp_t flags,
				   int node) __assume_slab_alignment __malloc;
#define kmem_cache_alloc_node(...)	alloc_hooks(kmem_cache_alloc_node_noprof(__VA_ARGS__))
#define alloc_hooks_tag(_tag, _do_alloc)				\
({									\
	struct alloc_tag * __maybe_unused _old = alloc_tag_save(_tag);	\
	typeof(_do_alloc) _res = _do_alloc;				\
	alloc_tag_restore(_tag, _old);					\
	_res;								\
})

#define alloc_hooks(_do_alloc)						\
({									\
	DEFINE_ALLOC_TAG(_alloc_tag);					\
	alloc_hooks_tag(&_alloc_tag, _do_alloc);			\
})

我们注意下⚠️这两个宏的使用方法,这是在最新的Linux kernel新变更的方法。
它使用了 GCC 的编译器扩展,具体来说是 GCC 的 statement expressions 功能,允许在宏中执行多条语句并返回一个值。这种宏通常用于封装一些在内存分配前后需要执行的钩子(hook)函数。

对于宏alloc_hooks_tag:

  • _tag:这是一个指向 struct alloc_tag 的指针,它可能用于存储当前的分配标签或上下文。
  • _do_alloc:这是一个表达式,通常是一个内存分配函数调用,如 kmalloc 或 kmem_cache_alloc。

宏的工作流程如下:

  • alloc_tag_save(_tag):保存当前的分配标签或上下文,并返回旧的标签。
    _old:用于存储 alloc_tag_save 的返回值,即旧的标签。
  • typeof(_do_alloc) _res = _do_alloc;:执行实际的内存分配操作,并将其结果存储在 _res 中。typeof 关键字用于获取 _do_alloc 表达式的返回类型,确保 _res 具有正确的类型。
  • alloc_tag_restore(_tag, _old);:恢复之前的分配标签或上下文。
    _res;:返回 _do_alloc 的结果。

对于宏alloc_hooks:

  • _do_alloc:同上,这是一个内存分配函数调用。

宏的工作流程如下:

  • DEFINE_ALLOC_TAG(_alloc_tag);:定义一个分配标签的实例 _alloc_tag。这个宏可能会初始化 _alloc_tag 结构,准备用于跟踪分配。
  • alloc_hooks_tag(&_alloc_tag, _do_alloc);:调用 alloc_hooks_tag 宏,传入 _alloc_tag 的地址和 _do_alloc 表达式。

那么,Linux内核为什么使用这两个宏呢?它有什么好处?

这两个宏提供了一种机制,允许在内存分配前后执行额外的逻辑,如保存和恢复分配上下文,这对于调试和跟踪内存分配问题非常有用。通过使用这些宏,内核开发者可以在不修改分配函数本身的情况下,添加额外的逻辑来处理特定的需求。

/**
 * kmem_cache_alloc_node - Allocate an object on the specified node
 * @s: The cache to allocate from.
 * @gfpflags: See kmalloc().
 * @node: node number of the target node.
 *
 * Identical to kmem_cache_alloc but it will allocate memory on the given
 * node, which can improve the performance for cpu bound structures.
 *
 * Fallback to other node is possible if __GFP_THISNODE is not set.
 *
 * Return: pointer to the new object or %NULL in case of error
 */
void *kmem_cache_alloc_node_noprof(struct kmem_cache *s, gfp_t gfpflags, int node)
{
	void *ret = slab_alloc_node(s, NULL, gfpflags, node, _RET_IP_, s->object_size);

	trace_kmem_cache_alloc(_RET_IP_, ret, s, gfpflags, node);

	return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc_node_noprof);
``
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值