Skip to content

Commit

Permalink
Use memfd_create when available (#105178)
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Sitnik <[email protected]>
Co-authored-by: Stephen Toub <[email protected]>
  • Loading branch information
3 people authored Aug 20, 2024
1 parent ef146da commit 0a594be
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -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, int isReadonly);

[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsMemfdSupported", SetLastError = true)]
private static partial int MemfdSupportedImpl();

private static volatile sbyte s_memfdSupported;

internal static bool IsMemfdSupported
{
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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
Link="Common\Interop\Unix\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
Link="Common\Interop\Unix\Interop.Errors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Close.cs"
Link="Common\Interop\Unix\System.Native\Interop.Close.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Fcntl.cs"
Link="Common\Interop\Unix\Interop.Fcntl.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs"
Expand Down Expand Up @@ -119,6 +121,8 @@
Link="Common\Interop\Unix\Interop.MAdvise.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ShmOpen.cs"
Link="Common\Interop\Unix\Interop.ShmOpen.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MemfdCreate.cs"
Link="Common\Interop\Unix\Interop.MemfdCreate.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Unlink.cs"
Link="Common\Interop\Unix\Interop.Unlink.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,13 @@ private static FileAccess TranslateProtectionsToFileAccess(Interop.Sys.MemoryMap

private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
{
return CreateSharedBackingObjectUsingMemory(protections, capacity, inheritability)
?? CreateSharedBackingObjectUsingFile(protections, capacity, inheritability);
return Interop.Sys.IsMemfdSupported ?
CreateSharedBackingObjectUsingMemoryMemfdCreate(protections, capacity, inheritability) :
CreateSharedBackingObjectUsingMemoryShmOpen(protections, capacity, inheritability)
?? CreateSharedBackingObjectUsingFile(protections, capacity, inheritability);
}

private static SafeFileHandle? CreateSharedBackingObjectUsingMemory(
private static SafeFileHandle? CreateSharedBackingObjectUsingMemoryShmOpen(
Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
{
// Determine the flags to use when creating the shared memory object
Expand Down Expand Up @@ -244,27 +246,66 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped
fd.Dispose();
throw;
}
}

private static string GenerateMapName()
{
// macOS shm_open documentation says that the sys-call can fail with ENAMETOOLONG if the name exceeds SHM_NAME_MAX characters.
// The problem is that SHM_NAME_MAX is not defined anywhere and is not consistent amongst macOS versions (arm64 vs x64 for example).
// It was reported in 2008 (https://lists.apple.com/archives/xcode-users/2008/Apr/msg00523.html),
// but considered to be by design (http://web.archive.org/web/20140109200632/http://lists.apple.com/archives/darwin-development/2003/Mar/msg00244.html).
// According to https://github.com/qt/qtbase/blob/1ed449e168af133184633d174fd7339a13d1d595/src/corelib/kernel/qsharedmemory.cpp#L53-L56 the actual value is 30.
// Some other OSS libs use 32 (we did as well, but it was not enough) or 31, but we prefer 30 just to be extra safe.
const int MaxNameLength = 30;
// The POSIX shared memory object name must begin with '/'. After that we just want something short (30) and unique.
const string NamePrefix = "/dotnet_";
return string.Create(MaxNameLength, 0, (span, state) =>
{
Span<char> guid = stackalloc char[32];
Guid.NewGuid().TryFormat(guid, out int charsWritten, "N");
Debug.Assert(charsWritten == 32);
NamePrefix.CopyTo(span);
guid.Slice(0, MaxNameLength - NamePrefix.Length).CopyTo(span.Slice(NamePrefix.Length));
Debug.Assert(Encoding.UTF8.GetByteCount(span) <= MaxNameLength); // the standard uses Utf8
});
}

private static SafeFileHandle CreateSharedBackingObjectUsingMemoryMemfdCreate(
Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
{
int isReadonly = ((protections & Interop.Sys.MemoryMappedProtections.PROT_READ) != 0 &&
(protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) == 0) ? 1 : 0;

static string GenerateMapName()
SafeFileHandle fd = Interop.Sys.MemfdCreate(GenerateMapName(), isReadonly);
if (fd.IsInvalid)
{
// macOS shm_open documentation says that the sys-call can fail with ENAMETOOLONG if the name exceeds SHM_NAME_MAX characters.
// The problem is that SHM_NAME_MAX is not defined anywhere and is not consistent amongst macOS versions (arm64 vs x64 for example).
// It was reported in 2008 (https://lists.apple.com/archives/xcode-users/2008/Apr/msg00523.html),
// but considered to be by design (http://web.archive.org/web/20140109200632/http://lists.apple.com/archives/darwin-development/2003/Mar/msg00244.html).
// According to https://github.com/qt/qtbase/blob/1ed449e168af133184633d174fd7339a13d1d595/src/corelib/kernel/qsharedmemory.cpp#L53-L56 the actual value is 30.
// Some other OSS libs use 32 (we did as well, but it was not enough) or 31, but we prefer 30 just to be extra safe.
const int MaxNameLength = 30;
// The POSIX shared memory object name must begin with '/'. After that we just want something short (30) and unique.
const string NamePrefix = "/dotnet_";
return string.Create(MaxNameLength, 0, (span, state) =>
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
fd.Dispose();

throw Interop.GetExceptionForIoErrno(errorInfo);
}

try
{
// 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,
// lseek fails on shared memory objects, causing the FileStream to think it's unseekable,
// causing it to preemptively throw from SetLength.
Interop.CheckIo(Interop.Sys.FTruncate(fd, capacity));

// SystemNative_MemfdCreate sets CLOEXEC implicitly. If the inheritability requested is Inheritable, remove CLOEXEC.
if (inheritability == HandleInheritability.Inheritable &&
Interop.Sys.Fcntl.SetFD(fd, 0) == -1)
{
Span<char> guid = stackalloc char[32];
Guid.NewGuid().TryFormat(guid, out int charsWritten, "N");
Debug.Assert(charsWritten == 32);
NamePrefix.CopyTo(span);
guid.Slice(0, MaxNameLength - NamePrefix.Length).CopyTo(span.Slice(NamePrefix.Length));
Debug.Assert(Encoding.UTF8.GetByteCount(span) <= MaxNameLength); // the standard uses Utf8
});
throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo());
}

return fd;
}
catch
{
fd.Dispose();
throw;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 2 additions & 0 deletions src/native/libs/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_Close)
DllImportEntry(SystemNative_Dup)
DllImportEntry(SystemNative_Unlink)
DllImportEntry(SystemNative_IsMemfdSupported)
DllImportEntry(SystemNative_MemfdCreate)
DllImportEntry(SystemNative_ShmOpen)
DllImportEntry(SystemNative_ShmUnlink)
DllImportEntry(SystemNative_GetReadDirRBufferSize)
Expand Down
68 changes: 67 additions & 1 deletion src/native/libs/System.Native/pal_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
#include "pal_types.h"

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <errno.h>
#include <fnmatch.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -369,6 +369,72 @@ int32_t SystemNative_Unlink(const char* path)
return result;
}

#ifdef __NR_memfd_create
#ifndef MFD_CLOEXEC
#define MFD_CLOEXEC 0x0001U
#endif
#ifndef MFD_ALLOW_SEALING
#define MFD_ALLOW_SEALING 0x0002U
#endif
#ifndef F_ADD_SEALS
#define F_ADD_SEALS (1024 + 9)
#endif
#ifndef F_SEAL_WRITE
#define F_SEAL_WRITE 0x0008
#endif
#endif

int32_t SystemNative_IsMemfdSupported(void)
{
#ifdef __NR_memfd_create
#ifdef TARGET_LINUX
struct utsname uts;
int32_t major, minor;

// memfd_create is known to only work properly on kernel version > 3.17.
// On earlier versions, it may raise SIGSEGV instead of returning ENOTSUP.
if (uname(&uts) == 0 && sscanf(uts.release, "%d.%d", &major, &minor) == 2 && (major < 3 || (major == 3 && minor < 17)))
{
return 0;
}
#endif

// Note that the name has no affect on file descriptor behavior. From linux manpage:
// Names do not affect the behavior of the file descriptor, and as such multiple files can have the same name without any side effects.
int32_t fd = (int32_t)syscall(__NR_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, int32_t isReadonly)
{
#ifdef __NR_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

int32_t fd = (int32_t)syscall(__NR_memfd_create, name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (!isReadonly || fd < 0) return fd;

// Add a write seal when readonly protection requested
while (fcntl(fd, F_ADD_SEALS, F_SEAL_WRITE) < 0 && errno == EINTR);
return fd;
#else
(void)name;
(void)isReadonly;
errno = ENOTSUP;
return -1;
#endif
}

intptr_t SystemNative_ShmOpen(const char* name, int32_t flags, int32_t mode)
{
#if defined(SHM_NAME_MAX) // macOS
Expand Down
14 changes: 14 additions & 0 deletions src/native/libs/System.Native/pal_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_IsMemfdSupported(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, int32_t isReadonly);

/**
* Open or create a shared memory object. Implemented as shim to shm_open(3).
*
Expand Down

0 comments on commit 0a594be

Please sign in to comment.