diff --git a/.github/workflows/devdax.yml b/.github/workflows/devdax.yml new file mode 100644 index 000000000..3f2b3afc9 --- /dev/null +++ b/.github/workflows/devdax.yml @@ -0,0 +1,66 @@ +# This workflow builds and tests the devdax memory provider. +# It requires a DAX device (e.g. /dev/dax0.0) configured in the OS. +# This DAX device should be specified using UMF_TESTS_DEVDAX_PATH and UMF_TESTS_DEVDAX_SIZE +# CI environment variables. + +name: DevDax + +on: [workflow_call] + +permissions: + contents: read + +env: + UMF_TESTS_DEVDAX_PATH : "/dev/dax0.0" + UMF_TESTS_DEVDAX_SIZE : 1054867456 + BUILD_DIR : "${{github.workspace}}/build" + INSTL_DIR : "${{github.workspace}}/../install-dir" + +jobs: + devdax: + name: Build + # run only on upstream; forks may not have a DAX device + if: github.repository == 'oneapi-src/unified-memory-framework' + strategy: + matrix: + build_type: [Debug, Release] + shared_library: ['ON', 'OFF'] + + runs-on: ["DSS-DEVDAX", "DSS-Ubuntu"] + steps: + - name: Check if the devdax exists + run: | + ndctl list -N --device-dax + ls -al ${{env.UMF_TESTS_DEVDAX_PATH}} + + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + + - name: Configure build + run: > + cmake + -B ${{env.BUILD_DIR}} + -DCMAKE_INSTALL_PREFIX="${{env.INSTL_DIR}}" + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} + -DCMAKE_C_COMPILER=gcc + -DCMAKE_CXX_COMPILER=g++ + -DUMF_BUILD_SHARED_LIBRARY=${{matrix.shared_library}} + -DUMF_BUILD_BENCHMARKS=OFF + -DUMF_BUILD_TESTS=ON + -DUMF_BUILD_GPU_TESTS=OFF + -DUMF_BUILD_GPU_EXAMPLES=OFF + -DUMF_FORMAT_CODE_STYLE=OFF + -DUMF_DEVELOPER_MODE=ON + -DUMF_BUILD_LIBUMF_POOL_DISJOINT=ON + -DUMF_BUILD_LIBUMF_POOL_JEMALLOC=ON + -DUMF_BUILD_LEVEL_ZERO_PROVIDER=OFF + -DUMF_TESTS_FAIL_ON_SKIP=ON + + - name: Build UMF + run: cmake --build ${{env.BUILD_DIR}} --config ${{matrix.build_type}} -j $(nproc) + + - name: Run only devdax tests + working-directory: ${{env.BUILD_DIR}} + run: ctest -C ${{matrix.build_type}} -R devdax -V diff --git a/.github/workflows/pr_push.yml b/.github/workflows/pr_push.yml index c42d6e098..4c7a27c1d 100644 --- a/.github/workflows/pr_push.yml +++ b/.github/workflows/pr_push.yml @@ -85,6 +85,9 @@ jobs: name: Basic builds needs: [FastBuild] uses: ./.github/workflows/basic.yml + DevDax: + needs: [FastBuild] + uses: ./.github/workflows/devdax.yml Sanitizers: needs: [FastBuild] uses: ./.github/workflows/sanitizers.yml diff --git a/CMakeLists.txt b/CMakeLists.txt index df2e10c16..2ceb75368 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -419,6 +419,9 @@ endif() if(NOT UMF_DISABLE_HWLOC) add_optional_symbol(umfOsMemoryProviderOps) + if(LINUX) + add_optional_symbol(umfDevDaxMemoryProviderOps) + endif() endif() add_subdirectory(src) diff --git a/README.md b/README.md index 0004f0923..64894ecf2 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,22 @@ Additionally, required for tests: 5) Required packages: - liblevel-zero-dev (Linux) or level-zero-sdk (Windows) +#### DevDax memory provider (Linux only) + +A memory provider that provides memory from a device DAX (a character device file /dev/daxX.Y). +It can be used when large memory mappings are needed. + +The DevDax memory provider does not support the free operation +(`umfMemoryProviderFree()` always returns `UMF_RESULT_ERROR_NOT_SUPPORTED`), +so it should be used with a pool manager that will take over +the managing of the provided memory - for example the jemalloc pool +with the `disable_provider_free` parameter set to true. + +##### Requirements + +1) Linux OS +2) A character device file /dev/daxX.Y created in the OS. + ### Memory pool managers #### Proxy pool (part of libumf) diff --git a/include/umf/providers/provider_devdax_memory.h b/include/umf/providers/provider_devdax_memory.h new file mode 100644 index 000000000..113d38372 --- /dev/null +++ b/include/umf/providers/provider_devdax_memory.h @@ -0,0 +1,58 @@ +/* + * 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 +*/ + +#ifndef UMF_DEVDAX_MEMORY_PROVIDER_H +#define UMF_DEVDAX_MEMORY_PROVIDER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// @cond +#define UMF_DEVDAX_RESULTS_START_FROM 2000 +/// @endcond + +/// @brief Memory provider settings struct +typedef struct umf_devdax_memory_provider_params_t { + /// path of the device DAX + char *path; + /// size of the device DAX in bytes + size_t size; + /// combination of 'umf_mem_protection_flags_t' flags + unsigned protection; +} umf_devdax_memory_provider_params_t; + +/// @brief Devdax Memory Provider operation results +typedef enum umf_devdax_memory_provider_native_error { + UMF_DEVDAX_RESULT_SUCCESS = UMF_DEVDAX_RESULTS_START_FROM, ///< Success + UMF_DEVDAX_RESULT_ERROR_ALLOC_FAILED, ///< Memory allocation failed + UMF_DEVDAX_RESULT_ERROR_ADDRESS_NOT_ALIGNED, ///< Allocated address is not aligned + UMF_DEVDAX_RESULT_ERROR_FREE_FAILED, ///< Memory deallocation failed + UMF_DEVDAX_RESULT_ERROR_PURGE_FORCE_FAILED, ///< Force purging failed +} umf_devdax_memory_provider_native_error_t; + +umf_memory_provider_ops_t *umfDevDaxMemoryProviderOps(void); + +/// @brief Create default params for the devdax memory provider +static inline umf_devdax_memory_provider_params_t +umfDevDaxMemoryProviderParamsDefault(char *path, size_t size) { + umf_devdax_memory_provider_params_t params = { + path, /* path of the device DAX */ + size, /* size of the device DAX in bytes */ + UMF_PROTECTION_READ | UMF_PROTECTION_WRITE, /* protection */ + }; + + return params; +} + +#ifdef __cplusplus +} +#endif + +#endif /* UMF_DEVDAX_MEMORY_PROVIDER_H */ diff --git a/scripts/docs_config/api.rst b/scripts/docs_config/api.rst index ea0912785..5e39361e1 100644 --- a/scripts/docs_config/api.rst +++ b/scripts/docs_config/api.rst @@ -96,6 +96,14 @@ A memory provider that provides memory from L0 device. .. doxygenfile:: provider_level_zero.h :sections: define enum typedef func var +DevDax Memory Provider +------------------------------------------ + +A memory provider that provides memory from a device DAX (a character device file /dev/daxX.Y). + +.. doxygenfile:: provider_devdax_memory.h + :sections: define enum typedef func var + Memspace ========================================== diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 839bb8bd2..00aeb8a47 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -101,6 +101,7 @@ set(UMF_SOURCES_MACOSX libumf_linux.c) set(UMF_SOURCES_WINDOWS libumf_windows.c) set(UMF_SOURCES_COMMON_LINUX_MACOSX + provider/provider_devdax_memory.c provider/provider_os_memory.c provider/provider_os_memory_posix.c memtargets/memtarget_numa.c diff --git a/src/provider/provider_devdax_memory.c b/src/provider/provider_devdax_memory.c new file mode 100644 index 000000000..68f2689d8 --- /dev/null +++ b/src/provider/provider_devdax_memory.c @@ -0,0 +1,523 @@ +/* + * 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 + +#include "base_alloc_global.h" +#include "provider_devdax_memory_internal.h" +#include "provider_os_memory_internal.h" +#include "utils_common.h" +#include "utils_concurrency.h" +#include "utils_log.h" + +#include +#include +#include + +#define NODESET_STR_BUF_LEN 1024 + +#define TLS_MSG_BUF_LEN 1024 + +typedef struct devdax_last_native_error_t { + int32_t native_error; + int errno_value; + char msg_buff[TLS_MSG_BUF_LEN]; +} devdax_last_native_error_t; + +static __TLS devdax_last_native_error_t TLS_last_native_error; + +// helper values used only in the Native_error_str array +#define _UMF_DEVDAX_RESULT_SUCCESS \ + (UMF_DEVDAX_RESULT_SUCCESS - UMF_DEVDAX_RESULT_SUCCESS) +#define _UMF_DEVDAX_RESULT_ERROR_ALLOC_FAILED \ + (UMF_DEVDAX_RESULT_ERROR_ALLOC_FAILED - UMF_DEVDAX_RESULT_SUCCESS) +#define _UMF_DEVDAX_RESULT_ERROR_ADDRESS_NOT_ALIGNED \ + (UMF_DEVDAX_RESULT_ERROR_ADDRESS_NOT_ALIGNED - UMF_DEVDAX_RESULT_SUCCESS) +#define _UMF_DEVDAX_RESULT_ERROR_FREE_FAILED \ + (UMF_DEVDAX_RESULT_ERROR_FREE_FAILED - UMF_DEVDAX_RESULT_SUCCESS) +#define _UMF_DEVDAX_RESULT_ERROR_PURGE_FORCE_FAILED \ + (UMF_DEVDAX_RESULT_ERROR_PURGE_FORCE_FAILED - UMF_DEVDAX_RESULT_SUCCESS) + +static const char *Native_error_str[] = { + [_UMF_DEVDAX_RESULT_SUCCESS] = "success", + [_UMF_DEVDAX_RESULT_ERROR_ALLOC_FAILED] = "memory allocation failed", + [_UMF_DEVDAX_RESULT_ERROR_ADDRESS_NOT_ALIGNED] = + "allocated address is not aligned", + [_UMF_DEVDAX_RESULT_ERROR_FREE_FAILED] = "memory deallocation failed", + [_UMF_DEVDAX_RESULT_ERROR_PURGE_FORCE_FAILED] = "force purging failed", +}; + +static void devdax_store_last_native_error(int32_t native_error, + int errno_value) { + TLS_last_native_error.native_error = native_error; + TLS_last_native_error.errno_value = errno_value; +} + +static umf_result_t +devdax_translate_params(umf_devdax_memory_provider_params_t *in_params, + devdax_memory_provider_t *provider) { + umf_result_t result; + + result = os_translate_mem_protection_flags(in_params->protection, + &provider->protection); + if (result != UMF_RESULT_SUCCESS) { + LOG_ERR("incorrect memory protection flags: %u", in_params->protection); + return result; + } + + return UMF_RESULT_SUCCESS; +} + +static umf_result_t devdax_initialize(void *params, void **provider) { + umf_result_t ret; + + if (provider == NULL || params == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + umf_devdax_memory_provider_params_t *in_params = + (umf_devdax_memory_provider_params_t *)params; + + if (in_params->path == NULL) { + LOG_ERR("devdax path is missing"); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + if (in_params->size == 0) { + LOG_ERR("devdax size is 0"); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + devdax_memory_provider_t *devdax_provider = + umf_ba_global_alloc(sizeof(*devdax_provider)); + if (!devdax_provider) { + return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY; + } + + memset(devdax_provider, 0, sizeof(*devdax_provider)); + + ret = devdax_translate_params(in_params, devdax_provider); + if (ret != UMF_RESULT_SUCCESS) { + goto err_free_devdax_provider; + } + + devdax_provider->size = in_params->size; + if (util_copy_path(in_params->path, devdax_provider->path, PATH_MAX)) { + goto err_free_devdax_provider; + } + + int fd = os_devdax_open(in_params->path); + if (fd == -1) { + LOG_ERR("cannot open the device DAX: %s", in_params->path); + ret = UMF_RESULT_ERROR_INVALID_ARGUMENT; + goto err_free_devdax_provider; + } + + devdax_provider->base = os_devdax_mmap(NULL, devdax_provider->size, + devdax_provider->protection, fd); + utils_close_fd(fd); + if (devdax_provider->base == NULL) { + LOG_PDEBUG("devdax memory mapping failed (path=%s, size=%zu)", + in_params->path, devdax_provider->size); + ret = UMF_RESULT_ERROR_UNKNOWN; + goto err_free_devdax_provider; + } + + LOG_DEBUG("devdax memory mapped (path=%s, size=%zu, addr=%p)", + in_params->path, devdax_provider->size, devdax_provider->base); + + if (util_mutex_init(&devdax_provider->lock) == NULL) { + LOG_ERR("lock init failed"); + ret = UMF_RESULT_ERROR_UNKNOWN; + goto err_unmap_devdax; + } + + *provider = devdax_provider; + + return UMF_RESULT_SUCCESS; + +err_unmap_devdax: + os_munmap(devdax_provider->base, devdax_provider->size); +err_free_devdax_provider: + umf_ba_global_free(devdax_provider); + return ret; +} + +static void devdax_finalize(void *provider) { + if (provider == NULL) { + assert(0); + return; + } + + devdax_memory_provider_t *devdax_provider = provider; + util_mutex_destroy_not_free(&devdax_provider->lock); + os_munmap(devdax_provider->base, devdax_provider->size); + umf_ba_global_free(devdax_provider); +} + +static int devdax_alloc_aligned(size_t length, size_t alignment, void *base, + size_t size, os_mutex_t *lock, void **out_addr, + size_t *offset) { + assert(out_addr); + + if (util_mutex_lock(lock)) { + LOG_ERR("locking file offset failed"); + return -1; + } + + uintptr_t ptr = (uintptr_t)base + *offset; + uintptr_t rest_of_div = alignment ? (ptr % alignment) : 0; + + if (alignment > 0 && rest_of_div > 0) { + ptr += alignment - rest_of_div; + } + + size_t new_offset = ptr - (uintptr_t)base + length; + + if (new_offset > size) { + util_mutex_unlock(lock); + LOG_ERR("cannot allocate more memory than the device DAX size: %zu", + size); + return -1; + } + + *offset = new_offset; + *out_addr = (void *)ptr; + + util_mutex_unlock(lock); + + return 0; +} + +static umf_result_t devdax_alloc(void *provider, size_t size, size_t alignment, + void **resultPtr) { + int ret; + + if (provider == NULL || resultPtr == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + // alignment must be a power of two and a multiple of sizeof(void *) + if (alignment && + ((alignment & (alignment - 1)) || (alignment % sizeof(void *)))) { + LOG_ERR("wrong alignment: %zu (not a power of 2 or a multiple of " + "sizeof(void *))", + alignment); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + devdax_memory_provider_t *devdax_provider = + (devdax_memory_provider_t *)provider; + + void *addr = NULL; + errno = 0; + ret = devdax_alloc_aligned(size, alignment, devdax_provider->base, + devdax_provider->size, &devdax_provider->lock, + &addr, &devdax_provider->offset); + if (ret) { + devdax_store_last_native_error(UMF_DEVDAX_RESULT_ERROR_ALLOC_FAILED, 0); + LOG_ERR("memory allocation failed"); + return UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC; + } + + *resultPtr = addr; + + return UMF_RESULT_SUCCESS; +} + +// free() is not supported +static umf_result_t devdax_free(void *provider, void *ptr, size_t size) { + (void)provider; // unused + (void)ptr; // unused + (void)size; // unused + + return UMF_RESULT_ERROR_NOT_SUPPORTED; +} + +static void devdax_get_last_native_error(void *provider, const char **ppMessage, + int32_t *pError) { + (void)provider; // unused + + if (ppMessage == NULL || pError == NULL) { + assert(0); + return; + } + + *pError = TLS_last_native_error.native_error; + if (TLS_last_native_error.errno_value == 0) { + *ppMessage = Native_error_str[*pError - UMF_DEVDAX_RESULT_SUCCESS]; + return; + } + + const char *msg; + size_t len; + size_t pos = 0; + + msg = Native_error_str[*pError - UMF_DEVDAX_RESULT_SUCCESS]; + len = strlen(msg); + memcpy(TLS_last_native_error.msg_buff + pos, msg, len + 1); + pos += len; + + msg = ": "; + len = strlen(msg); + memcpy(TLS_last_native_error.msg_buff + pos, msg, len + 1); + pos += len; + + os_strerror(TLS_last_native_error.errno_value, + TLS_last_native_error.msg_buff + pos, TLS_MSG_BUF_LEN - pos); + + *ppMessage = TLS_last_native_error.msg_buff; +} + +static umf_result_t devdax_get_recommended_page_size(void *provider, + size_t size, + size_t *page_size) { + (void)size; // unused + + if (provider == NULL || page_size == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + *page_size = os_get_page_size(); + + return UMF_RESULT_SUCCESS; +} + +static umf_result_t devdax_get_min_page_size(void *provider, void *ptr, + size_t *page_size) { + (void)ptr; // unused + + return devdax_get_recommended_page_size(provider, 0, page_size); +} + +static umf_result_t devdax_purge_lazy(void *provider, void *ptr, size_t size) { + (void)provider; // unused + (void)ptr; // unused + (void)size; // unused + // purge_lazy is unsupported in case of the devdax memory provider, + // because the MADV_FREE operation can be applied + // only to private anonymous pages (see madvise(2)). + return UMF_RESULT_ERROR_NOT_SUPPORTED; +} + +static umf_result_t devdax_purge_force(void *provider, void *ptr, size_t size) { + if (provider == NULL || ptr == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + errno = 0; + if (os_purge(ptr, size, UMF_PURGE_FORCE)) { + devdax_store_last_native_error( + UMF_DEVDAX_RESULT_ERROR_PURGE_FORCE_FAILED, errno); + LOG_PERR("force purging failed"); + return UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC; + } + return UMF_RESULT_SUCCESS; +} + +static const char *devdax_get_name(void *provider) { + (void)provider; // unused + return "DEVDAX"; +} + +static umf_result_t devdax_allocation_split(void *provider, void *ptr, + size_t totalSize, + size_t firstSize) { + (void)provider; + (void)ptr; + (void)totalSize; + (void)firstSize; + + return UMF_RESULT_SUCCESS; +} + +static umf_result_t devdax_allocation_merge(void *provider, void *lowPtr, + void *highPtr, size_t totalSize) { + (void)provider; + + if ((uintptr_t)highPtr <= (uintptr_t)lowPtr) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + if ((uintptr_t)highPtr - (uintptr_t)lowPtr <= totalSize) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + return UMF_RESULT_SUCCESS; +} + +typedef struct devdax_ipc_data_t { + char dd_path[PATH_MAX]; // path to the /dev/dax + size_t dd_size; // size of the /dev/dax + size_t offset; // offset of the data +} devdax_ipc_data_t; + +static umf_result_t devdax_get_ipc_handle_size(void *provider, size_t *size) { + if (provider == NULL || size == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + *size = sizeof(devdax_ipc_data_t); + + return UMF_RESULT_SUCCESS; +} + +static umf_result_t devdax_get_ipc_handle(void *provider, const void *ptr, + size_t size, void *providerIpcData) { + (void)size; // unused + + if (provider == NULL || ptr == NULL || providerIpcData == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + devdax_memory_provider_t *devdax_provider = + (devdax_memory_provider_t *)provider; + + devdax_ipc_data_t *devdax_ipc_data = (devdax_ipc_data_t *)providerIpcData; + devdax_ipc_data->offset = + (size_t)((uintptr_t)ptr - (uintptr_t)devdax_provider->base); + strncpy(devdax_ipc_data->dd_path, devdax_provider->path, PATH_MAX - 1); + devdax_ipc_data->dd_path[PATH_MAX - 1] = '\0'; + devdax_ipc_data->dd_size = devdax_provider->size; + + return UMF_RESULT_SUCCESS; +} + +static umf_result_t devdax_put_ipc_handle(void *provider, + void *providerIpcData) { + if (provider == NULL || providerIpcData == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + devdax_memory_provider_t *devdax_provider = + (devdax_memory_provider_t *)provider; + devdax_ipc_data_t *devdax_ipc_data = (devdax_ipc_data_t *)providerIpcData; + + // verify the path of the /dev/dax + if (strncmp(devdax_ipc_data->dd_path, devdax_provider->path, PATH_MAX)) { + LOG_ERR("devdax path mismatch (local: %s, ipc: %s)", + devdax_provider->path, devdax_ipc_data->dd_path); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + // verify the size of the /dev/dax + if (devdax_ipc_data->dd_size != devdax_provider->size) { + LOG_ERR("devdax size mismatch (local: %zu, ipc: %zu)", + devdax_provider->size, devdax_ipc_data->dd_size); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + return UMF_RESULT_SUCCESS; +} + +static umf_result_t devdax_open_ipc_handle(void *provider, + void *providerIpcData, void **ptr) { + if (provider == NULL || providerIpcData == NULL || ptr == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + devdax_memory_provider_t *devdax_provider = + (devdax_memory_provider_t *)provider; + devdax_ipc_data_t *devdax_ipc_data = (devdax_ipc_data_t *)providerIpcData; + + // verify it is the same devdax - first verify the path + if (strncmp(devdax_ipc_data->dd_path, devdax_provider->path, PATH_MAX)) { + LOG_ERR("devdax path mismatch (local: %s, ipc: %s)", + devdax_provider->path, devdax_ipc_data->dd_path); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + // verify the size of the /dev/dax + if (devdax_ipc_data->dd_size != devdax_provider->size) { + LOG_ERR("devdax size mismatch (local: %zu, ipc: %zu)", + devdax_provider->size, devdax_ipc_data->dd_size); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + umf_result_t ret = UMF_RESULT_SUCCESS; + int fd = os_devdax_open(devdax_provider->path); + if (fd <= 0) { + LOG_PERR("opening a devdax (%s) failed", devdax_provider->path); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + char *base = os_devdax_mmap(NULL, devdax_provider->size, + devdax_provider->protection, fd); + if (base == NULL) { + devdax_store_last_native_error(UMF_DEVDAX_RESULT_ERROR_ALLOC_FAILED, + errno); + LOG_PERR("devdax mapping failed (path: %s, size: %zu, protection: %i, " + "fd: %i)", + devdax_provider->path, devdax_provider->size, + devdax_provider->protection, fd); + ret = UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC; + } + + LOG_DEBUG("devdax mapped (path: %s, size: %zu, protection: %i, fd: %i, " + "offset: %zu)", + devdax_provider->path, devdax_provider->size, + devdax_provider->protection, fd, devdax_ipc_data->offset); + + (void)utils_close_fd(fd); + + *ptr = base + devdax_ipc_data->offset; + + return ret; +} + +static umf_result_t devdax_close_ipc_handle(void *provider, void *ptr, + size_t size) { + if (provider == NULL || ptr == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + devdax_memory_provider_t *devdax_provider = + (devdax_memory_provider_t *)provider; + + errno = 0; + int ret = os_munmap(devdax_provider->base, devdax_provider->size); + // ignore error when size == 0 + if (ret && (size > 0)) { + devdax_store_last_native_error(UMF_DEVDAX_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_DEVDAX_MEMORY_PROVIDER_OPS = { + .version = UMF_VERSION_CURRENT, + .initialize = devdax_initialize, + .finalize = devdax_finalize, + .alloc = devdax_alloc, + .free = devdax_free, + .get_last_native_error = devdax_get_last_native_error, + .get_recommended_page_size = devdax_get_recommended_page_size, + .get_min_page_size = devdax_get_min_page_size, + .get_name = devdax_get_name, + .ext.purge_lazy = devdax_purge_lazy, + .ext.purge_force = devdax_purge_force, + .ext.allocation_merge = devdax_allocation_merge, + .ext.allocation_split = devdax_allocation_split, + .ipc.get_ipc_handle_size = devdax_get_ipc_handle_size, + .ipc.get_ipc_handle = devdax_get_ipc_handle, + .ipc.put_ipc_handle = devdax_put_ipc_handle, + .ipc.open_ipc_handle = devdax_open_ipc_handle, + .ipc.close_ipc_handle = devdax_close_ipc_handle}; + +umf_memory_provider_ops_t *umfDevDaxMemoryProviderOps(void) { + return &UMF_DEVDAX_MEMORY_PROVIDER_OPS; +} diff --git a/src/provider/provider_devdax_memory_internal.h b/src/provider/provider_devdax_memory_internal.h new file mode 100644 index 000000000..981089e08 --- /dev/null +++ b/src/provider/provider_devdax_memory_internal.h @@ -0,0 +1,32 @@ +/* + * 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 +*/ + +#ifndef UMF_DEVDAX_MEMORY_PROVIDER_INTERNAL_H +#define UMF_DEVDAX_MEMORY_PROVIDER_INTERNAL_H + +#include + +#include "utils_concurrency.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct devdax_memory_provider_t { + char path[PATH_MAX]; // a path to the device DAX + size_t size; // size of the file used for memory mapping + void *base; // base address of memory mapping + size_t offset; // offset in the file used for memory mapping + os_mutex_t lock; // lock of ptr and offset + unsigned protection; // combination of OS-specific protection flags +} devdax_memory_provider_t; + +#ifdef __cplusplus +} +#endif + +#endif /* UMF_DEVDAX_MEMORY_PROVIDER_INTERNAL_H */ diff --git a/src/provider/provider_os_memory_internal.h b/src/provider/provider_os_memory_internal.h index b9a863f0d..e3bf4002c 100644 --- a/src/provider/provider_os_memory_internal.h +++ b/src/provider/provider_os_memory_internal.h @@ -97,6 +97,8 @@ 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); +void *os_devdax_mmap(void *hint_addr, size_t length, int prot, int fd); + int os_munmap(void *addr, size_t length); int os_purge(void *addr, size_t length, int advice); @@ -105,6 +107,8 @@ size_t os_get_page_size(void); void os_strerror(int errnum, char *buf, size_t buflen); +int os_devdax_open(const char *path); + #ifdef __cplusplus } #endif diff --git a/src/provider/provider_os_memory_linux.c b/src/provider/provider_os_memory_linux.c index e9fd53790..a472dd4b7 100644 --- a/src/provider/provider_os_memory_linux.c +++ b/src/provider/provider_os_memory_linux.c @@ -154,3 +154,27 @@ int os_set_file_size(int fd, size_t size) { } return ret; } + +/* + * MMap a /dev/dax device. + * First try to mmap with (MAP_SHARED_VALIDATE | MAP_SYNC) flags + * which allows flushing from the user-space. If MAP_SYNC fails + * try to mmap with MAP_SHARED flag (without MAP_SYNC). + */ +void *os_devdax_mmap(void *hint_addr, size_t length, int prot, int fd) { + void *ptr = + os_mmap(hint_addr, length, prot, MAP_SHARED_VALIDATE | MAP_SYNC, fd, 0); + if (ptr) { + LOG_DEBUG( + "devdax mapped with the (MAP_SHARED_VALIDATE | MAP_SYNC) flags"); + return ptr; + } + + ptr = os_mmap(hint_addr, length, prot, MAP_SHARED, fd, 0); + if (ptr) { + LOG_DEBUG("devdax mapped with the MAP_SHARED flag"); + return ptr; + } + + return NULL; +} diff --git a/src/provider/provider_os_memory_macosx.c b/src/provider/provider_os_memory_macosx.c index 0075ece64..87bae1b0e 100644 --- a/src/provider/provider_os_memory_macosx.c +++ b/src/provider/provider_os_memory_macosx.c @@ -59,3 +59,11 @@ int os_set_file_size(int fd, size_t size) { (void)size; // unused return 0; // ignored on MacOSX } + +void *os_devdax_mmap(void *hint_addr, size_t length, int prot, int fd) { + (void)hint_addr; // unused + (void)length; // unused + (void)prot; // unused + (void)fd; // unused + return NULL; // not supported on Windows +} diff --git a/src/provider/provider_os_memory_posix.c b/src/provider/provider_os_memory_posix.c index 9308f6a18..63f38cca3 100644 --- a/src/provider/provider_os_memory_posix.c +++ b/src/provider/provider_os_memory_posix.c @@ -6,10 +6,13 @@ */ #include +#include #include #include #include +#include #include +#include #include #include @@ -106,3 +109,39 @@ void os_strerror(int errnum, char *buf, size_t buflen) { LOG_PERR("Retrieving error code description failed"); } } + +// open a devdax +int os_devdax_open(const char *path) { + if (path == NULL) { + LOG_ERR("empty path"); + return -1; + } + + if (strstr(path, "/dev/dax") != path) { + LOG_ERR("path of the file \"%s\" does not start with \"/dev/dax\"", + path); + return -1; + } + + int fd = open(path, O_RDWR); + if (fd == -1) { + LOG_PERR("cannot open the file: %s", path); + return -1; + } + + struct stat statbuf; + int ret = stat(path, &statbuf); + if (ret) { + LOG_PERR("stat(%s) failed", path); + close(fd); + return -1; + } + + if (!S_ISCHR(statbuf.st_mode)) { + LOG_ERR("file %s is not a character device", path); + close(fd); + return -1; + } + + return fd; +} diff --git a/src/provider/provider_os_memory_windows.c b/src/provider/provider_os_memory_windows.c index 994f4d53c..f9232fb66 100644 --- a/src/provider/provider_os_memory_windows.c +++ b/src/provider/provider_os_memory_windows.c @@ -111,6 +111,14 @@ void *os_mmap(void *hint_addr, size_t length, int prot, int flag, int fd, return VirtualAlloc(hint_addr, length, MEM_RESERVE | MEM_COMMIT, prot); } +void *os_devdax_mmap(void *hint_addr, size_t length, int prot, int fd) { + (void)hint_addr; // unused + (void)length; // unused + (void)prot; // unused + (void)fd; // unused + return NULL; // not supported on Windows +} + int os_munmap(void *addr, size_t length) { // If VirtualFree() succeeds, the return value is nonzero. // If VirtualFree() fails, the return value is 0 (zero). @@ -151,3 +159,10 @@ size_t os_get_page_size(void) { void os_strerror(int errnum, char *buf, size_t buflen) { strerror_s(buf, buflen, errnum); } + +// open a devdax +int os_devdax_open(const char *path) { + (void)path; // unused + + return -1; +} diff --git a/src/utils/utils_common.c b/src/utils/utils_common.c index fc6bd58ef..e94126b33 100644 --- a/src/utils/utils_common.c +++ b/src/utils/utils_common.c @@ -75,3 +75,19 @@ const char *util_parse_var(const char *var, const char *option, return found; } + +int util_copy_path(const char *in_path, char out_path[], size_t path_max) { + // (- 1) because there should be a room for the terminating null byte ('\0') + size_t max_len = path_max - 1; + + if (strlen(in_path) > max_len) { + LOG_ERR("path of the %s file is longer than %zu bytes", in_path, + max_len); + return -1; + } + + strncpy(out_path, in_path, max_len); + out_path[path_max - 1] = '\0'; // the terminating null byte + + return 0; +} diff --git a/src/utils/utils_common.h b/src/utils/utils_common.h index 28579eb00..efef2c0c1 100644 --- a/src/utils/utils_common.h +++ b/src/utils/utils_common.h @@ -20,6 +20,8 @@ extern "C" { #endif +#define NAME_MAX 255 + #define DO_WHILE_EMPTY \ do { \ } while (0) @@ -85,6 +87,8 @@ int utils_close_fd(int fd); // obtain a duplicate of another process's file descriptor umf_result_t utils_duplicate_fd(int pid, int fd_in, int *fd_out); +int util_copy_path(const char *in_path, char out_path[], size_t path_max); + #ifdef __cplusplus } #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0d51643de..be6d9ad7d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -228,6 +228,10 @@ if(LINUX AND (NOT UMF_DISABLE_HWLOC)) # OS-specific functions are implemented NAME memtarget SRCS memspaces/memtarget.cpp LIBS ${LIBNUMA_LIBRARIES} ${LIBHWLOC_LIBRARIES}) + add_umf_test( + NAME provider_devdax_memory + SRCS provider_devdax_memory.cpp + LIBS ${UMF_UTILS_FOR_TEST}) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND UMF_BUILD_FUZZTESTS) add_subdirectory(fuzz) endif() @@ -346,6 +350,22 @@ if(LINUX) common/ipc_os_prov_common.c) add_umf_ipc_test(TEST ipc_os_prov_anon_fd) add_umf_ipc_test(TEST ipc_os_prov_shm) + + build_umf_test( + NAME + ipc_devdax_prov_consumer + SRCS + ipc_devdax_prov_consumer.c + common/ipc_common.c + common/ipc_os_prov_common.c) + build_umf_test( + NAME + ipc_devdax_prov_producer + SRCS + ipc_devdax_prov_producer.c + common/ipc_common.c + common/ipc_os_prov_common.c) + add_umf_ipc_test(TEST ipc_devdax_prov) endif() if(UMF_BUILD_GPU_TESTS AND UMF_BUILD_LEVEL_ZERO_PROVIDER) build_umf_test( diff --git a/test/common/ipc_common.c b/test/common/ipc_common.c index 7e08e79f0..18ce263d8 100644 --- a/test/common/ipc_common.c +++ b/test/common/ipc_common.c @@ -16,7 +16,6 @@ #define INET_ADDR "127.0.0.1" #define MSG_SIZE 1024 -#define RECV_BUFF_SIZE 1024 // consumer's response message #define CONSUMER_MSG \ @@ -111,7 +110,6 @@ int run_consumer(int port, umf_memory_provider_ops_t *provider_ops, void *provider_params, memcopy_callback_t memcopy_callback, void *memcopy_ctx) { char consumer_message[MSG_SIZE]; - char recv_buffer[RECV_BUFF_SIZE]; int producer_socket = -1; int ret = -1; umf_memory_provider_handle_t provider = NULL; @@ -138,16 +136,20 @@ int run_consumer(int port, umf_memory_provider_ops_t *provider_ops, goto err_umfMemoryProviderDestroy; } + // allocate the zeroed receive buffer + char *recv_buffer = calloc(1, IPC_handle_size); + if (!recv_buffer) { + fprintf(stderr, "[consumer] ERROR: out of memory\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 recv_len = recv(producer_socket, recv_buffer, RECV_BUFF_SIZE, 0); + ssize_t recv_len = recv(producer_socket, recv_buffer, IPC_handle_size, 0); if (recv_len < 0) { fprintf(stderr, "[consumer] ERROR: recv() failed\n"); goto err_close_producer_socket; diff --git a/test/ipc_devdax_prov.sh b/test/ipc_devdax_prov.sh new file mode 100755 index 000000000..7c5ba3675 --- /dev/null +++ b/test/ipc_devdax_prov.sh @@ -0,0 +1,34 @@ +# +# 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 + +set -e + +if [ "$UMF_TESTS_DEVDAX_PATH" = "" ]; then + echo "Test skipped, UMF_TESTS_DEVDAX_PATH is not set" + exit 0 +fi + +if [ "$UMF_TESTS_DEVDAX_SIZE" = "" ]; then + echo "Test skipped, UMF_TESTS_DEVDAX_SIZE is not set" + exit 0 +fi + +# port should be a number from the range <1024, 65535> +PORT=$(( 1024 + ( $$ % ( 65535 - 1024 )))) + +UMF_LOG_VAL="level:debug;flush:debug;output:stderr;pid:yes" + +echo "Starting ipc_devdax_prov CONSUMER on port $PORT ..." +UMF_LOG=$UMF_LOG_VAL ./umf_test-ipc_devdax_prov_consumer $PORT & + +echo "Waiting 1 sec ..." +sleep 1 + +echo "Starting ipc_devdax_prov PRODUCER on port $PORT ..." +UMF_LOG=$UMF_LOG_VAL ./umf_test-ipc_devdax_prov_producer $PORT diff --git a/test/ipc_devdax_prov_consumer.c b/test/ipc_devdax_prov_consumer.c new file mode 100644 index 000000000..05478e436 --- /dev/null +++ b/test/ipc_devdax_prov_consumer.c @@ -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 + */ + +#include +#include + +#include + +#include "ipc_common.h" +#include "ipc_os_prov_common.h" + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return -1; + } + + int port = atoi(argv[1]); + + char *path = getenv("UMF_TESTS_DEVDAX_PATH"); + if (path == NULL || path[0] == 0) { + fprintf(stderr, "Test skipped, UMF_TESTS_DEVDAX_PATH is not set\n"); + return 0; + } + + char *size = getenv("UMF_TESTS_DEVDAX_SIZE"); + if (size == NULL || size[0] == 0) { + fprintf(stderr, "Test skipped, UMF_TESTS_DEVDAX_SIZE is not set\n"); + return 0; + } + + umf_devdax_memory_provider_params_t devdax_params = + umfDevDaxMemoryProviderParamsDefault( + getenv("UMF_TESTS_DEVDAX_PATH"), + atol(getenv("UMF_TESTS_DEVDAX_SIZE"))); + + return run_consumer(port, umfDevDaxMemoryProviderOps(), &devdax_params, + memcopy, NULL); +} diff --git a/test/ipc_devdax_prov_producer.c b/test/ipc_devdax_prov_producer.c new file mode 100644 index 000000000..820d0fba9 --- /dev/null +++ b/test/ipc_devdax_prov_producer.c @@ -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 + */ + +#include +#include + +#include + +#include "ipc_common.h" +#include "ipc_os_prov_common.h" + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return -1; + } + + int port = atoi(argv[1]); + + char *path = getenv("UMF_TESTS_DEVDAX_PATH"); + if (path == NULL || path[0] == 0) { + fprintf(stderr, "Test skipped, UMF_TESTS_DEVDAX_PATH is not set\n"); + return 0; + } + + char *size = getenv("UMF_TESTS_DEVDAX_SIZE"); + if (size == NULL || size[0] == 0) { + fprintf(stderr, "Test skipped, UMF_TESTS_DEVDAX_SIZE is not set\n"); + return 0; + } + + umf_devdax_memory_provider_params_t devdax_params = + umfDevDaxMemoryProviderParamsDefault( + getenv("UMF_TESTS_DEVDAX_PATH"), + atol(getenv("UMF_TESTS_DEVDAX_SIZE"))); + + return run_producer(port, umfDevDaxMemoryProviderOps(), &devdax_params, + memcopy, NULL); +} diff --git a/test/provider_devdax_memory.cpp b/test/provider_devdax_memory.cpp new file mode 100644 index 000000000..8b0f0360f --- /dev/null +++ b/test/provider_devdax_memory.cpp @@ -0,0 +1,390 @@ +// 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 + +#ifndef _WIN32 +#include +#include +#include +#endif + +#include "base.hpp" + +#include "cpp_helpers.hpp" + +#include +#include + +using umf_test::test; + +#define INVALID_PTR ((void *)0x01) + +#define ASSERT_IS_ALIGNED(ptr, alignment) \ + ASSERT_EQ(((uintptr_t)ptr % alignment), 0) + +typedef enum purge_t { + PURGE_NONE = 0, + PURGE_LAZY = 1, + PURGE_FORCE = 2, +} purge_t; + +static const char *Native_error_str[] = { + "success", // UMF_DEVDAX_RESULT_SUCCESS + "memory allocation failed", // UMF_DEVDAX_RESULT_ERROR_ALLOC_FAILED + "allocated address is not aligned", // UMF_DEVDAX_RESULT_ERROR_ADDRESS_NOT_ALIGNED + "memory deallocation failed", // UMF_DEVDAX_RESULT_ERROR_FREE_FAILED + "force purging failed", // UMF_DEVDAX_RESULT_ERROR_PURGE_FORCE_FAILED +}; + +// test helpers + +static int compare_native_error_str(const char *message, int error) { + const char *error_str = Native_error_str[error - UMF_DEVDAX_RESULT_SUCCESS]; + size_t len = strlen(error_str); + return strncmp(message, error_str, len); +} + +using providerCreateExtParams = std::tuple; + +static void providerCreateExt(providerCreateExtParams params, + umf::provider_unique_handle_t *handle) { + umf_memory_provider_handle_t hProvider = nullptr; + auto [provider_ops, provider_params] = params; + + auto ret = + umfMemoryProviderCreate(provider_ops, provider_params, &hProvider); + ASSERT_EQ(ret, UMF_RESULT_SUCCESS); + ASSERT_NE(hProvider, nullptr); + + *handle = + umf::provider_unique_handle_t(hProvider, &umfMemoryProviderDestroy); +} + +struct umfProviderTest + : umf_test::test, + ::testing::WithParamInterface { + void SetUp() override { + char *path = getenv("UMF_TESTS_DEVDAX_PATH"); + if (path == nullptr || path[0] == 0) { + GTEST_SKIP() << "Test skipped, UMF_TESTS_DEVDAX_PATH is not set"; + } + + char *size = getenv("UMF_TESTS_DEVDAX_SIZE"); + if (size == nullptr || size[0] == 0) { + GTEST_SKIP() << "Test skipped, UMF_TESTS_DEVDAX_SIZE is not set"; + } + + test::SetUp(); + providerCreateExt(this->GetParam(), &provider); + umf_result_t umf_result = umfMemoryProviderGetMinPageSize( + provider.get(), nullptr, &page_size); + ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS); + + page_plus_64 = page_size + 64; + } + + void TearDown() override { test::TearDown(); } + + umf::provider_unique_handle_t provider; + size_t page_size; + size_t page_plus_64; +}; + +static void test_alloc_free_success(umf_memory_provider_handle_t provider, + size_t size, size_t alignment, + purge_t purge) { + void *ptr = nullptr; + + umf_result_t umf_result = + umfMemoryProviderAlloc(provider, size, alignment, &ptr); + ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS); + ASSERT_NE(ptr, nullptr); + + memset(ptr, 0xFF, size); + + if (purge == PURGE_LAZY) { + umf_result = umfMemoryProviderPurgeLazy(provider, ptr, size); + ASSERT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED); + } else if (purge == PURGE_FORCE) { + umf_result = umfMemoryProviderPurgeForce(provider, ptr, size); + ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS); + } + + umf_result = umfMemoryProviderFree(provider, ptr, size); + ASSERT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED); +} + +static void verify_last_native_error(umf_memory_provider_handle_t provider, + int32_t err) { + const char *message; + int32_t error; + umfMemoryProviderGetLastNativeError(provider, &message, &error); + ASSERT_EQ(error, err); + ASSERT_EQ(compare_native_error_str(message, error), 0); +} + +static void test_alloc_failure(umf_memory_provider_handle_t provider, + size_t size, size_t alignment, + umf_result_t result, int32_t err) { + void *ptr = nullptr; + umf_result_t umf_result = + umfMemoryProviderAlloc(provider, size, alignment, &ptr); + ASSERT_EQ(umf_result, result); + ASSERT_EQ(ptr, nullptr); + + if (umf_result == UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC) { + verify_last_native_error(provider, err); + } +} + +// TESTS + +// Test checking if devdax was mapped with the MAP_SYNC flag: +// 1) Open and read the /proc/self/smaps file. +// 2) Look for the section of the devdax (the /dev/daxX.Y path). +// 3) Check if the VmFlags of the /dev/daxX.Y contains the "sf" flag +// marking that the devdax was mapped with the MAP_SYNC flag. +TEST_F(test, test_if_mapped_with_MAP_SYNC) { + umf_memory_provider_handle_t hProvider = nullptr; + umf_result_t umf_result; + + char *path = getenv("UMF_TESTS_DEVDAX_PATH"); + if (path == nullptr || path[0] == 0) { + GTEST_SKIP() << "Test skipped, UMF_TESTS_DEVDAX_PATH is not set"; + } + + char *size_str = getenv("UMF_TESTS_DEVDAX_SIZE"); + if (size_str == nullptr || size_str[0] == 0) { + GTEST_SKIP() << "Test skipped, UMF_TESTS_DEVDAX_SIZE is not set"; + } + + size_t size = atol(size_str); + auto params = umfDevDaxMemoryProviderParamsDefault(path, size); + + umf_result = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), ¶ms, + &hProvider); + ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS); + ASSERT_NE(hProvider, nullptr); + + char *buf; + umf_result = umfMemoryProviderAlloc(hProvider, size, 0, (void **)&buf); + ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS); + ASSERT_NE(buf, nullptr); + memset(buf, 0, size); + + int fd = open("/proc/self/smaps", O_RDONLY); + ASSERT_NE(fd, -1); + + // number of bytes read from the file + ssize_t nbytes = 1; + // string starting from the path of the devdax + char *devdax = nullptr; + + // Read the "/proc/self/smaps" file + // until the path of the devdax is found + // or EOF is reached. + while (nbytes > 0 && devdax == nullptr) { + memset(buf, 0, nbytes); // erase previous data + nbytes = read(fd, buf, size); + // look for the path of the devdax + devdax = strstr(buf, path); + } + + // String starting from the "sf" flag + // marking that memory was mapped with the MAP_SYNC flag. + char *sf_flag = nullptr; + + if (devdax) { + // look for the "VmFlags:" string + char *VmFlags = strstr(devdax, "VmFlags:"); + if (VmFlags) { + // look for the EOL + char *eol = strstr(VmFlags, "\n"); + if (eol) { + // End the VmFlags string at EOL. + *eol = 0; + // Now the VmFlags string contains only one line with all VmFlags. + + // Look for the "sf" flag in VmFlags + // marking that memory was mapped + // with the MAP_SYNC flag. + sf_flag = strstr(VmFlags, "sf"); + } + } + } + + umf_result = umfMemoryProviderFree(hProvider, buf, size); + ASSERT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED); + + umfMemoryProviderDestroy(hProvider); + + // fail test if the "sf" flag was not found + ASSERT_NE(sf_flag, nullptr); +} + +// positive tests using test_alloc_free_success + +auto defaultParams = umfDevDaxMemoryProviderParamsDefault( + getenv("UMF_TESTS_DEVDAX_PATH"), + atol(getenv("UMF_TESTS_DEVDAX_SIZE") ? getenv("UMF_TESTS_DEVDAX_SIZE") + : "0")); + +INSTANTIATE_TEST_SUITE_P(devdaxProviderTest, umfProviderTest, + ::testing::Values(providerCreateExtParams{ + umfDevDaxMemoryProviderOps(), &defaultParams})); + +TEST_P(umfProviderTest, create_destroy) {} + +TEST_P(umfProviderTest, alloc_page64_align_0) { + test_alloc_free_success(provider.get(), page_plus_64, 0, PURGE_NONE); +} + +TEST_P(umfProviderTest, alloc_page64_align_page_div_2) { + test_alloc_free_success(provider.get(), page_plus_64, page_size / 2, + PURGE_NONE); +} + +TEST_P(umfProviderTest, purge_lazy) { + test_alloc_free_success(provider.get(), page_plus_64, 0, PURGE_LAZY); +} + +TEST_P(umfProviderTest, purge_force) { + test_alloc_free_success(provider.get(), page_plus_64, 0, PURGE_FORCE); +} + +// negative tests using test_alloc_failure + +TEST_P(umfProviderTest, alloc_page64_align_page_minus_1_WRONG_ALIGNMENT_1) { + test_alloc_failure(provider.get(), page_plus_64, page_size - 1, + UMF_RESULT_ERROR_INVALID_ARGUMENT, 0); +} + +TEST_P(umfProviderTest, alloc_page64_align_one_half_pages_WRONG_ALIGNMENT_2) { + test_alloc_failure(provider.get(), page_plus_64, + page_size + (page_size / 2), + UMF_RESULT_ERROR_INVALID_ARGUMENT, 0); +} + +TEST_P(umfProviderTest, alloc_page64_WRONG_ALIGNMENT_3_pages) { + test_alloc_failure(provider.get(), page_plus_64, 3 * page_size, + UMF_RESULT_ERROR_INVALID_ARGUMENT, 0); +} + +TEST_P(umfProviderTest, alloc_3_pages_WRONG_ALIGNMENT_3_pages) { + test_alloc_failure(provider.get(), 3 * page_size, 3 * page_size, + UMF_RESULT_ERROR_INVALID_ARGUMENT, 0); +} + +TEST_P(umfProviderTest, alloc_WRONG_SIZE) { + test_alloc_failure(provider.get(), -1, 0, + UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC, + UMF_DEVDAX_RESULT_ERROR_ALLOC_FAILED); +} + +// other positive tests + +TEST_P(umfProviderTest, get_min_page_size) { + size_t min_page_size; + umf_result_t umf_result = umfMemoryProviderGetMinPageSize( + provider.get(), nullptr, &min_page_size); + ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS); + ASSERT_LE(min_page_size, page_size); +} + +TEST_P(umfProviderTest, get_recommended_page_size) { + size_t min_page_size; + umf_result_t umf_result = umfMemoryProviderGetMinPageSize( + provider.get(), nullptr, &min_page_size); + ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS); + ASSERT_LE(min_page_size, page_size); + + size_t recommended_page_size; + umf_result = umfMemoryProviderGetRecommendedPageSize( + provider.get(), 0, &recommended_page_size); + ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS); + ASSERT_GE(recommended_page_size, min_page_size); +} + +TEST_P(umfProviderTest, get_name) { + const char *name = umfMemoryProviderGetName(provider.get()); + ASSERT_STREQ(name, "DEVDAX"); +} + +TEST_P(umfProviderTest, free_size_0_ptr_not_null) { + umf_result_t umf_result = + umfMemoryProviderFree(provider.get(), INVALID_PTR, 0); + ASSERT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED); +} + +TEST_P(umfProviderTest, free_NULL) { + umf_result_t umf_result = umfMemoryProviderFree(provider.get(), nullptr, 0); + ASSERT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED); +} + +// other negative tests + +TEST_P(umfProviderTest, free_INVALID_POINTER_SIZE_GT_0) { + umf_result_t umf_result = + umfMemoryProviderFree(provider.get(), INVALID_PTR, page_plus_64); + ASSERT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED); +} + +TEST_P(umfProviderTest, purge_lazy_INVALID_POINTER) { + umf_result_t umf_result = + umfMemoryProviderPurgeLazy(provider.get(), INVALID_PTR, 1); + ASSERT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED); +} + +TEST_P(umfProviderTest, purge_force_INVALID_POINTER) { + umf_result_t umf_result = + umfMemoryProviderPurgeForce(provider.get(), INVALID_PTR, 1); + ASSERT_EQ(umf_result, UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC); + + verify_last_native_error(provider.get(), + UMF_DEVDAX_RESULT_ERROR_PURGE_FORCE_FAILED); +} + +// negative tests + +TEST_F(test, create_empty_path) { + umf_memory_provider_handle_t hProvider = nullptr; + const char *path = ""; + auto wrong_params = + umfDevDaxMemoryProviderParamsDefault((char *)path, 4096); + auto ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), + &wrong_params, &hProvider); + EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(hProvider, nullptr); +} + +TEST_F(test, create_wrong_path) { + umf_memory_provider_handle_t hProvider = nullptr; + const char *path = "/tmp/dev/dax0.0"; + auto wrong_params = + umfDevDaxMemoryProviderParamsDefault((char *)path, 4096); + auto ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), + &wrong_params, &hProvider); + EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(hProvider, nullptr); +} + +TEST_F(test, create_wrong_path_not_exist) { + umf_memory_provider_handle_t hProvider = nullptr; + const char *path = "/dev/dax1.1"; + auto wrong_params = + umfDevDaxMemoryProviderParamsDefault((char *)path, 4096); + auto ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), + &wrong_params, &hProvider); + EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(hProvider, nullptr); +} + +TEST_F(test, create_wrong_size_0) { + umf_memory_provider_handle_t hProvider = nullptr; + const char *path = "/dev/dax0.0"; + auto wrong_params = umfDevDaxMemoryProviderParamsDefault((char *)path, 0); + auto ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), + &wrong_params, &hProvider); + EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(hProvider, nullptr); +} diff --git a/test/test_valgrind.sh b/test/test_valgrind.sh index ea4e155fb..4c8a5633a 100755 --- a/test/test_valgrind.sh +++ b/test/test_valgrind.sh @@ -88,6 +88,10 @@ for test in $(ls -1 umf_test-*); do echo "- SKIPPED" continue; # skip testing helper binaries used by the ipc_os_prov_* tests ;; + umf_test-ipc_devdax_prov_*) + echo "- SKIPPED" + continue; # skip testing helper binaries used by the ipc_devdax_prov_* tests + ;; umf_test-memspace_host_all) FILTER='--gtest_filter="-*allocsSpreadAcrossAllNumaNodes"' ;;