From patchwork Thu Nov 11 01:33:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Narcisa Ana Maria Vasile X-Patchwork-Id: 104138 X-Patchwork-Delegate: david.marchand@redhat.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id BB2A9A0C4B; Thu, 11 Nov 2021 02:35:03 +0100 (CET) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 69B4641145; Thu, 11 Nov 2021 02:34:57 +0100 (CET) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by mails.dpdk.org (Postfix) with ESMTP id 76AF740E28 for ; Thu, 11 Nov 2021 02:34:53 +0100 (CET) Received: by linux.microsoft.com (Postfix, from userid 1059) id 9C42B20C3571; Wed, 10 Nov 2021 17:34:52 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 9C42B20C3571 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1636594492; bh=16avzK0eNxHXYlRLFto/kA9fgfC8kIHCPDnG+3s8vzw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=j1DjF0gJe4mMWSJNkCXwX6vH/dCEg/4s8LVinW08m1BPd/yuwMio/srf61VcGKOUH BsSzj14g4nrrJzWC3F4LrF3+EfkonI8q/oAflgJF/fd5N5VC6VGSeMplAKRLmuVw3b shIG9pSRHENQJ5bdiyyklJIZdw34o4n/o/fhiDhU= From: Narcisa Ana Maria Vasile To: dev@dpdk.org, thomas@monjalon.net, dmitry.kozliuk@gmail.com, khot@microsoft.com, navasile@microsoft.com, dmitrym@microsoft.com, roretzla@microsoft.com, talshn@nvidia.com, ocardona@microsoft.com Cc: bruce.richardson@intel.com, david.marchand@redhat.com, pallavi.kadam@intel.com Subject: [PATCH v18 1/8] eal: add basic threading functions Date: Wed, 10 Nov 2021 17:33:38 -0800 Message-Id: <1636594425-9692-2-git-send-email-navasile@linux.microsoft.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> References: <1636513302-7359-1-git-send-email-navasile@linux.microsoft.com> <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Narcisa Vasile Use a portable, type-safe representation for the thread identifier. Add functions for comparing thread ids and obtaining the thread id for the current thread. Signed-off-by: Narcisa Vasile --- lib/eal/common/meson.build | 1 + lib/eal/{unix => common}/rte_thread.c | 57 ++++++++++++++++---------- lib/eal/include/rte_thread.h | 53 ++++++++++++++++++------ lib/eal/unix/meson.build | 1 - lib/eal/version.map | 3 ++ lib/eal/windows/rte_thread.c | 58 +++++++++++++++++---------- 6 files changed, 117 insertions(+), 56 deletions(-) rename lib/eal/{unix => common}/rte_thread.c (66%) diff --git a/lib/eal/common/meson.build b/lib/eal/common/meson.build index 917758cc65..6bdc9cd854 100644 --- a/lib/eal/common/meson.build +++ b/lib/eal/common/meson.build @@ -52,5 +52,6 @@ if not is_windows 'hotplug_mp.c', 'malloc_mp.c', 'rte_keepalive.c', + 'rte_thread.c' ) endif diff --git a/lib/eal/unix/rte_thread.c b/lib/eal/common/rte_thread.c similarity index 66% rename from lib/eal/unix/rte_thread.c rename to lib/eal/common/rte_thread.c index c72d619ec1..92a7451b0a 100644 --- a/lib/eal/unix/rte_thread.c +++ b/lib/eal/common/rte_thread.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: BSD-3-Clause * Copyright 2021 Mellanox Technologies, Ltd + * Copyright(c) 2021 Microsoft Corporation */ #include @@ -16,25 +17,41 @@ struct eal_tls_key { pthread_key_t thread_index; }; +rte_thread_t +rte_thread_self(void) +{ + rte_thread_t thread_id; + + thread_id.opaque_id = (uintptr_t)pthread_self(); + + return thread_id; +} + +int +rte_thread_equal(rte_thread_t t1, rte_thread_t t2) +{ + return pthread_equal((pthread_t)t1.opaque_id, (pthread_t)t2.opaque_id); +} + int rte_thread_key_create(rte_thread_key *key, void (*destructor)(void *)) { int err; + rte_thread_key k; - *key = malloc(sizeof(**key)); - if ((*key) == NULL) { + k = malloc(sizeof(*k)); + if (k == NULL) { RTE_LOG(DEBUG, EAL, "Cannot allocate TLS key.\n"); - rte_errno = ENOMEM; - return -1; + return EINVAL; } - err = pthread_key_create(&((*key)->thread_index), destructor); - if (err) { + err = pthread_key_create(&(k->thread_index), destructor); + if (err != 0) { RTE_LOG(DEBUG, EAL, "pthread_key_create failed: %s\n", strerror(err)); - free(*key); - rte_errno = ENOEXEC; - return -1; + free(k); + return err; } + *key = k; return 0; } @@ -43,18 +60,16 @@ rte_thread_key_delete(rte_thread_key key) { int err; - if (!key) { + if (key == NULL) { RTE_LOG(DEBUG, EAL, "Invalid TLS key.\n"); - rte_errno = EINVAL; - return -1; + return EINVAL; } err = pthread_key_delete(key->thread_index); - if (err) { + if (err != 0) { RTE_LOG(DEBUG, EAL, "pthread_key_delete failed: %s\n", strerror(err)); free(key); - rte_errno = ENOEXEC; - return -1; + return err; } free(key); return 0; @@ -65,17 +80,15 @@ rte_thread_value_set(rte_thread_key key, const void *value) { int err; - if (!key) { + if (key == NULL) { RTE_LOG(DEBUG, EAL, "Invalid TLS key.\n"); - rte_errno = EINVAL; - return -1; + return EINVAL; } err = pthread_setspecific(key->thread_index, value); - if (err) { + if (err != 0) { RTE_LOG(DEBUG, EAL, "pthread_setspecific failed: %s\n", strerror(err)); - rte_errno = ENOEXEC; - return -1; + return err; } return 0; } @@ -83,7 +96,7 @@ rte_thread_value_set(rte_thread_key key, const void *value) void * rte_thread_value_get(rte_thread_key key) { - if (!key) { + if (key == NULL) { RTE_LOG(DEBUG, EAL, "Invalid TLS key.\n"); rte_errno = EINVAL; return NULL; diff --git a/lib/eal/include/rte_thread.h b/lib/eal/include/rte_thread.h index 8be8ed8f36..c9cdeb07aa 100644 --- a/lib/eal/include/rte_thread.h +++ b/lib/eal/include/rte_thread.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2021 Mellanox Technologies, Ltd + * Copyright(c) 2021 Microsoft Corporation */ +#include #include #include @@ -20,11 +22,45 @@ extern "C" { #endif +#include + +/** + * Thread id descriptor. + */ +typedef struct rte_thread_tag { + uintptr_t opaque_id; /**< thread identifier */ +} rte_thread_t; + /** * TLS key type, an opaque pointer. */ typedef struct eal_tls_key *rte_thread_key; +/** + * Get the id of the calling thread. + * + * @return + * Return the thread id of the calling thread. + */ +__rte_experimental +rte_thread_t rte_thread_self(void); + +/** + * Check if 2 thread ids are equal. + * + * @param t1 + * First thread id. + * + * @param t2 + * Second thread id. + * + * @return + * If the ids are equal, return nonzero. + * Otherwise, return 0. + */ +__rte_experimental +int rte_thread_equal(rte_thread_t t1, rte_thread_t t2); + #ifdef RTE_HAS_CPUSET /** @@ -63,9 +99,7 @@ void rte_thread_get_affinity(rte_cpuset_t *cpusetp); * * @return * On success, zero. - * On failure, a negative number and an error number is set in rte_errno. - * rte_errno can be: ENOMEM - Memory allocation error. - * ENOEXEC - Specific OS error. + * On failure, return a positive errno-style error number. */ __rte_experimental @@ -80,9 +114,7 @@ int rte_thread_key_create(rte_thread_key *key, * * @return * On success, zero. - * On failure, a negative number and an error number is set in rte_errno. - * rte_errno can be: EINVAL - Invalid parameter passed. - * ENOEXEC - Specific OS error. + * On failure, return a positive errno-style error number. */ __rte_experimental int rte_thread_key_delete(rte_thread_key key); @@ -97,9 +129,7 @@ int rte_thread_key_delete(rte_thread_key key); * * @return * On success, zero. - * On failure, a negative number and an error number is set in rte_errno. - * rte_errno can be: EINVAL - Invalid parameter passed. - * ENOEXEC - Specific OS error. + * On failure, return a positive errno-style error number. */ __rte_experimental int rte_thread_value_set(rte_thread_key key, const void *value); @@ -112,9 +142,8 @@ int rte_thread_value_set(rte_thread_key key, const void *value); * * @return * On success, value data pointer (can also be NULL). - * On failure, NULL and an error number is set in rte_errno. - * rte_errno can be: EINVAL - Invalid parameter passed. - * ENOEXEC - Specific OS error. + * On failure, NULL and a positive error number is set in rte_errno. + * */ __rte_experimental void *rte_thread_value_get(rte_thread_key key); diff --git a/lib/eal/unix/meson.build b/lib/eal/unix/meson.build index e3ecd3e956..cb6d233721 100644 --- a/lib/eal/unix/meson.build +++ b/lib/eal/unix/meson.build @@ -6,5 +6,4 @@ sources += files( 'eal_unix_memory.c', 'eal_unix_timer.c', 'eal_firmware.c', - 'rte_thread.c', ) diff --git a/lib/eal/version.map b/lib/eal/version.map index ab28c22791..24e9747795 100644 --- a/lib/eal/version.map +++ b/lib/eal/version.map @@ -420,6 +420,9 @@ EXPERIMENTAL { rte_intr_instance_free; rte_intr_type_get; rte_intr_type_set; + + rte_thread_self; + rte_thread_equal; }; INTERNAL { diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c index 667287c387..26c8dd4ebe 100644 --- a/lib/eal/windows/rte_thread.c +++ b/lib/eal/windows/rte_thread.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: BSD-3-Clause * Copyright 2021 Mellanox Technologies, Ltd + * Copyright(c) 2021 Microsoft Corporation */ #include @@ -11,22 +12,38 @@ struct eal_tls_key { DWORD thread_index; }; +rte_thread_t +rte_thread_self(void) +{ + rte_thread_t thread_id; + + thread_id.opaque_id = GetCurrentThreadId(); + + return thread_id; +} + +int +rte_thread_equal(rte_thread_t t1, rte_thread_t t2) +{ + return t1.opaque_id == t2.opaque_id; +} + int rte_thread_key_create(rte_thread_key *key, __rte_unused void (*destructor)(void *)) { + int ret; + *key = malloc(sizeof(**key)); if ((*key) == NULL) { RTE_LOG(DEBUG, EAL, "Cannot allocate TLS key.\n"); - rte_errno = ENOMEM; - return -1; + return ENOMEM; } (*key)->thread_index = TlsAlloc(); if ((*key)->thread_index == TLS_OUT_OF_INDEXES) { - RTE_LOG_WIN32_ERR("TlsAlloc()"); + ret = thread_log_last_error("TlsAlloc()"); free(*key); - rte_errno = ENOEXEC; - return -1; + return ret; } return 0; } @@ -34,16 +51,16 @@ rte_thread_key_create(rte_thread_key *key, int rte_thread_key_delete(rte_thread_key key) { - if (!key) { + int ret; + + if (key == NULL) { RTE_LOG(DEBUG, EAL, "Invalid TLS key.\n"); - rte_errno = EINVAL; - return -1; + return EINVAL; } if (!TlsFree(key->thread_index)) { - RTE_LOG_WIN32_ERR("TlsFree()"); + ret = thread_log_last_error("TlsFree()"); free(key); - rte_errno = ENOEXEC; - return -1; + return ret; } free(key); return 0; @@ -54,17 +71,14 @@ rte_thread_value_set(rte_thread_key key, const void *value) { char *p; - if (!key) { + if (key == NULL) { RTE_LOG(DEBUG, EAL, "Invalid TLS key.\n"); - rte_errno = EINVAL; - return -1; + return EINVAL; } /* discard const qualifier */ p = (char *) (uintptr_t) value; if (!TlsSetValue(key->thread_index, p)) { - RTE_LOG_WIN32_ERR("TlsSetValue()"); - rte_errno = ENOEXEC; - return -1; + return thread_log_last_error("TlsSetValue()"); } return 0; } @@ -73,16 +87,18 @@ void * rte_thread_value_get(rte_thread_key key) { void *output; + DWORD ret = 0; - if (!key) { + if (key == NULL) { RTE_LOG(DEBUG, EAL, "Invalid TLS key.\n"); rte_errno = EINVAL; return NULL; } output = TlsGetValue(key->thread_index); - if (GetLastError() != ERROR_SUCCESS) { - RTE_LOG_WIN32_ERR("TlsGetValue()"); - rte_errno = ENOEXEC; + ret = GetLastError(); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "GetLastError()=%lu: TlsGetValue()\n", ret); + rte_errno = thread_translate_win32_error(ret); return NULL; } return output; From patchwork Thu Nov 11 01:33:39 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Narcisa Ana Maria Vasile X-Patchwork-Id: 104140 X-Patchwork-Delegate: david.marchand@redhat.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 48837A0C4B; Thu, 11 Nov 2021 02:35:15 +0100 (CET) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id B072541154; Thu, 11 Nov 2021 02:34:59 +0100 (CET) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by mails.dpdk.org (Postfix) with ESMTP id 7B23B4113C for ; Thu, 11 Nov 2021 02:34:53 +0100 (CET) Received: by linux.microsoft.com (Postfix, from userid 1059) id A802E20C3575; Wed, 10 Nov 2021 17:34:52 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com A802E20C3575 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1636594492; bh=dZLTkRTue5ZpuS8VWNjKE4iQs/9/pZf6XCY6jppnII4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ff03IFnfwD7cR+8ZJ13XmQmxZWPLY+cM5r9uym3YzxKcKQGoOFFMMZSPR/QJ8PtiC way2lNbgCx/76z3PHcPBbaGJ9RZazK9ryTWFD5wAGjrTbaWxvY/+3eYjHWpb8+F73c IdaTtBnQGqQeHhuo8pIGQperutXuGcPgdN+ulDgg= From: Narcisa Ana Maria Vasile To: dev@dpdk.org, thomas@monjalon.net, dmitry.kozliuk@gmail.com, khot@microsoft.com, navasile@microsoft.com, dmitrym@microsoft.com, roretzla@microsoft.com, talshn@nvidia.com, ocardona@microsoft.com Cc: bruce.richardson@intel.com, david.marchand@redhat.com, pallavi.kadam@intel.com Subject: [PATCH v18 2/8] eal: add thread attributes Date: Wed, 10 Nov 2021 17:33:39 -0800 Message-Id: <1636594425-9692-3-git-send-email-navasile@linux.microsoft.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> References: <1636513302-7359-1-git-send-email-navasile@linux.microsoft.com> <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Narcisa Vasile Implement thread attributes for: * thread affinity * thread priority Implement functions for managing thread attributes. Priority is represented through an enum that allows for two levels: - RTE_THREAD_PRIORITY_NORMAL - RTE_THREAD_PRIORITY_REALTIME_CRITICAL Affinity is described by the rte_cpuset_t type. An rte_thread_attr_t object can be set to the default values by calling rte_thread_attr_init(). Signed-off-by: Narcisa Vasile --- lib/eal/common/rte_thread.c | 46 ++++++++++++++++++ lib/eal/include/rte_thread.h | 91 ++++++++++++++++++++++++++++++++++++ lib/eal/version.map | 4 ++ lib/eal/windows/rte_thread.c | 44 +++++++++++++++++ 4 files changed, 185 insertions(+) diff --git a/lib/eal/common/rte_thread.c b/lib/eal/common/rte_thread.c index 92a7451b0a..27ad1c7eb0 100644 --- a/lib/eal/common/rte_thread.c +++ b/lib/eal/common/rte_thread.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,51 @@ rte_thread_equal(rte_thread_t t1, rte_thread_t t2) return pthread_equal((pthread_t)t1.opaque_id, (pthread_t)t2.opaque_id); } +int +rte_thread_attr_init(rte_thread_attr_t *attr) +{ + RTE_VERIFY(attr != NULL); + + CPU_ZERO(&attr->cpuset); + attr->priority = RTE_THREAD_PRIORITY_NORMAL; + + return 0; +} + +int +rte_thread_attr_set_affinity(rte_thread_attr_t *thread_attr, + rte_cpuset_t *cpuset) +{ + RTE_VERIFY(thread_attr != NULL); + RTE_VERIFY(cpuset != NULL); + + thread_attr->cpuset = *cpuset; + + return 0; +} + +int +rte_thread_attr_get_affinity(rte_thread_attr_t *thread_attr, + rte_cpuset_t *cpuset) +{ + RTE_VERIFY(thread_attr != NULL); + RTE_VERIFY(cpuset != NULL); + + *cpuset = thread_attr->cpuset; + + return 0; +} + +int +rte_thread_attr_set_priority(rte_thread_attr_t *thread_attr, + enum rte_thread_priority priority) +{ + RTE_VERIFY(thread_attr != NULL); + + thread_attr->priority = priority; + return 0; +} + int rte_thread_key_create(rte_thread_key *key, void (*destructor)(void *)) { diff --git a/lib/eal/include/rte_thread.h b/lib/eal/include/rte_thread.h index c9cdeb07aa..8a20215a94 100644 --- a/lib/eal/include/rte_thread.h +++ b/lib/eal/include/rte_thread.h @@ -31,6 +31,28 @@ typedef struct rte_thread_tag { uintptr_t opaque_id; /**< thread identifier */ } rte_thread_t; +/** + * Thread priority values. + */ +enum rte_thread_priority { + RTE_THREAD_PRIORITY_NORMAL = 0, + /**< normal thread priority, the default */ + RTE_THREAD_PRIORITY_REALTIME_CRITICAL = 1, + /**< highest thread priority allowed */ +}; + +#ifdef RTE_HAS_CPUSET + +/** + * Representation for thread attributes. + */ +typedef struct { + enum rte_thread_priority priority; /**< thread priority */ + rte_cpuset_t cpuset; /**< thread affinity */ +} rte_thread_attr_t; + +#endif /* RTE_HAS_CPUSET */ + /** * TLS key type, an opaque pointer. */ @@ -63,6 +85,75 @@ int rte_thread_equal(rte_thread_t t1, rte_thread_t t2); #ifdef RTE_HAS_CPUSET +/** + * Initialize the attributes of a thread. + * These attributes can be passed to the rte_thread_create() function + * that will create a new thread and set its attributes according to attr. + * + * @param attr + * Thread attributes to initialize. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_attr_init(rte_thread_attr_t *attr); + +/** + * Set the CPU affinity value in the thread attributes pointed to + * by 'thread_attr'. + * + * @param thread_attr + * Points to the thread attributes in which affinity will be updated. + * + * @param cpuset + * Points to the value of the affinity to be set. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_attr_set_affinity(rte_thread_attr_t *thread_attr, + rte_cpuset_t *cpuset); + +/** + * Get the value of CPU affinity that is set in the thread attributes pointed + * to by 'thread_attr'. + * + * @param thread_attr + * Points to the thread attributes from which affinity will be retrieved. + * + * @param cpuset + * Pointer to the memory that will store the affinity. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_attr_get_affinity(rte_thread_attr_t *thread_attr, + rte_cpuset_t *cpuset); + +/** + * Set the thread priority value in the thread attributes pointed to + * by 'thread_attr'. + * + * @param thread_attr + * Points to the thread attributes in which priority will be updated. + * + * @param priority + * Points to the value of the priority to be set. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_attr_set_priority(rte_thread_attr_t *thread_attr, + enum rte_thread_priority priority); + /** * Set core affinity of the current thread. * Support both EAL and non-EAL thread and update TLS. diff --git a/lib/eal/version.map b/lib/eal/version.map index 24e9747795..3fc33fcd70 100644 --- a/lib/eal/version.map +++ b/lib/eal/version.map @@ -423,6 +423,10 @@ EXPERIMENTAL { rte_thread_self; rte_thread_equal; + rte_thread_attr_init; + rte_thread_attr_get_affinity; + rte_thread_attr_set_affinity; + rte_thread_attr_set_priority; }; INTERNAL { diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c index 26c8dd4ebe..f65919f46d 100644 --- a/lib/eal/windows/rte_thread.c +++ b/lib/eal/windows/rte_thread.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -28,6 +29,49 @@ rte_thread_equal(rte_thread_t t1, rte_thread_t t2) return t1.opaque_id == t2.opaque_id; } +int +rte_thread_attr_init(rte_thread_attr_t *attr) +{ + RTE_VERIFY(attr != NULL); + + attr->priority = RTE_THREAD_PRIORITY_NORMAL; + CPU_ZERO(&attr->cpuset); + + return 0; +} + +int +rte_thread_attr_set_affinity(rte_thread_attr_t *thread_attr, + rte_cpuset_t *cpuset) +{ + RTE_VERIFY(thread_attr != NULL); + thread_attr->cpuset = *cpuset; + + return 0; +} + +int +rte_thread_attr_get_affinity(rte_thread_attr_t *thread_attr, + rte_cpuset_t *cpuset) +{ + RTE_VERIFY(thread_attr != NULL); + + *cpuset = thread_attr->cpuset; + + return 0; +} + +int +rte_thread_attr_set_priority(rte_thread_attr_t *thread_attr, + enum rte_thread_priority priority) +{ + RTE_VERIFY(thread_attr != NULL); + + thread_attr->priority = priority; + + return 0; +} + int rte_thread_key_create(rte_thread_key *key, __rte_unused void (*destructor)(void *)) From patchwork Thu Nov 11 01:33:40 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Narcisa Ana Maria Vasile X-Patchwork-Id: 104136 X-Patchwork-Delegate: david.marchand@redhat.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 75D84A0C4B; Thu, 11 Nov 2021 02:34:54 +0100 (CET) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 5F94F4113D; Thu, 11 Nov 2021 02:34:54 +0100 (CET) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by mails.dpdk.org (Postfix) with ESMTP id 586ED40E03 for ; Thu, 11 Nov 2021 02:34:53 +0100 (CET) Received: by linux.microsoft.com (Postfix, from userid 1059) id B377420C357B; Wed, 10 Nov 2021 17:34:52 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com B377420C357B DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1636594492; bh=rD/M91jtoS/MuZlmWPE70yePJPzOcstk1NAqkOPIc14=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UJWu2F7bE391Ua5HrX2wW34VF41Ty/YOZlee/7DgvJFs2BTep/BRUVda9h36DTTTs UQa8iCkETatO2ReSdKQ+xQkgmncY4NinquuHOszY7TuuJXPNLsdV7WHvWz8MJUMCMb nQpsv8Ee3k+ETnjTyOrNqVp2mm41k0Uudz6uA/DI= From: Narcisa Ana Maria Vasile To: dev@dpdk.org, thomas@monjalon.net, dmitry.kozliuk@gmail.com, khot@microsoft.com, navasile@microsoft.com, dmitrym@microsoft.com, roretzla@microsoft.com, talshn@nvidia.com, ocardona@microsoft.com Cc: bruce.richardson@intel.com, david.marchand@redhat.com, pallavi.kadam@intel.com Subject: [PATCH v18 3/8] eal/windows: translate Windows errors to errno-style errors Date: Wed, 10 Nov 2021 17:33:40 -0800 Message-Id: <1636594425-9692-4-git-send-email-navasile@linux.microsoft.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> References: <1636513302-7359-1-git-send-email-navasile@linux.microsoft.com> <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Narcisa Vasile Add function to translate Windows error codes to errno-style error codes. The possible return values are chosen so that we have as much semantical compatibility between platforms as possible. Signed-off-by: Narcisa Vasile --- lib/eal/windows/rte_thread.c | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c index f65919f46d..c1ecfbd6ae 100644 --- a/lib/eal/windows/rte_thread.c +++ b/lib/eal/windows/rte_thread.c @@ -13,6 +13,54 @@ struct eal_tls_key { DWORD thread_index; }; +/* Translates the most common error codes related to threads */ +static int +thread_translate_win32_error(DWORD error) +{ + switch (error) { + case ERROR_SUCCESS: + return 0; + + case ERROR_INVALID_PARAMETER: + return EINVAL; + + case ERROR_INVALID_HANDLE: + return EFAULT; + + case ERROR_NOT_ENOUGH_MEMORY: + /* FALLTHROUGH */ + case ERROR_NO_SYSTEM_RESOURCES: + return ENOMEM; + + case ERROR_PRIVILEGE_NOT_HELD: + /* FALLTHROUGH */ + case ERROR_ACCESS_DENIED: + return EACCES; + + case ERROR_ALREADY_EXISTS: + return EEXIST; + + case ERROR_POSSIBLE_DEADLOCK: + return EDEADLK; + + case ERROR_INVALID_FUNCTION: + /* FALLTHROUGH */ + case ERROR_CALL_NOT_IMPLEMENTED: + return ENOSYS; + } + + return EINVAL; +} + +static int +thread_log_last_error(const char *message) +{ + DWORD error = GetLastError(); + RTE_LOG(DEBUG, EAL, "GetLastError()=%lu: %s\n", error, message); + + return thread_translate_win32_error(error); +} + rte_thread_t rte_thread_self(void) { From patchwork Thu Nov 11 01:33:41 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Narcisa Ana Maria Vasile X-Patchwork-Id: 104139 X-Patchwork-Delegate: david.marchand@redhat.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 57A2AA0C4B; Thu, 11 Nov 2021 02:35:09 +0100 (CET) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 80F224114D; Thu, 11 Nov 2021 02:34:58 +0100 (CET) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by mails.dpdk.org (Postfix) with ESMTP id 79084410F7 for ; Thu, 11 Nov 2021 02:34:53 +0100 (CET) Received: by linux.microsoft.com (Postfix, from userid 1059) id BF26820C357D; Wed, 10 Nov 2021 17:34:52 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com BF26820C357D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1636594492; bh=4LVnA6HfcZgzX9eXbDGMjHALoh0WFVFuZQz6pNzJaYE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BJd/UdDtCtGF4+I0B5EAvpcqqBSsYvxZm217SUZcBXUVAAHpyYfq6pBu3IVQnlVqT +J610rN7JUGTqaZVvEKJU0uIf6qFM2TDBCuh04vVRSDPMx8FBJo1qiYYTT5a1tcYum iQ80mM7PlJGiJ5zUQDvwiwty3cdQLjA79yiF9Ep4= From: Narcisa Ana Maria Vasile To: dev@dpdk.org, thomas@monjalon.net, dmitry.kozliuk@gmail.com, khot@microsoft.com, navasile@microsoft.com, dmitrym@microsoft.com, roretzla@microsoft.com, talshn@nvidia.com, ocardona@microsoft.com Cc: bruce.richardson@intel.com, david.marchand@redhat.com, pallavi.kadam@intel.com Subject: [PATCH v18 4/8] eal: implement functions for thread affinity management Date: Wed, 10 Nov 2021 17:33:41 -0800 Message-Id: <1636594425-9692-5-git-send-email-navasile@linux.microsoft.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> References: <1636513302-7359-1-git-send-email-navasile@linux.microsoft.com> <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Narcisa Vasile Implement functions for getting/setting thread affinity. Threads can be pinned to specific cores by setting their affinity attribute. Signed-off-by: Narcisa Vasile Signed-off-by: Dmitry Malloy --- lib/eal/common/rte_thread.c | 16 ++++ lib/eal/include/rte_thread.h | 36 +++++++ lib/eal/version.map | 2 + lib/eal/windows/eal_lcore.c | 176 +++++++++++++++++++++++++--------- lib/eal/windows/eal_windows.h | 10 ++ lib/eal/windows/rte_thread.c | 125 +++++++++++++++++++++++- 6 files changed, 319 insertions(+), 46 deletions(-) diff --git a/lib/eal/common/rte_thread.c b/lib/eal/common/rte_thread.c index 27ad1c7eb0..73b7b3141c 100644 --- a/lib/eal/common/rte_thread.c +++ b/lib/eal/common/rte_thread.c @@ -34,6 +34,22 @@ rte_thread_equal(rte_thread_t t1, rte_thread_t t2) return pthread_equal((pthread_t)t1.opaque_id, (pthread_t)t2.opaque_id); } +int +rte_thread_set_affinity_by_id(rte_thread_t thread_id, + const rte_cpuset_t *cpuset) +{ + return pthread_setaffinity_np((pthread_t)thread_id.opaque_id, + sizeof(*cpuset), cpuset); +} + +int +rte_thread_get_affinity_by_id(rte_thread_t thread_id, + rte_cpuset_t *cpuset) +{ + return pthread_getaffinity_np((pthread_t)thread_id.opaque_id, + sizeof(*cpuset), cpuset); +} + int rte_thread_attr_init(rte_thread_attr_t *attr) { diff --git a/lib/eal/include/rte_thread.h b/lib/eal/include/rte_thread.h index 8a20215a94..5b100cafda 100644 --- a/lib/eal/include/rte_thread.h +++ b/lib/eal/include/rte_thread.h @@ -85,6 +85,42 @@ int rte_thread_equal(rte_thread_t t1, rte_thread_t t2); #ifdef RTE_HAS_CPUSET +/** + * Set the affinity of thread 'thread_id' to the cpu set + * specified by 'cpuset'. + * + * @param thread_id + * Id of the thread for which to set the affinity. + * + * @param cpuset + * Pointer to CPU affinity to set. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_set_affinity_by_id(rte_thread_t thread_id, + const rte_cpuset_t *cpuset); + +/** + * Get the affinity of thread 'thread_id' and store it + * in 'cpuset'. + * + * @param thread_id + * Id of the thread for which to get the affinity. + * + * @param cpuset + * Pointer for storing the affinity value. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_get_affinity_by_id(rte_thread_t thread_id, + rte_cpuset_t *cpuset); + /** * Initialize the attributes of a thread. * These attributes can be passed to the rte_thread_create() function diff --git a/lib/eal/version.map b/lib/eal/version.map index 3fc33fcd70..193a79a4d2 100644 --- a/lib/eal/version.map +++ b/lib/eal/version.map @@ -427,6 +427,8 @@ EXPERIMENTAL { rte_thread_attr_get_affinity; rte_thread_attr_set_affinity; rte_thread_attr_set_priority; + rte_thread_get_affinity_by_id; + rte_thread_set_affinity_by_id; }; INTERNAL { diff --git a/lib/eal/windows/eal_lcore.c b/lib/eal/windows/eal_lcore.c index 476c2d2bdf..295af50698 100644 --- a/lib/eal/windows/eal_lcore.c +++ b/lib/eal/windows/eal_lcore.c @@ -2,7 +2,6 @@ * Copyright(c) 2019 Intel Corporation */ -#include #include #include @@ -27,13 +26,15 @@ struct socket_map { }; struct cpu_map { - unsigned int socket_count; unsigned int lcore_count; + unsigned int socket_count; + unsigned int cpu_count; struct lcore_map lcores[RTE_MAX_LCORE]; struct socket_map sockets[RTE_MAX_NUMA_NODES]; + GROUP_AFFINITY cpus[CPU_SETSIZE]; }; -static struct cpu_map cpu_map = { 0 }; +static struct cpu_map cpu_map; /* eal_create_cpu_map() is called before logging is initialized */ static void @@ -47,13 +48,118 @@ log_early(const char *format, ...) va_end(va); } +static int +eal_query_group_affinity(void) +{ + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *infos = NULL; + unsigned int *cpu_count = &cpu_map.cpu_count; + DWORD infos_size = 0; + int ret = 0; + USHORT group_count; + KAFFINITY affinity; + USHORT group_no; + unsigned int i; + + if (!GetLogicalProcessorInformationEx(RelationGroup, NULL, + &infos_size)) { + DWORD error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER) { + log_early("Cannot get group information size, " + "error %lu\n", error); + rte_errno = EINVAL; + ret = -1; + goto cleanup; + } + } + + infos = malloc(infos_size); + if (infos == NULL) { + log_early("Cannot allocate memory for NUMA node information\n"); + rte_errno = ENOMEM; + ret = -1; + goto cleanup; + } + + if (!GetLogicalProcessorInformationEx(RelationGroup, infos, + &infos_size)) { + log_early("Cannot get group information, error %lu\n", + GetLastError()); + rte_errno = EINVAL; + ret = -1; + goto cleanup; + } + + *cpu_count = 0; + group_count = infos->Group.ActiveGroupCount; + for (group_no = 0; group_no < group_count; group_no++) { + + affinity = infos->Group.GroupInfo[group_no].ActiveProcessorMask; + + for (i = 0; i < EAL_PROCESSOR_GROUP_SIZE; i++) { + if ((affinity & ((KAFFINITY)1 << i)) == 0) + continue; + cpu_map.cpus[*cpu_count].Group = group_no; + cpu_map.cpus[*cpu_count].Mask = (KAFFINITY)1 << i; + (*cpu_count)++; + } + } + +cleanup: + free(infos); + return ret; +} + +static bool +eal_create_lcore_map(const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *info) +{ + const unsigned int node_id = info->NumaNode.NodeNumber; + const GROUP_AFFINITY *cores = &info->NumaNode.GroupMask; + struct lcore_map *lcore; + unsigned int socket_id; + unsigned int i; + + /* NUMA node may be reported multiple times if it includes + * cores from different processor groups, e. g. 80 cores + * of a physical processor comprise one NUMA node, but two + * processor groups, because group size is limited by 32/64. + */ + for (socket_id = 0; socket_id < cpu_map.socket_count; socket_id++) { + if (cpu_map.sockets[socket_id].node_id == node_id) + break; + } + + if (socket_id == cpu_map.socket_count) { + if (socket_id == RTE_DIM(cpu_map.sockets)) + return true; + + cpu_map.sockets[socket_id].node_id = node_id; + cpu_map.socket_count++; + } + + for (i = 0; i < EAL_PROCESSOR_GROUP_SIZE; i++) { + if ((cores->Mask & ((KAFFINITY)1 << i)) == 0) + continue; + + if (cpu_map.lcore_count == RTE_DIM(cpu_map.lcores)) + return true; + + lcore = &cpu_map.lcores[cpu_map.lcore_count]; + lcore->socket_id = socket_id; + lcore->core_id = cores->Group * EAL_PROCESSOR_GROUP_SIZE + i; + cpu_map.lcore_count++; + } + return false; +} + int eal_create_cpu_map(void) { SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *infos, *info; DWORD infos_size; bool full = false; + int ret = 0; + infos = NULL; infos_size = 0; if (!GetLogicalProcessorInformationEx( RelationNumaNode, NULL, &infos_size)) { @@ -78,57 +184,29 @@ eal_create_cpu_map(void) log_early("Cannot get NUMA node information, error %lu\n", GetLastError()); rte_errno = EINVAL; - return -1; + ret = -1; + goto exit; } info = infos; while ((uint8_t *)info - (uint8_t *)infos < infos_size) { - unsigned int node_id = info->NumaNode.NodeNumber; - GROUP_AFFINITY *cores = &info->NumaNode.GroupMask; - struct lcore_map *lcore; - unsigned int i, socket_id; - - /* NUMA node may be reported multiple times if it includes - * cores from different processor groups, e. g. 80 cores - * of a physical processor comprise one NUMA node, but two - * processor groups, because group size is limited by 32/64. - */ - for (socket_id = 0; socket_id < cpu_map.socket_count; - socket_id++) { - if (cpu_map.sockets[socket_id].node_id == node_id) - break; - } - - if (socket_id == cpu_map.socket_count) { - if (socket_id == RTE_DIM(cpu_map.sockets)) { - full = true; - goto exit; - } - - cpu_map.sockets[socket_id].node_id = node_id; - cpu_map.socket_count++; - } - - for (i = 0; i < EAL_PROCESSOR_GROUP_SIZE; i++) { - if ((cores->Mask & ((KAFFINITY)1 << i)) == 0) - continue; - - if (cpu_map.lcore_count == RTE_DIM(cpu_map.lcores)) { - full = true; - goto exit; - } - - lcore = &cpu_map.lcores[cpu_map.lcore_count]; - lcore->socket_id = socket_id; - lcore->core_id = - cores->Group * EAL_PROCESSOR_GROUP_SIZE + i; - cpu_map.lcore_count++; + if (eal_create_lcore_map(info)) { + full = true; + break; } info = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)( (uint8_t *)info + info->Size); } + if (eal_query_group_affinity()) { + /* + * No need to set rte_errno here. + * It is set by eal_query_group_affinity(). + */ + ret = -1; + goto exit; + } exit: if (full) { /* Not a fatal error, but important for troubleshooting. */ @@ -138,7 +216,7 @@ eal_create_cpu_map(void) free(infos); - return 0; + return ret; } int @@ -164,3 +242,11 @@ eal_socket_numa_node(unsigned int socket_id) { return cpu_map.sockets[socket_id].node_id; } + +PGROUP_AFFINITY +eal_get_cpu_affinity(size_t cpu_index) +{ + RTE_VERIFY(cpu_index < CPU_SETSIZE); + + return &cpu_map.cpus[cpu_index]; +} diff --git a/lib/eal/windows/eal_windows.h b/lib/eal/windows/eal_windows.h index 23ead6d30c..355ef181a5 100644 --- a/lib/eal/windows/eal_windows.h +++ b/lib/eal/windows/eal_windows.h @@ -55,6 +55,16 @@ int eal_thread_create(pthread_t *thread); */ unsigned int eal_socket_numa_node(unsigned int socket_id); +/** + * Get pointer to the group affinity for the cpu. + * + * @param cpu_index + * Index of the cpu, as it comes from rte_cpuset_t. + * @return + * Pointer to the group affinity for the cpu. + */ +PGROUP_AFFINITY eal_get_cpu_affinity(size_t cpu_index); + /** * Schedule code for execution in the interrupt thread. * diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c index c1ecfbd6ae..0127119f49 100644 --- a/lib/eal/windows/rte_thread.c +++ b/lib/eal/windows/rte_thread.c @@ -7,7 +7,8 @@ #include #include #include -#include + +#include "eal_windows.h" struct eal_tls_key { DWORD thread_index; @@ -77,6 +78,128 @@ rte_thread_equal(rte_thread_t t1, rte_thread_t t2) return t1.opaque_id == t2.opaque_id; } +static int +rte_convert_cpuset_to_affinity(const rte_cpuset_t *cpuset, + PGROUP_AFFINITY affinity) +{ + int ret = 0; + PGROUP_AFFINITY cpu_affinity = NULL; + unsigned int cpu_idx; + + memset(affinity, 0, sizeof(GROUP_AFFINITY)); + affinity->Group = (USHORT)-1; + + /* Check that all cpus of the set belong to the same processor group and + * accumulate thread affinity to be applied. + */ + for (cpu_idx = 0; cpu_idx < CPU_SETSIZE; cpu_idx++) { + if (!CPU_ISSET(cpu_idx, cpuset)) + continue; + + cpu_affinity = eal_get_cpu_affinity(cpu_idx); + + if (affinity->Group == (USHORT)-1) { + affinity->Group = cpu_affinity->Group; + } else if (affinity->Group != cpu_affinity->Group) { + ret = EINVAL; + goto cleanup; + } + + affinity->Mask |= cpu_affinity->Mask; + } + + if (affinity->Mask == 0) { + ret = EINVAL; + goto cleanup; + } + +cleanup: + return ret; +} + +int +rte_thread_set_affinity_by_id(rte_thread_t thread_id, + const rte_cpuset_t *cpuset) +{ + int ret = 0; + GROUP_AFFINITY thread_affinity; + HANDLE thread_handle = NULL; + + RTE_VERIFY(cpuset != NULL); + + ret = rte_convert_cpuset_to_affinity(cpuset, &thread_affinity); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "Unable to convert cpuset to thread affinity\n"); + goto cleanup; + } + + thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, + thread_id.opaque_id); + if (thread_handle == NULL) { + ret = thread_log_last_error("OpenThread()"); + goto cleanup; + } + + if (!SetThreadGroupAffinity(thread_handle, &thread_affinity, NULL)) { + ret = thread_log_last_error("SetThreadGroupAffinity()"); + goto cleanup; + } + +cleanup: + if (thread_handle != NULL) { + CloseHandle(thread_handle); + thread_handle = NULL; + } + + return ret; +} + +int +rte_thread_get_affinity_by_id(rte_thread_t thread_id, + rte_cpuset_t *cpuset) +{ + HANDLE thread_handle = NULL; + PGROUP_AFFINITY cpu_affinity; + GROUP_AFFINITY thread_affinity; + unsigned int cpu_idx; + int ret = 0; + + RTE_VERIFY(cpuset != NULL); + + thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, + thread_id.opaque_id); + if (thread_handle == NULL) { + ret = thread_log_last_error("OpenThread()"); + goto cleanup; + } + + /* obtain previous thread affinity */ + if (!GetThreadGroupAffinity(thread_handle, &thread_affinity)) { + ret = thread_log_last_error("GetThreadGroupAffinity()"); + goto cleanup; + } + + CPU_ZERO(cpuset); + + /* Convert affinity to DPDK cpu set */ + for (cpu_idx = 0; cpu_idx < CPU_SETSIZE; cpu_idx++) { + + cpu_affinity = eal_get_cpu_affinity(cpu_idx); + + if ((cpu_affinity->Group == thread_affinity.Group) && + ((cpu_affinity->Mask & thread_affinity.Mask) != 0)) { + CPU_SET(cpu_idx, cpuset); + } + } + +cleanup: + if (thread_handle != NULL) { + CloseHandle(thread_handle); + thread_handle = NULL; + } + return ret; +} + int rte_thread_attr_init(rte_thread_attr_t *attr) { From patchwork Thu Nov 11 01:33:42 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Narcisa Ana Maria Vasile X-Patchwork-Id: 104143 X-Patchwork-Delegate: david.marchand@redhat.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 1CB5BA0C4B; Thu, 11 Nov 2021 02:35:33 +0100 (CET) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id D746E41169; Thu, 11 Nov 2021 02:35:02 +0100 (CET) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by mails.dpdk.org (Postfix) with ESMTP id 4805F410F7 for ; Thu, 11 Nov 2021 02:34:54 +0100 (CET) Received: by linux.microsoft.com (Postfix, from userid 1059) id CA9BB20C3583; Wed, 10 Nov 2021 17:34:52 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com CA9BB20C3583 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1636594492; bh=XNcxcpW82wsjqfdq11hwRNHJEgairwx+bA4euoiEK+4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UUdzpFoNO8bKvIGh7TmV5LBW6L7PMKQNTJFiG0Zc3T5F7QcLomjLzUtKq/9ieccnF 9U4JIkWPJVbdERBi6ZXzraUqBMzTny7Aul1E5XID7B+vHjqNgv1F1Pv6KBIA06I3s8 m3BH6vMu7A674lr5Ch2RLvzmBAi3vIWbt/O978RI= From: Narcisa Ana Maria Vasile To: dev@dpdk.org, thomas@monjalon.net, dmitry.kozliuk@gmail.com, khot@microsoft.com, navasile@microsoft.com, dmitrym@microsoft.com, roretzla@microsoft.com, talshn@nvidia.com, ocardona@microsoft.com Cc: bruce.richardson@intel.com, david.marchand@redhat.com, pallavi.kadam@intel.com Subject: [PATCH v18 5/8] eal: implement thread priority management functions Date: Wed, 10 Nov 2021 17:33:42 -0800 Message-Id: <1636594425-9692-6-git-send-email-navasile@linux.microsoft.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> References: <1636513302-7359-1-git-send-email-navasile@linux.microsoft.com> <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Narcisa Vasile Add functions for setting and getting the priority of a thread. Priorities on multiple platforms are similarly determined by a priority value and a priority class/policy. Currently in DPDK most threads operate at the OS-default priority level but there are cases when increasing the priority is useful. For example, high performance applications may require elevated priority levels. For these reasons, EAL will expose two priority levels which are named suggestively "normal" and "realtime_critical" and are computed as follows: On Linux, the following mapping is created: RTE_THREAD_PRIORITY_NORMAL corresponds to * policy SCHED_OTHER * priority value: (sched_get_priority_min(SCHED_OTHER) + sched_get_priority_max(SCHED_OTHER))/2; RTE_THREAD_PRIORITY_REALTIME_CRITICAL corresponds to * policy SCHED_RR * priority value: sched_get_priority_max(SCHED_RR); On Windows, the following mapping is created: RTE_THREAD_PRIORITY_NORMAL corresponds to * class NORMAL_PRIORITY_CLASS * priority THREAD_PRIORITY_NORMAL RTE_THREAD_PRIORITY_REALTIME_CRITICAL corresponds to * class REALTIME_PRIORITY_CLASS * priority THREAD_PRIORITY_TIME_CRITICAL Note that on Linux the resulting priority value will be 0, in accordance to the docs that mention the value should be 0 for SCHED_OTHER policy. Signed-off-by: Narcisa Vasile --- lib/eal/common/rte_thread.c | 103 ++++++++++++++++++++++++++++ lib/eal/include/rte_thread.h | 34 ++++++++++ lib/eal/version.map | 2 + lib/eal/windows/rte_thread.c | 127 +++++++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+) diff --git a/lib/eal/common/rte_thread.c b/lib/eal/common/rte_thread.c index 73b7b3141c..fc5d7c5b1a 100644 --- a/lib/eal/common/rte_thread.c +++ b/lib/eal/common/rte_thread.c @@ -50,6 +50,109 @@ rte_thread_get_affinity_by_id(rte_thread_t thread_id, sizeof(*cpuset), cpuset); } +static int +thread_map_priority_to_os_value(enum rte_thread_priority eal_pri, + int *os_pri, int *pol) +{ + /* Clear the output parameters */ + *os_pri = sched_get_priority_min(SCHED_OTHER) - 1; + *pol = -1; + + switch (eal_pri) { + case RTE_THREAD_PRIORITY_NORMAL: + *pol = SCHED_OTHER; + + /* + * Choose the middle of the range to represent + * the priority 'normal'. + * On Linux, this should be 0, since both + * sched_get_priority_min/_max return 0 for SCHED_OTHER. + */ + *os_pri = (sched_get_priority_min(SCHED_OTHER) + + sched_get_priority_max(SCHED_OTHER))/2; + break; + case RTE_THREAD_PRIORITY_REALTIME_CRITICAL: + *pol = SCHED_RR; + *os_pri = sched_get_priority_max(SCHED_RR); + break; + default: + RTE_LOG(DEBUG, EAL, "The requested priority value is invalid.\n"); + return EINVAL; + } + return 0; +} + +static int +thread_map_os_priority_to_eal_priority(int policy, int os_pri, + enum rte_thread_priority *eal_pri) +{ + switch (policy) { + case SCHED_OTHER: + if (os_pri == (sched_get_priority_min(SCHED_OTHER) + + sched_get_priority_max(SCHED_OTHER))/2) { + *eal_pri = RTE_THREAD_PRIORITY_NORMAL; + return 0; + } + break; + case SCHED_RR: + if (os_pri == sched_get_priority_max(SCHED_RR)) { + *eal_pri = RTE_THREAD_PRIORITY_REALTIME_CRITICAL; + return 0; + } + break; + default: + RTE_LOG(DEBUG, EAL, "The OS priority value does not map to an EAL-defined priority.\n"); + return EINVAL; + } + + return 0; +} + +int +rte_thread_get_priority(rte_thread_t thread_id, + enum rte_thread_priority *priority) +{ + int ret; + int policy; + struct sched_param param; + + ret = pthread_getschedparam((pthread_t)thread_id.opaque_id, &policy, + ¶m); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "pthread_getschedparam failed\n"); + goto cleanup; + } + + return thread_map_os_priority_to_eal_priority(policy, + param.sched_priority, priority); + +cleanup: + return ret; +} + +int +rte_thread_set_priority(rte_thread_t thread_id, + enum rte_thread_priority priority) +{ + int ret; + int policy; + struct sched_param param; + +/* Realtime priority can cause crashes on non-Windows platforms. */ +#ifndef RTE_EXEC_ENV_WINDOWS + if (priority == RTE_THREAD_PRIORITY_REALTIME_CRITICAL) + return ENOTSUP; +#endif + + ret = thread_map_priority_to_os_value(priority, ¶m.sched_priority, + &policy); + if (ret != 0) + return ret; + + return pthread_setschedparam((pthread_t)thread_id.opaque_id, + policy, ¶m); +} + int rte_thread_attr_init(rte_thread_attr_t *attr) { diff --git a/lib/eal/include/rte_thread.h b/lib/eal/include/rte_thread.h index 5b100cafda..7077c9ce46 100644 --- a/lib/eal/include/rte_thread.h +++ b/lib/eal/include/rte_thread.h @@ -213,6 +213,40 @@ void rte_thread_get_affinity(rte_cpuset_t *cpusetp); #endif /* RTE_HAS_CPUSET */ +/** + * Get the priority of a thread. + * + * @param thread_id + * Id of the thread for which to get priority. + * + * @param priority + * Location to store the retrieved priority. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_get_priority(rte_thread_t thread_id, + enum rte_thread_priority *priority); + +/** + * Set the priority of a thread. + * + * @param thread_id + * Id of the thread for which to set priority. + * + * @param priority + * Priority value to be set. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_set_priority(rte_thread_t thread_id, + enum rte_thread_priority priority); + /** * Create a TLS data key visible to all threads in the process. * the created key is later used to get/set a value. diff --git a/lib/eal/version.map b/lib/eal/version.map index 193a79a4d2..5bc5a6cf76 100644 --- a/lib/eal/version.map +++ b/lib/eal/version.map @@ -429,6 +429,8 @@ EXPERIMENTAL { rte_thread_attr_set_priority; rte_thread_get_affinity_by_id; rte_thread_set_affinity_by_id; + rte_thread_get_priority; + rte_thread_set_priority; }; INTERNAL { diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c index 0127119f49..5c02a6eaff 100644 --- a/lib/eal/windows/rte_thread.c +++ b/lib/eal/windows/rte_thread.c @@ -200,6 +200,133 @@ rte_thread_get_affinity_by_id(rte_thread_t thread_id, return ret; } +static int +thread_map_priority_to_os_value(enum rte_thread_priority eal_pri, + int *os_pri, int *pri_class) +{ + /* Clear the output parameters */ + *os_pri = -1; + *pri_class = -1; + + switch (eal_pri) { + case RTE_THREAD_PRIORITY_NORMAL: + *pri_class = NORMAL_PRIORITY_CLASS; + *os_pri = THREAD_PRIORITY_NORMAL; + break; + case RTE_THREAD_PRIORITY_REALTIME_CRITICAL: + *pri_class = REALTIME_PRIORITY_CLASS; + *os_pri = THREAD_PRIORITY_TIME_CRITICAL; + break; + default: + RTE_LOG(DEBUG, EAL, "The requested priority value is invalid.\n"); + return EINVAL; + } + + return 0; +} + +static int +thread_map_os_priority_to_eal_value(int os_pri, int pri_class, + enum rte_thread_priority *eal_pri) +{ + switch (pri_class) { + case NORMAL_PRIORITY_CLASS: + if (os_pri == THREAD_PRIORITY_NORMAL) { + *eal_pri = RTE_THREAD_PRIORITY_NORMAL; + return 0; + } + break; + case REALTIME_PRIORITY_CLASS: + if (os_pri == THREAD_PRIORITY_TIME_CRITICAL) { + *eal_pri = RTE_THREAD_PRIORITY_REALTIME_CRITICAL; + return 0; + } + break; + default: + RTE_LOG(DEBUG, EAL, "The OS priority value does not map to an EAL-defined priority.\n"); + return EINVAL; + } + return 0; +} + +int +rte_thread_get_priority(rte_thread_t thread_id, + enum rte_thread_priority *priority) +{ + HANDLE thread_handle = NULL; + DWORD pri_class; + int os_pri; + int ret; + + pri_class = GetPriorityClass(GetCurrentProcess()); + if (pri_class == 0) { + ret = thread_log_last_error("GetPriorityClass()"); + goto cleanup; + } + + thread_handle = OpenThread(THREAD_SET_INFORMATION | + THREAD_QUERY_INFORMATION, FALSE, thread_id.opaque_id); + if (thread_handle == NULL) { + ret = thread_log_last_error("OpenThread()"); + goto cleanup; + } + + os_pri = GetThreadPriority(thread_handle); + if (os_pri == THREAD_PRIORITY_ERROR_RETURN) { + ret = thread_log_last_error("GetThreadPriority()"); + goto cleanup; + } + + ret = thread_map_os_priority_to_eal_value(os_pri, pri_class, priority); + if (ret != 0) + goto cleanup; + +cleanup: + if (thread_handle != NULL) + CloseHandle(thread_handle); + + return ret; +} + +int +rte_thread_set_priority(rte_thread_t thread_id, + enum rte_thread_priority priority) +{ + HANDLE thread_handle; + int priority_class; + int os_priority; + int ret = 0; + + thread_handle = OpenThread(THREAD_SET_INFORMATION | + THREAD_QUERY_INFORMATION, FALSE, + thread_id.opaque_id); + if (thread_handle == NULL) { + ret = thread_log_last_error("OpenThread()"); + goto cleanup; + } + + ret = thread_map_priority_to_os_value(priority, &os_priority, + &priority_class); + if (ret != 0) + goto cleanup; + + if (!SetPriorityClass(GetCurrentProcess(), priority_class)) { + ret = thread_log_last_error("SetPriorityClass()"); + goto cleanup; + } + + if (!SetThreadPriority(thread_handle, os_priority)) { + ret = thread_log_last_error("SetThreadPriority()"); + goto cleanup; + } + +cleanup: + if (thread_handle != NULL) + CloseHandle(thread_handle); + + return ret; +} + int rte_thread_attr_init(rte_thread_attr_t *attr) { From patchwork Thu Nov 11 01:33:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Narcisa Ana Maria Vasile X-Patchwork-Id: 104142 X-Patchwork-Delegate: david.marchand@redhat.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 54773A0C4B; Thu, 11 Nov 2021 02:35:27 +0100 (CET) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id DDC964115E; Thu, 11 Nov 2021 02:35:01 +0100 (CET) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by mails.dpdk.org (Postfix) with ESMTP id 3DFC340E28 for ; Thu, 11 Nov 2021 02:34:54 +0100 (CET) Received: by linux.microsoft.com (Postfix, from userid 1059) id D617620C3586; Wed, 10 Nov 2021 17:34:52 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com D617620C3586 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1636594492; bh=lHepfH9qe/0dZVIEViVQ9bfKKTNk/mzBb7YPka+RmRc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nQ487PJKOhPG2GloIjiomx30QSqac3/MQccykGCO6nz6oXrXS9mj4AT5YIpL19+Xx 6gT2mAkWzFSeWh1Q39ZsuGoDCIq4u4UXy8tilzHXXuq3qLIcQq77aP44q+wK+PmvNA rZ3wPxhU2QAfLTxps0Pe6EFfS8csKdTqj7lidoEU= From: Narcisa Ana Maria Vasile To: dev@dpdk.org, thomas@monjalon.net, dmitry.kozliuk@gmail.com, khot@microsoft.com, navasile@microsoft.com, dmitrym@microsoft.com, roretzla@microsoft.com, talshn@nvidia.com, ocardona@microsoft.com Cc: bruce.richardson@intel.com, david.marchand@redhat.com, pallavi.kadam@intel.com Subject: [PATCH v18 6/8] eal: add thread lifetime management Date: Wed, 10 Nov 2021 17:33:43 -0800 Message-Id: <1636594425-9692-7-git-send-email-navasile@linux.microsoft.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> References: <1636513302-7359-1-git-send-email-navasile@linux.microsoft.com> <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Narcisa Vasile Add functions for thread creation, joining, detaching. The *rte_thread_create()* function can optionally receive an rte_thread_attr_t object that will cause the thread to be created with the affinity and priority described by the attributes object. If no rte_thread_attr_t is passed (parameter is NULL), the default affinity and priority are used. On Windows, the function executed by a thread when the thread starts is represeneted by a function pointer of type DWORD (*func) (void*). On other platforms, the function pointer is a void* (*func) (void*). Performing a cast between these two types of function pointers to uniformize the API on all platforms may result in undefined behavior. TO fix this issue, a wrapper that respects the signature required by CreateThread() has been created on Windows. Add unit tests: - verify rte_thread_self() correctly retrieves the thread id. - verify that affinity and priority can be set successfully. - verify that threads are created and cleaned up correctly. Signed-off-by: Narcisa Vasile --- app/test/meson.build | 2 + app/test/test_threads.c | 217 ++++++++++++++++++++++++++++++++ lib/eal/common/rte_thread.c | 111 ++++++++++++++++ lib/eal/include/rte_thread.h | 55 ++++++++ lib/eal/version.map | 3 + lib/eal/windows/include/sched.h | 2 +- lib/eal/windows/rte_thread.c | 134 ++++++++++++++++++++ 7 files changed, 523 insertions(+), 1 deletion(-) create mode 100644 app/test/test_threads.c diff --git a/app/test/meson.build b/app/test/meson.build index 96670c3504..9fd34459e9 100644 --- a/app/test/meson.build +++ b/app/test/meson.build @@ -146,6 +146,7 @@ test_sources = files( 'test_tailq.c', 'test_thash.c', 'test_thash_perf.c', + 'test_threads.c', 'test_timer.c', 'test_timer_perf.c', 'test_timer_racecond.c', @@ -287,6 +288,7 @@ fast_tests = [ ['reorder_autotest', true], ['service_autotest', true], ['thash_autotest', true], + ['threads_autotest', true], ['trace_autotest', true], ] diff --git a/app/test/test_threads.c b/app/test/test_threads.c new file mode 100644 index 0000000000..9fcae34179 --- /dev/null +++ b/app/test/test_threads.c @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) 2021 Microsoft. + */ + +#include +#include + +#include "test.h" + +#define THREADS_COUNT 20 + +RTE_LOG_REGISTER(threads_logtype_test, test.threads, INFO); + +static void * +thread_loop_self(void *arg) +{ + rte_thread_t *id = arg; + + *id = rte_thread_self(); + + return NULL; +} + +static int +test_thread_self(void) +{ + rte_thread_t threads_ids[THREADS_COUNT]; + rte_thread_t self_ids[THREADS_COUNT] = {}; + int ret; + int i; + + for (i = 0; i < THREADS_COUNT; ++i) { + ret = rte_thread_create(&threads_ids[i], NULL, thread_loop_self, + &self_ids[i]); + RTE_TEST_ASSERT(ret == 0, "Failed to create threads!"); + } + + for (i = 0; i < THREADS_COUNT; ++i) { + RTE_TEST_ASSERT(rte_thread_join(threads_ids[i], NULL) == 0, "Failed to join thread!"); + RTE_TEST_ASSERT_EQUAL(threads_ids[i].opaque_id, + self_ids[i].opaque_id, "Unexpected thread id!"); + } + + return 0; +} + +struct thread_affinity_ctx { + rte_cpuset_t *cpuset; + unsigned int result; +}; + +static void * +thread_loop_attributes_affinity(void *arg) +{ + struct thread_affinity_ctx *ctx = arg; + rte_cpuset_t cpuset; + size_t i; + + ctx->result = 0; + + CPU_ZERO(&cpuset); + if (rte_thread_get_affinity_by_id(rte_thread_self(), &cpuset) != 0) { + ctx->result = 1; + rte_log(RTE_LOG_DEBUG, threads_logtype_test, "Failed to get thread affinity!"); + return NULL; + } + + /* + * Check that the thread is not running on CPUs which were not + * specified in the affinity mask. Note that the CPU mask + * retrieved above can be different than the original mask specified + * with rte_thread_attr_set_affinity(), since some CPUs may not be + * available on the system. + */ + for (i = 0; i < CPU_SETSIZE; ++i) { + if (!CPU_ISSET(i, ctx->cpuset) && CPU_ISSET(i, &cpuset)) { + ctx->result = 1; + rte_log(RTE_LOG_DEBUG, threads_logtype_test, "CPU %zu should not be set for this thread!\n", + i); + return NULL; + } + } + + return NULL; +} + +static int +test_thread_attributes_affinity(void) +{ + rte_thread_t threads_ids[THREADS_COUNT]; + struct thread_affinity_ctx ctx[THREADS_COUNT] = {}; + rte_thread_attr_t attr; + rte_cpuset_t cpuset; + size_t i; + int ret = 0; + + ret = rte_thread_attr_init(&attr); + RTE_TEST_ASSERT(ret == 0, "Failed to initialize thread attributes!"); + + CPU_ZERO(&cpuset); + ret = rte_thread_get_affinity_by_id(rte_thread_self(), &cpuset); + RTE_TEST_ASSERT(ret == 0, "Failed to get main thread affinity!"); + + ret = rte_thread_attr_set_affinity(&attr, &cpuset); + RTE_TEST_ASSERT(ret == 0, "Failed to set thread attributes!"); + + for (i = 0; i < THREADS_COUNT; ++i) { + ctx[i].cpuset = &cpuset; + ret = rte_thread_create(&threads_ids[i], &attr, + thread_loop_attributes_affinity, &ctx[i]); + RTE_TEST_ASSERT(ret == 0, "Failed to create threads!"); + } + + for (i = 0; i < THREADS_COUNT; ++i) { + ret = rte_thread_join(threads_ids[i], NULL); + RTE_TEST_ASSERT(ret == 0, "Failed to join threads!"); + + RTE_TEST_ASSERT_EQUAL(ctx[i].result, 0, "Unexpected thread affinity!"); + } + + return ret; +} + +static void * +thread_loop_priority(void *arg) +{ + int ret; + enum rte_thread_priority priority; + int *result = arg; + + *result = 1; + ret = rte_thread_get_priority(rte_thread_self(), &priority); + if (ret != 0 || priority != RTE_THREAD_PRIORITY_NORMAL) + *result = 2; + + return NULL; +} + +static int +test_thread_attributes_priority(void) +{ + rte_thread_t threads_ids[THREADS_COUNT]; + rte_thread_attr_t attr; + size_t i; + int ret = 0; + int results[THREADS_COUNT] = {}; + + ret = rte_thread_attr_init(&attr); + RTE_TEST_ASSERT(ret == 0, "Failed to initialize thread attributes!"); + + ret = rte_thread_attr_set_priority(&attr, RTE_THREAD_PRIORITY_NORMAL); + RTE_TEST_ASSERT(ret == 0, "Failed to set thread priority!"); + + for (i = 0; i < THREADS_COUNT; ++i) { + ret = rte_thread_create(&threads_ids[i], &attr, + thread_loop_priority, &results[i]); + RTE_TEST_ASSERT(ret == 0, "Failed to create threads!"); + } + + for (i = 0; i < THREADS_COUNT; ++i) { + ret = rte_thread_join(threads_ids[i], NULL); + RTE_TEST_ASSERT(ret == 0, "Failed to join threads!"); + + RTE_TEST_ASSERT_EQUAL(results[i], 1, "Unexpected priority value!"); + } + + return ret; +} + +static void * +thread_loop_return(void *arg) +{ + RTE_SET_USED(arg); + return NULL; +} + +static int +test_thread_detach(void) +{ + rte_thread_t threads_ids[THREADS_COUNT]; + size_t i; + int ret = 0; + + for (i = 0; i < THREADS_COUNT; ++i) { + ret = rte_thread_create(&threads_ids[i], NULL, + thread_loop_return, NULL); + RTE_TEST_ASSERT(ret == 0, "Failed to create threads!"); + } + + for (i = 0; i < THREADS_COUNT; ++i) { + ret = rte_thread_detach(threads_ids[i]); + RTE_TEST_ASSERT(ret == 0, "Failed to detach thread!"); + } + + return ret; +} + +static struct unit_test_suite threads_test_suite = { + .suite_name = "threads autotest", + .setup = NULL, + .teardown = NULL, + .unit_test_cases = { + TEST_CASE(test_thread_self), + TEST_CASE(test_thread_attributes_affinity), + TEST_CASE(test_thread_attributes_priority), + TEST_CASE(test_thread_detach), + TEST_CASES_END() + } +}; + +static int +test_threads(void) +{ + return unit_test_suite_runner(&threads_test_suite); +} + +REGISTER_TEST_COMMAND(threads_autotest, test_threads); diff --git a/lib/eal/common/rte_thread.c b/lib/eal/common/rte_thread.c index fc5d7c5b1a..910c39eb88 100644 --- a/lib/eal/common/rte_thread.c +++ b/lib/eal/common/rte_thread.c @@ -198,6 +198,117 @@ rte_thread_attr_set_priority(rte_thread_attr_t *thread_attr, return 0; } +int +rte_thread_create(rte_thread_t *thread_id, + const rte_thread_attr_t *thread_attr, + rte_thread_func thread_func, void *args) +{ + int ret = 0; + pthread_attr_t attr; + pthread_attr_t *attrp = NULL; + struct sched_param param = { + .sched_priority = 0, + }; + int policy = SCHED_OTHER; + + if (thread_attr != NULL) { + ret = pthread_attr_init(&attr); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "pthread_attr_init failed\n"); + goto cleanup; + } + + attrp = &attr; + + /* + * Set the inherit scheduler parameter to explicit, + * otherwise the priority attribute is ignored. + */ + ret = pthread_attr_setinheritsched(attrp, + PTHREAD_EXPLICIT_SCHED); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "pthread_attr_setinheritsched failed\n"); + goto cleanup; + } + + +/* Realtime priority can cause crashes on non-Windows platforms. */ +#ifndef RTE_EXEC_ENV_WINDOWS + if (thread_attr->priority == + RTE_THREAD_PRIORITY_REALTIME_CRITICAL) { + ret = ENOTSUP; + goto cleanup; + } +#endif + ret = thread_map_priority_to_os_value(thread_attr->priority, + ¶m.sched_priority, &policy); + if (ret != 0) + goto cleanup; + + ret = pthread_attr_setschedpolicy(attrp, policy); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "pthread_attr_setschedpolicy failed\n"); + goto cleanup; + } + + ret = pthread_attr_setschedparam(attrp, ¶m); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "pthread_attr_setschedparam failed\n"); + goto cleanup; + } + } + + ret = pthread_create((pthread_t *)&thread_id->opaque_id, attrp, + thread_func, args); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "pthread_create failed\n"); + goto cleanup; + } + + if (thread_attr != NULL && CPU_COUNT(&thread_attr->cpuset) > 0) { + ret = rte_thread_set_affinity_by_id(*thread_id, + &thread_attr->cpuset); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "rte_thread_set_affinity_by_id failed\n"); + goto cleanup; + } + } + +cleanup: + if (attrp != NULL) + pthread_attr_destroy(&attr); + + return ret; +} + +int +rte_thread_join(rte_thread_t thread_id, unsigned long *value_ptr) +{ + int ret = 0; + void *res = NULL; + void **pres = NULL; + + if (value_ptr != NULL) + pres = &res; + + ret = pthread_join((pthread_t)thread_id.opaque_id, pres); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "pthread_join failed\n"); + return ret; + } + + if (value_ptr != NULL && *pres != NULL) + *value_ptr = *(unsigned long *)(*pres); + + return 0; +} + +int +rte_thread_detach(rte_thread_t thread_id) +{ + return pthread_detach((pthread_t)thread_id.opaque_id); +} + int rte_thread_key_create(rte_thread_key *key, void (*destructor)(void *)) { diff --git a/lib/eal/include/rte_thread.h b/lib/eal/include/rte_thread.h index 7077c9ce46..e841321819 100644 --- a/lib/eal/include/rte_thread.h +++ b/lib/eal/include/rte_thread.h @@ -31,6 +31,7 @@ typedef struct rte_thread_tag { uintptr_t opaque_id; /**< thread identifier */ } rte_thread_t; +typedef void* (*rte_thread_func) (void *); /** * Thread priority values. */ @@ -211,6 +212,60 @@ int rte_thread_set_affinity(rte_cpuset_t *cpusetp); */ void rte_thread_get_affinity(rte_cpuset_t *cpusetp); +/** + * Create a new thread that will invoke the 'thread_func' routine. + * + * @param thread_id + * A pointer that will store the id of the newly created thread. + * + * @param thread_attr + * Attributes that are used at the creation of the new thread. + * + * @param thread_func + * The routine that the new thread will invoke when starting execution. + * + * @param args + * Arguments to be passed to the 'thread_func' routine. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_create(rte_thread_t *thread_id, + const rte_thread_attr_t *thread_attr, + rte_thread_func thread_func, void *args); + +/** + * Waits for the thread identified by 'thread_id' to terminate + * + * @param thread_id + * The identifier of the thread. + * + * @param value_ptr + * Stores the exit status of the thread. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_join(rte_thread_t thread_id, unsigned long *value_ptr); + +/** + * Indicate that the return value of the thread is not needed and + * all thread resources should be release when the thread terminates. + * + * @param thread_id + * The id of the thread to be detached. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_detach(rte_thread_t thread_id); + #endif /* RTE_HAS_CPUSET */ /** diff --git a/lib/eal/version.map b/lib/eal/version.map index 5bc5a6cf76..0384a09fa2 100644 --- a/lib/eal/version.map +++ b/lib/eal/version.map @@ -431,6 +431,9 @@ EXPERIMENTAL { rte_thread_set_affinity_by_id; rte_thread_get_priority; rte_thread_set_priority; + rte_thread_create; + rte_thread_join; + rte_thread_detach; }; INTERNAL { diff --git a/lib/eal/windows/include/sched.h b/lib/eal/windows/include/sched.h index bc31cc8465..912fed12c2 100644 --- a/lib/eal/windows/include/sched.h +++ b/lib/eal/windows/include/sched.h @@ -44,7 +44,7 @@ typedef struct _rte_cpuset_s { (1LL << _WHICH_BIT(b))) != 0LL) static inline int -count_cpu(rte_cpuset_t *s) +count_cpu(const rte_cpuset_t *s) { unsigned int _i; int count = 0; diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c index 5c02a6eaff..669a68d6a8 100644 --- a/lib/eal/windows/rte_thread.c +++ b/lib/eal/windows/rte_thread.c @@ -14,6 +14,11 @@ struct eal_tls_key { DWORD thread_index; }; +struct thread_routine_ctx { + rte_thread_func thread_func; + void *routine_args; +}; + /* Translates the most common error codes related to threads */ static int thread_translate_win32_error(DWORD error) @@ -370,6 +375,135 @@ rte_thread_attr_set_priority(rte_thread_attr_t *thread_attr, return 0; } +static DWORD +thread_func_wrapper(void *args) +{ + struct thread_routine_ctx *pctx = args; + struct thread_routine_ctx ctx; + + ctx.thread_func = pctx->thread_func; + ctx.routine_args = pctx->routine_args; + + free(pctx); + + return (DWORD)(uintptr_t)ctx.thread_func(ctx.routine_args); +} + +int +rte_thread_create(rte_thread_t *thread_id, + const rte_thread_attr_t *thread_attr, + rte_thread_func thread_func, void *args) +{ + int ret = 0; + DWORD tid; + HANDLE thread_handle = NULL; + GROUP_AFFINITY thread_affinity; + struct thread_routine_ctx *ctx = NULL; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + RTE_LOG(DEBUG, EAL, "Insufficient memory for thread context allocations\n"); + ret = ENOMEM; + goto cleanup; + } + ctx->routine_args = args; + ctx->thread_func = thread_func; + + thread_handle = CreateThread(NULL, 0, thread_func_wrapper, ctx, + CREATE_SUSPENDED, &tid); + if (thread_handle == NULL) { + ret = thread_log_last_error("CreateThread()"); + free(ctx); + goto cleanup; + } + thread_id->opaque_id = tid; + + if (thread_attr != NULL) { + if (CPU_COUNT(&thread_attr->cpuset) > 0) { + ret = rte_convert_cpuset_to_affinity( + &thread_attr->cpuset, + &thread_affinity + ); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "Unable to convert cpuset to thread affinity\n"); + goto cleanup; + } + + if (!SetThreadGroupAffinity(thread_handle, + &thread_affinity, NULL)) { + ret = thread_log_last_error("SetThreadGroupAffinity()"); + goto cleanup; + } + } + ret = rte_thread_set_priority(*thread_id, + thread_attr->priority); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "Unable to set thread priority\n"); + goto cleanup; + } + } + + if (ResumeThread(thread_handle) == (DWORD)-1) { + ret = thread_log_last_error("ResumeThread()"); + goto cleanup; + } + +cleanup: + if (thread_handle != NULL) { + CloseHandle(thread_handle); + thread_handle = NULL; + } + return ret; +} + +int +rte_thread_join(rte_thread_t thread_id, unsigned long *value_ptr) +{ + HANDLE thread_handle; + DWORD result; + DWORD exit_code = 0; + BOOL err; + int ret = 0; + + thread_handle = OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION, + FALSE, thread_id.opaque_id); + if (thread_handle == NULL) { + ret = thread_log_last_error("OpenThread()"); + goto cleanup; + } + + result = WaitForSingleObject(thread_handle, INFINITE); + if (result != WAIT_OBJECT_0) { + ret = thread_log_last_error("WaitForSingleObject()"); + goto cleanup; + } + + if (value_ptr != NULL) { + err = GetExitCodeThread(thread_handle, &exit_code); + if (err == 0) { + ret = thread_log_last_error("GetExitCodeThread()"); + goto cleanup; + } + *value_ptr = exit_code; + } + +cleanup: + if (thread_handle != NULL) { + CloseHandle(thread_handle); + thread_handle = NULL; + } + + return ret; +} + +int +rte_thread_detach(rte_thread_t thread_id) +{ + /* No resources that need to be released. */ + RTE_SET_USED(thread_id); + return 0; +} + int rte_thread_key_create(rte_thread_key *key, __rte_unused void (*destructor)(void *)) From patchwork Thu Nov 11 01:33:44 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Narcisa Ana Maria Vasile X-Patchwork-Id: 104144 X-Patchwork-Delegate: david.marchand@redhat.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 125D4A0C4B; Thu, 11 Nov 2021 02:35:38 +0100 (CET) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id BC7F841176; Thu, 11 Nov 2021 02:35:03 +0100 (CET) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by mails.dpdk.org (Postfix) with ESMTP id 3853E40E03 for ; Thu, 11 Nov 2021 02:34:54 +0100 (CET) Received: by linux.microsoft.com (Postfix, from userid 1059) id E1A3620C358A; Wed, 10 Nov 2021 17:34:52 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com E1A3620C358A DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1636594492; bh=OV5OTvfz5TC3XDe3okd6ei0TJfKRWsYa2ZMU9T1OxA8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=E/rD3LxHKk2zodxcvQxdYmd5bhqoUpGSTJZ8NkZWWcBSTCIsVUGoJRQ2PPVlIOQil 0EfCKdiKbD/uG5xJEOAhaL/zNsoj/a0GOUcuQH84Zv19ypB+3Dog/isqandB+kJlfj 8//OxKxT8Fmp1IiQUovMaf4OOt3sB4KIGrUGjTts= From: Narcisa Ana Maria Vasile To: dev@dpdk.org, thomas@monjalon.net, dmitry.kozliuk@gmail.com, khot@microsoft.com, navasile@microsoft.com, dmitrym@microsoft.com, roretzla@microsoft.com, talshn@nvidia.com, ocardona@microsoft.com Cc: bruce.richardson@intel.com, david.marchand@redhat.com, pallavi.kadam@intel.com Subject: [PATCH v18 7/8] eal: implement functions for thread barrier management Date: Wed, 10 Nov 2021 17:33:44 -0800 Message-Id: <1636594425-9692-8-git-send-email-navasile@linux.microsoft.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> References: <1636513302-7359-1-git-send-email-navasile@linux.microsoft.com> <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Narcisa Vasile Add functions for barrier init, destroy, wait. A portable type is used to represent a barrier identifier. The rte_thread_barrier_wait() function returns the same value on all platforms. Add unit tests to verify that the barrier correctly synchronizes all threads. Verify that the threads are unblocked after the required number of threads have called barrier_wait(). Signed-off-by: Narcisa Vasile --- app/test/test_threads.c | 49 +++++++++++++++++++++++++++++ lib/eal/common/rte_thread.c | 61 ++++++++++++++++++++++++++++++++++++ lib/eal/include/rte_thread.h | 58 ++++++++++++++++++++++++++++++++++ lib/eal/version.map | 3 ++ lib/eal/windows/rte_thread.c | 56 +++++++++++++++++++++++++++++++++ 5 files changed, 227 insertions(+) diff --git a/app/test/test_threads.c b/app/test/test_threads.c index 9fcae34179..00f604ab7e 100644 --- a/app/test/test_threads.c +++ b/app/test/test_threads.c @@ -195,6 +195,54 @@ test_thread_detach(void) return ret; } +struct thread_context { + rte_thread_barrier *barrier; + int barrier_result; +}; + +static void * +thread_loop_barrier(void *arg) +{ + struct thread_context *ctx = arg; + + ctx->barrier_result = rte_thread_barrier_wait(ctx->barrier); + if (ctx->barrier_result > 0) + rte_log(RTE_LOG_DEBUG, threads_logtype_test, "Failed to wait at barrier!"); + + return NULL; +} + +static int +test_thread_barrier(void) +{ + rte_thread_t thread_id; + struct thread_context ctx; + rte_thread_barrier barrier; + int ret = 0; + int result = 0; + + ret = rte_thread_barrier_init(&barrier, 2); + RTE_TEST_ASSERT(ret == 0, "Failed to initialize barrier!"); + + ctx.barrier = &barrier; + ret = rte_thread_create(&thread_id, NULL, thread_loop_barrier, &ctx); + RTE_TEST_ASSERT(ret == 0, "Failed to create thread!"); + + result = rte_thread_barrier_wait(&barrier); + RTE_TEST_ASSERT(result <= 0, "Failed to wait at the barrier!"); + + ret = rte_thread_join(thread_id, NULL); + RTE_TEST_ASSERT(ret == 0, "Failed to join threads!"); + + ret = rte_thread_barrier_destroy(&barrier); + RTE_TEST_ASSERT(ret == 0, "Failed to destroy barrier!"); + + RTE_TEST_ASSERT(ctx.barrier_result <= 0, "Child thread failed to wait at the barrier!"); + RTE_TEST_ASSERT_NOT_EQUAL(ctx.barrier_result, result, "Threads were not blocked at the barrier!"); + + return 0; +} + static struct unit_test_suite threads_test_suite = { .suite_name = "threads autotest", .setup = NULL, @@ -204,6 +252,7 @@ static struct unit_test_suite threads_test_suite = { TEST_CASE(test_thread_attributes_affinity), TEST_CASE(test_thread_attributes_priority), TEST_CASE(test_thread_detach), + TEST_CASE(test_thread_barrier), TEST_CASES_END() } }; diff --git a/lib/eal/common/rte_thread.c b/lib/eal/common/rte_thread.c index 910c39eb88..d30a8a7ca3 100644 --- a/lib/eal/common/rte_thread.c +++ b/lib/eal/common/rte_thread.c @@ -309,6 +309,67 @@ rte_thread_detach(rte_thread_t thread_id) return pthread_detach((pthread_t)thread_id.opaque_id); } +int +rte_thread_barrier_init(rte_thread_barrier *barrier, int count) +{ + int ret = 0; + pthread_barrier_t *pthread_barrier = NULL; + + RTE_VERIFY(barrier != NULL); + RTE_VERIFY(count > 0); + + pthread_barrier = calloc(1, sizeof(*pthread_barrier)); + if (pthread_barrier == NULL) { + RTE_LOG(DEBUG, EAL, "Unable to initialize barrier. Insufficient memory!\n"); + ret = ENOMEM; + goto cleanup; + } + ret = pthread_barrier_init(pthread_barrier, NULL, count); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "Failed to init barrier, ret = %d\n", ret); + goto cleanup; + } + + barrier->barrier_id = pthread_barrier; + pthread_barrier = NULL; + +cleanup: + free(pthread_barrier); + return ret; +} + +int +rte_thread_barrier_wait(rte_thread_barrier *barrier) +{ + int ret = 0; + + RTE_VERIFY(barrier != NULL); + RTE_VERIFY(barrier->barrier_id != NULL); + + ret = pthread_barrier_wait(barrier->barrier_id); + if (ret == PTHREAD_BARRIER_SERIAL_THREAD) + ret = RTE_THREAD_BARRIER_SERIAL_THREAD; + + return ret; +} + +int +rte_thread_barrier_destroy(rte_thread_barrier *barrier) +{ + int ret = 0; + + RTE_VERIFY(barrier != NULL); + + ret = pthread_barrier_destroy(barrier->barrier_id); + if (ret != 0) + RTE_LOG(DEBUG, EAL, "Failed to destroy barrier: %d\n", ret); + + free(barrier->barrier_id); + barrier->barrier_id = NULL; + + return ret; +} + int rte_thread_key_create(rte_thread_key *key, void (*destructor)(void *)) { diff --git a/lib/eal/include/rte_thread.h b/lib/eal/include/rte_thread.h index e841321819..7c84e32988 100644 --- a/lib/eal/include/rte_thread.h +++ b/lib/eal/include/rte_thread.h @@ -54,6 +54,18 @@ typedef struct { #endif /* RTE_HAS_CPUSET */ +/** + * Returned by rte_thread_barrier_wait() when call is successful. + */ +#define RTE_THREAD_BARRIER_SERIAL_THREAD -1 + +/** + * Thread barrier representation. + */ +typedef struct rte_thread_barrier_tag { + void *barrier_id; /**< barrrier identifier */ +} rte_thread_barrier; + /** * TLS key type, an opaque pointer. */ @@ -302,6 +314,52 @@ __rte_experimental int rte_thread_set_priority(rte_thread_t thread_id, enum rte_thread_priority priority); +/** + * Initializes a synchronization barrier. + * + * @param barrier + * A pointer that references the newly created 'barrier' object. + * + * @param count + * The number of threads that must enter the barrier before + * the threads can continue execution. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_barrier_init(rte_thread_barrier *barrier, int count); + +/** + * Causes the calling thread to wait at the synchronization barrier 'barrier'. + * + * @param barrier + * The barrier used for synchronizing the threads. + * + * @return + * Return RTE_THREAD_BARRIER_SERIAL_THREAD for the thread synchronized + * at the barrier. + * Return 0 for all other threads. + * Return a positive errno-style error number, in case of failure. + */ +__rte_experimental +int rte_thread_barrier_wait(rte_thread_barrier *barrier); + +/** + * Releases all resources used by a synchronization barrier + * and uninitializes it. + * + * @param barrier + * The barrier to be destroyed. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_barrier_destroy(rte_thread_barrier *barrier); + /** * Create a TLS data key visible to all threads in the process. * the created key is later used to get/set a value. diff --git a/lib/eal/version.map b/lib/eal/version.map index 0384a09fa2..06e5f82da2 100644 --- a/lib/eal/version.map +++ b/lib/eal/version.map @@ -427,6 +427,9 @@ EXPERIMENTAL { rte_thread_attr_get_affinity; rte_thread_attr_set_affinity; rte_thread_attr_set_priority; + rte_thread_barrier_init; + rte_thread_barrier_wait; + rte_thread_barrier_destroy; rte_thread_get_affinity_by_id; rte_thread_set_affinity_by_id; rte_thread_get_priority; diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c index 669a68d6a8..3f72bbf716 100644 --- a/lib/eal/windows/rte_thread.c +++ b/lib/eal/windows/rte_thread.c @@ -504,6 +504,62 @@ rte_thread_detach(rte_thread_t thread_id) return 0; } +int +rte_thread_barrier_init(rte_thread_barrier *barrier, int count) +{ + int ret = 0; + SYNCHRONIZATION_BARRIER *sync_barrier = NULL; + + RTE_VERIFY(barrier != NULL); + RTE_VERIFY(count > 0); + + sync_barrier = calloc(1, sizeof(*sync_barrier)); + if (sync_barrier == NULL) { + RTE_LOG(DEBUG, EAL, "Unable to initialize barrier. Insufficient memory!\n"); + ret = ENOMEM; + goto cleanup; + } + if (!InitializeSynchronizationBarrier(sync_barrier, count, -1)) { + ret = thread_log_last_error("InitializeSynchronizationBarrier()"); + goto cleanup; + } + + barrier->barrier_id = sync_barrier; + sync_barrier = NULL; + +cleanup: + free(sync_barrier); + return ret; +} + +int +rte_thread_barrier_wait(rte_thread_barrier *barrier) +{ + RTE_VERIFY(barrier != NULL); + RTE_VERIFY(barrier->barrier_id != NULL); + + if (EnterSynchronizationBarrier(barrier->barrier_id, + SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY)) { + + return RTE_THREAD_BARRIER_SERIAL_THREAD; + } + + return 0; +} + +int +rte_thread_barrier_destroy(rte_thread_barrier *barrier) +{ + RTE_VERIFY(barrier != NULL); + + DeleteSynchronizationBarrier(barrier->barrier_id); + + free(barrier->barrier_id); + barrier->barrier_id = NULL; + + return 0; +} + int rte_thread_key_create(rte_thread_key *key, __rte_unused void (*destructor)(void *)) From patchwork Thu Nov 11 01:33:45 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Narcisa Ana Maria Vasile X-Patchwork-Id: 104141 X-Patchwork-Delegate: david.marchand@redhat.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 590D9A0C4B; Thu, 11 Nov 2021 02:35:21 +0100 (CET) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id C55C441158; Thu, 11 Nov 2021 02:35:00 +0100 (CET) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by mails.dpdk.org (Postfix) with ESMTP id 3149D40DF5 for ; Thu, 11 Nov 2021 02:34:54 +0100 (CET) Received: by linux.microsoft.com (Postfix, from userid 1059) id ED54220C358B; Wed, 10 Nov 2021 17:34:52 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com ED54220C358B DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1636594492; bh=arzBqOCEVzJfUYyLX/tPn4p8iFMgMhzQpz4cg0+/aeM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=iN/Ukq9f74e2G25t9jNpb6N+9Qyblq995TKw2xrAVkAGcIqzQLev2+jAS7exSV/V3 6ij0JOr2AWppnrP2hMEVNaFYvgGpNmewIuCCa1jnKG8NnlbluhT/7z+V0h07Xyb5Jy 8nBbEnS2X52ZZg7F/fKDeu8eqJazpzpvRugibSCQ= From: Narcisa Ana Maria Vasile To: dev@dpdk.org, thomas@monjalon.net, dmitry.kozliuk@gmail.com, khot@microsoft.com, navasile@microsoft.com, dmitrym@microsoft.com, roretzla@microsoft.com, talshn@nvidia.com, ocardona@microsoft.com Cc: bruce.richardson@intel.com, david.marchand@redhat.com, pallavi.kadam@intel.com Subject: [PATCH v18 8/8] eal: implement functions for mutex management Date: Wed, 10 Nov 2021 17:33:45 -0800 Message-Id: <1636594425-9692-9-git-send-email-navasile@linux.microsoft.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> References: <1636513302-7359-1-git-send-email-navasile@linux.microsoft.com> <1636594425-9692-1-git-send-email-navasile@linux.microsoft.com> X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Narcisa Vasile Add functions for mutex init, destroy, lock, unlock, trylock. Windows does not have a static initializer. Initialization is only done through InitializeCriticalSection(). To overcome this, RTE_INIT_MUTEX macro is added to replace static initialization of mutexes. The macro calls rte_thread_mutex_init(). Add unit tests to verify that the mutex correctly locks/unlocks and protects the data. Check both static and dynamic mutexes. Signed-off-by: Narcisa Vasile --- app/test/test_threads.c | 106 +++++++++++++++++++++++++++++++++++ lib/eal/common/rte_thread.c | 69 +++++++++++++++++++++++ lib/eal/include/rte_thread.h | 85 ++++++++++++++++++++++++++++ lib/eal/version.map | 5 ++ lib/eal/windows/rte_thread.c | 64 +++++++++++++++++++++ 5 files changed, 329 insertions(+) diff --git a/app/test/test_threads.c b/app/test/test_threads.c index 00f604ab7e..91155a04e3 100644 --- a/app/test/test_threads.c +++ b/app/test/test_threads.c @@ -243,6 +243,110 @@ test_thread_barrier(void) return 0; } +RTE_INIT_MUTEX(static_mutex); + +struct mutex_loop_args { + rte_thread_barrier *barrier; + rte_thread_mutex *mutex; + unsigned long result_A; + unsigned long result_B; +}; + +static void * +thread_loop_mutex_B(void *arg) +{ + struct mutex_loop_args *args = arg; + + if (rte_thread_mutex_try_lock(args->mutex) == 0) { + rte_thread_barrier_wait(args->barrier); + rte_thread_mutex_unlock(args->mutex); + args->result_B = 1; + } else { + rte_thread_barrier_wait(args->barrier); + args->result_B = 2; + } + + return NULL; +} + +static void * +thread_loop_mutex_A(void *arg) +{ + struct mutex_loop_args *args = arg; + + if (rte_thread_mutex_try_lock(args->mutex) != 0) { + rte_thread_barrier_wait(args->barrier); + args->result_A = 2; + } else { + rte_thread_barrier_wait(args->barrier); + rte_thread_mutex_unlock(args->mutex); + args->result_A = 1; + } + + return NULL; +} + +static int +test_thread_mutex(rte_thread_mutex *pmutex) +{ + rte_thread_t thread_A; + rte_thread_t thread_B; + rte_thread_mutex mutex; + rte_thread_barrier barrier; + struct mutex_loop_args args; + int ret = 0; + + /* If mutex is not statically initialized */ + if (pmutex == NULL) { + ret = rte_thread_mutex_init(&mutex); + RTE_TEST_ASSERT(ret == 0, "Failed to initialize mutex!"); + } else + mutex = *pmutex; + + ret = rte_thread_barrier_init(&barrier, 2); + RTE_TEST_ASSERT(ret == 0, "Failed to initialize barrier!"); + + args.mutex = &mutex; + args.barrier = &barrier; + + ret = rte_thread_create(&thread_A, NULL, thread_loop_mutex_A, &args); + RTE_TEST_ASSERT(ret == 0, "Failed to create thread!"); + + ret = rte_thread_create(&thread_B, NULL, thread_loop_mutex_B, &args); + RTE_TEST_ASSERT(ret == 0, "Failed to create thread!"); + + ret = rte_thread_join(thread_A, NULL); + RTE_TEST_ASSERT(ret == 0, "Failed to join thread!"); + + ret = rte_thread_join(thread_B, NULL); + RTE_TEST_ASSERT(ret == 0, "Failed to join thread!"); + + RTE_TEST_ASSERT(args.result_A != args.result_B, "Mutex failed to be acquired or was acquired by both threads!"); + + /* Destroy if dynamically initialized */ + if (pmutex == NULL) { + ret = rte_thread_mutex_destroy(&mutex); + RTE_TEST_ASSERT(ret == 0, "Failed to destroy mutex!"); + } + + ret = rte_thread_barrier_destroy(&barrier); + RTE_TEST_ASSERT(ret == 0, "Failed to destroy barrier!"); + + return ret; +} + +static int +test_thread_mutex_static(void) +{ + return test_thread_mutex(&static_mutex); +} + +static int +test_thread_mutex_dynamic(void) +{ + return test_thread_mutex(NULL); +} + static struct unit_test_suite threads_test_suite = { .suite_name = "threads autotest", .setup = NULL, @@ -253,6 +357,8 @@ static struct unit_test_suite threads_test_suite = { TEST_CASE(test_thread_attributes_priority), TEST_CASE(test_thread_detach), TEST_CASE(test_thread_barrier), + TEST_CASE(test_thread_mutex_static), + TEST_CASE(test_thread_mutex_dynamic), TEST_CASES_END() } }; diff --git a/lib/eal/common/rte_thread.c b/lib/eal/common/rte_thread.c index d30a8a7ca3..4a9a1b6e07 100644 --- a/lib/eal/common/rte_thread.c +++ b/lib/eal/common/rte_thread.c @@ -309,6 +309,75 @@ rte_thread_detach(rte_thread_t thread_id) return pthread_detach((pthread_t)thread_id.opaque_id); } +int +rte_thread_mutex_init(rte_thread_mutex *mutex) +{ + int ret = 0; + pthread_mutex_t *m = NULL; + + RTE_VERIFY(mutex != NULL); + + m = calloc(1, sizeof(*m)); + if (m == NULL) { + RTE_LOG(DEBUG, EAL, "Unable to initialize mutex. Insufficient memory!\n"); + ret = ENOMEM; + goto cleanup; + } + + ret = pthread_mutex_init(m, NULL); + if (ret != 0) { + RTE_LOG(DEBUG, EAL, "Failed to init mutex. ret = %d\n", ret); + goto cleanup; + } + + mutex->mutex_id = m; + m = NULL; + +cleanup: + free(m); + return ret; +} + +int +rte_thread_mutex_lock(rte_thread_mutex *mutex) +{ + RTE_VERIFY(mutex != NULL); + + return pthread_mutex_lock((pthread_mutex_t *)mutex->mutex_id); +} + +int +rte_thread_mutex_unlock(rte_thread_mutex *mutex) +{ + RTE_VERIFY(mutex != NULL); + + return pthread_mutex_unlock((pthread_mutex_t *)mutex->mutex_id); +} + +int +rte_thread_mutex_try_lock(rte_thread_mutex *mutex) +{ + RTE_VERIFY(mutex != NULL); + + return pthread_mutex_trylock((pthread_mutex_t *)mutex->mutex_id); +} + +int +rte_thread_mutex_destroy(rte_thread_mutex *mutex) +{ + int ret = 0; + RTE_VERIFY(mutex != NULL); + + ret = pthread_mutex_destroy((pthread_mutex_t *)mutex->mutex_id); + if (ret != 0) + RTE_LOG(DEBUG, EAL, "Unable to destroy mutex, ret = %d\n", ret); + + free(mutex->mutex_id); + mutex->mutex_id = NULL; + + return ret; +} + int rte_thread_barrier_init(rte_thread_barrier *barrier, int count) { diff --git a/lib/eal/include/rte_thread.h b/lib/eal/include/rte_thread.h index 7c84e32988..09a5fd8add 100644 --- a/lib/eal/include/rte_thread.h +++ b/lib/eal/include/rte_thread.h @@ -54,6 +54,25 @@ typedef struct { #endif /* RTE_HAS_CPUSET */ +#define RTE_DECLARE_MUTEX(private_lock) rte_thread_mutex private_lock + +#define RTE_DEFINE_MUTEX(private_lock)\ +RTE_INIT(__rte_ ## private_lock ## _init)\ +{\ + RTE_VERIFY(rte_thread_mutex_init(&private_lock) == 0);\ +} + +#define RTE_INIT_MUTEX(private_lock)\ +static RTE_DECLARE_MUTEX(private_lock);\ +RTE_DEFINE_MUTEX(private_lock) + +/** + * Thread mutex representation. + */ +typedef struct rte_thread_mutex_tag { + void *mutex_id; /**< mutex identifier */ +} rte_thread_mutex; + /** * Returned by rte_thread_barrier_wait() when call is successful. */ @@ -314,6 +333,72 @@ __rte_experimental int rte_thread_set_priority(rte_thread_t thread_id, enum rte_thread_priority priority); +/** + * Initializes a mutex. + * + * @param mutex + * The mutex to be initialized. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_mutex_init(rte_thread_mutex *mutex); + +/** + * Locks a mutex. + * + * @param mutex + * The mutex to be locked. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_mutex_lock(rte_thread_mutex *mutex); + +/** + * Unlocks a mutex. + * + * @param mutex + * The mutex to be unlocked. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_mutex_unlock(rte_thread_mutex *mutex); + +/** + * Tries to lock a mutex.If the mutex is already held by a different thread, + * the function returns without blocking. + * + * @param mutex + * The mutex that will be acquired, if not already locked. + * + * @return + * On success, if the mutex is acquired, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_mutex_try_lock(rte_thread_mutex *mutex); + +/** + * Releases all resources associated with a mutex. + * + * @param mutex + * The mutex to be uninitialized. + * + * @return + * On success, return 0. + * On failure, return a positive errno-style error number. + */ +__rte_experimental +int rte_thread_mutex_destroy(rte_thread_mutex *mutex); + /** * Initializes a synchronization barrier. * diff --git a/lib/eal/version.map b/lib/eal/version.map index 06e5f82da2..e80eea4316 100644 --- a/lib/eal/version.map +++ b/lib/eal/version.map @@ -431,6 +431,11 @@ EXPERIMENTAL { rte_thread_barrier_wait; rte_thread_barrier_destroy; rte_thread_get_affinity_by_id; + rte_thread_mutex_init; + rte_thread_mutex_lock; + rte_thread_mutex_unlock; + rte_thread_mutex_try_lock; + rte_thread_mutex_destroy; rte_thread_set_affinity_by_id; rte_thread_get_priority; rte_thread_set_priority; diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c index 3f72bbf716..11b4863fe8 100644 --- a/lib/eal/windows/rte_thread.c +++ b/lib/eal/windows/rte_thread.c @@ -504,6 +504,70 @@ rte_thread_detach(rte_thread_t thread_id) return 0; } +int +rte_thread_mutex_init(rte_thread_mutex *mutex) +{ + int ret = 0; + CRITICAL_SECTION *m = NULL; + + RTE_VERIFY(mutex != NULL); + + m = calloc(1, sizeof(*m)); + if (m == NULL) { + RTE_LOG(DEBUG, EAL, "Unable to initialize mutex. Insufficient memory!\n"); + ret = ENOMEM; + goto cleanup; + } + + InitializeCriticalSection(m); + mutex->mutex_id = m; + m = NULL; + +cleanup: + return ret; +} + +int +rte_thread_mutex_lock(rte_thread_mutex *mutex) +{ + RTE_VERIFY(mutex != NULL); + + EnterCriticalSection(mutex->mutex_id); + return 0; +} + +int +rte_thread_mutex_unlock(rte_thread_mutex *mutex) +{ + RTE_VERIFY(mutex != NULL); + + LeaveCriticalSection(mutex->mutex_id); + return 0; +} + +int +rte_thread_mutex_try_lock(rte_thread_mutex *mutex) +{ + RTE_VERIFY(mutex != NULL); + + if (TryEnterCriticalSection(mutex->mutex_id) != 0) + return 0; + + return EBUSY; +} + +int +rte_thread_mutex_destroy(rte_thread_mutex *mutex) +{ + RTE_VERIFY(mutex != NULL); + + DeleteCriticalSection(mutex->mutex_id); + free(mutex->mutex_id); + mutex->mutex_id = NULL; + + return 0; +} + int rte_thread_barrier_init(rte_thread_barrier *barrier, int count) {