-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Use memfd_create when available #105178
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
Changes from 8 commits
15e4957
b946a15
78da311
56a091d
74adb28
d2ec4c9
67ca808
6a93c49
b8e89bc
4425b0e
4c3b65b
f69b0e4
26f991e
75faa79
22be83c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
|
|
||
| [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_MemfdSupported", 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 |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -244,27 +246,72 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped | |
| fd.Dispose(); | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| static string GenerateMapName() | ||
| 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) => | ||
| { | ||
| // 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) | ||
| { | ||
| SafeFileHandle fd = Interop.Sys.MemfdCreate(GenerateMapName()); | ||
| if (fd.IsInvalid) | ||
| { | ||
| Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); | ||
| fd.Dispose(); | ||
|
|
||
| throw Interop.GetExceptionForIoErrno(errorInfo); | ||
| } | ||
|
|
||
| try | ||
| { | ||
| // Add a write seal for readonly case when readonly protection requested | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given a request for these permissions, sealing like this makes sense. But just to make sure I fully understand, would such a request ever be useful? If you can't write to the newly created in-memory-only backing store, what can you do with such a thing?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that's a good point, I was focused on feature parity with shm_open that I didn't looked at the call chain. 😅 |
||
| if ((protections & Interop.Sys.MemoryMappedProtections.PROT_READ) != 0 && | ||
| (protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) == 0 && | ||
| Interop.Sys.Fcntl.SetSealWrite(fd) == -1) | ||
| { | ||
| // seal write failed | ||
| throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo()); | ||
| } | ||
|
|
||
| // 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.
|
||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -369,6 +369,51 @@ 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 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 = 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); | |||||||||||||||||||||||||||||||||||||||||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can avoid Current benchmakrs:
|
|||||||||||||||||||||||||||||||||||||||||||||||
| #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 | |||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -621,6 +666,13 @@ int32_t SystemNative_Pipe(int32_t pipeFds[2], int32_t flags) | ||||||||||||||||||||||||||||||||||||||||||||||
| return result; | |||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||
| int32_t SystemNative_FcntlSetSealWrite(intptr_t fd) | |||||||||||||||||||||||||||||||||||||||||||||||
| { | |||||||||||||||||||||||||||||||||||||||||||||||
| int result; | |||||||||||||||||||||||||||||||||||||||||||||||
| while ((result = fcntl(ToFileDescriptor(fd), F_ADD_SEALS, F_SEAL_WRITE)) < 0 && errno == EINTR); | |||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
|||||||||||||||||||||||||||||||||||||||||||||||
| return result; | |||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||
| int32_t SystemNative_FcntlSetFD(intptr_t fd, int32_t flags) | |||||||||||||||||||||||||||||||||||||||||||||||
| { | |||||||||||||||||||||||||||||||||||||||||||||||
| int result; | |||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.