DPDK patches and discussions
 help / color / mirror / Atom feed
From: Chengwen Feng <fengchengwen@huawei.com>
To: <thomas@monjalon.net>, <ferruh.yigit@amd.com>
Cc: <dev@dpdk.org>
Subject: [RFC 1/3] lib/coroutine: add coroutine library
Date: Mon, 24 Apr 2023 13:02:06 +0000	[thread overview]
Message-ID: <20230424130208.9517-2-fengchengwen@huawei.com> (raw)
In-Reply-To: <20230424130208.9517-1-fengchengwen@huawei.com>

This patch adds coroutine library. The main elements are:
1. scheduler: container of coroutines, which is responsible for
scheduling coroutine.
2. coroutine: Minimum scheduling unit, it should associated to one
scheduler.

In the coroutine callback, application could invoke rte_co_yield() to
give up the CPU, and invoke rte_co_delay() to delay the specified
microsecond.

Signed-off-by: Chengwen Feng <fengchengwen@huawei.com>
---
 lib/coroutine/meson.build         |   8 ++
 lib/coroutine/rte_coroutine.c     | 190 ++++++++++++++++++++++++++++++
 lib/coroutine/rte_coroutine.h     | 110 +++++++++++++++++
 lib/coroutine/rte_coroutine_imp.h |  46 ++++++++
 lib/coroutine/version.map         |  11 ++
 lib/meson.build                   |   1 +
 6 files changed, 366 insertions(+)
 create mode 100644 lib/coroutine/meson.build
 create mode 100644 lib/coroutine/rte_coroutine.c
 create mode 100644 lib/coroutine/rte_coroutine.h
 create mode 100644 lib/coroutine/rte_coroutine_imp.h
 create mode 100644 lib/coroutine/version.map

