mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "wuqiang.matt" <wuqiang.matt@bytedance.com>
To: "Masami Hiramatsu (Google)" <mhiramat@kernel.org>
Cc: linux-trace-kernel@vger.kernel.org, davem@davemloft.net,
	anil.s.keshavamurthy@intel.com, naveen.n.rao@linux.ibm.com,
	rostedt@goodmis.org, peterz@infradead.org,
	akpm@linux-foundation.org, sander@svanheule.net,
	ebiggers@google.com, dan.j.williams@intel.com,
	jpoimboe@kernel.org, linux-kernel@vger.kernel.org, lkp@intel.com,
	mattwu@163.com
Subject: Re: [PATCH v9 1/5] lib: objpool added: ring-array based lockless MPMC
Date: Fri, 13 Oct 2023 00:16:08 +0800	[thread overview]
Message-ID: <da8d0422-756a-364d-9d57-bc3455ab97dc@bytedance.com> (raw)
In-Reply-To: <20231009231938.c6830d986103da57ce55d26d@kernel.org>

Hello Masami,

I've udpated the objpool patch and did some function testings for X64 and
ARM64. Later I'll do the performance testings and more regressions.

Here are the changelogs:
1) new struct objpool_node added to represent the real percpu ring arrary
    and struct objpool_slot now represents the expansion of objpool_node.
    ages[] and entries[] are now managed by objpool_slot (which is managed
    by objpool_head)
2) ages[] added back to objpool_try_add_slot and objpool_try_get_slot
3) unnecessary OBJPOOL_FLAG definitions are removed
4) unnecessary head/tail loading removed since try_cmpxchg_acuiqre and
    try_cmxchg_release have inherent memory loading embeded
5) objpool_fini refined to make sure the extra refcount to be released

The new version is attached in this mail for your review. And I will
prepare the full patch after the regression testings.

Best regards,
wuqiang


