From 15e49578925525d4ba3057b3c4dee58733333add Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Sat, 20 Jul 2024 22:54:11 +0300 Subject: [PATCH] Use memfd_create when available --- .../Unix/System.Native/Interop.MemfdCreate.cs | 34 +++++++++++++++ .../src/System.IO.MemoryMappedFiles.csproj | 4 ++ .../MemoryMappedFile.Unix.cs | 22 +++++++--- .../src/System/Environment.SunOS.cs | 2 +- src/native/libs/Common/pal_config.h.in | 1 + src/native/libs/System.Native/entrypoints.c | 2 + src/native/libs/System.Native/pal_io.c | 42 +++++++++++++++++++ src/native/libs/System.Native/pal_io.h | 14 +++++++ src/native/libs/configure.cmake | 4 ++ 9 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemfdCreate.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemfdCreate.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemfdCreate.cs new file mode 100644 index 0000000000000..f323677e5788e --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemfdCreate.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_MemfdCreate", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static partial SafeFileHandle MemfdCreate(string name); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_MemfdSupported", SetLastError = true)] + private static partial int MemfdSupportedImpl(); + + private static volatile sbyte s_memfdSupported; + + internal static bool MemfdSupported + { + get + { + sbyte memfdSupported = s_memfdSupported; + if (memfdSupported == 0) + { + Interlocked.CompareExchange(ref s_memfdSupported, (sbyte)(MemfdSupportedImpl() == 1 ? 1 : -1), 0); + memfdSupported = s_memfdSupported; + } + return memfdSupported > 0; + } + } + } +} diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj index 8c7cd9cfdda07..d024cdd7d7311 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj +++ b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj @@ -91,6 +91,8 @@ Link="Common\Interop\Unix\Interop.Libraries.cs" /> + + diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs index 2c49129705d1f..c4c13e3703fd2 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs @@ -190,7 +190,14 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped do { mapName = GenerateMapName(); - fd = Interop.Sys.ShmOpen(mapName, flags, (int)perms); // Create the shared memory object. + if (Interop.Sys.MemfdSupported) + { + fd = Interop.Sys.MemfdCreate(mapName); + } + else + { + fd = Interop.Sys.ShmOpen(mapName, flags, (int)perms); // Create the shared memory object. + } if (fd.IsInvalid) { @@ -204,7 +211,7 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped // the result of native shm_open does not work well with our subsequent call to mmap. return null; } - else if (errorInfo.Error == Interop.Error.ENAMETOOLONG) + else if (!Interop.Sys.MemfdSupported && errorInfo.Error == Interop.Error.ENAMETOOLONG) { Debug.Fail($"shm_open failed with ENAMETOOLONG for {Encoding.UTF8.GetByteCount(mapName)} byte long name."); // in theory it should not happen anymore, but just to be extra safe we use the fallback @@ -219,10 +226,13 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped try { - // Unlink the shared memory object immediately so that it'll go away once all handles - // to it are closed (as with opened then unlinked files, it'll remain usable via - // the open handles even though it's unlinked and can't be opened anew via its name). - Interop.CheckIo(Interop.Sys.ShmUnlink(mapName)); + if (!Interop.Sys.MemfdSupported) + { + // Unlink the shared memory object immediately so that it'll go away once all handles + // to it are closed (as with opened then unlinked files, it'll remain usable via + // the open handles even though it's unlinked and can't be opened anew via its name). + Interop.CheckIo(Interop.Sys.ShmUnlink(mapName)); + } // Give it the right capacity. We do this directly with ftruncate rather // than via FileStream.SetLength after the FileStream is created because, on some systems, diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs index 84199a3d306d2..fb7ff75cb2e6c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs @@ -8,6 +8,6 @@ namespace System public static partial class Environment { public static long WorkingSet => - (long)(Interop.procfs.TryReadProcessStatusInfo(Interop.procfs.ProcPid.Self, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0); + (long)(Interop.procfs.TryReadProcessStatusInfo(ProcessId, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0); } } diff --git a/src/native/libs/Common/pal_config.h.in b/src/native/libs/Common/pal_config.h.in index 0960f6aa971c8..211bd2949ae42 100644 --- a/src/native/libs/Common/pal_config.h.in +++ b/src/native/libs/Common/pal_config.h.in @@ -11,6 +11,7 @@ #cmakedefine01 HAVE_F_DUPFD #cmakedefine01 HAVE_F_FULLFSYNC #cmakedefine01 HAVE_O_CLOEXEC +#cmakedefine01 HAVE_MEMFD_CREATE #cmakedefine01 HAVE_GETIFADDRS #cmakedefine01 HAVE_UTSNAME_DOMAINNAME #cmakedefine01 HAVE_STAT64 diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 51c761109159b..d9f9b4d537b0a 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -62,6 +62,8 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_Close) DllImportEntry(SystemNative_Dup) DllImportEntry(SystemNative_Unlink) + DllImportEntry(SystemNative_MemfdSupported) + DllImportEntry(SystemNative_MemfdCreate) DllImportEntry(SystemNative_ShmOpen) DllImportEntry(SystemNative_ShmUnlink) DllImportEntry(SystemNative_GetReadDirRBufferSize) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index 4051656d35ac0..8a0b85d79f844 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -369,6 +369,48 @@ int32_t SystemNative_Unlink(const char* path) return result; } +int32_t SystemNative_MemfdSupported(void) +{ +#if HAVE_MEMFD_CREATE +#ifdef TARGET_LINUX + struct utsname uts; + int32_t major, minor; + + // memfd_create is only known to work properly on kernel version > 3.17 and throws SIGSEGV instead of ENOTSUP + if (uname(&uts) == 0 && sscanf(uts.release, "%d.%d", &major, &minor) == 2 && (major < 3 || (major == 3 && minor < 17))) + { + return 0; + } +#endif + + int32_t fd = memfd_create("test", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd < 0) return 0; + + close(fd); + return 1; +#else + errno = ENOTSUP; + return 0; +#endif +} + +intptr_t SystemNative_MemfdCreate(const char* name) +{ +#if HAVE_MEMFD_CREATE +#if defined(SHM_NAME_MAX) // macOS + assert(strlen(name) <= SHM_NAME_MAX); +#elif defined(PATH_MAX) // other Unixes + assert(strlen(name) <= PATH_MAX); +#endif + + return memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING); +#else + (void)name; + errno = ENOTSUP; + return -1; +#endif +} + intptr_t SystemNative_ShmOpen(const char* name, int32_t flags, int32_t mode) { #if defined(SHM_NAME_MAX) // macOS diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 03fd94cea2541..a5a8261e56937 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -369,6 +369,20 @@ PALEXPORT intptr_t SystemNative_Dup(intptr_t oldfd); */ PALEXPORT int32_t SystemNative_Unlink(const char* path); +/** + * Check if the system supports memfd_create(2). + * + * Returns 1 if memfd_create is supported, 0 if not supported, or -1 on failure. Sets errno on failure. + */ +PALEXPORT int32_t SystemNative_MemfdSupported(void); + +/** + * Create an anonymous file descriptor. Implemented as shim to memfd_create(2). + * + * Returns file descriptor or -1 on failure. Sets errno on failure. + */ +PALEXPORT intptr_t SystemNative_MemfdCreate(const char* name); + /** * Open or create a shared memory object. Implemented as shim to shm_open(3). * diff --git a/src/native/libs/configure.cmake b/src/native/libs/configure.cmake index 871e5bd2ea809..07f7918406d92 100644 --- a/src/native/libs/configure.cmake +++ b/src/native/libs/configure.cmake @@ -137,6 +137,10 @@ check_symbol_exists( fcntl.h HAVE_F_FULLFSYNC) +check_function_exists( + memfd_create + HAVE_MEMFD_CREATE) + check_function_exists( getifaddrs HAVE_GETIFADDRS)