Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use memfd_create when available #105178

Merged
merged 15 commits into from
Aug 20, 2024
Merged
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);
am11 marked this conversation as resolved.
Show resolved Hide resolved
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;
}
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}

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);
am11 marked this conversation as resolved.
Show resolved Hide resolved
}
}
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);
jkotas marked this conversation as resolved.
Show resolved Hide resolved
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
Loading