diff --git a/lib/coroutine/meson.build b/lib/coroutine/meson.build
new file mode 100644
index 0000000000..2064fb1909
--- /dev/null
+++ b/lib/coroutine/meson.build
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 HiSilicon Limited.
+
+sources = files('rte_coroutine.c')
+headers = files('rte_coroutine.h')
+indirect_headers += files('rte_coroutine_imp.h')
+
+deps += ['ring']
diff --git a/lib/coroutine/rte_coroutine.c b/lib/coroutine/rte_coroutine.c
new file mode 100644
index 0000000000..07c79fc901
--- /dev/null
+++ b/lib/coroutine/rte_coroutine.c
@@ -0,0 +1,190 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 HiSilicon Limited
+ */
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ucontext.h>
+
+#include <rte_alarm.h>
+#include <rte_ring.h>
+
+#include "rte_coroutine.h"
+#include "rte_coroutine_imp.h"
+
+#define FATAL(fmt, args...) printf("[FATAL] %s() %d: " fmt "\n", __func__, __LINE__, ##args)
+
+static __thread struct rte_schedule *co_schedule;
+
+struct rte_schedule *
+rte_schedule_create(const char *name, uint32_t max_coroutines)
+{
+	struct rte_schedule *s = calloc(1, sizeof(struct rte_schedule));
+	if (s == NULL)
+		return NULL;
+
+	s->ring = rte_ring_create(name, max_coroutines, rte_socket_id(),
+				  RING_F_SC_DEQ);
+	if (s->ring == NULL) {
+		free(s);
+		return NULL;
+	}
+
+	s->max_coroutines = max_coroutines;
+
+	return s;
+}
+
+static void
+co_run_func(uint32_t low, uint32_t hi)
+{
+	uintptr_t ptr = (uint64_t)low | ((uint64_t)hi << 32);
+	struct rte_cocontext *co = (struct rte_cocontext *)ptr;
+	co->cb(co->arg);
+	/* Run complete, so free it. */
+	free(co->stack);
+	free(co);
+}
+
+int
+rte_schedule_run(struct rte_schedule *s)
+{
+	struct rte_cocontext *co = NULL;
+	uintptr_t ptr;
+
+	/* Set local thread variable as input argument. */
+	co_schedule = s;
+
+	while (!rte_ring_empty(s->ring)) {
+		rte_ring_dequeue(s->ring, (void **)&co);
+		if (co->state == COROUTINE_READY) {
+			getcontext(&co->ctx);
+			co->ctx.uc_stack.ss_sp = co->stack;
+			co->ctx.uc_stack.ss_size = co->stack_sz;
+			co->ctx.uc_link = &s->main;
+			co->state = COROUTINE_RUNNING;
+			s->running = co;
+			ptr = (uintptr_t)co;
+			makecontext(&co->ctx, (void (*)(void))co_run_func, 2,
+				    (uint32_t)ptr, (uint32_t)(ptr >> 32));
+			swapcontext(&s->main, &co->ctx);
+		} else if (co->state == COROUTINE_SUSPEND) {
+			co->state = COROUTINE_RUNNING;
+			s->running = co;
+			swapcontext(&s->main, &co->ctx);
+		} else {
+			FATAL("invalid state!");
+		}
+	}
+
+	while (s->yield_head != NULL) {
+		co = s->yield_head;
+		s->yield_head = co->yield_next;
+		if (co->state == COROUTINE_YIELD) {
+			co->state = COROUTINE_RUNNING;
+			s->running = co;
+			swapcontext(&s->main, &co->ctx);
+		} else {
+			FATAL("invalid yield state!");
+		}
+	}
+
+	return 0;
+}
+
+int
+rte_co_create(struct rte_schedule *s, coroutine_callback_t cb, void *arg, uint32_t stack_sz)
+{
+	struct rte_cocontext *co = calloc(1, sizeof(struct rte_cocontext));
+	int ret;
+	if (co == NULL)
+		return -ENOMEM;
+
+	co->owner = s;
+	co->state = COROUTINE_READY;
+	co->cb = cb;
+	co->arg = arg;
+	if (stack_sz < MIN_STACK_SIZE)
+		stack_sz = MIN_STACK_SIZE;
+	co->stack_sz = stack_sz;
+	co->stack = calloc(1, stack_sz);
+	if (co->stack == NULL) {
+		free(co);
+		return -ENOMEM;
+	}
+
+	ret = rte_ring_enqueue(s->ring, co);
+	if (ret != 0) {
+		free(co->stack);
+		free(co);
+	}
+
+	return ret;
+}
+
+static inline void
+co_addto_yield_list(struct rte_schedule *s, struct rte_cocontext *co)
+{
+	co->yield_next = NULL;
+	if (s->yield_head == NULL) {
+		s->yield_head = s->yield_tail = co;
+	} else {
+		s->yield_tail->yield_next = co;
+		s->yield_tail = co;
+	}
+}
+
+void
+rte_co_yield(void)
+{
+	struct rte_schedule *s = co_schedule;
+	struct rte_cocontext *co;
+	if (s == NULL) {
+		FATAL("thread co_schedule is NULL!");
+		return;
+	}
+	co = s->running;
+	if (co == NULL) {
+		FATAL("running is NULL!");
+		return;
+	}
+	co->state = COROUTINE_YIELD;
+	s->running = NULL;
+	co_addto_yield_list(s, co);
+	swapcontext(&co->ctx, &s->main);
+}
+
+static void
+co_delay_imp(void *arg)
+{
+	struct rte_cocontext *co = (struct rte_cocontext *)arg;
+	int ret;
+
+	ret = rte_ring_enqueue(co->owner->ring, (void *)co);
+	if (ret != 0)
+		FATAL("enqueue failed!");
+}
+
+void
+rte_co_delay(unsigned int us)
+{
+	struct rte_schedule *s = co_schedule;
+	struct rte_cocontext *co;
+
+	if (s == NULL) {
+		FATAL("thread co_schedule is NULL!");
+		return;
+	}
+
+	co = s->running;
+	if (co == NULL) {
+		FATAL("running is NULL!");
+		return;
+	}
+
+	rte_eal_alarm_set(us, co_delay_imp, (void *)co);
+	co->state = COROUTINE_SUSPEND;
+	s->running = NULL;
+	swapcontext(&co->ctx, &s->main);
+}
diff --git a/lib/coroutine/rte_coroutine.h b/lib/coroutine/rte_coroutine.h
new file mode 100644
index 0000000000..71ac9488b6
--- /dev/null
+++ b/lib/coroutine/rte_coroutine.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 HiSilicon Limited
+ */
+
+#ifndef RTE_COROUTINE_H
+#define RTE_COROUTINE_H
+
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+/**
+ * Callback prototype of coroutine.
+ *
+ * @param arg
+ *   An arg pointer coming from the caller.
+ */
+typedef void (*coroutine_callback_t)(void *arg);
+
+struct rte_schedule;
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Create coroutine scheduler.
+ *
+ * The scheduler is a coroutines container, which could schedule coroutine
+ * running.
+ *
+ * @param name
+ *   The unique name of scheduler.
+ * @param max_coroutines
+ *   Maximum number of coroutines that can be processed by the scheduler.
+ *
+ * @return
+ *   Non-NULL on success. Otherwise NULL value is returned.
+ */
+__rte_experimental
+struct rte_schedule *rte_schedule_create(const char *name, uint32_t max_coroutines);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Run the coroutine scheduler.
+ *
+ * This function will schedule all associated coroutine, it will return zero if
+ * no coroutines are active after scheduled.
+ *
+ * @param s
+ *   The pointer of the scheduler.
+ *
+ * @return
+ *   0 on success. Otherwise negative value is returned.
+ */
+__rte_experimental
+int rte_schedule_run(struct rte_schedule *s);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Create one coroutine.
+ *
+ * This function will create one coroutine which associated target scheduler
+ * 's'.
+ *
+ * @param s
+ *   The pointer of the scheduler.
+ * @param cb
+ *   The callback function.
+ * @param arg
+ *   The argument parameter for callback function.
+ * @param stack_sz
+ *   The stack size which associated to coroutine. The value zero indicates that
+ *   the default size (which is 8KB) is used.
+ *
+ * @return
+ *   0 on success. Otherwise negative value is returned.
+ */
+__rte_experimental
+int rte_co_create(struct rte_schedule *s, coroutine_callback_t cb, void *arg, uint32_t stack_sz);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Coroutine yield the CPU.
+ *
+ * This function yield the cpu of current coroutine.
+ */
+__rte_experimental
+void rte_co_yield(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Coroutine delay function.
+ *
+ * This function support delay microseconds of current coroutine.
+ *
+ * @param us
+ *   The time in microseconds which need delay.
+ */
+__rte_experimental
+void rte_co_delay(unsigned int us);
+
+#endif /* RTE_COROUTINE_H */
diff --git a/lib/coroutine/rte_coroutine_imp.h b/lib/coroutine/rte_coroutine_imp.h
new file mode 100644
index 0000000000..70b4f19670
--- /dev/null
+++ b/lib/coroutine/rte_coroutine_imp.h
@@ -0,0 +1,46 @@
+#ifndef RTE_COROUTINE_IMP_H
+#define RTE_COROUTINE_IMP_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <ucontext.h>
+
+#include "rte_coroutine.h"
+
+#define MIN_STACK_SIZE	8192
+
+enum rte_costate {
+	COROUTINE_INVALID,
+	COROUTINE_READY,
+	COROUTINE_RUNNING,
+	COROUTINE_YIELD,
+	COROUTINE_SUSPEND,
+};
+
+struct rte_cocontext {
+	struct rte_schedule *owner; /**< Which scheduler this coroutine belongs. */
+	int state; /**< The current coroutine state. */
+	ucontext_t ctx;
+
+	coroutine_callback_t cb; /**< The coroutine callback function. */
+	void *arg; /**< The coroutine callback function's input argument. */
+
+	struct rte_cocontext *yield_next;
+
+	void *stack; /**< The allocated stack pointer. */
+	uint32_t stack_sz; /**< The allocated stack size. */
+};
+
+struct rte_schedule {
+	uint32_t max_coroutines; /**< Max coroutines which this scheduler supports. */
+
+	ucontext_t main;
+	struct rte_cocontext *running; /**< Current running coroutine. */
+
+	struct rte_ring *ring; /**< Command ring for schedule. */
+
+	struct rte_cocontext *yield_head; /**< Yield coroutine list for schedule. */
+	struct rte_cocontext *yield_tail;
+};
+
+#endif /* RTE_COROUTINE_IMP_H */
diff --git a/lib/coroutine/version.map b/lib/coroutine/version.map
new file mode 100644
index 0000000000..393b8979a6
--- /dev/null
+++ b/lib/coroutine/version.map
@@ -0,0 +1,11 @@
+EXPERIMENTAL {
+	global:
+
+	rte_co_create;
+	rte_co_delay;
+	rte_co_yield;
+	rte_schedule_create;
+	rte_schedule_run;
+
+	local: *;
+};
diff --git a/lib/meson.build b/lib/meson.build
index dc8aa4ac84..50e41f1511 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -64,6 +64,7 @@ libraries = [
         'flow_classify', # flow_classify lib depends on pkt framework table lib
         'graph',
         'node',
+        'coroutine',
 ]
 
 optional_libs = [
-- 
2.17.1


  reply	other threads:[~2023-04-24 13:09 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-04-24 13:02 [RFC 0/3] introduce " Chengwen Feng
2023-04-24 13:02 ` Chengwen Feng [this message]
2023-04-26 11:28   ` [RFC 1/3] lib/coroutine: add " Ferruh Yigit
2023-04-24 13:02 ` [RFC 2/3] examples/coroutine: support coroutine examples Chengwen Feng
2023-04-24 13:02 ` [RFC 3/3] net/hns3: refactor reset process with coroutine Chengwen Feng
2023-04-24 16:08 ` [RFC 0/3] introduce coroutine library Stephen Hemminger
2023-04-25  2:11   ` fengchengwen
2023-04-25  2:16     ` Stephen Hemminger
2023-04-25  2:50       ` fengchengwen
2023-04-25  2:59         ` Garrett D'Amore
2023-04-25 21:06           ` Stephen Hemminger
2023-04-26 11:27     ` Ferruh Yigit
2023-04-28  7:20       ` fengchengwen
2023-04-25  9:27 ` Mattias Rönnblom

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=20230424130208.9517-2-fengchengwen@huawei.com \
    --to=fengchengwen@huawei.com \
    --cc=dev@dpdk.org \
    --cc=ferruh.yigit@amd.com \
    --cc=thomas@monjalon.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;
as well as URLs for NNTP newsgroup(s).