From 0241dbef078b5c79d48ff97d7800ad1e7d726eae Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Tue, 7 May 2024 15:47:10 +0200 Subject: [PATCH 1/5] Add support for MAP_SHARED to OS memory provider for Linux only Add support for MAP_SHARED to OS memory provider for Linux only and for the UMF_NUMA_MODE_DEFAULT only. Signed-off-by: Lukasz Dorau --- README.md | 7 + benchmark/ubench.c | 1 + include/umf/memory_provider_ops.h | 4 + include/umf/providers/provider_os_memory.h | 9 ++ src/provider/provider_os_memory.c | 155 +++++++++++++++++++-- src/provider/provider_os_memory_internal.h | 12 +- src/provider/provider_os_memory_linux.c | 130 ++++++++++++++++- src/provider/provider_os_memory_windows.c | 31 ++++- 8 files changed, 329 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 60e49388f..e18b08eec 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,13 @@ More detailed documentation is available here: https://oneapi-src.github.io/unif #### OS memory provider A memory provider that provides memory from an operating system. +It supports two types of memory mappings +1) private memory mapping (`UMF_MEM_MAP_PRIVATE`) +2) shared memory mapping (`UMF_MEM_MAP_SHARED` - supported on Linux only yet) + +If the shared memory mapping is used then an anonymous file descriptor for memory mapping is created using: +1) `memfd_secret()` syscall - (if it is implemented and) if the `UMF_MEM_FD_FUNC` environment variable does not contain the "memfd_create" string or +2) `memfd_create()` syscall - otherwise (and if it is implemented). ##### Requirements diff --git a/benchmark/ubench.c b/benchmark/ubench.c index dfc95bcf8..d48fe5718 100644 --- a/benchmark/ubench.c +++ b/benchmark/ubench.c @@ -107,6 +107,7 @@ UBENCH_EX(simple, glibc_malloc) { static umf_os_memory_provider_params_t UMF_OS_MEMORY_PROVIDER_PARAMS = { /* .protection = */ UMF_PROTECTION_READ | UMF_PROTECTION_WRITE, + /* .visibility */ UMF_MEM_MAP_PRIVATE, // NUMA config /* .nodemask = */ NULL, diff --git a/include/umf/memory_provider_ops.h b/include/umf/memory_provider_ops.h index 62cf84284..a61e0aad0 100644 --- a/include/umf/memory_provider_ops.h +++ b/include/umf/memory_provider_ops.h @@ -50,6 +50,8 @@ typedef struct umf_memory_provider_ext_ops_t { /// @brief Merges two coarse grain allocations into a single allocation that /// can be managed (freed) as a whole. /// allocation_split and allocation_merge should be both set or both NULL. + /// allocation_merge should NOT be called concurrently with allocation_split() + /// with the same pointer. /// @param hProvider handle to the memory provider /// @param lowPtr pointer to the first allocation /// @param highPtr pointer to the second allocation (should be > lowPtr) @@ -64,6 +66,8 @@ typedef struct umf_memory_provider_ext_ops_t { /// @brief Splits a coarse grain allocation into 2 adjacent allocations that /// can be managed (freed) separately. /// allocation_split and allocation_merge should be both set or both NULL. + /// allocation_split should NOT be called concurrently with allocation_merge() + /// with the same pointer. /// @param hProvider handle to the memory provider /// @param ptr pointer to the beginning of the allocation /// @param totalSize total size of the allocation to be split diff --git a/include/umf/providers/provider_os_memory.h b/include/umf/providers/provider_os_memory.h index 663f1ee7b..48864248c 100644 --- a/include/umf/providers/provider_os_memory.h +++ b/include/umf/providers/provider_os_memory.h @@ -29,6 +29,12 @@ typedef enum umf_mem_protection_flags_t { /// @endcond } umf_mem_protection_flags_t; +/// @brief Memory visibility mode +typedef enum umf_memory_visibility_t { + UMF_MEM_MAP_PRIVATE = 1, ///< private memory mapping + UMF_MEM_MAP_SHARED, ///< shared memory mapping (supported on Linux only) +} umf_memory_visibility_t; + /// @brief Memory binding mode /// Specifies how memory is bound to NUMA nodes on systems that support NUMA. /// Not every mode is supported on every system. @@ -61,6 +67,8 @@ typedef enum umf_numa_mode_t { typedef struct umf_os_memory_provider_params_t { /// Combination of 'umf_mem_protection_flags_t' flags unsigned protection; + /// memory visibility mode + umf_memory_visibility_t visibility; // NUMA config /// ordered list of numa nodes @@ -91,6 +99,7 @@ static inline umf_os_memory_provider_params_t umfOsMemoryProviderParamsDefault(void) { umf_os_memory_provider_params_t params = { UMF_PROTECTION_READ | UMF_PROTECTION_WRITE, /* protection */ + UMF_MEM_MAP_PRIVATE, /* visibility mode */ NULL, /* numa_list */ 0, /* numa_list_len */ UMF_NUMA_MODE_DEFAULT, /* numa_mode */ diff --git a/src/provider/provider_os_memory.c b/src/provider/provider_os_memory.c index 5857ea60b..74d5105f6 100644 --- a/src/provider/provider_os_memory.c +++ b/src/provider/provider_os_memory.c @@ -15,6 +15,7 @@ #include #include "base_alloc_global.h" +#include "critnib.h" #include "provider_os_memory_internal.h" #include "utils_log.h" @@ -26,6 +27,16 @@ typedef struct os_memory_provider_t { unsigned protection; // combination of OS-specific protection flags + unsigned visibility; // memory visibility mode + int fd; // file descriptor for memory mapping + size_t size_fd; // size of file used for memory mapping + size_t max_size_fd; // maximum size of file used for memory mapping + // A critnib map storing (ptr, fd_offset + 1) pairs. We add 1 to fd_offset + // in order to be able to store fd_offset equal 0, because + // critnib_get() returns value or NULL, so a value cannot equal 0. + // It is needed mainly in the get_ipc_handle and open_ipc_handle hooks + // to mmap a specific part of a file. + critnib *fd_offset_map; // NUMA config hwloc_bitmap_t nodeset; @@ -191,6 +202,34 @@ static umf_result_t translate_params(umf_os_memory_provider_params_t *in_params, return result; } + result = os_translate_mem_visibility_flag(in_params->visibility, + &provider->visibility); + if (result != UMF_RESULT_SUCCESS) { + LOG_ERR("incorrect memory visibility flag: %u", in_params->visibility); + return result; + } + + provider->fd = os_create_anonymous_fd(provider->visibility); + if (provider->fd == -1) { + LOG_ERR( + "creating an anonymous file descriptor for memory mapping failed"); + return UMF_RESULT_ERROR_UNKNOWN; + } + + provider->size_fd = 0; // will be increased during each allocation + provider->max_size_fd = get_max_file_size(); + + if (provider->fd > 0) { + int ret = os_set_file_size(provider->fd, provider->max_size_fd); + if (ret) { + LOG_ERR("setting file size %zu failed", provider->max_size_fd); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + } + + LOG_DEBUG("size of the memory mapped file set to %zu", + provider->max_size_fd); + // NUMA config int emptyNodeset = in_params->numa_list_len == 0; result = translate_numa_mode(in_params->numa_mode, emptyNodeset, @@ -218,6 +257,14 @@ static umf_result_t os_initialize(void *params, void **provider) { umf_os_memory_provider_params_t *in_params = (umf_os_memory_provider_params_t *)params; + if (in_params->visibility == UMF_MEM_MAP_SHARED && + in_params->numa_mode != UMF_NUMA_MODE_DEFAULT) { + LOG_ERR("Unsupported NUMA mode for the UMF_MEM_MAP_SHARED memory " + "visibility mode (only the UMF_NUMA_MODE_DEFAULT is supported " + "for now)"); + return UMF_RESULT_ERROR_NOT_SUPPORTED; + } + os_memory_provider_t *os_provider = umf_ba_global_alloc(sizeof(os_memory_provider_t)); if (!os_provider) { @@ -261,10 +308,19 @@ static umf_result_t os_initialize(void *params, void **provider) { } } + os_provider->fd_offset_map = critnib_new(); + if (!os_provider->fd_offset_map) { + LOG_ERR("creating file descriptor offset map failed"); + ret = UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY; + goto err_free_nodeset_str_buf; + } + *provider = os_provider; return UMF_RESULT_SUCCESS; +err_free_nodeset_str_buf: + umf_ba_global_free(os_provider->nodeset_str_buf); err_destroy_hwloc_topology: hwloc_topology_destroy(os_provider->topo); err_free_os_provider: @@ -279,6 +335,9 @@ static void os_finalize(void *provider) { } os_memory_provider_t *os_provider = provider; + + critnib_delete(os_provider->fd_offset_map); + if (os_provider->nodeset_str_buf) { umf_ba_global_free(os_provider->nodeset_str_buf); } @@ -334,7 +393,9 @@ static inline void assert_is_page_aligned(uintptr_t ptr, size_t page_size) { } static int os_mmap_aligned(void *hint_addr, size_t length, size_t alignment, - size_t page_size, int prot, void **out_addr) { + size_t page_size, int prot, int flag, int fd, + size_t max_fd_size, void **out_addr, + size_t *fd_size) { assert(out_addr); size_t extended_length = length; @@ -347,8 +408,21 @@ static int os_mmap_aligned(void *hint_addr, size_t length, size_t alignment, extended_length += alignment; } - void *ptr = os_mmap(hint_addr, extended_length, prot); + size_t fd_offset = 0; + + if (fd > 0) { + if (*fd_size + extended_length > max_fd_size) { + LOG_ERR("cannot grow a file size beyond %zu", max_fd_size); + return -1; + } + + fd_offset = *fd_size; + *fd_size += extended_length; + } + + void *ptr = os_mmap(hint_addr, extended_length, prot, flag, fd, fd_offset); if (ptr == NULL) { + LOG_PDEBUG("memory mapping failed"); return -1; } @@ -414,15 +488,17 @@ static umf_result_t os_alloc(void *provider, size_t size, size_t alignment, return UMF_RESULT_ERROR_INVALID_ARGUMENT; } - int protection = os_provider->protection; + size_t fd_offset = os_provider->size_fd; // needed for critnib_insert() void *addr = NULL; errno = 0; - ret = os_mmap_aligned(NULL, size, alignment, page_size, protection, &addr); + ret = os_mmap_aligned(NULL, size, alignment, page_size, + os_provider->protection, os_provider->visibility, + os_provider->fd, os_provider->max_size_fd, &addr, + &os_provider->size_fd); if (ret) { os_store_last_native_error(UMF_OS_RESULT_ERROR_ALLOC_FAILED, errno); LOG_PERR("memory allocation failed"); - return UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC; } @@ -432,7 +508,6 @@ static umf_result_t os_alloc(void *provider, size_t size, size_t alignment, LOG_ERR("allocated address 0x%llx is not aligned to %zu (0x%zx) " "bytes", (unsigned long long)addr, alignment, alignment); - goto err_unmap; } @@ -463,6 +538,18 @@ static umf_result_t os_alloc(void *provider, size_t size, size_t alignment, } } + if (os_provider->fd > 0) { + // store (fd_offset + 1) to be able to store fd_offset == 0 + ret = + critnib_insert(os_provider->fd_offset_map, (uintptr_t)addr, + (void *)(uintptr_t)(fd_offset + 1), 0 /* update */); + if (ret) { + LOG_ERR("os_alloc(): inserting a value to the file descriptor " + "offset map failed (addr=%p, offset=%zu)", + addr, fd_offset); + } + } + *resultPtr = addr; return UMF_RESULT_SUCCESS; @@ -477,6 +564,12 @@ static umf_result_t os_free(void *provider, void *ptr, size_t size) { return UMF_RESULT_ERROR_INVALID_ARGUMENT; } + os_memory_provider_t *os_provider = (os_memory_provider_t *)provider; + + if (os_provider->fd > 0) { + critnib_remove(os_provider->fd_offset_map, (uintptr_t)ptr); + } + errno = 0; int ret = os_munmap(ptr, size); // ignore error when size == 0 @@ -581,23 +674,59 @@ static const char *os_get_name(void *provider) { return "OS"; } +// This function is supposed to be thread-safe, so it should NOT be called concurrently +// with os_allocation_merge() with the same pointer. static umf_result_t os_allocation_split(void *provider, void *ptr, size_t totalSize, size_t firstSize) { - (void)provider; - (void)ptr; (void)totalSize; - (void)firstSize; - // nop + + os_memory_provider_t *os_provider = (os_memory_provider_t *)provider; + if (os_provider->fd <= 0) { + return UMF_RESULT_SUCCESS; + } + + void *value = critnib_get(os_provider->fd_offset_map, (uintptr_t)ptr); + if (value == NULL) { + LOG_ERR("os_allocation_split(): getting a value from the file " + "descriptor offset map failed (addr=%p)", + ptr); + return UMF_RESULT_ERROR_UNKNOWN; + } else { + uintptr_t new_key = (uintptr_t)ptr + firstSize; + void *new_value = (void *)((uintptr_t)value + firstSize); + int ret = critnib_insert(os_provider->fd_offset_map, new_key, new_value, + 0 /* update */); + if (ret) { + LOG_ERR("os_allocation_split(): inserting a value to the file " + "descriptor offset map failed (addr=%p, offset=%zu)", + (void *)new_key, (size_t)new_value - 1); + return UMF_RESULT_ERROR_UNKNOWN; + } + } + return UMF_RESULT_SUCCESS; } +// It should NOT be called concurrently with os_allocation_split() with the same pointer. static umf_result_t os_allocation_merge(void *provider, void *lowPtr, void *highPtr, size_t totalSize) { - (void)provider; (void)lowPtr; - (void)highPtr; (void)totalSize; - // nop + + os_memory_provider_t *os_provider = (os_memory_provider_t *)provider; + if (os_provider->fd <= 0) { + return UMF_RESULT_SUCCESS; + } + + void *value = + critnib_remove(os_provider->fd_offset_map, (uintptr_t)highPtr); + if (value == NULL) { + LOG_ERR("os_allocation_merge(): removing a value from the file " + "descriptor offset map failed (addr=%p)", + highPtr); + return UMF_RESULT_ERROR_UNKNOWN; + } + return UMF_RESULT_SUCCESS; } diff --git a/src/provider/provider_os_memory_internal.h b/src/provider/provider_os_memory_internal.h index 020292dc5..0498cfc92 100644 --- a/src/provider/provider_os_memory_internal.h +++ b/src/provider/provider_os_memory_internal.h @@ -28,7 +28,17 @@ umf_result_t os_translate_flags(unsigned in_flags, unsigned max, umf_result_t os_translate_mem_protection_flags(unsigned in_protection, unsigned *out_protection); -void *os_mmap(void *hint_addr, size_t length, int prot); +umf_result_t os_translate_mem_visibility_flag(umf_memory_visibility_t in_flag, + unsigned *out_flag); + +int os_create_anonymous_fd(unsigned translated_memory_flag); + +size_t get_max_file_size(void); + +int os_set_file_size(int fd, size_t size); + +void *os_mmap(void *hint_addr, size_t length, int prot, int flag, int fd, + size_t fd_offset); int os_munmap(void *addr, size_t length); diff --git a/src/provider/provider_os_memory_linux.c b/src/provider/provider_os_memory_linux.c index 7fb88c94e..db6efa64e 100644 --- a/src/provider/provider_os_memory_linux.c +++ b/src/provider/provider_os_memory_linux.c @@ -6,13 +6,24 @@ */ #include +#include +#include #include #include +#include #include -#include "provider_os_memory_internal.h" #include +#include "provider_os_memory_internal.h" +#include "utils_log.h" + +// maximum value of the off_t type +#define OFF_T_MAX \ + (sizeof(off_t) == sizeof(long long) \ + ? LLONG_MAX \ + : (sizeof(off_t) == sizeof(long) ? LONG_MAX : INT_MAX)) + umf_result_t os_translate_mem_protection_one_flag(unsigned in_protection, unsigned *out_protection) { switch (in_protection) { @@ -32,6 +43,109 @@ umf_result_t os_translate_mem_protection_one_flag(unsigned in_protection, return UMF_RESULT_ERROR_INVALID_ARGUMENT; } +umf_result_t os_translate_mem_visibility_flag(umf_memory_visibility_t in_flag, + unsigned *out_flag) { + switch (in_flag) { + case UMF_MEM_MAP_PRIVATE: + *out_flag = MAP_PRIVATE; + return UMF_RESULT_SUCCESS; + case UMF_MEM_MAP_SHARED: +#ifdef __APPLE__ + return UMF_RESULT_ERROR_NOT_SUPPORTED; // not supported on MacOSX +#else + *out_flag = MAP_SHARED; + return UMF_RESULT_SUCCESS; +#endif + } + return UMF_RESULT_ERROR_INVALID_ARGUMENT; +} + +#ifndef __APPLE__ +static int syscall_memfd_secret(void) { + int fd = -1; +#ifdef __NR_memfd_secret + // SYS_memfd_secret is supported since Linux 5.14 + fd = syscall(SYS_memfd_secret, 0); + if (fd == -1) { + LOG_PERR("memfd_secret() failed"); + } + if (fd > 0) { + LOG_DEBUG("anonymous file descriptor created using memfd_secret()"); + } +#endif /* __NR_memfd_secret */ + return fd; +} + +static int syscall_memfd_create(void) { + int fd = -1; +#ifdef __NR_memfd_create + // SYS_memfd_create is supported since Linux 3.17, glibc 2.27 + fd = syscall(SYS_memfd_create, "anon_fd_name", 0); + if (fd == -1) { + LOG_PERR("memfd_create() failed"); + } + if (fd > 0) { + LOG_DEBUG("anonymous file descriptor created using memfd_create()"); + } +#endif /* __NR_memfd_create */ + return fd; +} +#endif /* __APPLE__ */ + +// create an anonymous file descriptor +int os_create_anonymous_fd(unsigned translated_memory_flag) { +#ifdef __APPLE__ + (void)translated_memory_flag; // unused + return 0; // ignored on MacOSX +#else /* !__APPLE__ */ + // fd is created only for MAP_SHARED + if (translated_memory_flag != MAP_SHARED) { + return 0; + } + + int fd = -1; + + if (!util_env_var_has_str("UMF_MEM_FD_FUNC", "memfd_create")) { + fd = syscall_memfd_secret(); + if (fd > 0) { + return fd; + } + } + + // The SYS_memfd_secret syscall can fail with errno == ENOTSYS (function not implemented). + // We should try to call the SYS_memfd_create syscall in this case. + + fd = syscall_memfd_create(); + +#if !(defined __NR_memfd_secret) && !(defined __NR_memfd_create) + if (fd == = 1) { + LOG_ERR("cannot create an anonymous file descriptor - neither " + "memfd_secret() nor memfd_create() are defined"); + } +#endif /* !(defined __NR_memfd_secret) && !(defined __NR_memfd_create) */ + + return fd; + +#endif /* !__APPLE__ */ +} + +size_t get_max_file_size(void) { return OFF_T_MAX; } + +int os_set_file_size(int fd, size_t size) { +#ifdef __APPLE__ + (void)fd; // unused + (void)size; // unused + return 0; // ignored on MacOSX +#else + errno = 0; + int ret = ftruncate(fd, size); + if (ret) { + LOG_PERR("ftruncate(%i, %zu) failed", fd, size); + } + return ret; +#endif /* __APPLE__ */ +} + umf_result_t os_translate_mem_protection_flags(unsigned in_protection, unsigned *out_protection) { // translate protection - combination of 'umf_mem_protection_flags_t' flags @@ -51,13 +165,19 @@ static int os_translate_purge_advise(umf_purge_advise_t advise) { return -1; } -void *os_mmap(void *hint_addr, size_t length, int prot) { - // MAP_ANONYMOUS - the mapping is not backed by any file - void *ptr = - mmap(hint_addr, length, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); +void *os_mmap(void *hint_addr, size_t length, int prot, int flag, int fd, + size_t fd_offset) { + fd = (fd == 0) ? -1 : fd; + if (fd == -1) { + // MAP_ANONYMOUS - the mapping is not backed by any file + flag |= MAP_ANONYMOUS; + } + + void *ptr = mmap(hint_addr, length, prot, flag, fd, fd_offset); if (ptr == MAP_FAILED) { return NULL; } + return ptr; } diff --git a/src/provider/provider_os_memory_windows.c b/src/provider/provider_os_memory_windows.c index 8a90f36dd..d6d08edd8 100644 --- a/src/provider/provider_os_memory_windows.c +++ b/src/provider/provider_os_memory_windows.c @@ -54,7 +54,36 @@ umf_result_t os_translate_mem_protection_flags(unsigned in_protection, return UMF_RESULT_ERROR_INVALID_ARGUMENT; } -void *os_mmap(void *hint_addr, size_t length, int prot) { +umf_result_t os_translate_mem_visibility_flag(umf_memory_visibility_t in_flag, + unsigned *out_flag) { + switch (in_flag) { + case UMF_MEM_MAP_PRIVATE: + *out_flag = 0; // ignored on Windows + return UMF_RESULT_SUCCESS; + case UMF_MEM_MAP_SHARED: + return UMF_RESULT_ERROR_NOT_SUPPORTED; // not supported on Windows yet + } + return UMF_RESULT_ERROR_INVALID_ARGUMENT; +} + +int os_create_anonymous_fd(unsigned translated_memory_flag) { + (void)translated_memory_flag; // unused + return 0; // ignored on Windows +} + +size_t get_max_file_size(void) { return SIZE_MAX; } + +int os_set_file_size(int fd, size_t size) { + (void)fd; // unused + (void)size; // unused + return 0; // ignored on Windows +} + +void *os_mmap(void *hint_addr, size_t length, int prot, int flag, int fd, + size_t fd_offset) { + (void)flag; // ignored on Windows + (void)fd; // ignored on Windows + (void)fd_offset; // ignored on Windows return VirtualAlloc(hint_addr, length, MEM_RESERVE | MEM_COMMIT, prot); } From c4c75265e46e2529120e98d71d23653961b1c89c Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Tue, 23 Apr 2024 15:55:39 +0200 Subject: [PATCH 2/5] Add support for MAP_SHARED to the proxy library for Linux only Signed-off-by: Lukasz Dorau --- README.md | 4 ++++ src/proxy_lib/proxy_lib.c | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index e18b08eec..1a0a2d9f1 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,10 @@ In case of Linux it can be done without any code changes using the `LD_PRELOAD` $ LD_PRELOAD=/usr/lib/libumf_proxy.so myprogram ``` +The memory used by the proxy memory allocator is mmap'ed: +1) with the `MAP_PRIVATE` flag by default or +2) with the `MAP_SHARED` flag only if the `UMF_PROXY` environment variable contains the `page.disposition=shared` string. + #### Windows In case of Windows it requires: diff --git a/src/proxy_lib/proxy_lib.c b/src/proxy_lib/proxy_lib.c index 359863a76..0780d7c46 100644 --- a/src/proxy_lib/proxy_lib.c +++ b/src/proxy_lib/proxy_lib.c @@ -111,6 +111,13 @@ void proxy_lib_create_common(void) { umfOsMemoryProviderParamsDefault(); enum umf_result_t umf_result; +#ifndef _WIN32 + if (util_env_var_has_str("UMF_PROXY", "page.disposition=shared")) { + LOG_DEBUG("proxy_lib: using the MAP_SHARED visibility mode"); + os_params.visibility = UMF_MEM_MAP_SHARED; + } +#endif + umf_result = umfMemoryProviderCreate(umfOsMemoryProviderOps(), &os_params, &OS_memory_provider); if (umf_result != UMF_RESULT_SUCCESS) { From 6b94829481d7f5c179f1ceb1636789657446c618 Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Fri, 26 Apr 2024 17:47:17 +0200 Subject: [PATCH 3/5] Implement IPC hooks in OS memory provider Signed-off-by: Lukasz Dorau --- src/provider/provider_os_memory.c | 121 ++++++++++++++++++++- src/provider/provider_os_memory_internal.h | 6 + src/provider/provider_os_memory_linux.c | 46 ++++++++ src/provider/provider_os_memory_windows.c | 14 +++ 4 files changed, 182 insertions(+), 5 deletions(-) diff --git a/src/provider/provider_os_memory.c b/src/provider/provider_os_memory.c index 74d5105f6..e96fd2cdc 100644 --- a/src/provider/provider_os_memory.c +++ b/src/provider/provider_os_memory.c @@ -730,6 +730,117 @@ static umf_result_t os_allocation_merge(void *provider, void *lowPtr, return UMF_RESULT_SUCCESS; } +typedef struct os_ipc_data_t { + int pid; + int fd; + size_t fd_offset; + size_t size; +} os_ipc_data_t; + +static umf_result_t os_get_ipc_handle_size(void *provider, size_t *size) { + (void)provider; // unused + + if (size == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + *size = sizeof(os_ipc_data_t); + return UMF_RESULT_SUCCESS; +} + +static umf_result_t os_get_ipc_handle(void *provider, const void *ptr, + size_t size, void *providerIpcData) { + if (provider == NULL || ptr == NULL || providerIpcData == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + os_memory_provider_t *os_provider = (os_memory_provider_t *)provider; + if (os_provider->fd <= 0) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + void *value = critnib_get(os_provider->fd_offset_map, (uintptr_t)ptr); + if (value == NULL) { + LOG_ERR("os_get_ipc_handle(): getting a value from the IPC cache " + "failed (addr=%p)", + ptr); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + os_ipc_data_t *os_ipc_data = (os_ipc_data_t *)providerIpcData; + os_ipc_data->pid = os_getpid(); + os_ipc_data->fd = os_provider->fd; + os_ipc_data->fd_offset = (size_t)value - 1; + os_ipc_data->size = size; + + return UMF_RESULT_SUCCESS; +} + +static umf_result_t os_put_ipc_handle(void *provider, void *providerIpcData) { + if (provider == NULL || providerIpcData == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + os_memory_provider_t *os_provider = (os_memory_provider_t *)provider; + os_ipc_data_t *os_ipc_data = (os_ipc_data_t *)providerIpcData; + + if (os_ipc_data->fd != os_provider->fd || os_ipc_data->pid != os_getpid()) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + return UMF_RESULT_SUCCESS; +} + +static umf_result_t os_open_ipc_handle(void *provider, void *providerIpcData, + void **ptr) { + if (provider == NULL || providerIpcData == NULL || ptr == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + os_memory_provider_t *os_provider = (os_memory_provider_t *)provider; + os_ipc_data_t *os_ipc_data = (os_ipc_data_t *)providerIpcData; + umf_result_t ret = UMF_RESULT_SUCCESS; + int fd; + + umf_result_t umf_result = + os_duplicate_fd(os_ipc_data->pid, os_ipc_data->fd, &fd); + if (umf_result != UMF_RESULT_SUCCESS) { + LOG_PERR("duplicating file descriptor failed"); + return umf_result; + } + + *ptr = os_mmap(NULL, os_ipc_data->size, os_provider->protection, + os_provider->visibility, fd, os_ipc_data->fd_offset); + if (*ptr == NULL) { + os_store_last_native_error(UMF_OS_RESULT_ERROR_ALLOC_FAILED, errno); + LOG_PERR("memory mapping failed"); + ret = UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC; + } + + (void)os_close_fd(fd); + + return ret; +} + +static umf_result_t os_close_ipc_handle(void *provider, void *ptr, + size_t size) { + if (provider == NULL || ptr == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + errno = 0; + int ret = os_munmap(ptr, size); + // ignore error when size == 0 + if (ret && (size > 0)) { + os_store_last_native_error(UMF_OS_RESULT_ERROR_FREE_FAILED, errno); + LOG_PERR("memory unmapping failed"); + + return UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC; + } + + return UMF_RESULT_SUCCESS; +} + static umf_memory_provider_ops_t UMF_OS_MEMORY_PROVIDER_OPS = { .version = UMF_VERSION_CURRENT, .initialize = os_initialize, @@ -744,11 +855,11 @@ static umf_memory_provider_ops_t UMF_OS_MEMORY_PROVIDER_OPS = { .ext.purge_force = os_purge_force, .ext.allocation_merge = os_allocation_merge, .ext.allocation_split = os_allocation_split, - .ipc.get_ipc_handle_size = NULL, - .ipc.get_ipc_handle = NULL, - .ipc.put_ipc_handle = NULL, - .ipc.open_ipc_handle = NULL, - .ipc.close_ipc_handle = NULL}; + .ipc.get_ipc_handle_size = os_get_ipc_handle_size, + .ipc.get_ipc_handle = os_get_ipc_handle, + .ipc.put_ipc_handle = os_put_ipc_handle, + .ipc.open_ipc_handle = os_open_ipc_handle, + .ipc.close_ipc_handle = os_close_ipc_handle}; umf_memory_provider_ops_t *umfOsMemoryProviderOps(void) { return &UMF_OS_MEMORY_PROVIDER_OPS; diff --git a/src/provider/provider_os_memory_internal.h b/src/provider/provider_os_memory_internal.h index 0498cfc92..c9eb2e619 100644 --- a/src/provider/provider_os_memory_internal.h +++ b/src/provider/provider_os_memory_internal.h @@ -48,6 +48,12 @@ size_t os_get_page_size(void); void os_strerror(int errnum, char *buf, size_t buflen); +int os_getpid(void); + +umf_result_t os_duplicate_fd(int pid, int fd_in, int *fd_out); + +umf_result_t os_close_fd(int fd); + #ifdef __cplusplus } #endif diff --git a/src/provider/provider_os_memory_linux.c b/src/provider/provider_os_memory_linux.c index db6efa64e..bc4b35fb3 100644 --- a/src/provider/provider_os_memory_linux.c +++ b/src/provider/provider_os_memory_linux.c @@ -192,3 +192,49 @@ int os_purge(void *addr, size_t length, int advice) { void os_strerror(int errnum, char *buf, size_t buflen) { strerror_r(errnum, buf, buflen); } + +int os_getpid(void) { return getpid(); } + +umf_result_t os_duplicate_fd(int pid, int fd_in, int *fd_out) { +// pidfd_getfd(2) is used to obtain a duplicate of another process's file descriptor. +// Permission to duplicate another process's file descriptor +// is governed by a ptrace access mode PTRACE_MODE_ATTACH_REALCREDS check (see ptrace(2)) +// that can be changed using the /proc/sys/kernel/yama/ptrace_scope interface. +// pidfd_getfd(2) is supported since Linux 5.6 +// pidfd_open(2) is supported since Linux 5.3 +#if defined(__NR_pidfd_open) && defined(__NR_pidfd_getfd) + errno = 0; + int pid_fd = syscall(SYS_pidfd_open, pid, 0); + if (pid_fd == -1) { + LOG_PDEBUG("SYS_pidfd_open"); + return UMF_RESULT_ERROR_UNKNOWN; + } + + int fd_dup = syscall(SYS_pidfd_getfd, pid_fd, fd_in, 0); + close(pid_fd); + if (fd_dup == -1) { + LOG_PDEBUG("SYS_pidfd_getfd"); + return UMF_RESULT_ERROR_UNKNOWN; + } + + *fd_out = fd_dup; + + return UMF_RESULT_SUCCESS; +#else + // TODO: find another way to obtain a duplicate of another process's file descriptor + (void)pid; // unused + (void)fd_in; // unused + (void)fd_out; // unused + errno = ENOTSUP; + return UMF_RESULT_ERROR_NOT_SUPPORTED; // unsupported +#endif /* defined(__NR_pidfd_open) && defined(__NR_pidfd_getfd) */ +} + +umf_result_t os_close_fd(int fd) { + if (close(fd)) { + LOG_PERR("close() failed"); + return UMF_RESULT_ERROR_UNKNOWN; + } + + return UMF_RESULT_SUCCESS; +} diff --git a/src/provider/provider_os_memory_windows.c b/src/provider/provider_os_memory_windows.c index d6d08edd8..2bf0b7a9f 100644 --- a/src/provider/provider_os_memory_windows.c +++ b/src/provider/provider_os_memory_windows.c @@ -115,3 +115,17 @@ size_t os_get_page_size(void) { void os_strerror(int errnum, char *buf, size_t buflen) { strerror_s(buf, buflen, errnum); } + +int os_getpid(void) { return GetCurrentProcessId(); } + +umf_result_t os_duplicate_fd(int pid, int fd_in, int *fd_out) { + (void)pid; // unused + (void)fd_in; // unused + (void)fd_out; // unused + return UMF_RESULT_ERROR_NOT_SUPPORTED; // unsupported +} + +umf_result_t os_close_fd(int fd) { + (void)fd; // unused + return UMF_RESULT_ERROR_NOT_SUPPORTED; // unsupported +} From 3331207e90c270515f1c803c93d62f8a8a0c966c Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Mon, 6 May 2024 10:15:03 +0200 Subject: [PATCH 4/5] Add an IPC shared memory test Add an IPC shared memory test using only the OS memory provider API (not using the API from ipc.h). Signed-off-by: Lukasz Dorau --- test/CMakeLists.txt | 32 +++++ test/ipc_os_prov.sh | 43 ++++++ test/ipc_os_prov_consumer.c | 264 ++++++++++++++++++++++++++++++++++ test/ipc_os_prov_producer.c | 279 ++++++++++++++++++++++++++++++++++++ test/test_valgrind.sh | 4 + 5 files changed, 622 insertions(+) create mode 100755 test/ipc_os_prov.sh create mode 100644 test/ipc_os_prov_consumer.c create mode 100644 test/ipc_os_prov_producer.c diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0a6fc27b3..36abfd873 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -240,3 +240,35 @@ endif() if(UMF_ENABLE_POOL_TRACKING) add_umf_test(NAME ipc SRCS ipcAPI.cpp) endif() + +if(LINUX) + set(BASE_NAME ipc_os_prov) + set(TEST_NAME umf-${BASE_NAME}) + + foreach(loop_var IN ITEMS "producer" "consumer") + set(EXEC_NAME umf_test-${BASE_NAME}_${loop_var}) + add_umf_executable( + NAME ${EXEC_NAME} + SRCS ${BASE_NAME}_${loop_var}.c + LIBS umf) + + target_include_directories( + ${EXEC_NAME} PRIVATE ${UMF_CMAKE_SOURCE_DIR}/src/utils + ${UMF_CMAKE_SOURCE_DIR}/include) + + target_link_directories(${EXEC_NAME} PRIVATE ${LIBHWLOC_LIBRARY_DIRS}) + endforeach(loop_var) + + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/${BASE_NAME}.sh + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + + add_test( + NAME ${TEST_NAME} + COMMAND ${BASE_NAME}.sh + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + set_tests_properties(${TEST_NAME} PROPERTIES LABELS "umf") +else() + message( + STATUS "IPC shared memory test is supported on Linux only - skipping") +endif() diff --git a/test/ipc_os_prov.sh b/test/ipc_os_prov.sh new file mode 100755 index 000000000..09ca5361f --- /dev/null +++ b/test/ipc_os_prov.sh @@ -0,0 +1,43 @@ +# +# Copyright (C) 2024 Intel Corporation +# +# Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# + +#!/bin/bash + +# port should be a number from the range <1024, 65535> +PORT=$(( 1024 + ( $$ % ( 65535 - 1024 )))) + +# The ipc_os_prov example requires using pidfd_getfd(2) +# to obtain a duplicate of another process's file descriptor. +# Permission to duplicate another process's file descriptor +# is governed by a ptrace access mode PTRACE_MODE_ATTACH_REALCREDS check (see ptrace(2)) +# that can be changed using the /proc/sys/kernel/yama/ptrace_scope interface. +PTRACE_SCOPE_FILE="/proc/sys/kernel/yama/ptrace_scope" +VAL=0 +if [ -f $PTRACE_SCOPE_FILE ]; then + PTRACE_SCOPE_VAL=$(cat $PTRACE_SCOPE_FILE) + if [ $PTRACE_SCOPE_VAL -ne $VAL ]; then + echo "Setting ptrace_scope to 0 (classic ptrace permissions) ..." + echo "$ sudo bash -c \"echo $VAL > $PTRACE_SCOPE_FILE\"" + sudo bash -c "echo $VAL > $PTRACE_SCOPE_FILE" + fi + PTRACE_SCOPE_VAL=$(cat $PTRACE_SCOPE_FILE) + if [ $PTRACE_SCOPE_VAL -ne $VAL ]; then + echo "SKIP: setting ptrace_scope to 0 (classic ptrace permissions) FAILED - skipping the test" + exit 0 + fi +fi + +UMF_LOG_VAL="level:debug;flush:debug;output:stderr;pid:yes" + +echo "Starting ipc_os_prov CONSUMER on port $PORT ..." +UMF_LOG=$UMF_LOG_VAL ./umf_test-ipc_os_prov_consumer $PORT & + +echo "Waiting 1 sec ..." +sleep 1 + +echo "Starting ipc_os_prov PRODUCER on port $PORT ..." +UMF_LOG=$UMF_LOG_VAL ./umf_test-ipc_os_prov_producer $PORT diff --git a/test/ipc_os_prov_consumer.c b/test/ipc_os_prov_consumer.c new file mode 100644 index 000000000..16d0fce60 --- /dev/null +++ b/test/ipc_os_prov_consumer.c @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2024 Intel Corporation + * + * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define INET_ADDR "127.0.0.1" +#define MSG_SIZE 256 +#define RECV_BUFF_SIZE 1024 + +// consumer's response message +#define CONSUMER_MSG \ + "This is the consumer. I just wrote a new number directly into your " \ + "shared memory!" + +/* +Generally communication between the producer and the consumer looks like: +- Consumer starts +- Consumer creates a socket +- Consumer listens for incoming connections +- Producer starts +- Producer's shared memory contains a number: 140722582213392 +- Producer gets the IPC handle +- Producer creates a socket +- Producer connects to the consumer +- Consumer connects at IP 127.0.0.1 and port 47770 to the producer +- Producer sents the IPC handle to the consumer (24 bytes) +- Consumer receives the IPC handle from the producer (24 bytes) +- Consumer opens the IPC handle received from the producer +- Consumer reads the number from the producer's shared memory: 140722582213392 +- Consumer writes a new number directly to the producer's shared memory: 70361291106696 +- Consumer sents a response message to the producer +- Consumer closes the IPC handle received from the producer +- Producer receives the response from the consumer: "This is the consumer. I just wrote a new number directly into your shared memory!" +- Producer verifies the consumer wrote the correct value (the old one / 2) to the producer's shared memory: 70361291106696 +- Producer puts the IPC handle +- Consumer shuts down +- Producer shuts down +*/ + +int consumer_connect(int port) { + struct sockaddr_in consumer_addr; + struct sockaddr_in producer_addr; + int producer_addr_len; + int producer_socket = -1; + int consumer_socket = -1; + int ret = -1; + + // create a socket + consumer_socket = socket(AF_INET, SOCK_STREAM, 0); + if (consumer_socket < 0) { + fprintf(stderr, "[consumer] ERROR: creating socket failed\n"); + return -1; + } + + fprintf(stderr, "[consumer] Socket created\n"); + + // set the IP address and the port + consumer_addr.sin_family = AF_INET; + consumer_addr.sin_port = htons(port); + consumer_addr.sin_addr.s_addr = inet_addr(INET_ADDR); + + // bind to the IP address and the port + if (bind(consumer_socket, (struct sockaddr *)&consumer_addr, + sizeof(consumer_addr)) < 0) { + fprintf(stderr, "[consumer] ERROR: cannot bind to the port\n"); + goto err_close_consumer_socket; + } + + fprintf(stderr, "[consumer] Binding done\n"); + + // listen for the producer + if (listen(consumer_socket, 1) < 0) { + fprintf(stderr, "[consumer] ERROR: listen() failed\n"); + goto err_close_consumer_socket; + } + + fprintf(stderr, "[consumer] Listening for incoming connections ...\n"); + + // accept an incoming connection + producer_addr_len = sizeof(producer_addr); + producer_socket = accept(consumer_socket, (struct sockaddr *)&producer_addr, + (socklen_t *)&producer_addr_len); + if (producer_socket < 0) { + fprintf(stderr, "[consumer] ERROR: accept() failed\n"); + goto err_close_consumer_socket; + } + + fprintf(stderr, "[consumer] Producer connected at IP %s and port %i\n", + inet_ntoa(producer_addr.sin_addr), ntohs(producer_addr.sin_port)); + + ret = producer_socket; // success + +err_close_consumer_socket: + close(consumer_socket); + + return ret; +} + +int main(int argc, char *argv[]) { + char consumer_message[MSG_SIZE]; + char recv_buffer[RECV_BUFF_SIZE]; + int producer_socket = -1; + int ret = -1; + + if (argc < 2) { + fprintf(stderr, "usage: %s port\n", argv[0]); + return -1; + } + + int port = atoi(argv[1]); + + // zero the consumer_message buffer + memset(consumer_message, 0, sizeof(consumer_message)); + + umf_memory_provider_handle_t OS_memory_provider = NULL; + umf_os_memory_provider_params_t os_params; + enum umf_result_t umf_result; + + os_params = umfOsMemoryProviderParamsDefault(); + os_params.visibility = UMF_MEM_MAP_SHARED; + + // create OS memory provider + umf_result = umfMemoryProviderCreate(umfOsMemoryProviderOps(), &os_params, + &OS_memory_provider); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, + "[consumer] ERROR: creating OS memory provider failed\n"); + return -1; + } + + // get the size of the IPC handle + size_t IPC_handle_size; + umf_result = + umfMemoryProviderGetIPCHandleSize(OS_memory_provider, &IPC_handle_size); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, + "[consumer] ERROR: getting size of the IPC handle failed\n"); + goto err_umfMemoryProviderDestroy; + } + + producer_socket = consumer_connect(port); + if (producer_socket < 0) { + goto err_umfMemoryProviderDestroy; + } + + // zero the receive buffer + memset(recv_buffer, 0, RECV_BUFF_SIZE); + + // receive a producer's message + ssize_t len = recv(producer_socket, recv_buffer, RECV_BUFF_SIZE, 0); + if (len < 0) { + fprintf(stderr, "[consumer] ERROR: recv() failed\n"); + goto err_close_producer_socket; + } + if (len != IPC_handle_size) { + fprintf(stderr, + "[consumer] ERROR: recv() received a wrong number of bytes " + "(%zi != %zu expected)\n", + len, IPC_handle_size); + goto err_close_producer_socket; + } + + void *IPC_handle = recv_buffer; + + fprintf( + stderr, + "[consumer] Received the IPC handle from the producer (%zi bytes)\n", + len); + + void *SHM_ptr; + umf_result = umfMemoryProviderOpenIPCHandle(OS_memory_provider, IPC_handle, + &SHM_ptr); + if (umf_result == UMF_RESULT_ERROR_NOT_SUPPORTED) { + fprintf(stderr, + "[consumer] SKIP: opening the IPC handle is not supported\n"); + ret = 1; // SKIP + + // write the SKIP response to the consumer_message buffer + strcpy(consumer_message, "SKIP"); + + // send the SKIP response to the producer + send(producer_socket, consumer_message, strlen(consumer_message) + 1, + 0); + + goto err_close_producer_socket; + } + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, "[consumer] ERROR: opening the IPC handle failed\n"); + goto err_close_producer_socket; + } + + fprintf(stderr, + "[consumer] Opened the IPC handle received from the producer\n"); + + // read the current value from the shared memory + unsigned long long SHM_number_1 = *(unsigned long long *)SHM_ptr; + fprintf( + stderr, + "[consumer] Read the number from the producer's shared memory: %llu\n", + SHM_number_1); + + // calculate the new value + unsigned long long SHM_number_2 = SHM_number_1 / 2; + + // write the new number directly to the producer's shared memory + *(unsigned long long *)SHM_ptr = SHM_number_2; + fprintf(stderr, + "[consumer] Wrote a new number directly to the producer's shared " + "memory: %llu\n", + SHM_number_2); + + // write the response to the consumer_message buffer + strcpy(consumer_message, CONSUMER_MSG); + + // send response to the producer + if (send(producer_socket, consumer_message, strlen(consumer_message) + 1, + 0) < 0) { + fprintf(stderr, "[consumer] ERROR: send() failed\n"); + goto err_closeIPCHandle; + } + + fprintf(stderr, "[consumer] Sent a response message to the producer\n"); + + ret = 0; // SUCCESS + +err_closeIPCHandle: + // we do not know the exact size of the remote shared memory + umf_result = umfMemoryProviderCloseIPCHandle(OS_memory_provider, SHM_ptr, + sizeof(unsigned long long)); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, "[consumer] ERROR: closing the IPC handle failed\n"); + } + + fprintf(stderr, + "[consumer] Closed the IPC handle received from the producer\n"); + +err_close_producer_socket: + close(producer_socket); + +err_umfMemoryProviderDestroy: + umfMemoryProviderDestroy(OS_memory_provider); + + if (ret == 0) { + fprintf(stderr, "[consumer] Shutting down (status OK) ...\n"); + } else if (ret == 1) { + fprintf(stderr, "[consumer] Shutting down (status SKIP) ...\n"); + ret = 0; + } else { + fprintf(stderr, "[consumer] Shutting down (status ERROR) ...\n"); + } + + return ret; +} diff --git a/test/ipc_os_prov_producer.c b/test/ipc_os_prov_producer.c new file mode 100644 index 000000000..20e2c1413 --- /dev/null +++ b/test/ipc_os_prov_producer.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2024 Intel Corporation + * + * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define INET_ADDR "127.0.0.1" +#define MSG_SIZE 1024 + +/* +Generally communication between the producer and the consumer looks like: +- Consumer starts +- Consumer creates a socket +- Consumer listens for incoming connections +- Producer starts +- Producer's shared memory contains a number: 140722582213392 +- Producer gets the IPC handle +- Producer creates a socket +- Producer connects to the consumer +- Consumer connects at IP 127.0.0.1 and port 47770 to the producer +- Producer sents the IPC handle to the consumer (24 bytes) +- Consumer receives the IPC handle from the producer (24 bytes) +- Consumer opens the IPC handle received from the producer +- Consumer reads the number from the producer's shared memory: 140722582213392 +- Consumer writes a new number directly to the producer's shared memory: 70361291106696 +- Consumer sents a response message to the producer +- Consumer closes the IPC handle received from the producer +- Producer receives the response from the consumer: "This is the consumer. I just wrote a new number directly into your shared memory!" +- Producer verifies the consumer wrote the correct value (the old one / 2) to the producer's shared memory: 70361291106696 +- Producer puts the IPC handle +- Consumer shuts down +- Producer shuts down +*/ + +int producer_connect(int port) { + struct sockaddr_in consumer_addr; + int producer_socket = -1; + + // create a producer socket + producer_socket = socket(AF_INET, SOCK_STREAM, 0); + if (producer_socket < 0) { + fprintf(stderr, "[producer] ERROR: Unable to create socket\n"); + return -1; + } + + fprintf(stderr, "[producer] Socket created\n"); + + // set IP address and port the same as for the consumer + consumer_addr.sin_family = AF_INET; + consumer_addr.sin_port = htons(port); + consumer_addr.sin_addr.s_addr = inet_addr(INET_ADDR); + + // send connection request to the consumer + if (connect(producer_socket, (struct sockaddr *)&consumer_addr, + sizeof(consumer_addr)) < 0) { + fprintf(stderr, + "[producer] ERROR: unable to connect to the consumer\n"); + goto err_close_producer_socket_connect; + } + + fprintf(stderr, "[producer] Connected to the consumer\n"); + + return producer_socket; // success + +err_close_producer_socket_connect: + close(producer_socket); + + return -1; +} + +int main(int argc, char *argv[]) { + char consumer_message[MSG_SIZE]; + int producer_socket = -1; + int ret = -1; + + if (argc < 2) { + fprintf(stderr, "Usage: %s port\n", argv[0]); + return -1; + } + + int port = atoi(argv[1]); + + umf_memory_provider_handle_t OS_memory_provider = NULL; + umf_os_memory_provider_params_t os_params; + enum umf_result_t umf_result; + + os_params = umfOsMemoryProviderParamsDefault(); + os_params.visibility = UMF_MEM_MAP_SHARED; + + // create OS memory provider + umf_result = umfMemoryProviderCreate(umfOsMemoryProviderOps(), &os_params, + &OS_memory_provider); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, + "[producer] ERROR: creating OS memory provider failed\n"); + return -1; + } + + size_t page_size; + umf_result = + umfMemoryProviderGetMinPageSize(OS_memory_provider, NULL, &page_size); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, + "[producer] ERROR: getting the minimum page size failed\n"); + goto err_umfMemoryProviderDestroy; + } + + // Make 3 allocations of size: 1 page, 2 pages and 3 pages + void *ptr1, *ptr2, *IPC_shared_memory; + size_t ptr1_size = 1 * page_size; + size_t ptr2_size = 2 * page_size; + size_t size_IPC_shared_memory = 3 * page_size; + + umf_result = + umfMemoryProviderAlloc(OS_memory_provider, ptr1_size, 0, &ptr1); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, "[producer] ERROR: allocating 1 page failed\n"); + goto err_umfMemoryProviderDestroy; + } + + umf_result = + umfMemoryProviderAlloc(OS_memory_provider, ptr2_size, 0, &ptr2); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, "[producer] ERROR: allocating 2 pages failed\n"); + goto err_free_ptr1; + } + + umf_result = umfMemoryProviderAlloc( + OS_memory_provider, size_IPC_shared_memory, 0, &IPC_shared_memory); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, "[producer] ERROR: allocating 3 pages failed\n"); + goto err_free_ptr2; + } + + // get size of the IPC handle + size_t IPC_handle_size; + umf_result = + umfMemoryProviderGetIPCHandleSize(OS_memory_provider, &IPC_handle_size); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, + "[producer] ERROR: getting size of the IPC handle failed\n"); + goto err_free_IPC_shared_memory; + } + + // allocate data for IPC provider + void *IPC_handle; + umf_result = umfMemoryProviderAlloc(OS_memory_provider, IPC_handle_size, 0, + &IPC_handle); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, + "[producer] ERROR: allocating data for IPC provider failed\n"); + goto err_free_IPC_shared_memory; + } + + // zero the IPC handle and the shared memory + memset(IPC_handle, 0, IPC_handle_size); + memset(IPC_shared_memory, 0, size_IPC_shared_memory); + + // save a random number (&OS_memory_provider) in the shared memory + unsigned long long SHM_number_1 = (unsigned long long)&OS_memory_provider; + *(unsigned long long *)IPC_shared_memory = SHM_number_1; + + fprintf(stderr, "[producer] My shared memory contains a number: %llu\n", + *(unsigned long long *)IPC_shared_memory); + + // get the IPC handle from the OS memory provider + umf_result = + umfMemoryProviderGetIPCHandle(OS_memory_provider, IPC_shared_memory, + size_IPC_shared_memory, IPC_handle); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, + "[producer] ERROR: getting the IPC handle from the OS memory " + "provider failed\n"); + goto err_free_IPC_handle; + } + + fprintf(stderr, "[producer] Got the IPC handle\n"); + + producer_socket = producer_connect(port); + if (producer_socket < 0) { + goto err_PutIPCHandle; + } + + // send the IPC_handle of IPC_handle_size to the consumer + if (send(producer_socket, IPC_handle, IPC_handle_size, 0) < 0) { + fprintf(stderr, "[producer] ERROR: unable to send the message\n"); + goto err_close_producer_socket; + } + + fprintf(stderr, + "[producer] Sent the IPC handle to the consumer (%zu bytes)\n", + IPC_handle_size); + + // zero the consumer_message buffer + memset(consumer_message, 0, sizeof(consumer_message)); + + // receive the consumer's response + if (recv(producer_socket, consumer_message, sizeof(consumer_message), 0) < + 0) { + fprintf( + stderr, + "[producer] ERROR: error while receiving the consumer's message\n"); + goto err_close_producer_socket; + } + + fprintf(stderr, "[producer] Received the consumer's response: \"%s\"\n", + consumer_message); + + if (strncmp(consumer_message, "SKIP", 5 /* length of "SKIP" + 1 */) == 0) { + fprintf(stderr, "[producer] SKIP: received the 'SKIP' response from " + "consumer, skipping ...\n"); + ret = 1; + goto err_close_producer_socket; + } + + // read a new value from the shared memory + unsigned long long SHM_number_2 = *(unsigned long long *)IPC_shared_memory; + + // the expected correct value is: SHM_number_2 == (SHM_number_1 / 2) + if (SHM_number_2 == (SHM_number_1 / 2)) { + ret = 0; // got the correct value - success! + fprintf( + stderr, + "[producer] The consumer wrote the correct value (the old one / 2) " + "to my shared memory: %llu\n", + SHM_number_2); + } else { + fprintf( + stderr, + "[producer] ERROR: The consumer did NOT write the correct value " + "(the old one / 2 = %llu) to my shared memory: %llu\n", + (SHM_number_1 / 2), SHM_number_2); + } + +err_close_producer_socket: + close(producer_socket); + +err_PutIPCHandle: + umf_result = umfMemoryProviderPutIPCHandle(OS_memory_provider, IPC_handle); + if (umf_result != UMF_RESULT_SUCCESS) { + fprintf(stderr, "[producer] ERROR: putting the IPC handle failed\n"); + } + + fprintf(stderr, "[producer] Put the IPC handle\n"); + +err_free_IPC_handle: + (void)umfMemoryProviderFree(OS_memory_provider, IPC_handle, + IPC_handle_size); +err_free_IPC_shared_memory: + (void)umfMemoryProviderFree(OS_memory_provider, IPC_shared_memory, + size_IPC_shared_memory); +err_free_ptr2: + (void)umfMemoryProviderFree(OS_memory_provider, ptr2, ptr2_size); +err_free_ptr1: + (void)umfMemoryProviderFree(OS_memory_provider, ptr1, ptr1_size); +err_umfMemoryProviderDestroy: + umfMemoryProviderDestroy(OS_memory_provider); + + if (ret == 0) { + fprintf(stderr, "[producer] Shutting down (status OK) ...\n"); + } else if (ret == 1) { + fprintf(stderr, "[producer] Shutting down (status SKIP) ...\n"); + ret = 0; + } else { + fprintf(stderr, "[producer] Shutting down (status ERROR) ...\n"); + } + + return ret; +} diff --git a/test/test_valgrind.sh b/test/test_valgrind.sh index 94319b589..c0bfc41a4 100755 --- a/test/test_valgrind.sh +++ b/test/test_valgrind.sh @@ -84,6 +84,10 @@ for test in $(ls -1 umf_test-*); do # skip tests incompatible with valgrind FILTER="" case $test in + umf_test-ipc_os_prov_*) + echo "- SKIPPED" + continue; # skip it - this is a 2 processes test run using the ipc_os_prov.sh script + ;; umf_test-memspace_host_all) FILTER='--gtest_filter="-*allocsSpreadAcrossAllNumaNodes"' ;; From aeb0c72c49d121678be093a17f2761eeb406f612 Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Tue, 7 May 2024 15:47:36 +0200 Subject: [PATCH 5/5] Extract common sources for Linux and MacOSX Signed-off-by: Lukasz Dorau --- src/CMakeLists.txt | 15 ++- src/provider/provider_os_memory_linux.c | 145 +---------------------- src/provider/provider_os_memory_macosx.c | 37 ++++++ src/provider/provider_os_memory_posix.c | 137 +++++++++++++++++++++ 4 files changed, 186 insertions(+), 148 deletions(-) create mode 100644 src/provider/provider_os_memory_macosx.c create mode 100644 src/provider/provider_os_memory_posix.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 663fe542f..436eabcff 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,6 +66,8 @@ set(UMF_SOURCES set(UMF_SOURCES_LINUX libumf_linux.c) +set(UMF_SOURCES_MACOSX libumf_linux.c) + set(UMF_SOURCES_WINDOWS libumf_windows.c) # Compile definitions for UMF library. @@ -73,15 +75,20 @@ set(UMF_SOURCES_WINDOWS libumf_windows.c) # TODO: Cleanup the compile definitions across all the CMake files set(UMF_PRIVATE_COMPILE_DEFINITIONS "") -set(UMF_SOURCES_LINUX - ${UMF_SOURCES_LINUX} +set(UMF_SOURCES_COMMON_LINUX_MACOSX provider/provider_os_memory.c - provider/provider_os_memory_linux.c + provider/provider_os_memory_posix.c memory_targets/memory_target_numa.c memspaces/memspace_numa.c memspaces/memspace_host_all.c memspaces/memspace_highest_capacity.c) +set(UMF_SOURCES_LINUX ${UMF_SOURCES_LINUX} ${UMF_SOURCES_COMMON_LINUX_MACOSX} + provider/provider_os_memory_linux.c) + +set(UMF_SOURCES_MACOSX ${UMF_SOURCES_MACOSX} ${UMF_SOURCES_COMMON_LINUX_MACOSX} + provider/provider_os_memory_macosx.c) + set(UMF_SOURCES_WINDOWS ${UMF_SOURCES_WINDOWS} provider/provider_os_memory.c provider/provider_os_memory_windows.c) @@ -89,8 +96,6 @@ set(UMF_LIBS ${UMF_LIBS} ${LIBHWLOC_LIBRARIES}) set(UMF_PRIVATE_LIBRARY_DIRS ${UMF_PRIVATE_LIBRARY_DIRS} ${LIBHWLOC_LIBRARY_DIRS}) -set(UMF_SOURCES_MACOSX ${UMF_SOURCES_MACOSX} ${UMF_SOURCES_LINUX}) - if(LINUX) set(UMF_SOURCES ${UMF_SOURCES} ${UMF_SOURCES_LINUX}) elseif(WINDOWS) diff --git a/src/provider/provider_os_memory_linux.c b/src/provider/provider_os_memory_linux.c index bc4b35fb3..9859165c2 100644 --- a/src/provider/provider_os_memory_linux.c +++ b/src/provider/provider_os_memory_linux.c @@ -1,14 +1,11 @@ /* - * Copyright (C) 2023 Intel Corporation + * Copyright (C) 2023-2024 Intel Corporation * * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ -#include #include -#include -#include #include #include #include @@ -18,31 +15,6 @@ #include "provider_os_memory_internal.h" #include "utils_log.h" -// maximum value of the off_t type -#define OFF_T_MAX \ - (sizeof(off_t) == sizeof(long long) \ - ? LLONG_MAX \ - : (sizeof(off_t) == sizeof(long) ? LONG_MAX : INT_MAX)) - -umf_result_t os_translate_mem_protection_one_flag(unsigned in_protection, - unsigned *out_protection) { - switch (in_protection) { - case UMF_PROTECTION_NONE: - *out_protection = PROT_NONE; - return UMF_RESULT_SUCCESS; - case UMF_PROTECTION_READ: - *out_protection = PROT_READ; - return UMF_RESULT_SUCCESS; - case UMF_PROTECTION_WRITE: - *out_protection = PROT_WRITE; - return UMF_RESULT_SUCCESS; - case UMF_PROTECTION_EXEC: - *out_protection = PROT_EXEC; - return UMF_RESULT_SUCCESS; - } - return UMF_RESULT_ERROR_INVALID_ARGUMENT; -} - umf_result_t os_translate_mem_visibility_flag(umf_memory_visibility_t in_flag, unsigned *out_flag) { switch (in_flag) { @@ -50,17 +22,12 @@ umf_result_t os_translate_mem_visibility_flag(umf_memory_visibility_t in_flag, *out_flag = MAP_PRIVATE; return UMF_RESULT_SUCCESS; case UMF_MEM_MAP_SHARED: -#ifdef __APPLE__ - return UMF_RESULT_ERROR_NOT_SUPPORTED; // not supported on MacOSX -#else *out_flag = MAP_SHARED; return UMF_RESULT_SUCCESS; -#endif } return UMF_RESULT_ERROR_INVALID_ARGUMENT; } -#ifndef __APPLE__ static int syscall_memfd_secret(void) { int fd = -1; #ifdef __NR_memfd_secret @@ -90,14 +57,9 @@ static int syscall_memfd_create(void) { #endif /* __NR_memfd_create */ return fd; } -#endif /* __APPLE__ */ // create an anonymous file descriptor int os_create_anonymous_fd(unsigned translated_memory_flag) { -#ifdef __APPLE__ - (void)translated_memory_flag; // unused - return 0; // ignored on MacOSX -#else /* !__APPLE__ */ // fd is created only for MAP_SHARED if (translated_memory_flag != MAP_SHARED) { return 0; @@ -118,123 +80,20 @@ int os_create_anonymous_fd(unsigned translated_memory_flag) { fd = syscall_memfd_create(); #if !(defined __NR_memfd_secret) && !(defined __NR_memfd_create) - if (fd == = 1) { + if (fd == -1) { LOG_ERR("cannot create an anonymous file descriptor - neither " "memfd_secret() nor memfd_create() are defined"); } #endif /* !(defined __NR_memfd_secret) && !(defined __NR_memfd_create) */ return fd; - -#endif /* !__APPLE__ */ } -size_t get_max_file_size(void) { return OFF_T_MAX; } - int os_set_file_size(int fd, size_t size) { -#ifdef __APPLE__ - (void)fd; // unused - (void)size; // unused - return 0; // ignored on MacOSX -#else errno = 0; int ret = ftruncate(fd, size); if (ret) { LOG_PERR("ftruncate(%i, %zu) failed", fd, size); } return ret; -#endif /* __APPLE__ */ -} - -umf_result_t os_translate_mem_protection_flags(unsigned in_protection, - unsigned *out_protection) { - // translate protection - combination of 'umf_mem_protection_flags_t' flags - return os_translate_flags(in_protection, UMF_PROTECTION_MAX, - os_translate_mem_protection_one_flag, - out_protection); -} - -static int os_translate_purge_advise(umf_purge_advise_t advise) { - switch (advise) { - case UMF_PURGE_LAZY: - return MADV_FREE; - case UMF_PURGE_FORCE: - return MADV_DONTNEED; - } - assert(0); - return -1; -} - -void *os_mmap(void *hint_addr, size_t length, int prot, int flag, int fd, - size_t fd_offset) { - fd = (fd == 0) ? -1 : fd; - if (fd == -1) { - // MAP_ANONYMOUS - the mapping is not backed by any file - flag |= MAP_ANONYMOUS; - } - - void *ptr = mmap(hint_addr, length, prot, flag, fd, fd_offset); - if (ptr == MAP_FAILED) { - return NULL; - } - - return ptr; -} - -int os_munmap(void *addr, size_t length) { return munmap(addr, length); } - -size_t os_get_page_size(void) { return sysconf(_SC_PAGE_SIZE); } - -int os_purge(void *addr, size_t length, int advice) { - return madvise(addr, length, os_translate_purge_advise(advice)); -} - -void os_strerror(int errnum, char *buf, size_t buflen) { - strerror_r(errnum, buf, buflen); -} - -int os_getpid(void) { return getpid(); } - -umf_result_t os_duplicate_fd(int pid, int fd_in, int *fd_out) { -// pidfd_getfd(2) is used to obtain a duplicate of another process's file descriptor. -// Permission to duplicate another process's file descriptor -// is governed by a ptrace access mode PTRACE_MODE_ATTACH_REALCREDS check (see ptrace(2)) -// that can be changed using the /proc/sys/kernel/yama/ptrace_scope interface. -// pidfd_getfd(2) is supported since Linux 5.6 -// pidfd_open(2) is supported since Linux 5.3 -#if defined(__NR_pidfd_open) && defined(__NR_pidfd_getfd) - errno = 0; - int pid_fd = syscall(SYS_pidfd_open, pid, 0); - if (pid_fd == -1) { - LOG_PDEBUG("SYS_pidfd_open"); - return UMF_RESULT_ERROR_UNKNOWN; - } - - int fd_dup = syscall(SYS_pidfd_getfd, pid_fd, fd_in, 0); - close(pid_fd); - if (fd_dup == -1) { - LOG_PDEBUG("SYS_pidfd_getfd"); - return UMF_RESULT_ERROR_UNKNOWN; - } - - *fd_out = fd_dup; - - return UMF_RESULT_SUCCESS; -#else - // TODO: find another way to obtain a duplicate of another process's file descriptor - (void)pid; // unused - (void)fd_in; // unused - (void)fd_out; // unused - errno = ENOTSUP; - return UMF_RESULT_ERROR_NOT_SUPPORTED; // unsupported -#endif /* defined(__NR_pidfd_open) && defined(__NR_pidfd_getfd) */ -} - -umf_result_t os_close_fd(int fd) { - if (close(fd)) { - LOG_PERR("close() failed"); - return UMF_RESULT_ERROR_UNKNOWN; - } - - return UMF_RESULT_SUCCESS; } diff --git a/src/provider/provider_os_memory_macosx.c b/src/provider/provider_os_memory_macosx.c new file mode 100644 index 000000000..6b3a25b82 --- /dev/null +++ b/src/provider/provider_os_memory_macosx.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023-2024 Intel Corporation + * + * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +*/ + +#include + +#include + +#include "provider_os_memory_internal.h" +#include "utils_log.h" + +umf_result_t os_translate_mem_visibility_flag(umf_memory_visibility_t in_flag, + unsigned *out_flag) { + switch (in_flag) { + case UMF_MEM_MAP_PRIVATE: + *out_flag = MAP_PRIVATE; + return UMF_RESULT_SUCCESS; + case UMF_MEM_MAP_SHARED: + return UMF_RESULT_ERROR_NOT_SUPPORTED; // not supported on MacOSX + } + return UMF_RESULT_ERROR_INVALID_ARGUMENT; +} + +// create an anonymous file descriptor +int os_create_anonymous_fd(unsigned translated_memory_flag) { + (void)translated_memory_flag; // unused + return 0; // ignored on MacOSX +} + +int os_set_file_size(int fd, size_t size) { + (void)fd; // unused + (void)size; // unused + return 0; // ignored on MacOSX +} diff --git a/src/provider/provider_os_memory_posix.c b/src/provider/provider_os_memory_posix.c new file mode 100644 index 000000000..c3a5c6a77 --- /dev/null +++ b/src/provider/provider_os_memory_posix.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2023-2024 Intel Corporation + * + * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +*/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "provider_os_memory_internal.h" +#include "utils_log.h" + +// maximum value of the off_t type +#define OFF_T_MAX \ + (sizeof(off_t) == sizeof(long long) \ + ? LLONG_MAX \ + : (sizeof(off_t) == sizeof(long) ? LONG_MAX : INT_MAX)) + +umf_result_t os_translate_mem_protection_one_flag(unsigned in_protection, + unsigned *out_protection) { + switch (in_protection) { + case UMF_PROTECTION_NONE: + *out_protection = PROT_NONE; + return UMF_RESULT_SUCCESS; + case UMF_PROTECTION_READ: + *out_protection = PROT_READ; + return UMF_RESULT_SUCCESS; + case UMF_PROTECTION_WRITE: + *out_protection = PROT_WRITE; + return UMF_RESULT_SUCCESS; + case UMF_PROTECTION_EXEC: + *out_protection = PROT_EXEC; + return UMF_RESULT_SUCCESS; + } + return UMF_RESULT_ERROR_INVALID_ARGUMENT; +} + +size_t get_max_file_size(void) { return OFF_T_MAX; } + +umf_result_t os_translate_mem_protection_flags(unsigned in_protection, + unsigned *out_protection) { + // translate protection - combination of 'umf_mem_protection_flags_t' flags + return os_translate_flags(in_protection, UMF_PROTECTION_MAX, + os_translate_mem_protection_one_flag, + out_protection); +} + +static int os_translate_purge_advise(umf_purge_advise_t advise) { + switch (advise) { + case UMF_PURGE_LAZY: + return MADV_FREE; + case UMF_PURGE_FORCE: + return MADV_DONTNEED; + } + return -1; +} + +void *os_mmap(void *hint_addr, size_t length, int prot, int flag, int fd, + size_t fd_offset) { + fd = (fd == 0) ? -1 : fd; + if (fd == -1) { + // MAP_ANONYMOUS - the mapping is not backed by any file + flag |= MAP_ANONYMOUS; + } + + void *ptr = mmap(hint_addr, length, prot, flag, fd, fd_offset); + if (ptr == MAP_FAILED) { + return NULL; + } + + return ptr; +} + +int os_munmap(void *addr, size_t length) { return munmap(addr, length); } + +size_t os_get_page_size(void) { return sysconf(_SC_PAGE_SIZE); } + +int os_purge(void *addr, size_t length, int advice) { + return madvise(addr, length, os_translate_purge_advise(advice)); +} + +void os_strerror(int errnum, char *buf, size_t buflen) { + strerror_r(errnum, buf, buflen); +} + +int os_getpid(void) { return getpid(); } + +umf_result_t os_duplicate_fd(int pid, int fd_in, int *fd_out) { +// pidfd_getfd(2) is used to obtain a duplicate of another process's file descriptor. +// Permission to duplicate another process's file descriptor +// is governed by a ptrace access mode PTRACE_MODE_ATTACH_REALCREDS check (see ptrace(2)) +// that can be changed using the /proc/sys/kernel/yama/ptrace_scope interface. +// pidfd_getfd(2) is supported since Linux 5.6 +// pidfd_open(2) is supported since Linux 5.3 +#if defined(__NR_pidfd_open) && defined(__NR_pidfd_getfd) + errno = 0; + int pid_fd = syscall(SYS_pidfd_open, pid, 0); + if (pid_fd == -1) { + LOG_PDEBUG("SYS_pidfd_open"); + return UMF_RESULT_ERROR_UNKNOWN; + } + + int fd_dup = syscall(SYS_pidfd_getfd, pid_fd, fd_in, 0); + close(pid_fd); + if (fd_dup == -1) { + LOG_PDEBUG("SYS_pidfd_getfd"); + return UMF_RESULT_ERROR_UNKNOWN; + } + + *fd_out = fd_dup; + + return UMF_RESULT_SUCCESS; +#else + // TODO: find another way to obtain a duplicate of another process's file descriptor + (void)pid; // unused + (void)fd_in; // unused + (void)fd_out; // unused + errno = ENOTSUP; + return UMF_RESULT_ERROR_NOT_SUPPORTED; // unsupported +#endif /* defined(__NR_pidfd_open) && defined(__NR_pidfd_getfd) */ +} + +umf_result_t os_close_fd(int fd) { + if (close(fd)) { + LOG_PERR("close() failed"); + return UMF_RESULT_ERROR_UNKNOWN; + } + + return UMF_RESULT_SUCCESS; +}