Show a patch.

GET /api/patches/73345/
Content-Type: application/json
Vary: Accept

    "id": 73345,
    "url": "",
    "web_url": "",
    "project": {
        "id": 1,
        "url": "",
        "name": "DPDK",
        "link_name": "dpdk",
        "list_id": "",
        "list_email": "",
        "web_url": "",
        "scm_url": "git://",
        "webscm_url": ""
    "msgid": "<>",
    "date": "2020-07-06T20:52:30",
    "name": "[v6,06/10] eal: register non-EAL threads as lcores",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": true,
    "hash": "999bf9df9609ce61251fb88536a8535bff0f75d9",
    "submitter": {
        "id": 1173,
        "url": "",
        "name": "David Marchand",
        "email": ""
    "delegate": {
        "id": 1,
        "url": "",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": ""
    "mbox": "",
    "series": [
            "id": 10826,
            "url": "",
            "web_url": "",
            "date": "2020-07-06T20:52:25",
            "name": "Register non-EAL threads as lcore",
            "version": 6,
            "mbox": ""
    "comments": "",
    "check": "fail",
    "checks": "",
    "tags": {},
    "headers": {
        "List-Archive": "<>",
        "Return-Path": "<>",
        "To": "",
        "X-MC-Unique": "-sLRaC3APZm6YcESMdx_iQ-1",
        "Message-Id": "<>",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;;\n s=mimecast20190719; t=1594068807;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=7GnBLaBgohnmmMWWfUZKbaGzOT3iZtxW1Iw49Yb2uT0=;\n b=iASDCLVv6V8XQ35BTP5eu3bnXmx/vtjQZQ1x163GjHgTae3utQKfW7isdEJFd9X2hD/Ndd\n ynmFtzb2WhtN5xRaCBmigGiqRYDknTpgLqTQ8pwiSH+CZYfQlaPYIF3DjdnYK5K6aNMPPN\n QwmP5nFjWP4n6cuB94xb2eNYo6MzBzY=",
        "X-Scanned-By": "MIMEDefang 2.84 on",
        "List-Subscribe": "<>,\n <>",
        "X-BeenThere": "",
        "Received": [
            "from ( [])\n\tby (Postfix) with ESMTP id CA3FFA00C5;\n\tMon,  6 Jul 2020 22:53:53 +0200 (CEST)",
            "from [] (localhost [])\n\tby (Postfix) with ESMTP id 4C8871DDCE;\n\tMon,  6 Jul 2020 22:53:29 +0200 (CEST)",
            "from (\n []) by (Postfix) with ESMTP id 1EEEA1DDCE\n for <>; Mon,  6 Jul 2020 22:53:28 +0200 (CEST)",
            "from (\n []) (Using TLS) by with ESMTP id\n us-mta-243--sLRaC3APZm6YcESMdx_iQ-1; Mon, 06 Jul 2020 16:53:23 -0400",
            "from (\n [])\n (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n (No client certificate requested)\n by (Postfix) with ESMTPS id 64CA2107ACCD;\n Mon,  6 Jul 2020 20:53:21 +0000 (UTC)",
            "from dmarchan.remote.csb (unknown [])\n by (Postfix) with ESMTP id 7D66E1001B07;\n Mon,  6 Jul 2020 20:53:13 +0000 (UTC)"
        "Subject": "[dpdk-dev] [PATCH v6 06/10] eal: register non-EAL threads as lcores",
        "In-Reply-To": "<>",
        "Authentication-Results": ";\n auth=pass smtp.auth=CUSA124A263",
        "X-Mimecast-Spam-Score": "0",
        "List-Id": "DPDK patches and discussions <>",
        "List-Unsubscribe": "<>,\n <>",
        "List-Post": "<>",
        "Cc": ",,,\n,,,\n,,,\n, John McNamara <>,\n Marko Kovacevic <>,\n Anatoly Burakov <>,\n Neil Horman <>,\n Harini Ramakrishnan <>,\n Omar Cardona <>,\n Pallavi Kadam <>,\n Ranjit Menon <>",
        "Precedence": "list",
        "From": "David Marchand <>",
        "Content-Transfer-Encoding": "8bit",
        "MIME-Version": "1.0",
        "References": "<>\n <>",
        "X-Original-To": "",
        "Sender": "\"dev\" <>",
        "Errors-To": "",
        "List-Help": "<>",
        "Content-Type": "text/plain; charset=US-ASCII",
        "Delivered-To": "",
        "Date": "Mon,  6 Jul 2020 22:52:30 +0200",
        "X-Mimecast-Originator": "",
        "X-Mailman-Version": "2.1.15"
    "content": "DPDK allows calling some part of its API from a non-EAL thread but this\nhas some limitations.\nOVS (and other applications) has its own thread management but still\nwant to avoid such limitations by hacking RTE_PER_LCORE(_lcore_id) and\nfaking EAL threads potentially unknown of some DPDK component.\n\nIntroduce a new API to register non-EAL thread and associate them to a\nfree lcore with a new NON_EAL role.\nThis role denotes lcores that do not run DPDK mainloop and as such\nprevents use of rte_eal_wait_lcore() and consorts.\n\nMultiprocess is not supported as the need for cohabitation with this new\nfeature is unclear at the moment.\n\nSigned-off-by: David Marchand <>\nAcked-by: Andrew Rybchenko <>\nAcked-by: Thomas Monjalon <>\n---\nChanges since v5:\n- added release notes,\n- fixed windows build,\n\nChanges since v4:\n- returned an error when rte_thread_register fails + set rte_errno,\n- prefixed unit tests logs with Error: when applicable,\n\nChanges since v2:\n- refused multiprocess init once rte_thread_register got called, and\n  vice versa,\n- added warning on multiprocess in rte_thread_register doxygen,\n\nChanges since v1:\n- moved cleanup on lcore role code in patch 5,\n- added unit test,\n- updated documentation,\n- changed naming from \"external thread\" to \"registered non-EAL thread\"\n\n---\n MAINTAINERS                                   |   1 +\n app/test/Makefile                             |   1 +\n app/test/                     |   6 +\n app/test/                          |   2 +\n app/test/test_lcores.c                        | 143 ++++++++++++++++++\n doc/guides/howto/debug_troubleshoot.rst       |   5 +-\n .../prog_guide/env_abstraction_layer.rst      |  22 +--\n doc/guides/prog_guide/mempool_lib.rst         |   2 +-\n doc/guides/rel_notes/release_20_08.rst        |   6 +\n lib/librte_eal/common/eal_common_lcore.c      |  45 +++++-\n lib/librte_eal/common/eal_common_proc.c       |  42 +++++\n lib/librte_eal/common/eal_common_thread.c     |  49 +++++-\n lib/librte_eal/common/eal_memcfg.h            |   2 +\n lib/librte_eal/common/eal_private.h           |  28 ++++\n lib/librte_eal/freebsd/eal.c                  |   4 +\n lib/librte_eal/include/rte_lcore.h            |  28 +++-\n lib/librte_eal/linux/eal.c                    |   4 +\n lib/librte_eal/            |   4 +\n lib/librte_eal/windows/eal.c                  |   6 +\n lib/librte_mempool/rte_mempool.h              |  11 +-\n 20 files changed, 388 insertions(+), 23 deletions(-)\n create mode 100644 app/test/test_lcores.c",
    "diff": "diff --git a/MAINTAINERS b/MAINTAINERS\nindex 27a7be8e64..5a0e1b48b2 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -182,6 +182,7 @@ F: app/test/test_cycles.c\n F: app/test/test_debug.c\n F: app/test/test_eal*\n F: app/test/test_errno.c\n+F: app/test/test_lcores.c\n F: app/test/test_logs.c\n F: app/test/test_memcpy*\n F: app/test/test_per_lcore.c\ndiff --git a/app/test/Makefile b/app/test/Makefile\nindex e5440774b1..f4065271e4 100644\n--- a/app/test/Makefile\n+++ b/app/test/Makefile\n@@ -98,6 +98,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_FLOW_CLASSIFY) += test_flow_classify.c\n endif\n \n SRCS-y += test_rwlock.c\n+SRCS-y += test_lcores.c\n \n SRCS-$(CONFIG_RTE_LIBRTE_STACK) += test_stack.c\n SRCS-$(CONFIG_RTE_LIBRTE_STACK) += test_stack_perf.c\ndiff --git a/app/test/ b/app/test/\nindex 238ab631b4..4b7da45e09 100644\n--- a/app/test/\n+++ b/app/test/\n@@ -62,6 +62,12 @@\n         \"Func\":    rwlock_autotest,\n         \"Report\":  None,\n     },\n+    {\n+        \"Name\":    \"Lcores autotest\",\n+        \"Command\": \"lcores_autotest\",\n+        \"Func\":    default_autotest,\n+        \"Report\":  None,\n+    },\n     {\n         \"Name\":    \"Logs autotest\",\n         \"Command\": \"logs_autotest\",\ndiff --git a/app/test/ b/app/test/\nindex 56591db4e0..86b4d4d490 100644\n--- a/app/test/\n+++ b/app/test/\n@@ -67,6 +67,7 @@ test_sources = files('commands.c',\n \t'test_ipsec_perf.c',\n \t'test_kni.c',\n \t'test_kvargs.c',\n+\t'test_lcores.c',\n \t'test_logs.c',\n \t'test_lpm.c',\n \t'test_lpm6.c',\n@@ -207,6 +208,7 @@ fast_tests = [\n         ['hash_autotest', true],\n         ['interrupt_autotest', true],\n         ['ipfrag_autotest', false],\n+        ['lcores_autotest', true],\n         ['logs_autotest', true],\n         ['lpm_autotest', true],\n         ['lpm6_autotest', true],\ndiff --git a/app/test/test_lcores.c b/app/test/test_lcores.c\nnew file mode 100644\nindex 0000000000..afb9cdd444\n--- /dev/null\n+++ b/app/test/test_lcores.c\n@@ -0,0 +1,143 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright (c) 2020 Red Hat, Inc.\n+ */\n+\n+#include <pthread.h>\n+#include <string.h>\n+\n+#include <rte_errno.h>\n+#include <rte_lcore.h>\n+\n+#include \"test.h\"\n+\n+struct thread_context {\n+\tenum { INIT, ERROR, DONE } state;\n+\tbool lcore_id_any;\n+\tpthread_t id;\n+\tunsigned int *registered_count;\n+};\n+\n+static void *thread_loop(void *arg)\n+{\n+\tstruct thread_context *t = arg;\n+\tunsigned int lcore_id;\n+\n+\tlcore_id = rte_lcore_id();\n+\tif (lcore_id != LCORE_ID_ANY) {\n+\t\tprintf(\"Error: incorrect lcore id for new thread %u\\n\", lcore_id);\n+\t\tt->state = ERROR;\n+\t}\n+\tif (rte_thread_register() < 0)\n+\t\tprintf(\"Warning: could not register new thread (this might be expected during this test), reason %s\\n\",\n+\t\t\trte_strerror(rte_errno));\n+\tlcore_id = rte_lcore_id();\n+\tif ((t->lcore_id_any && lcore_id != LCORE_ID_ANY) ||\n+\t\t\t(!t->lcore_id_any && lcore_id == LCORE_ID_ANY)) {\n+\t\tprintf(\"Error: could not register new thread, got %u while %sexpecting %u\\n\",\n+\t\t\tlcore_id, t->lcore_id_any ? \"\" : \"not \", LCORE_ID_ANY);\n+\t\tt->state = ERROR;\n+\t}\n+\t/* Report register happened to the control thread. */\n+\t__atomic_add_fetch(t->registered_count, 1, __ATOMIC_RELEASE);\n+\n+\t/* Wait for release from the control thread. */\n+\twhile (__atomic_load_n(t->registered_count, __ATOMIC_ACQUIRE) != 0)\n+\t\t;\n+\trte_thread_unregister();\n+\tlcore_id = rte_lcore_id();\n+\tif (lcore_id != LCORE_ID_ANY) {\n+\t\tprintf(\"Error: could not unregister new thread, %u still assigned\\n\",\n+\t\t\tlcore_id);\n+\t\tt->state = ERROR;\n+\t}\n+\n+\tif (t->state != ERROR)\n+\t\tt->state = DONE;\n+\n+\treturn NULL;\n+}\n+\n+static int\n+test_non_eal_lcores(unsigned int eal_threads_count)\n+{\n+\tstruct thread_context thread_contexts[RTE_MAX_LCORE];\n+\tunsigned int non_eal_threads_count;\n+\tunsigned int registered_count;\n+\tstruct thread_context *t;\n+\tunsigned int i;\n+\tint ret;\n+\n+\tnon_eal_threads_count = 0;\n+\tregistered_count = 0;\n+\n+\t/* Try to create as many threads as possible. */\n+\tfor (i = 0; i < RTE_MAX_LCORE - eal_threads_count; i++) {\n+\t\tt = &thread_contexts[i];\n+\t\tt->state = INIT;\n+\t\tt->registered_count = &registered_count;\n+\t\tt->lcore_id_any = false;\n+\t\tif (pthread_create(&t->id, NULL, thread_loop, t) != 0)\n+\t\t\tbreak;\n+\t\tnon_eal_threads_count++;\n+\t}\n+\tprintf(\"non-EAL threads count: %u\\n\", non_eal_threads_count);\n+\t/* Wait all non-EAL threads to register. */\n+\twhile (__atomic_load_n(&registered_count, __ATOMIC_ACQUIRE) !=\n+\t\t\tnon_eal_threads_count)\n+\t\t;\n+\n+\t/* We managed to create the max number of threads, let's try to create\n+\t * one more. This will allow one more check.\n+\t */\n+\tif (eal_threads_count + non_eal_threads_count < RTE_MAX_LCORE)\n+\t\tgoto skip_lcore_any;\n+\tt = &thread_contexts[non_eal_threads_count];\n+\tt->state = INIT;\n+\tt->registered_count = &registered_count;\n+\tt->lcore_id_any = true;\n+\tif (pthread_create(&t->id, NULL, thread_loop, t) == 0) {\n+\t\tnon_eal_threads_count++;\n+\t\tprintf(\"non-EAL threads count: %u\\n\", non_eal_threads_count);\n+\t\twhile (__atomic_load_n(&registered_count, __ATOMIC_ACQUIRE) !=\n+\t\t\t\tnon_eal_threads_count)\n+\t\t\t;\n+\t}\n+\n+skip_lcore_any:\n+\t/* Release all threads, and check their states. */\n+\t__atomic_store_n(&registered_count, 0, __ATOMIC_RELEASE);\n+\tret = 0;\n+\tfor (i = 0; i < non_eal_threads_count; i++) {\n+\t\tt = &thread_contexts[i];\n+\t\tpthread_join(t->id, NULL);\n+\t\tif (t->state != DONE)\n+\t\t\tret = -1;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int\n+test_lcores(void)\n+{\n+\tunsigned int eal_threads_count = 0;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i < RTE_MAX_LCORE; i++) {\n+\t\tif (!rte_lcore_has_role(i, ROLE_OFF))\n+\t\t\teal_threads_count++;\n+\t}\n+\tif (eal_threads_count == 0) {\n+\t\tprintf(\"Error: something is broken, no EAL thread detected.\\n\");\n+\t\treturn TEST_FAILED;\n+\t}\n+\tprintf(\"EAL threads count: %u, RTE_MAX_LCORE=%u\\n\", eal_threads_count,\n+\t\tRTE_MAX_LCORE);\n+\n+\tif (test_non_eal_lcores(eal_threads_count) < 0)\n+\t\treturn TEST_FAILED;\n+\n+\treturn TEST_SUCCESS;\n+}\n+\n+REGISTER_TEST_COMMAND(lcores_autotest, test_lcores);\ndiff --git a/doc/guides/howto/debug_troubleshoot.rst b/doc/guides/howto/debug_troubleshoot.rst\nindex cef016b2fe..5a46f5fba3 100644\n--- a/doc/guides/howto/debug_troubleshoot.rst\n+++ b/doc/guides/howto/debug_troubleshoot.rst\n@@ -307,8 +307,9 @@ Custom worker function :numref:`dtg_distributor_worker`.\n \n #. Configuration issue isolation\n \n-   * Identify core role using ``rte_eal_lcore_role`` to identify RTE, OFF and\n-     SERVICE. Check performance functions are mapped to run on the cores.\n+   * Identify core role using ``rte_eal_lcore_role`` to identify RTE, OFF,\n+     SERVICE and NON_EAL. Check performance functions are mapped to run on the\n+     cores.\n \n    * For high-performance execution logic ensure running it on correct NUMA\n      and non-master core.\ndiff --git a/doc/guides/prog_guide/env_abstraction_layer.rst b/doc/guides/prog_guide/env_abstraction_layer.rst\nindex 48a2fec066..f64ae953d1 100644\n--- a/doc/guides/prog_guide/env_abstraction_layer.rst\n+++ b/doc/guides/prog_guide/env_abstraction_layer.rst\n@@ -564,9 +564,13 @@ It's also compatible with the pattern of corelist('-l') option.\n non-EAL pthread support\n ~~~~~~~~~~~~~~~~~~~~~~~\n \n-It is possible to use the DPDK execution context with any user pthread (aka. Non-EAL pthreads).\n-In a non-EAL pthread, the *_lcore_id* is always LCORE_ID_ANY which identifies that it is not an EAL thread with a valid, unique, *_lcore_id*.\n-Some libraries will use an alternative unique ID (e.g. TID), some will not be impacted at all, and some will work but with limitations (e.g. timer and mempool libraries).\n+It is possible to use the DPDK execution context with any user pthread (aka. non-EAL pthreads).\n+There are two kinds of non-EAL pthreads:\n+\n+- a registered non-EAL pthread with a valid *_lcore_id* that was successfully assigned by calling ``rte_thread_register()``,\n+- a non registered non-EAL pthread with a LCORE_ID_ANY,\n+\n+For non registered non-EAL pthread (with a LCORE_ID_ANY *_lcore_id*), some libraries will use an alternative unique ID (e.g. TID), some will not be impacted at all, and some will work but with limitations (e.g. timer and mempool libraries).\n \n All these impacts are mentioned in :ref:`known_issue_label` section.\n \n@@ -613,9 +617,9 @@ Known Issues\n + rte_mempool\n \n   The rte_mempool uses a per-lcore cache inside the mempool.\n-  For non-EAL pthreads, ``rte_lcore_id()`` will not return a valid number.\n-  So for now, when rte_mempool is used with non-EAL pthreads, the put/get operations will bypass the default mempool cache and there is a performance penalty because of this bypass.\n-  Only user-owned external caches can be used in a non-EAL context in conjunction with ``rte_mempool_generic_put()`` and ``rte_mempool_generic_get()`` that accept an explicit cache parameter.\n+  For unregistered non-EAL pthreads, ``rte_lcore_id()`` will not return a valid number.\n+  So for now, when rte_mempool is used with unregistered non-EAL pthreads, the put/get operations will bypass the default mempool cache and there is a performance penalty because of this bypass.\n+  Only user-owned external caches can be used in an unregistered non-EAL context in conjunction with ``rte_mempool_generic_put()`` and ``rte_mempool_generic_get()`` that accept an explicit cache parameter.\n \n + rte_ring\n \n@@ -660,15 +664,15 @@ Known Issues\n \n + rte_timer\n \n-  Running  ``rte_timer_manage()`` on a non-EAL pthread is not allowed. However, resetting/stopping the timer from a non-EAL pthread is allowed.\n+  Running  ``rte_timer_manage()`` on an unregistered non-EAL pthread is not allowed. However, resetting/stopping the timer from a non-EAL pthread is allowed.\n \n + rte_log\n \n-  In non-EAL pthreads, there is no per thread loglevel and logtype, global loglevels are used.\n+  In unregistered non-EAL pthreads, there is no per thread loglevel and logtype, global loglevels are used.\n \n + misc\n \n-  The debug statistics of rte_ring, rte_mempool and rte_timer are not supported in a non-EAL pthread.\n+  The debug statistics of rte_ring, rte_mempool and rte_timer are not supported in an unregistered non-EAL pthread.\n \n cgroup control\n ~~~~~~~~~~~~~~\ndiff --git a/doc/guides/prog_guide/mempool_lib.rst b/doc/guides/prog_guide/mempool_lib.rst\nindex f8b430d656..e3e1f940be 100644\n--- a/doc/guides/prog_guide/mempool_lib.rst\n+++ b/doc/guides/prog_guide/mempool_lib.rst\n@@ -103,7 +103,7 @@ The maximum size of the cache is static and is defined at compilation time (CONF\n Alternatively to the internal default per-lcore local cache, an application can create and manage external caches through the ``rte_mempool_cache_create()``, ``rte_mempool_cache_free()`` and ``rte_mempool_cache_flush()`` calls.\n These user-owned caches can be explicitly passed to ``rte_mempool_generic_put()`` and ``rte_mempool_generic_get()``.\n The ``rte_mempool_default_cache()`` call returns the default internal cache if any.\n-In contrast to the default caches, user-owned caches can be used by non-EAL threads too.\n+In contrast to the default caches, user-owned caches can be used by unregistered non-EAL threads too.\n \n Mempool Handlers\n ------------------------\ndiff --git a/doc/guides/rel_notes/release_20_08.rst b/doc/guides/rel_notes/release_20_08.rst\nindex c0f81d8d40..1cb44be626 100644\n--- a/doc/guides/rel_notes/release_20_08.rst\n+++ b/doc/guides/rel_notes/release_20_08.rst\n@@ -56,6 +56,12 @@ New Features\n      Also, make sure to start the actual text at the margin.\n      =========================================================\n \n+* **Added non-EAL threads registration API.**\n+\n+  Added a new API to register non-EAL threads as lcores. This can be used by\n+  applications to have its threads known of DPDK without suffering from the\n+  non-EAL previous limitations in terms of performance.\n+\n * **Updated PCAP driver.**\n \n   Updated PCAP driver with new features and improvements, including:\ndiff --git a/lib/librte_eal/common/eal_common_lcore.c b/lib/librte_eal/common/eal_common_lcore.c\nindex 86d32a3dd7..2b7d262372 100644\n--- a/lib/librte_eal/common/eal_common_lcore.c\n+++ b/lib/librte_eal/common/eal_common_lcore.c\n@@ -6,13 +6,15 @@\n #include <limits.h>\n #include <string.h>\n \n-#include <rte_errno.h>\n-#include <rte_log.h>\n-#include <rte_eal.h>\n-#include <rte_lcore.h>\n #include <rte_common.h>\n #include <rte_debug.h>\n+#include <rte_eal.h>\n+#include <rte_errno.h>\n+#include <rte_lcore.h>\n+#include <rte_log.h>\n+#include <rte_spinlock.h>\n \n+#include \"eal_memcfg.h\"\n #include \"eal_private.h\"\n #include \"eal_thread.h\"\n \n@@ -220,3 +222,38 @@ rte_socket_id_by_idx(unsigned int idx)\n \t}\n \treturn config->numa_nodes[idx];\n }\n+\n+static rte_spinlock_t lcore_lock = RTE_SPINLOCK_INITIALIZER;\n+\n+unsigned int\n+eal_lcore_non_eal_allocate(void)\n+{\n+\tstruct rte_config *cfg = rte_eal_get_configuration();\n+\tunsigned int lcore_id;\n+\n+\trte_spinlock_lock(&lcore_lock);\n+\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\t\tif (cfg->lcore_role[lcore_id] != ROLE_OFF)\n+\t\t\tcontinue;\n+\t\tcfg->lcore_role[lcore_id] = ROLE_NON_EAL;\n+\t\tcfg->lcore_count++;\n+\t\tbreak;\n+\t}\n+\tif (lcore_id == RTE_MAX_LCORE)\n+\t\tRTE_LOG(DEBUG, EAL, \"No lcore available.\\n\");\n+\trte_spinlock_unlock(&lcore_lock);\n+\treturn lcore_id;\n+}\n+\n+void\n+eal_lcore_non_eal_release(unsigned int lcore_id)\n+{\n+\tstruct rte_config *cfg = rte_eal_get_configuration();\n+\n+\trte_spinlock_lock(&lcore_lock);\n+\tif (cfg->lcore_role[lcore_id] == ROLE_NON_EAL) {\n+\t\tcfg->lcore_role[lcore_id] = ROLE_OFF;\n+\t\tcfg->lcore_count--;\n+\t}\n+\trte_spinlock_unlock(&lcore_lock);\n+}\ndiff --git a/lib/librte_eal/common/eal_common_proc.c b/lib/librte_eal/common/eal_common_proc.c\nindex c649789a52..10142683f8 100644\n--- a/lib/librte_eal/common/eal_common_proc.c\n+++ b/lib/librte_eal/common/eal_common_proc.c\n@@ -29,6 +29,7 @@\n #include <rte_log.h>\n #include <rte_tailq.h>\n \n+#include \"eal_memcfg.h\"\n #include \"eal_private.h\"\n #include \"eal_filesystem.h\"\n #include \"eal_internal_cfg.h\"\n@@ -1232,3 +1233,44 @@ rte_mp_reply(struct rte_mp_msg *msg, const char *peer)\n \n \treturn mp_send(msg, peer, MP_REP);\n }\n+\n+/* Internally, the status of the mp feature is represented as a three-state:\n+ * - \"unknown\" as long as no secondary process attached to a primary process\n+ *   and there was no call to eal_disable_multiprocess yet,\n+ * - \"enabled\" as soon as a secondary process attaches to a primary process,\n+ * - \"disabled\" when a primary process successfully called\n+ *   eal_disable_multiprocess,\n+ */\n+enum mp_status {\n+\tMP_STATUS_UNKNOWN,\n+\tMP_STATUS_DISABLED,\n+\tMP_STATUS_ENABLED,\n+};\n+\n+static bool\n+set_mp_status(enum mp_status status)\n+{\n+\tstruct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;\n+\tuint8_t expected;\n+\tuint8_t desired;\n+\n+\texpected = MP_STATUS_UNKNOWN;\n+\tdesired = status;\n+\tif (__atomic_compare_exchange_n(&mcfg->mp_status, &expected, desired,\n+\t\t\tfalse, __ATOMIC_RELAXED, __ATOMIC_RELAXED))\n+\t\treturn true;\n+\n+\treturn __atomic_load_n(&mcfg->mp_status, __ATOMIC_RELAXED) == desired;\n+}\n+\n+bool\n+eal_disable_multiprocess(void)\n+{\n+\treturn set_mp_status(MP_STATUS_DISABLED);\n+}\n+\n+bool\n+eal_enable_multiprocess(void)\n+{\n+\treturn set_mp_status(MP_STATUS_ENABLED);\n+}\ndiff --git a/lib/librte_eal/common/eal_common_thread.c b/lib/librte_eal/common/eal_common_thread.c\nindex b1b69ea58c..cead8ea5cf 100644\n--- a/lib/librte_eal/common/eal_common_thread.c\n+++ b/lib/librte_eal/common/eal_common_thread.c\n@@ -12,9 +12,10 @@\n #include <assert.h>\n #include <string.h>\n \n+#include <rte_errno.h>\n #include <rte_lcore.h>\n-#include <rte_memory.h>\n #include <rte_log.h>\n+#include <rte_memory.h>\n #include <rte_trace_point.h>\n \n #include \"eal_internal_cfg.h\"\n@@ -241,3 +242,49 @@ rte_ctrl_thread_create(pthread_t *thread, const char *name,\n \tpthread_join(*thread, NULL);\n \treturn -ret;\n }\n+\n+int\n+rte_thread_register(void)\n+{\n+\tunsigned int lcore_id;\n+\trte_cpuset_t cpuset;\n+\n+\t/* EAL init flushes all lcores, we can't register before. */\n+\tif (eal_get_internal_configuration()->init_complete != 1) {\n+\t\tRTE_LOG(DEBUG, EAL, \"Called %s before EAL init.\\n\", __func__);\n+\t\trte_errno = EINVAL;\n+\t\treturn -1;\n+\t}\n+\tif (!eal_disable_multiprocess()) {\n+\t\tRTE_LOG(ERR, EAL, \"Multiprocess in use, registering non-EAL threads is not supported.\\n\");\n+\t\trte_errno = EINVAL;\n+\t\treturn -1;\n+\t}\n+\tif (pthread_getaffinity_np(pthread_self(), sizeof(cpuset),\n+\t\t\t&cpuset) != 0)\n+\t\tCPU_ZERO(&cpuset);\n+\tlcore_id = eal_lcore_non_eal_allocate();\n+\tif (lcore_id >= RTE_MAX_LCORE)\n+\t\tlcore_id = LCORE_ID_ANY;\n+\t__rte_thread_init(lcore_id, &cpuset);\n+\tif (lcore_id == LCORE_ID_ANY) {\n+\t\trte_errno = ENOMEM;\n+\t\treturn -1;\n+\t}\n+\tRTE_LOG(DEBUG, EAL, \"Registered non-EAL thread as lcore %u.\\n\",\n+\t\tlcore_id);\n+\treturn 0;\n+}\n+\n+void\n+rte_thread_unregister(void)\n+{\n+\tunsigned int lcore_id = rte_lcore_id();\n+\n+\tif (lcore_id != LCORE_ID_ANY)\n+\t\teal_lcore_non_eal_release(lcore_id);\n+\t__rte_thread_uninit();\n+\tif (lcore_id != LCORE_ID_ANY)\n+\t\tRTE_LOG(DEBUG, EAL, \"Unregistered non-EAL thread (was lcore %u).\\n\",\n+\t\t\tlcore_id);\n+}\ndiff --git a/lib/librte_eal/common/eal_memcfg.h b/lib/librte_eal/common/eal_memcfg.h\nindex 583fcb5953..ea013a9daf 100644\n--- a/lib/librte_eal/common/eal_memcfg.h\n+++ b/lib/librte_eal/common/eal_memcfg.h\n@@ -41,6 +41,8 @@ struct rte_mem_config {\n \trte_rwlock_t memory_hotplug_lock;\n \t/**< Indicates whether memory hotplug request is in progress. */\n \n+\tuint8_t mp_status; /**< Multiprocess status. */\n+\n \t/* memory segments and zones */\n \tstruct rte_fbarray memzones; /**< Memzone descriptors. */\n \ndiff --git a/lib/librte_eal/common/eal_private.h b/lib/librte_eal/common/eal_private.h\nindex a77ac7a963..e82fb80aa0 100644\n--- a/lib/librte_eal/common/eal_private.h\n+++ b/lib/librte_eal/common/eal_private.h\n@@ -396,6 +396,24 @@ uint64_t get_tsc_freq(void);\n  */\n uint64_t get_tsc_freq_arch(void);\n \n+/**\n+ * Allocate a free lcore to associate to a non-EAL thread.\n+ *\n+ * @return\n+ *   - the id of a lcore with role ROLE_NON_EAL on success.\n+ *   - RTE_MAX_LCORE if none was available.\n+ */\n+unsigned int eal_lcore_non_eal_allocate(void);\n+\n+/**\n+ * Release the lcore used by a non-EAL thread.\n+ * Counterpart of eal_lcore_non_eal_allocate().\n+ *\n+ * @param lcore_id\n+ *   The lcore with role ROLE_NON_EAL to release.\n+ */\n+void eal_lcore_non_eal_release(unsigned int lcore_id);\n+\n /**\n  * Prepare physical memory mapping\n  * i.e. hugepages on Linux and\n@@ -714,4 +732,14 @@ void __rte_thread_init(unsigned int lcore_id, rte_cpuset_t *cpuset);\n  */\n void __rte_thread_uninit(void);\n \n+/**\n+ * Mark primary process as not supporting multi-process.\n+ */\n+bool eal_disable_multiprocess(void);\n+\n+/**\n+ * Instruct primary process that a secondary process wants to attach.\n+ */\n+bool eal_enable_multiprocess(void);\n+\n #endif /* _EAL_PRIVATE_H_ */\ndiff --git a/lib/librte_eal/freebsd/eal.c b/lib/librte_eal/freebsd/eal.c\nindex fd577daf44..7e5001a864 100644\n--- a/lib/librte_eal/freebsd/eal.c\n+++ b/lib/librte_eal/freebsd/eal.c\n@@ -400,6 +400,10 @@ rte_config_init(void)\n \t\t}\n \t\tif (rte_eal_config_reattach() < 0)\n \t\t\treturn -1;\n+\t\tif (!eal_enable_multiprocess()) {\n+\t\t\tRTE_LOG(ERR, EAL, \"Primary process refused secondary attachment\\n\");\n+\t\t\treturn -1;\n+\t\t}\n \t\teal_mcfg_update_internal();\n \t\tbreak;\n \tcase RTE_PROC_AUTO:\ndiff --git a/lib/librte_eal/include/rte_lcore.h b/lib/librte_eal/include/rte_lcore.h\nindex 3968c40693..2fd1a03275 100644\n--- a/lib/librte_eal/include/rte_lcore.h\n+++ b/lib/librte_eal/include/rte_lcore.h\n@@ -31,6 +31,7 @@ enum rte_lcore_role_t {\n \tROLE_RTE,\n \tROLE_OFF,\n \tROLE_SERVICE,\n+\tROLE_NON_EAL,\n };\n \n /**\n@@ -67,7 +68,8 @@ rte_lcore_has_role(unsigned int lcore_id, enum rte_lcore_role_t role);\n  *   to run threads with lcore IDs 0, 1, 2 and 3 on physical core 10..\n  *\n  * @return\n- *  Logical core ID (in EAL thread) or LCORE_ID_ANY (in non-EAL thread)\n+ *  Logical core ID (in EAL thread or registered non-EAL thread) or\n+ *  LCORE_ID_ANY (in unregistered non-EAL thread)\n  */\n static inline unsigned\n rte_lcore_id(void)\n@@ -279,6 +281,30 @@ int rte_thread_setname(pthread_t id, const char *name);\n __rte_experimental\n int rte_thread_getname(pthread_t id, char *name, size_t len);\n \n+/**\n+ * Register current non-EAL thread as a lcore.\n+ *\n+ * @note This API is not compatible with the multi-process feature:\n+ * - if a primary process registers a non-EAL thread, then no secondary process\n+ *   will initialise.\n+ * - if a secondary process initialises successfully, trying to register a\n+ *   non-EAL thread from either primary or secondary processes will always end\n+ *   up with the thread getting LCORE_ID_ANY as lcore.\n+ *\n+ * @return\n+ *   On success, return 0; otherwise return -1 with rte_errno set.\n+ */\n+__rte_experimental\n+int\n+rte_thread_register(void);\n+\n+/**\n+ * Unregister current thread and release lcore if one was associated.\n+ */\n+__rte_experimental\n+void\n+rte_thread_unregister(void);\n+\n /**\n  * Create a control thread.\n  *\ndiff --git a/lib/librte_eal/linux/eal.c b/lib/librte_eal/linux/eal.c\nindex bd089cdd44..f4daf46c4e 100644\n--- a/lib/librte_eal/linux/eal.c\n+++ b/lib/librte_eal/linux/eal.c\n@@ -492,6 +492,10 @@ rte_config_init(void)\n \t\t}\n \t\tif (rte_eal_config_reattach() < 0)\n \t\t\treturn -1;\n+\t\tif (!eal_enable_multiprocess()) {\n+\t\t\tRTE_LOG(ERR, EAL, \"Primary process refused secondary attachment\\n\");\n+\t\t\treturn -1;\n+\t\t}\n \t\teal_mcfg_update_internal();\n \t\tbreak;\n \tcase RTE_PROC_AUTO:\ndiff --git a/lib/librte_eal/ b/lib/librte_eal/\nindex 0d42d44ce9..5503dd7620 100644\n--- a/lib/librte_eal/\n+++ b/lib/librte_eal/\n@@ -393,6 +393,10 @@ EXPERIMENTAL {\n \trte_trace_point_lookup;\n \trte_trace_regexp;\n \trte_trace_save;\n+\n+\t# added in 20.08\n+\trte_thread_register;\n+\trte_thread_unregister;\n };\n \n INTERNAL {\ndiff --git a/lib/librte_eal/windows/eal.c b/lib/librte_eal/windows/eal.c\nindex addac62ae5..9fc831fe6f 100644\n--- a/lib/librte_eal/windows/eal.c\n+++ b/lib/librte_eal/windows/eal.c\n@@ -66,6 +66,12 @@ eal_proc_type_detect(void)\n \treturn ptype;\n }\n \n+bool\n+eal_disable_multiprocess(void)\n+{\n+\treturn true;\n+}\n+\n /* display usage */\n static void\n eal_usage(const char *prgname)\ndiff --git a/lib/librte_mempool/rte_mempool.h b/lib/librte_mempool/rte_mempool.h\nindex 652d19f9f1..9e0ee052b3 100644\n--- a/lib/librte_mempool/rte_mempool.h\n+++ b/lib/librte_mempool/rte_mempool.h\n@@ -28,9 +28,9 @@\n  * rte_mempool_get() or rte_mempool_put() are designed to be called from an EAL\n  * thread due to the internal per-lcore cache. Due to the lack of caching,\n  * rte_mempool_get() or rte_mempool_put() performance will suffer when called\n- * by non-EAL threads. Instead, non-EAL threads should call\n- * rte_mempool_generic_get() or rte_mempool_generic_put() with a user cache\n- * created with rte_mempool_cache_create().\n+ * by unregistered non-EAL threads. Instead, unregistered non-EAL threads\n+ * should call rte_mempool_generic_get() or rte_mempool_generic_put() with a\n+ * user cache created with rte_mempool_cache_create().\n  */\n \n #include <stdio.h>\n@@ -1233,7 +1233,7 @@ void rte_mempool_dump(FILE *f, struct rte_mempool *mp);\n /**\n  * Create a user-owned mempool cache.\n  *\n- * This can be used by non-EAL threads to enable caching when they\n+ * This can be used by unregistered non-EAL threads to enable caching when they\n  * interact with a mempool.\n  *\n  * @param size\n@@ -1264,7 +1264,8 @@ rte_mempool_cache_free(struct rte_mempool_cache *cache);\n  * @param lcore_id\n  *   The logical core id.\n  * @return\n- *   A pointer to the mempool cache or NULL if disabled or non-EAL thread.\n+ *   A pointer to the mempool cache or NULL if disabled or unregistered non-EAL\n+ *   thread.\n  */\n static __rte_always_inline struct rte_mempool_cache *\n rte_mempool_default_cache(struct rte_mempool *mp, unsigned lcore_id)\n",
    "prefixes": [