diff --git a/include/linux/objpool.h b/include/linux/objpool.h
new file mode 100644
index 000000000000..f3e066601df2
--- /dev/null
+++ b/include/linux/objpool.h
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_OBJPOOL_H
+#define _LINUX_OBJPOOL_H
+
+#include <linux/types.h>
+#include <linux/refcount.h>
+
+/*
+ * objpool: ring-array based lockless MPMC queue
+ *
+ * Copyright: wuqiang.matt@bytedance.com,mhiramat@kernel.org
+ *
+ * The object pool is a scalable implementaion of high performance queue
+ * for objects allocation and reclamation, such as kretprobe instances.
+ *
+ * With leveraging per-cpu ring-array to mitigate the hot spots of memory
+ * contention, it could deliver near-linear scalability for high parallel
+ * scenarios. objpomol is best suited for following cases:
+ * 1) Memory allocation or reclamation are prohibited or too expensive
+ * 2) Consumers are of different priorities, such as irqs and threads
+ *
+ * Limitations:
+ * 1) Maximum objects (capacity) is determined during pool initializing
+ * 2) The memory of objects won't be freed until the pool is finalized
+ * 3) Object allocation (pop) may fail after trying all cpu slots
+ */
+
+/**
+ * struct objpool_node - percpu ring array of objpool
+ * @head: head sequence of the local ring array
+ * @tail: tail sequence of the local ring array
+ *
+ * Represents a cpu-local array-based ring buffer, its size is specialized
+ * during initialization of object pool. The percpu objpool node is to be
+ * allocated from local memory for NUMA system, and to be kept compact in
+ * continuous memory: CPU assigned number of objects are stored just after
+ * the body of objpool_node.
+ *
+ * Real size of the ring array is far too smaller than the value range of
+ * head and tail, typed as uint32_t: [0, 2^32), so only lower bits of head
+ * and tail are used as the actual position in the ring array. In general
+ * the ring array is acting like a small sliding window, which is always
+ * moving forward in the loop of [0, 2^32).
+ */
+ struct objpool_node {
+	uint32_t            head;
+	uint32_t            tail;
+} __packed;
+
+/**
+ * struct objpool_slot - the expansion of percpu objpool_node
+ * @node: the pointer of percpu objpool_node
+ * @ages: unique sequence number to avoid ABA
+ * @entries: object entries on this slot
+ */
+struct objpool_slot {
+	struct objpool_node *node;
+	uint32_t            *ages;
+	void *              *entries;
+};
+
+struct objpool_head;
+
+/*
+ * caller-specified callback for object initial setup, it's only called
+ * once for each object (just after the memory allocation of the object)
+ */
+typedef int (*objpool_init_obj_cb)(void *obj, void *context);
+
+/* caller-specified cleanup callback for objpool destruction */
+typedef int (*objpool_fini_cb)(struct objpool_head *head, void *context);
+
+/**
+ * struct objpool_head - object pooling metadata
+ * @obj_size:   object size, aligned to sizeof(void *)
+ * @nr_objs:    total objs (to be pre-allocated with objpool)
+ * @nr_cpus:    local copy of nr_cpu_ids
+ * @capacity:   max objs can be managed by one objpool_slot
+ * @gfp:        gfp flags for kmalloc & vmalloc
+ * @ref:        refcount of objpool
+ * @flags:      flags for objpool management
+ * @cpu_slots:  pointer to the array of objpool_slot
+ * @release:    resource cleanup callback
+ * @context:    caller-provided context
+ */
+struct objpool_head {
+	int                     obj_size;
+	int                     nr_objs;
+	int                     nr_cpus;
+	int                     capacity;
+	gfp_t                   gfp;
+	refcount_t              ref;
+	unsigned long           flags;
+	struct objpool_slot    *cpu_slots;
+	objpool_fini_cb         release;
+	void                   *context;
+};
+
+#define OBJPOOL_NR_OBJECT_MAX	(1UL << 24) /* maximum numbers of total objects */
+#define OBJPOOL_OBJECT_SIZE_MAX	(1UL << 16) /* maximum size of an object */
+
+/**
+ * objpool_init() - initialize objpool and pre-allocated objects
+ * @pool:    the object pool to be initialized, declared by caller
+ * @nr_objs: total objects to be pre-allocated by this object pool
+ * @object_size: size of an object (should be > 0)
+ * @gfp:     flags for memory allocation (via kmalloc or vmalloc)
+ * @context: user context for object initialization callback
+ * @objinit: object initialization callback for extra setup
+ * @release: cleanup callback for extra cleanup task
+ *
+ * return value: 0 for success, otherwise error code
+ *
+ * All pre-allocated objects are to be zeroed after memory allocation.
+ * Caller could do extra initialization in objinit callback. objinit()
+ * will be called just after slot allocation and will be only once for
+ * each object. Since then the objpool won't touch any content of the
+ * objects. It's caller's duty to perform reinitialization after each
+ * pop (object allocation) or do clearance before each push (object
+ * reclamation).
+ */
+int objpool_init(struct objpool_head *pool, int nr_objs, int object_size,
+		 gfp_t gfp, void *context, objpool_init_obj_cb objinit,
+		 objpool_fini_cb release);
+
+/**
+ * objpool_pop() - allocate an object from objpool
+ * @pool: object pool
+ *
+ * return value: object ptr or NULL if failed
+ */
+void *objpool_pop(struct objpool_head *pool);
+
+/**
+ * objpool_push() - reclaim the object and return back to objpool
+ * @obj:  object ptr to be pushed to objpool
+ * @pool: object pool
+ *
+ * return: 0 or error code (it fails only when user tries to push
+ * the same object multiple times or wrong "objects" into objpool)
+ */
+int objpool_push(void *obj, struct objpool_head *pool);
+
+/**
+ * objpool_drop() - discard the object and deref objpool
+ * @obj:  object ptr to be discarded
+ * @pool: object pool
+ *
+ * return: 0 if objpool was released; -EAGAIN if there are still
+ *         outstanding objects
+ *
+ * objpool_drop is normally for the release of outstanding objects
+ * after objpool cleanup (objpool_fini). Thinking of this example:
+ * kretprobe is unregistered and objpool_fini() is called to release
+ * all remained objects, but there are still objects being used by
+ * unfinished kretprobes (like blockable function: sys_accept). So
+ * only when the last outstanding object is dropped could the whole
+ * objpool be released along with the call of objpool_drop()
+ */
+int objpool_drop(void *obj, struct objpool_head *pool);
+
+/**
+ * objpool_free() - release objpool forcely (all objects to be freed)
+ * @pool: object pool to be released
+ */
+void objpool_free(struct objpool_head *pool);
+
+/**
+ * objpool_fini() - deref object pool (also releasing unused objects)
+ * @pool: object pool to be dereferenced
+ *
+ * objpool_fini() will try to release all remained free objects and
+ * then drop an extra reference of objpool. So if all objects are
+ * already returned to objpool, the objpool will be freed too. But
+ * if there are still outstanding objects (blockable kretprobes),
+ * the objpool won't be released until all the oustanding objects
+ * are dropped
+ */
+void objpool_fini(struct objpool_head *pool);
+
+#endif /* _LINUX_OBJPOOL_H */
diff --git a/lib/objpool.c b/lib/objpool.c
new file mode 100644
index 000000000000..628993d93638
--- /dev/null
+++ b/lib/objpool.c
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/objpool.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/atomic.h>
+#include <linux/prefetch.h>
+#include <linux/irqflags.h>
+#include <linux/cpumask.h>
+#include <linux/log2.h>
+
+/*
+ * objpool: ring-array based lockless MPMC/FIFO queues
+ *
+ * Copyright: wuqiang.matt@bytedance.com,mhiramat@kernel.org
+ */
+
+#define NODE_AGES(p, n) (uint32_t *)((char *)(n) + sizeof(struct objpool_node))
+#define NODE_ENTS(p, n) (void **)((char *)(n) + sizeof(struct objpool_node) + \
+			sizeof(uint32_t) * (p)->capacity)
+#define NODE_OBJS(p, n) (void *)((char *)(n) + sizeof(struct objpool_node) + \
+			(sizeof(uint32_t) + sizeof(void *)) * (p)->capacity)
+
+/* initialize percpu objpool_slot */
+static int
+objpool_init_percpu_slot(struct objpool_head *pool,
+			 struct objpool_slot *slot,
+			 struct objpool_node *node,
+			 int nodes, void *context,
+			 objpool_init_obj_cb objinit)
+{
+	uint32_t mask = pool->capacity - 1;
+	int i;
+
+	/* initialize percpu objpool_slot */
+	slot->node = node;
+	slot->ages = NODE_AGES(pool, node);
+	slot->entries = NODE_ENTS(pool, node);
+
+	/*
+	 * manually set head & tail to avoid possible conflict:
+	 * We assume that the head item is ready for retrieval
+	 * iff head is equal to ages[head & mask]. but ages is
+	 * initialized as 0, so in view of the caller of pop(),
+	 * the 1st item (0th) is always ready, but the reality
+	 * could be: push() is stalled before the final update,
+	 * thus the item being inserted will be lost forever
+	 */
+	node->head = node->tail = pool->capacity;
+
+	/* initialize ages and entries of this objpool_slot */
+	for (i = 0; i < nodes; i++) {
+		void *obj = NODE_OBJS(pool, node) + i * pool->obj_size;
+		if (objinit) {
+			int rc = objinit(obj, context);
+			if (rc)
+				return rc;
+		}
+		slot->ages[node->tail & mask] = node->tail;
+		slot->entries[node->tail & mask] = obj;
+		node->tail++;
+		pool->nr_objs++;
+	}
+
+	return 0;
+}
+
+/* allocate and initialize percpu slots */
+static int
+objpool_init_percpu_slots(struct objpool_head *pool, int nr_objs,
+			  void *context, objpool_init_obj_cb objinit)
+{
+	int i, cpu_count = 0;
+
+	for (i = 0; i < pool->nr_cpus; i++) {
+
+		struct objpool_node *node;
+		int nodes, size, rc;
+
+		/* skip the cpu node which could never be present */
+		if (!cpu_possible(i))
+			continue;
+
+		/* compute how many objects to be allocated with this slot */
+		nodes = nr_objs / num_possible_cpus();
+		if (cpu_count < (nr_objs % num_possible_cpus()))
+			nodes++;
+		cpu_count++;
+
+		size = pool->obj_size * nodes + sizeof(struct objpool_node) +
+		       (sizeof(void *) + sizeof(uint32_t)) * pool->capacity;
+
+		/*
+		 * here we allocate percpu-slot & objs together in a single
+		 * allocation to make it more compact, taking advantage of
+		 * warm caches and TLB hits. in default vmalloc is used to
+		 * reduce the pressure of kernel slab system. as we know,
+		 * mimimal size of vmalloc is one page since vmalloc would
+		 * always align the requested size to page size
+		 */
+		if (pool->gfp & GFP_ATOMIC)
+			node = kmalloc_node(size, pool->gfp, cpu_to_node(i));
+		else
+			node = __vmalloc_node(size, sizeof(void *), pool->gfp,
+				cpu_to_node(i), __builtin_return_address(0));
+		if (!node)
+			return -ENOMEM;
+		memset(node, 0, size);
+
+		/* initialize the objpool_slot of cpu node i */
+		rc = objpool_init_percpu_slot(pool, &pool->cpu_slots[i],
+					node, nodes, context, objinit);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+/* cleanup all percpu slots of the object pool */
+static void objpool_fini_percpu_slots(struct objpool_head *pool)
+{
+	int i;
+
+	if (!pool->cpu_slots)
+		return;
+
+	for (i = 0; i < pool->nr_cpus; i++)
+		kvfree(pool->cpu_slots[i].node);
+	kfree(pool->cpu_slots);
+}
+
+/* initialize object pool and pre-allocate objects */
+int objpool_init(struct objpool_head *pool, int nr_objs, int object_size,
+		gfp_t gfp, void *context, objpool_init_obj_cb objinit,
+		objpool_fini_cb release)
+{
+	int rc, capacity, slot_size;
+
+	/* check input parameters */
+	if (nr_objs <= 0 || nr_objs > OBJPOOL_NR_OBJECT_MAX ||
+	    object_size <= 0 || object_size > OBJPOOL_OBJECT_SIZE_MAX)
+		return -EINVAL;
+
+	/* align up to unsigned long size */
+	object_size = ALIGN(object_size, sizeof(long));
+
+	/* calculate capacity of percpu objpool_slot */
+	capacity = roundup_pow_of_two(nr_objs);
+	if (!capacity)
+		return -EINVAL;
+
+	/* initialize objpool pool */
+	memset(pool, 0, sizeof(struct objpool_head));
+	pool->nr_cpus = nr_cpu_ids;
+	pool->obj_size = object_size;
+	pool->capacity = capacity;
+	pool->gfp = gfp & ~__GFP_ZERO;
+	pool->context = context;
+	pool->release = release;
+	slot_size = pool->nr_cpus * sizeof(struct objpool_slot);
+	pool->cpu_slots = kzalloc(slot_size, pool->gfp);
+	if (!pool->cpu_slots)
+		return -ENOMEM;
+
+	/* initialize per-cpu slots */
+	rc = objpool_init_percpu_slots(pool, nr_objs, context, objinit);
+	if (rc)
+		objpool_fini_percpu_slots(pool);
+	else
+		refcount_set(&pool->ref, pool->nr_objs + 1);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(objpool_init);
+
+/* adding object to slot, abort if the slot was already full */
+static inline int
+objpool_try_add_slot(void *obj, struct objpool_head *pool, int cpu)
+{
+	struct objpool_slot *slot = &pool->cpu_slots[cpu];
+	struct objpool_node *node = slot->node;
+	uint32_t head, tail, mask = pool->capacity - 1;
+
+	/* loading tail and head as a local snapshot, tail first */
+	tail = READ_ONCE(node->tail);
+
+	do {
+		head = READ_ONCE(node->head);
+		/* fault caught: something must be wrong */
+		WARN_ON_ONCE(tail - head > pool->nr_objs);
+	} while (!try_cmpxchg_acquire(&node->tail, &tail, tail + 1));
+
+	/* now the tail position is reserved for the given obj */
+	WRITE_ONCE(slot->entries[tail & mask], obj);
+	/* update sequence to make this obj available for pop() */
+	smp_store_release(&slot->ages[tail & mask], tail);
+
+	return 0;
+}
+
+/* reclaim an object to object pool */
+int objpool_push(void *obj, struct objpool_head *pool)
+{
+	unsigned long flags;
+	int rc;
+
+	/* disable local irq to avoid preemption & interruption */
+	raw_local_irq_save(flags);
+	rc = objpool_try_add_slot(obj, pool, raw_smp_processor_id());
+	raw_local_irq_restore(flags);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(objpool_push);
+
+/* try to retrieve object from slot */
+static inline void *objpool_try_get_slot(struct objpool_head *pool, int cpu)
+{
+	struct objpool_slot *slot = &pool->cpu_slots[cpu];
+	struct objpool_node *node = slot->node;
+	uint32_t head, mask = pool->capacity - 1;
+
+	/* load node->head and save to local head */
+	head = smp_load_acquire(&node->head);
+
+	while (head != READ_ONCE(node->tail)) {
+		uint32_t pos = head & mask, prev = head;
+
+		/* do prefetching of the object pointer */
+		prefetch(&slot->entries[pos]);
+
+		/* check whether the object is ready for retrieval */
+		if (smp_load_acquire(&slot->ages[pos]) == head) {
+			/* obj must've been udpated by its push() */
+			void *obj = READ_ONCE(slot->entries[pos]);
+			/* try to commit and move forward by 1 */
+			if (try_cmpxchg_release(&node->head, &head, head + 1))
+				return obj;
+			/* head mismatch: was consumed by other nodes */
+		} else {
+			/* refresh head from memory and retry */
+			head = READ_ONCE(node->head);
+			/*
+			* head stays unchanged, so it's very likely there's
+			* an ongoing push() on other cpu nodes but yet not
+			* update ages[] to mark it's completion
+			*/
+			if (head == prev)
+				break;
+		}
+	}
+
+	return NULL;
+}
+
+/* allocate an object from object pool */
+void *objpool_pop(struct objpool_head *pool)
+{
+	void *obj = NULL;
+	unsigned long flags;
+	int i, cpu;
+
+	/* disable local irq to avoid preemption & interruption */
+	raw_local_irq_save(flags);
+
+	cpu = raw_smp_processor_id();
+	for (i = 0; i < num_possible_cpus(); i++) {
+		obj = objpool_try_get_slot(pool, cpu);
+		if (obj)
+			break;
+		cpu = cpumask_next_wrap(cpu, cpu_possible_mask, -1, 1);
+	}
+	raw_local_irq_restore(flags);
+
+	return obj;
+}
+EXPORT_SYMBOL_GPL(objpool_pop);
+
+/* release whole objpool forcely */
+void objpool_free(struct objpool_head *pool)
+{
+	if (!pool->cpu_slots)
+		return;
+
+	/* release percpu slots */
+	objpool_fini_percpu_slots(pool);
+
+	/* call user's cleanup callback if provided */
+	if (pool->release)
+		pool->release(pool, pool->context);
+}
+EXPORT_SYMBOL_GPL(objpool_free);
+
+/* drop the allocated object, rather reclaim it to objpool */
+int objpool_drop(void *obj, struct objpool_head *pool)
+{
+	if (!obj || !pool)
+		return -EINVAL;
+
+	if (refcount_dec_and_test(&pool->ref)) {
+		objpool_free(pool);
+		return 0;
+	}
+
+	return -EAGAIN;
+}
+EXPORT_SYMBOL_GPL(objpool_drop);
+
+/* drop unused objects and defref objpool for releasing */
+void objpool_fini(struct objpool_head *pool)
+{
+	void *obj;
+
+	do {
+		/* grab object from objpool and drop it */
+		obj = objpool_pop(pool);
+
+		/*
+		 * drop reference of objpool anyway even if
+		 * the obj is NULL, since one extra ref upon
+		 * objpool was already grabbed during pool
+		 * initialization in objpool_init()
+		 */
+		if (refcount_dec_and_test(&pool->ref))
+			objpool_free(pool);
+	} while (obj);
+}
+EXPORT_SYMBOL_GPL(objpool_fini);


On 2023/10/9 22:19, Masami Hiramatsu (Google) wrote:
> Hi,
> 
> On Mon, 9 Oct 2023 02:40:53 +0800
> wuqiang <wuqiang.matt@bytedance.com> wrote:
> 
>> On 2023/9/23 17:48, Masami Hiramatsu (Google) wrote:
>>> Hi Wuqiang,
>>>
>>> Sorry for replying later.
>>>
>>> On Tue,  5 Sep 2023 09:52:51 +0800
>>> "wuqiang.matt" <wuqiang.matt@bytedance.com> wrote:
>>>
>>>> The object pool is a scalable implementaion of high performance queue
>>>> for object allocation and reclamation, such as kretprobe instances.
>>>>
>>>> With leveraging percpu ring-array to mitigate the hot spot of memory
>>>> contention, it could deliver near-linear scalability for high parallel
>>>> scenarios. The objpool is best suited for following cases:
>>>> 1) Memory allocation or reclamation are prohibited or too expensive
>>>> 2) Consumers are of different priorities, such as irqs and threads
>>>>
>>>> Limitations:
>>>> 1) Maximum objects (capacity) is determined during pool initializing
>>>>      and can't be modified (extended) after objpool creation
>>>
>>> So the pool size is fixed in initialization.
>>
>> Right. The arrary size will be up-rounded to the exponent of 2, but the
>> actual number of objects (to be allocated) are the exact value specified
>> by user.
> 
> Yeah, this makes easy to use the seq-number as index.
> 
>>
>>>
>>>> 2) The memory of objects won't be freed until objpool is finalized
>>>> 3) Object allocation (pop) may fail after trying all cpu slots
>>>
>>> This means that object allocation will fail if the all pools are empty,
>>> right?
>>
>> Yes, pop() will return NULL for this case. pop() does the checking for
>> only 1 round of all cpu nodes.
>>
>> The objpool might not be empty since new object could be inserted back
>> in the meaintime by other nodes, which is natural for lockless queues.
> 
> OK.
> 
>>
>>>
>>>>
>>>> Signed-off-by: wuqiang.matt <wuqiang.matt@bytedance.com>
>>>> ---
>>>>    include/linux/objpool.h | 174 +++++++++++++++++++++
>>>>    lib/Makefile            |   2 +-
>>>>    lib/objpool.c           | 338 ++++++++++++++++++++++++++++++++++++++++
>>>>    3 files changed, 513 insertions(+), 1 deletion(-)
>>>>    create mode 100644 include/linux/objpool.h
>>>>    create mode 100644 lib/objpool.c
>>>>
>>>> diff --git a/include/linux/objpool.h b/include/linux/objpool.h
>>>> new file mode 100644
>>>> index 000000000000..33c832216b98
>>>> --- /dev/null
>>>> +++ b/include/linux/objpool.h
>>>> @@ -0,0 +1,174 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>> +
>>>> +#ifndef _LINUX_OBJPOOL_H
>>>> +#define _LINUX_OBJPOOL_H
>>>> +
>>>> +#include <linux/types.h>
>>>> +#include <linux/refcount.h>
>>>> +
>>>> +/*
>>>> + * objpool: ring-array based lockless MPMC queue
>>>> + *
>>>> + * Copyright: wuqiang.matt@bytedance.com
>>>> + *
>>>> + * The object pool is a scalable implementaion of high performance queue
>>>> + * for objects allocation and reclamation, such as kretprobe instances.
>>>> + *
>>>> + * With leveraging per-cpu ring-array to mitigate the hot spots of memory
>>>> + * contention, it could deliver near-linear scalability for high parallel
>>>> + * scenarios. The ring-array is compactly managed in a single cache-line
>>>> + * to benefit from warmed L1 cache for most cases (<= 4 objects per-core).
>>>> + * The body of pre-allocated objects is stored in continuous cache-lines
>>>> + * just after the ring-array.
>>>
>>> I consider the size of entries may be big if we have larger number of
>>> CPU cores, like 72-cores. And if user specifies (2^n) + 1 entries.
>>> In this case, each CPU has (2^n - 1)/72 objects, but has 2^(n + 1)
>>> entries in ring buffer. So it should be noted.
>>
>> Yes for the arrary size since it‘s up-rounded to the exponent of 2, but the
>> actual number of pre-allocated objects will stay the same as user specified.
>>
>>>> + *
>>>> + * The object pool is interrupt safe. Both allocation and reclamation
>>>> + * (object pop and push operations) can be preemptible or interruptable.
>>>
>>> You've added raw_spinlock_disable/enable(), so it is not preemptible
>>> or interruptible anymore. (Anyway, caller doesn't take care of that)
>>
>> Sure, this decription is imporper and unnecessary. Will be removed.
>>
>>>> + *
>>>> + * It's best suited for following cases:
>>>> + * 1) Memory allocation or reclamation are prohibited or too expensive
>>>> + * 2) Consumers are of different priorities, such as irqs and threads
>>>> + *
>>>> + * Limitations:
>>>> + * 1) Maximum objects (capacity) is determined during pool initializing
>>>> + * 2) The memory of objects won't be freed until the pool is finalized
>>>> + * 3) Object allocation (pop) may fail after trying all cpu slots
>>>> + */
>>>> +
>>>> +/**
>>>> + * struct objpool_slot - percpu ring array of objpool
>>>> + * @head: head of the local ring array (to retrieve at)
>>>> + * @tail: tail of the local ring array (to append at)
>>>> + * @bits: log2 of capacity (for bitwise operations)
>>>> + * @mask: capacity - 1
>>>
>>> These description does not give idea what those roles are.
>>
>> I'll refine the description. objpool_slot is totally internal to objpool.
>>
>>>
>>>> + *
>>>> + * Represents a cpu-local array-based ring buffer, its size is specialized
>>>> + * during initialization of object pool. The percpu objpool slot is to be
>>>> + * allocated from local memory for NUMA system, and to be kept compact in
>>>> + * continuous memory: ages[] is stored just after the body of objpool_slot,
>>>> + * and then entries[]. ages[] describes revision of each item, solely used
>>>> + * to avoid ABA; entries[] contains pointers of the actual objects
>>>> + *
>>>> + * Layout of objpool_slot in memory:
>>>> + *
>>>> + * 64bit:
>>>> + *        4      8      12     16        32                 64
>>>> + * | head | tail | bits | mask | ages[4] | ents[4]: (8 * 4) | objects
>>>> + *
>>>> + * 32bit:
>>>> + *        4      8      12     16        32        48       64
>>>> + * | head | tail | bits | mask | ages[4] | ents[4] | unused | objects
>>>
>>> Hm, the '4' here means number of objects after this objpool_slot?
>>> I don't recommend you to allocate several arraies after the header, instead,
>>> using another data structure like this;
>>>
>>> |head|tail|bits|mask|ents[N]{age:4|offs:4}|padding|objects
>>>
>>> here N means the number of total objects.
>>
>> Sorry for the confusion, I will make it more clear. Here 4/8/.../64 are offset
>> in bytes. The above is an example with the objpool_slot compacted in a single
>> cache line.
> 
> But in that case, the entry number may not be enough for storing all object.
> (or limit the number of objects)
> 
> Actually, since the rethook needs to make a shadow stack list per task not
> per cpu, the (safe) required number of object is usually proportional to the
> number of active tasks. kretprobes sets the default number of nodes according
> to the CPUs but it is minimum requirement. This is because,
> - most of the kernel functions are not nested, thus it is called once on each
>   thread in the kernel.
> - the thread can be scheduled or slept, thus the function return hook also is
>    not done until the thread comes back.
> So, usually, the recommended number of node (obj) will be 100-200 (depends on
> the system.) If it is a server, it may be more than 1000.
> 
>>
>>>
>>> struct objpool_entry {
>>> 	uint32_t	age;
>>> 	void *	ptr;
>>> } __attribute__((packed,aligned(8))) ;
>>>
>>>> + *
>>>> + */
>>>> +struct objpool_slot {
>>>> +	uint32_t                head;
>>>> +	uint32_t                tail;
>>>> +	uint32_t                bits;
>>>> +	uint32_t                mask;
>>>
>>> 	struct objpool_entry	entries[];
>>>
>>>> +} __packed;
>>>
>>> Then, you don't need complex macros to access object, but you need only one
>>> inline function to get the actual object address.
>>>
>>> static inline void *objpool_slot_object(struct objpool_slot *slot, int nth)
>>> {
>>> 	if (nth > (1 << bits))
>>> 		return NULL;
>>>
>>> 	return (void *)((unsigned long)slot + slot.entries[nth].offs);
>>> }
>>
>> The reason of these macroes is to compact objpool_slot/ages/ents to hot cache
>> lines and also minimize the memory footprint.
> 
> Hmm, at this moment, I don't recommend you to stick on the cache line but
> easier to read. If you have any number, you can add optimize patch afterwards.
> But the initial patch should take care about the readability.
> 
>>
>> objpool_head could be a better place to manage these pointers, similarly as
>> cpu_slots. I'll recheck the overhead.
>>
>>
>>>> +
>>>> +struct objpool_head;
>>>> +
>>>> +/*
>>>> + * caller-specified callback for object initial setup, it's only called
>>>> + * once for each object (just after the memory allocation of the object)
>>>> + */
>>>> +typedef int (*objpool_init_obj_cb)(void *obj, void *context);
>>>> +
>>>> +/* caller-specified cleanup callback for objpool destruction */
>>>> +typedef int (*objpool_fini_cb)(struct objpool_head *head, void *context);
>>>> +
>>>> +/**
>>>> + * struct objpool_head - object pooling metadata
>>>> + * @obj_size:   object & element size
>>>
>>> What does the 'element' mean?
>>
>> "object size" should be enough. "element" means object, so it's unnecessary.
>>
>>>
>>>> + * @nr_objs:    total objs (to be pre-allocated)
>>>
>>> but all objects must be pre-allocated, right? then it is simply
>>
>> Yes, all objects are pre-allocated for this implementation.
>>
>>>
>>> @nr_objs: the total number of objects in this objpool.
>>>
>>>> + * @nr_cpus:    nr_cpu_ids
>>>
>>> would we have to save it? or just use 'nr_cpu_ids'?
>>
>> Yes, it's just a local save of nr_cpu_ids, just to make the members of
>> objpool_head aligned by 64 bits (there could be a 4-byte hold anyway).
>> And possible beatification from hot TLB cache ?
> 
> Unless you pack the data structure, you don't need to care about
> the cache. And the compiler works better than human for initial work.
> At this moment, it is more important to reduce the members as simple
> as possible.
> 
>>
>>>
>>>> + * @capacity:   max objects per cpuslot
>>>
>>> what is 'cpuslot'?
>>> This seems the size of objpool_entry array in objpool_slot.
>>
>> Yes, should be "capacity per objpool_slot", i.e. "maximum objects could be
>> stored in a objpool_slot".
>>
>>>> + * @gfp:        gfp flags for kmalloc & vmalloc
>>>> + * @ref:        refcount for objpool
>>>> + * @flags:      flags for objpool management
>>>> + * @cpu_slots:  array of percpu slots
>>>> + * @slot_sizes:	size in bytes of slots
>>>> + * @release:    resource cleanup callback
>>>> + * @context:    caller-provided context
>>>> + */
>>>> +struct objpool_head {
>>>> +	int                     obj_size;
>>>> +	int                     nr_objs;
>>>> +	int                     nr_cpus;
>>>> +	int                     capacity;
>>>> +	gfp_t                   gfp;
>>>> +	refcount_t              ref;
>>>> +	unsigned long           flags;
>>>> +	struct objpool_slot   **cpu_slots;
>>>> +	int                    *slot_sizes;
>>>> +	objpool_fini_cb         release;
>>>> +	void                   *context;
>>>> +};
>>>> +
>>>> +#define OBJPOOL_FROM_VMALLOC	(0x800000000)	/* objpool allocated from vmalloc area */
>>>> +#define OBJPOOL_HAVE_OBJECTS	(0x400000000)	/* objects allocated along with objpool */
>>>> +
>>>> +/**
>>>> + * objpool_init() - initialize objpool and pre-allocated objects
>>>> + * @head:    the object pool to be initialized, declared by caller
>>>> + * @nr_objs: total objects to be pre-allocated by this object pool
>>>> + * @object_size: size of an object (should be > 0)
>>>> + * @gfp:     flags for memory allocation (via kmalloc or vmalloc)
>>>> + * @context: user context for object initialization callback
>>>> + * @objinit: object initialization callback for extra setup
>>>> + * @release: cleanup callback for extra cleanup task
>>>> + *
>>>> + * return value: 0 for success, otherwise error code
>>>> + *
>>>> + * All pre-allocated objects are to be zeroed after memory allocation.
>>>> + * Caller could do extra initialization in objinit callback. objinit()
>>>> + * will be called just after slot allocation and will be only once for
>>>> + * each object. Since then the objpool won't touch any content of the
>>>> + * objects. It's caller's duty to perform reinitialization after each
>>>> + * pop (object allocation) or do clearance before each push (object
>>>> + * reclamation).
>>>> + */
>>>> +int objpool_init(struct objpool_head *head, int nr_objs, int object_size,
>>>> +		 gfp_t gfp, void *context, objpool_init_obj_cb objinit,
>>>> +		 objpool_fini_cb release);
>>>> +
>>>> +/**
>>>> + * objpool_pop() - allocate an object from objpool
>>>> + * @head: object pool
>>>> + *
>>>> + * return value: object ptr or NULL if failed
>>>> + */
>>>> +void *objpool_pop(struct objpool_head *head);
>>>> +
>>>> +/**
>>>> + * objpool_push() - reclaim the object and return back to objpool
>>>> + * @obj:  object ptr to be pushed to objpool
>>>> + * @head: object pool
>>>> + *
>>>> + * return: 0 or error code (it fails only when user tries to push
>>>> + * the same object multiple times or wrong "objects" into objpool)
>>>> + */
>>>> +int objpool_push(void *obj, struct objpool_head *head);
>>>> +
>>>> +/**
>>>> + * objpool_drop() - discard the object and deref objpool
>>>> + * @obj:  object ptr to be discarded
>>>> + * @head: object pool
>>>> + *
>>>> + * return: 0 if objpool was released or error code
>>>> + */
>>>> +int objpool_drop(void *obj, struct objpool_head *head);
>>>> +
>>>> +/**
>>>> + * objpool_free() - release objpool forcely (all objects to be freed)
>>>> + * @head: object pool to be released
>>>> + */
>>>> +void objpool_free(struct objpool_head *head);
>>>> +
>>>> +/**
>>>> + * objpool_fini() - deref object pool (also releasing unused objects)
>>>> + * @head: object pool to be dereferenced
>>>> + */
>>>> +void objpool_fini(struct objpool_head *head);
>>>> +
>>>> +#endif /* _LINUX_OBJPOOL_H */
>>>> diff --git a/lib/Makefile b/lib/Makefile
>>>> index 1ffae65bb7ee..7a84c922d9ff 100644
>>>> --- a/lib/Makefile
>>>> +++ b/lib/Makefile
>>>> @@ -34,7 +34,7 @@ lib-y := ctype.o string.o vsprintf.o cmdline.o \
>>>>    	 is_single_threaded.o plist.o decompress.o kobject_uevent.o \
>>>>    	 earlycpio.o seq_buf.o siphash.o dec_and_lock.o \
>>>>    	 nmi_backtrace.o win_minmax.o memcat_p.o \
>>>> -	 buildid.o
>>>> +	 buildid.o objpool.o
>>>>    
>>>>    lib-$(CONFIG_PRINTK) += dump_stack.o
>>>>    lib-$(CONFIG_SMP) += cpumask.o
>>>> diff --git a/lib/objpool.c b/lib/objpool.c
>>>> new file mode 100644
>>>> index 000000000000..22e752371820
>>>> --- /dev/null
>>>> +++ b/lib/objpool.c
>>>> @@ -0,0 +1,338 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +
>>>> +#include <linux/objpool.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/vmalloc.h>
>>>> +#include <linux/atomic.h>
>>>> +#include <linux/prefetch.h>
>>>> +#include <linux/irqflags.h>
>>>> +#include <linux/cpumask.h>
>>>> +#include <linux/log2.h>
>>>> +
>>>> +/*
>>>> + * objpool: ring-array based lockless MPMC/FIFO queues
>>>> + *
>>>> + * Copyright: wuqiang.matt@bytedance.com
>>>> + */
>>>> +
>>>> +#define SLOT_AGES(s) ((uint32_t *)((char *)(s) + sizeof(struct objpool_slot)))
>>>> +#define SLOT_ENTS(s) ((void **)((char *)(s) + sizeof(struct objpool_slot) + \
>>>> +			(sizeof(uint32_t) << (s)->bits)))
>>>> +#define SLOT_OBJS(s) ((void *)((char *)(s) + sizeof(struct objpool_slot) + \
>>>> +			((sizeof(uint32_t) + sizeof(void *)) << (s)->bits)))
>>>> +#define SLOT_CORE(n) cpumask_nth((n) % num_possible_cpus(), cpu_possible_mask)
>>>> +
>>>> +/* compute the suitable num of objects to be managed per slot */
>>>> +static int objpool_nobjs(int size)
>>>> +{
>>>> +	return rounddown_pow_of_two((size - sizeof(struct objpool_slot)) /
>>>> +			(sizeof(uint32_t) + sizeof(void *)));
>>>> +}
>>>> +
>>>> +/* allocate and initialize percpu slots */
>>>
>>> @head: the objpool_head for managing this objpool
>>> @nobjs: the total number of objects in this objpool
>>> @context: context data for @objinit
>>> @objinit: initialize callback for each object.
>>
>> Got it. I didn't since objpool_init_percpu_slots is not public.
>>
>>>> +static int
>>>> +objpool_init_percpu_slots(struct objpool_head *head, int nobjs,
>>>> +			void *context, objpool_init_obj_cb objinit)
>>>> +{
>>>> +	int i, j, n, size, objsz, cpu = 0, nents = head->capacity;
>>>
>>> 'nents' is *round up to the power of 2* of the total number of objects.
>>>
>>>> +
>>>> +	/* aligned object size by sizeof(void *) */
>>>> +	objsz = ALIGN(head->obj_size, sizeof(void *));
>>>> +	/* shall we allocate objects along with percpu-slot */
>>>> +	if (objsz)
>>>> +		head->flags |= OBJPOOL_HAVE_OBJECTS;
>>>
>>> Is there any chance that objsz == 0?
>>
>> No chance. We always require non-zero objsz. Will update in next verion.
>>
>>>
>>>> +
>>>> +	/* vmalloc is used in default to allocate percpu-slots */
>>>> +	if (!(head->gfp & GFP_ATOMIC))
>>>> +		head->flags |= OBJPOOL_FROM_VMALLOC;
>>>> +
>>>> +	for (i = 0; i < head->nr_cpus; i++) {
>>>> +		struct objpool_slot *os;
>>>> +
>>>> +		/* skip the cpus which could never be present */
>>>> +		if (!cpu_possible(i))
>>>> +			continue;
>>>> +
>>>> +		/* compute how many objects to be managed by this slot */
>>>
>>> "to be managed"? or "to be allocated with"?
>>> It seems all objects are possible to be managed by each slot.
>>
>> Right. "to be allocated with" is preferable. Thanks.
>>
>>>> +		n = nobjs / num_possible_cpus();
>>>> +		if (cpu < (nobjs % num_possible_cpus()))
>>>> +			n++;
>>>> +		size = sizeof(struct objpool_slot) + sizeof(void *) * nents +
>>>> +		       sizeof(uint32_t) * nents + objsz * n;
>>>> +
>>>> +		/*
>>>> +		 * here we allocate percpu-slot & objects together in a single
>>>> +		 * allocation, taking advantage of warm caches and TLB hits as
>>>> +		 * vmalloc always aligns the request size to pages
>>>
>>> "Since the objpool_entry array in the slot is mostly accessed from the
>>>    i-th CPU, it should be allocated from the memory node for that CPU."
>>>
>>> I think the reason of the memory node allocation is mainly for reducing the
>>> penalty of the cache-miss, since it will be bigger if running on NUMA.
>>
>> Right, NUMA is addressed by objpool_slot. The above description is to explain
>> why a single memory allocation (not multiple). I'll try to make it more clear.
>>
>>>
>>>> +		 */
>>>> +		if (head->flags & OBJPOOL_FROM_VMALLOC)
>>>> +			os = __vmalloc_node(size, sizeof(void *), head->gfp,
>>>> +				cpu_to_node(i), __builtin_return_address(0));
>>>> +		else
>>>> +			os = kmalloc_node(size, head->gfp, cpu_to_node(i));
>>>> +		if (!os)
>>>> +			return -ENOMEM;
>>>> +
>>>> +		/* initialize percpu slot for the i-th slot */
>>>> +		memset(os, 0, size);
>>>> +		os->bits = ilog2(head->capacity);
>>>> +		os->mask = head->capacity - 1;
>>>> +		head->cpu_slots[i] = os;
>>>> +		head->slot_sizes[i] = size;
>>>> +		cpu = cpu + 1;
>>>> +
>>>> +		/*
>>>> +		 * manually set head & tail to avoid possible conflict:
>>>> +		 * We assume that the head item is ready for retrieval
>>>> +		 * iff head is equal to ages[head & mask]. but ages is
>>>> +		 * initialized as 0, so in view of the caller of pop(),
>>>> +		 * the 1st item (0th) is always ready, but the reality
>>>> +		 * could be: push() is stalled before the final update,
>>>> +		 * thus the item being inserted will be lost forever
>>>> +		 */
>>>> +		os->head = os->tail = head->capacity;
>>>> +
>>>> +		if (!objsz)
>>>> +			continue;
>>>
>>> Is it possible? and for what?
>>
>> Will be removed in next version.
>>
>>>
>>>> +
>>>> +		for (j = 0; j < n; j++) {
>>>> +			uint32_t *ages = SLOT_AGES(os);
>>>> +			void **ents = SLOT_ENTS(os);
>>>> +			void *obj = SLOT_OBJS(os) + j * objsz;
>>>> +			uint32_t ie = os->tail & os->mask;
>>>> +
>>>> +			/* perform object initialization */
>>>> +			if (objinit) {
>>>> +				int rc = objinit(obj, context);
>>>> +				if (rc)
>>>> +					return rc;
>>>> +			}
>>>> +
>>>> +			/* add obj into the ring array */
>>>> +			ents[ie] = obj;
>>>> +			ages[ie] = os->tail;
>>>> +			os->tail++;
>>>> +			head->nr_objs++;
>>>> +		}
>>>
>>> To simplify the code, this loop should be another static function.
>>
>> I'll reconsider the implementation. And the multiple computations of ages/ents
>> should be avoided too.
>>
>>>
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +/* cleanup all percpu slots of the object pool */
>>>> +static void objpool_fini_percpu_slots(struct objpool_head *head)
>>>> +{
>>>> +	int i;
>>>> +
>>>> +	if (!head->cpu_slots)
>>>> +		return;
>>>> +
>>>> +	for (i = 0; i < head->nr_cpus; i++) {
>>>> +		if (!head->cpu_slots[i])
>>>> +			continue;
>>>> +		if (head->flags & OBJPOOL_FROM_VMALLOC)
>>>> +			vfree(head->cpu_slots[i]);
>>>> +		else
>>>> +			kfree(head->cpu_slots[i]);
>>>> +	}
>>>> +	kfree(head->cpu_slots);
>>>> +	head->cpu_slots = NULL;
>>>> +	head->slot_sizes = NULL;
>>>> +}
>>>> +
>>>> +/* initialize object pool and pre-allocate objects */
>>>> +int objpool_init(struct objpool_head *head, int nr_objs, int object_size,
>>>> +		gfp_t gfp, void *context, objpool_init_obj_cb objinit,
>>>> +		objpool_fini_cb release)
>>>> +{
>>>> +	int nents, rc;
>>>> +
>>>> +	/* check input parameters */
>>>> +	if (nr_objs <= 0 || object_size <= 0)
>>>> +		return -EINVAL;
>>>> +
>>>> +	/* calculate percpu slot size (rounded to pow of 2) */
>>>> +	nents = max_t(int, roundup_pow_of_two(nr_objs),
>>>> +			objpool_nobjs(L1_CACHE_BYTES));
>>>> +
>>>> +	/* initialize objpool head */
>>>> +	memset(head, 0, sizeof(struct objpool_head));
>>>> +	head->nr_cpus = nr_cpu_ids;
>>>> +	head->obj_size = object_size;
>>>> +	head->capacity = nents;
>>>> +	head->gfp = gfp & ~__GFP_ZERO;
>>>> +	head->context = context;
>>>> +	head->release = release;
>>>> +
>>>> +	/* allocate array for percpu slots */
>>>> +	head->cpu_slots = kzalloc(head->nr_cpus * sizeof(void *) +
>>>> +			       head->nr_cpus * sizeof(int), head->gfp);
>>>> +	if (!head->cpu_slots)
>>>> +		return -ENOMEM;
>>>> +	head->slot_sizes = (int *)&head->cpu_slots[head->nr_cpus];
>>>> +
>>>> +	/* initialize per-cpu slots */
>>>> +	rc = objpool_init_percpu_slots(head, nr_objs, context, objinit);
>>>> +	if (rc)
>>>> +		objpool_fini_percpu_slots(head);
>>>> +	else
>>>> +		refcount_set(&head->ref, nr_objs + 1);
>>>> +
>>>> +	return rc;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(objpool_init);
>>>> +
>>>> +/* adding object to slot, abort if the slot was already full */
>>>> +static inline int objpool_try_add_slot(void *obj, struct objpool_slot *os)
>>>> +{
>>>> +	uint32_t *ages = SLOT_AGES(os);
>>>> +	void **ents = SLOT_ENTS(os);
>>>> +	uint32_t head, tail;
>>>> +
>>>> +	do {
>>>> +		/* perform memory loading for both head and tail */
>>>> +		head = READ_ONCE(os->head);
>>>> +		tail = READ_ONCE(os->tail);
>>>> +		/* just abort if slot is full */
>>>> +		if (tail - head > os->mask)
>>>> +			return -ENOENT;
>>>
>>> Is this really possible? The total number of objects must be less euqal to
>>> the os->mask. If it means a bug, please use WARN_ON_ONCE() here for debug.
>>
>> Yes, it's a BUG and the caller's fault. When user tries pushing wrong object
>> or repeatedly pushing a same object, it could break the objpool's consistency.
>> It's a 'worse' or 'more worse' choice, rather returning error than breaking
>> the consitency.
>>
>> As you adviced, better crash than problematic. I'll update in next version.
>>
>>>
>>>> +		/* try to extend tail by 1 using CAS to avoid races */
>>>> +		if (try_cmpxchg_acquire(&os->tail, &tail, tail + 1))
>>>> +			break;
>>>> +	} while (1);
>>>
>>> "if(cond) ~ break; } while(1)" should be "} (!cond);"
>>
>> I see. Just to make the codes more "balanced" with comments :)
>>
>>>
>>> And this seems to be buggy since tail++ can be 0, then "tail - head" < 0.
>>>
>>> if (tail < head)
>>> 	if (WARN_ON_ONCE(tail + (UINT32_MAX - head) > os->mask))
>>> 		return -ENOENT;
>>> else
>>> 	if (WARN_ON_ONCE(tail - head > os->mask))
>>> 		return -ENOENT;
>>
>> tail and head are unsigned, so "tail - head" is unsigned and should always
>> be the actual number of free objects in the objpool_slot.
>>
>>>> +
>>>> +	/* the tail-th of slot is reserved for the given obj */
>>>> +	WRITE_ONCE(ents[tail & os->mask], obj);
>>>> +	/* update epoch id to make this object available for pop() */
>>>> +	smp_store_release(&ages[tail & os->mask], tail);
>>>
>>> Note: since the ages array size is the power of 2, this is just a
>>> (32 - os->bits) loop counter. :)
>>>
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +/* reclaim an object to object pool */
>>>> +int objpool_push(void *obj, struct objpool_head *oh)
>>>> +{
>>>> +	unsigned long flags;
>>>> +	int cpu, rc;
>>>> +
>>>> +	/* disable local irq to avoid preemption & interruption */
>>>> +	raw_local_irq_save(flags);
>>>> +	cpu = raw_smp_processor_id();
>>>> +	do {
>>>> +		rc = objpool_try_add_slot(obj, oh->cpu_slots[cpu]);
>>>> +		if (!rc)
>>>> +			break;
>>>> +		cpu = cpumask_next_wrap(cpu, cpu_possible_mask, -1, 1);
>>>> +	} while (1);
>>>
>>> Hmm, as I said, head->capacity >= nr_all_obj, this must not happen,
>>> we can always push it on this CPU's slot, right?
>>
>> Right. If it happens, that means the user made mistakes. I'll refine
>> the codes.
>>
>>>
>>>> +	raw_local_irq_restore(flags);
>>>> +
>>>> +	return rc;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(objpool_push);
>>>> +
>>>> +/* drop the allocated object, rather reclaim it to objpool */
>>>> +int objpool_drop(void *obj, struct objpool_head *head)
>>>> +{
>>>> +	if (!obj || !head)
>>>> +		return -EINVAL;
>>>> +
>>>> +	if (refcount_dec_and_test(&head->ref)) {
>>>> +		objpool_free(head);
>>>> +		return 0;
>>>> +	}
>>>> +
>>>> +	return -EAGAIN;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(objpool_drop);
>>>> +
>>>> +/* try to retrieve object from slot */
>>>> +static inline void *objpool_try_get_slot(struct objpool_slot *os)
>>>> +{
>>>> +	uint32_t *ages = SLOT_AGES(os);
>>>> +	void **ents = SLOT_ENTS(os);
>>>> +	/* do memory load of head to local head */
>>>> +	uint32_t head = smp_load_acquire(&os->head);
>>>> +
>>>> +	/* loop if slot isn't empty */
>>>> +	while (head != READ_ONCE(os->tail)) {
>>>> +		uint32_t id = head & os->mask, prev = head;
>>>> +
>>>> +		/* do prefetching of object ents */
>>>> +		prefetch(&ents[id]);
>>>> +
>>>> +		/* check whether this item was ready for retrieval */
>>>> +		if (smp_load_acquire(&ages[id]) == head) {
>>>
>>> We may not need this check, since we know head != tail and the
>>> sizeof(ages) >= nr_all_objs.
>>>
>>> Hmm, I guess we can remove ages[] from the code.
>>
>> Just do a quick peek to avoid an unnecessary call of try_cmpxchg_release.
>> try_cmpxchg_release is implemented by heavy instruction with "LOCK" prefix,
>> which could bring cache invalidation among CPU nodes.
> 
> OK, I understand what this ages[] does. This is a nestable commit table
> for the ring array.
> 
>>
>>>
>>>> +			/* node must have been udpated by push() */
>>>> +			void *node = READ_ONCE(ents[id]);
>>>
>>> Please use the same word for the same object.
>>> I mean this is not 'node' but 'object'.
>>
>> Got it.
>>
>>>
>>>> +			/* commit and move forward head of the slot */
>>>> +			if (try_cmpxchg_release(&os->head, &head, head + 1))
>>>> +				return node;
>>>> +			/* head was already updated by others */
>>>> +		}
>>>> +
>>>> +		/* re-load head from memory and continue trying */
>>>> +		head = READ_ONCE(os->head);
>>>> +		/*
>>>> +		 * head stays unchanged, so it's very likely there's an
>>>> +		 * ongoing push() on other cpu nodes but yet not update
>>>> +		 * ages[] to mark it's completion
>>>> +		 */
>>>> +		if (head == prev)
>>>> +			break;
>>>
>>> This is OK. If we always push() on the current CPU slot, and pop() from
>>> any cpus, we can try again here if this slot is not current CPU. But that
>>> maybe to much :P
>>
>> Yes. For most cases, every CPU should only touch it's own objpool_slot.
>>
>>> Thank you,
>>
>> Thanks for your time.
> 
> Thank you for your reply!
> 


  reply	other threads:[~2023-10-12 16:16 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-09-05  1:52 [PATCH v9 0/5] lib,kprobes: kretprobe scalability improvement wuqiang.matt
2023-09-05  1:52 ` [PATCH v9 1/5] lib: objpool added: ring-array based lockless MPMC wuqiang.matt
2023-09-23  9:48   ` Masami Hiramatsu
2023-10-08 18:40     ` wuqiang
2023-10-09 14:19       ` Masami Hiramatsu
2023-10-12 16:16         ` wuqiang.matt [this message]
2023-09-24 14:21   ` Masami Hiramatsu
2023-09-25  9:42   ` Masami Hiramatsu
2023-10-08 19:04     ` wuqiang
2023-10-09  9:23     ` wuqiang
2023-10-09 13:51       ` Masami Hiramatsu
2023-10-12 14:02       ` Masami Hiramatsu
2023-10-12 17:36         ` wuqiang.matt
2023-10-13  1:59           ` Masami Hiramatsu
2023-10-13  3:03             ` wuqiang.matt
2023-09-05  1:52 ` [PATCH v9 2/5] lib: objpool test module added wuqiang.matt
2023-09-05  1:52 ` [PATCH v9 3/5] kprobes: kretprobe scalability improvement with objpool wuqiang.matt
2023-10-07  2:02   ` Masami Hiramatsu
2023-10-08 18:31     ` wuqiang
2023-10-08 23:20       ` Masami Hiramatsu
2023-09-05  1:52 ` [PATCH v9 4/5] kprobes: freelist.h removed wuqiang.matt
2023-09-05  1:52 ` [PATCH v9 5/5] MAINTAINERS: objpool added wuqiang.matt
2023-09-23  8:57 ` [PATCH v9 0/5] lib,kprobes: kretprobe scalability improvement Masami Hiramatsu
2023-10-08 18:33   ` wuqiang
2023-10-08 23:17     ` Masami Hiramatsu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=da8d0422-756a-364d-9d57-bc3455ab97dc@bytedance.com \
    --to=wuqiang.matt@bytedance.com \
    --cc=akpm@linux-foundation.org \
    --cc=anil.s.keshavamurthy@intel.com \
    --cc=dan.j.williams@intel.com \
    --cc=davem@davemloft.net \
    --cc=ebiggers@google.com \
    --cc=jpoimboe@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=lkp@intel.com \
    --cc=mattwu@163.com \
    --cc=mhiramat@kernel.org \
    --cc=naveen.n.rao@linux.ibm.com \
    --cc=peterz@infradead.org \
    --cc=rostedt@goodmis.org \
    --cc=sander@svanheule.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®