Skip to content

Commit 0a594be

Browse files
am11adamsitnikstephentoub
authored
Use memfd_create when available (#105178)
Co-authored-by: Adam Sitnik <[email protected]> Co-authored-by: Stephen Toub <[email protected]>
1 parent ef146da commit 0a594be

File tree

7 files changed

+184
-23
lines changed

7 files changed

+184
-23
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.InteropServices;
5+
using System.Threading;
6+
using Microsoft.Win32.SafeHandles;
7+
8+
internal static partial class Interop
9+
{
10+
internal static partial class Sys
11+
{
12+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_MemfdCreate", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
13+
internal static partial SafeFileHandle MemfdCreate(string name, int isReadonly);
14+
15+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsMemfdSupported", SetLastError = true)]
16+
private static partial int MemfdSupportedImpl();
17+
18+
private static volatile sbyte s_memfdSupported;
19+
20+
internal static bool IsMemfdSupported
21+
{
22+
get
23+
{
24+
sbyte memfdSupported = s_memfdSupported;
25+
if (memfdSupported == 0)
26+
{
27+
Interlocked.CompareExchange(ref s_memfdSupported, (sbyte)(MemfdSupportedImpl() == 1 ? 1 : -1), 0);
28+
memfdSupported = s_memfdSupported;
29+
}
30+
return memfdSupported > 0;
31+
}
32+
}
33+
}
34+
}

src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@
9191
Link="Common\Interop\Unix\Interop.Libraries.cs" />
9292
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
9393
Link="Common\Interop\Unix\Interop.Errors.cs" />
94+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Close.cs"
95+
Link="Common\Interop\Unix\System.Native\Interop.Close.cs" />
9496
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Fcntl.cs"
9597
Link="Common\Interop\Unix\Interop.Fcntl.cs" />
9698
<Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs"
@@ -119,6 +121,8 @@
119121
Link="Common\Interop\Unix\Interop.MAdvise.cs" />
120122
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ShmOpen.cs"
121123
Link="Common\Interop\Unix\Interop.ShmOpen.cs" />
124+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MemfdCreate.cs"
125+
Link="Common\Interop\Unix\Interop.MemfdCreate.cs" />
122126
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Unlink.cs"
123127
Link="Common\Interop\Unix\Interop.Unlink.cs" />
124128
</ItemGroup>

src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,13 @@ private static FileAccess TranslateProtectionsToFileAccess(Interop.Sys.MemoryMap
162162

163163
private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
164164
{
165-
return CreateSharedBackingObjectUsingMemory(protections, capacity, inheritability)
166-
?? CreateSharedBackingObjectUsingFile(protections, capacity, inheritability);
165+
return Interop.Sys.IsMemfdSupported ?
166+
CreateSharedBackingObjectUsingMemoryMemfdCreate(protections, capacity, inheritability) :
167+
CreateSharedBackingObjectUsingMemoryShmOpen(protections, capacity, inheritability)
168+
?? CreateSharedBackingObjectUsingFile(protections, capacity, inheritability);
167169
}
168170

169-
private static SafeFileHandle? CreateSharedBackingObjectUsingMemory(
171+
private static SafeFileHandle? CreateSharedBackingObjectUsingMemoryShmOpen(
170172
Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
171173
{
172174
// Determine the flags to use when creating the shared memory object
@@ -244,27 +246,66 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped
244246
fd.Dispose();
245247
throw;
246248
}
249+
}
250+
251+
private static string GenerateMapName()
252+
{
253+
// macOS shm_open documentation says that the sys-call can fail with ENAMETOOLONG if the name exceeds SHM_NAME_MAX characters.
254+
// The problem is that SHM_NAME_MAX is not defined anywhere and is not consistent amongst macOS versions (arm64 vs x64 for example).
255+
// It was reported in 2008 (https://lists.apple.com/archives/xcode-users/2008/Apr/msg00523.html),
256+
// but considered to be by design (http://web.archive.org/web/20140109200632/http://lists.apple.com/archives/darwin-development/2003/Mar/msg00244.html).
257+
// According to https://github.com/qt/qtbase/blob/1ed449e168af133184633d174fd7339a13d1d595/src/corelib/kernel/qsharedmemory.cpp#L53-L56 the actual value is 30.
258+
// 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.
259+
const int MaxNameLength = 30;
260+
// The POSIX shared memory object name must begin with '/'. After that we just want something short (30) and unique.
261+
const string NamePrefix = "/dotnet_";
262+
return string.Create(MaxNameLength, 0, (span, state) =>
263+
{
264+
Span<char> guid = stackalloc char[32];
265+
Guid.NewGuid().TryFormat(guid, out int charsWritten, "N");
266+
Debug.Assert(charsWritten == 32);
267+
NamePrefix.CopyTo(span);
268+
guid.Slice(0, MaxNameLength - NamePrefix.Length).CopyTo(span.Slice(NamePrefix.Length));
269+
Debug.Assert(Encoding.UTF8.GetByteCount(span) <= MaxNameLength); // the standard uses Utf8
270+
});
271+
}
272+
273+
private static SafeFileHandle CreateSharedBackingObjectUsingMemoryMemfdCreate(
274+
Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
275+
{
276+
int isReadonly = ((protections & Interop.Sys.MemoryMappedProtections.PROT_READ) != 0 &&
277+
(protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) == 0) ? 1 : 0;
247278

248-
static string GenerateMapName()
279+
SafeFileHandle fd = Interop.Sys.MemfdCreate(GenerateMapName(), isReadonly);
280+
if (fd.IsInvalid)
249281
{
250-
// macOS shm_open documentation says that the sys-call can fail with ENAMETOOLONG if the name exceeds SHM_NAME_MAX characters.
251-
// The problem is that SHM_NAME_MAX is not defined anywhere and is not consistent amongst macOS versions (arm64 vs x64 for example).
252-
// It was reported in 2008 (https://lists.apple.com/archives/xcode-users/2008/Apr/msg00523.html),
253-
// but considered to be by design (http://web.archive.org/web/20140109200632/http://lists.apple.com/archives/darwin-development/2003/Mar/msg00244.html).
254-
// According to https://github.com/qt/qtbase/blob/1ed449e168af133184633d174fd7339a13d1d595/src/corelib/kernel/qsharedmemory.cpp#L53-L56 the actual value is 30.
255-
// 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.
256-
const int MaxNameLength = 30;
257-
// The POSIX shared memory object name must begin with '/'. After that we just want something short (30) and unique.
258-
const string NamePrefix = "/dotnet_";
259-
return string.Create(MaxNameLength, 0, (span, state) =>
282+
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
283+
fd.Dispose();
284+
285+
throw Interop.GetExceptionForIoErrno(errorInfo);
286+
}
287+
288+
try
289+
{
290+
// Give it the right capacity. We do this directly with ftruncate rather
291+
// than via FileStream.SetLength after the FileStream is created because, on some systems,
292+
// lseek fails on shared memory objects, causing the FileStream to think it's unseekable,
293+
// causing it to preemptively throw from SetLength.
294+
Interop.CheckIo(Interop.Sys.FTruncate(fd, capacity));
295+
296+
// SystemNative_MemfdCreate sets CLOEXEC implicitly. If the inheritability requested is Inheritable, remove CLOEXEC.
297+
if (inheritability == HandleInheritability.Inheritable &&
298+
Interop.Sys.Fcntl.SetFD(fd, 0) == -1)
260299
{
261-
Span<char> guid = stackalloc char[32];
262-
Guid.NewGuid().TryFormat(guid, out int charsWritten, "N");
263-
Debug.Assert(charsWritten == 32);
264-
NamePrefix.CopyTo(span);
265-
guid.Slice(0, MaxNameLength - NamePrefix.Length).CopyTo(span.Slice(NamePrefix.Length));
266-
Debug.Assert(Encoding.UTF8.GetByteCount(span) <= MaxNameLength); // the standard uses Utf8
267-
});
300+
throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo());
301+
}
302+
303+
return fd;
304+
}
305+
catch
306+
{
307+
fd.Dispose();
308+
throw;
268309
}
269310
}
270311

src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ namespace System
88
public static partial class Environment
99
{
1010
public static long WorkingSet =>
11-
(long)(Interop.procfs.TryReadProcessStatusInfo(Interop.procfs.ProcPid.Self, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0);
11+
(long)(Interop.procfs.TryReadProcessStatusInfo(ProcessId, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0);
1212
}
1313
}

src/native/libs/System.Native/entrypoints.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ static const Entry s_sysNative[] =
6161
DllImportEntry(SystemNative_Close)
6262
DllImportEntry(SystemNative_Dup)
6363
DllImportEntry(SystemNative_Unlink)
64+
DllImportEntry(SystemNative_IsMemfdSupported)
65+
DllImportEntry(SystemNative_MemfdCreate)
6466
DllImportEntry(SystemNative_ShmOpen)
6567
DllImportEntry(SystemNative_ShmUnlink)
6668
DllImportEntry(SystemNative_GetReadDirRBufferSize)

src/native/libs/System.Native/pal_io.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
#include "pal_types.h"
1111

1212
#include <assert.h>
13-
#include <errno.h>
1413
#include <fcntl.h>
14+
#include <errno.h>
1515
#include <fnmatch.h>
1616
#include <stdio.h>
1717
#include <stdlib.h>
@@ -369,6 +369,72 @@ int32_t SystemNative_Unlink(const char* path)
369369
return result;
370370
}
371371

372+
#ifdef __NR_memfd_create
373+
#ifndef MFD_CLOEXEC
374+
#define MFD_CLOEXEC 0x0001U
375+
#endif
376+
#ifndef MFD_ALLOW_SEALING
377+
#define MFD_ALLOW_SEALING 0x0002U
378+
#endif
379+
#ifndef F_ADD_SEALS
380+
#define F_ADD_SEALS (1024 + 9)
381+
#endif
382+
#ifndef F_SEAL_WRITE
383+
#define F_SEAL_WRITE 0x0008
384+
#endif
385+
#endif
386+
387+
int32_t SystemNative_IsMemfdSupported(void)
388+
{
389+
#ifdef __NR_memfd_create
390+
#ifdef TARGET_LINUX
391+
struct utsname uts;
392+
int32_t major, minor;
393+
394+
// memfd_create is known to only work properly on kernel version > 3.17.
395+
// On earlier versions, it may raise SIGSEGV instead of returning ENOTSUP.
396+
if (uname(&uts) == 0 && sscanf(uts.release, "%d.%d", &major, &minor) == 2 && (major < 3 || (major == 3 && minor < 17)))
397+
{
398+
return 0;
399+
}
400+
#endif
401+
402+
// Note that the name has no affect on file descriptor behavior. From linux manpage:
403+
// Names do not affect the behavior of the file descriptor, and as such multiple files can have the same name without any side effects.
404+
int32_t fd = (int32_t)syscall(__NR_memfd_create, "test", MFD_CLOEXEC | MFD_ALLOW_SEALING);
405+
if (fd < 0) return 0;
406+
407+
close(fd);
408+
return 1;
409+
#else
410+
errno = ENOTSUP;
411+
return 0;
412+
#endif
413+
}
414+
415+
intptr_t SystemNative_MemfdCreate(const char* name, int32_t isReadonly)
416+
{
417+
#ifdef __NR_memfd_create
418+
#if defined(SHM_NAME_MAX) // macOS
419+
assert(strlen(name) <= SHM_NAME_MAX);
420+
#elif defined(PATH_MAX) // other Unixes
421+
assert(strlen(name) <= PATH_MAX);
422+
#endif
423+
424+
int32_t fd = (int32_t)syscall(__NR_memfd_create, name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
425+
if (!isReadonly || fd < 0) return fd;
426+
427+
// Add a write seal when readonly protection requested
428+
while (fcntl(fd, F_ADD_SEALS, F_SEAL_WRITE) < 0 && errno == EINTR);
429+
return fd;
430+
#else
431+
(void)name;
432+
(void)isReadonly;
433+
errno = ENOTSUP;
434+
return -1;
435+
#endif
436+
}
437+
372438
intptr_t SystemNative_ShmOpen(const char* name, int32_t flags, int32_t mode)
373439
{
374440
#if defined(SHM_NAME_MAX) // macOS

src/native/libs/System.Native/pal_io.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,20 @@ PALEXPORT intptr_t SystemNative_Dup(intptr_t oldfd);
369369
*/
370370
PALEXPORT int32_t SystemNative_Unlink(const char* path);
371371

372+
/**
373+
* Check if the system supports memfd_create(2).
374+
*
375+
* Returns 1 if memfd_create is supported, 0 if not supported, or -1 on failure. Sets errno on failure.
376+
*/
377+
PALEXPORT int32_t SystemNative_IsMemfdSupported(void);
378+
379+
/**
380+
* Create an anonymous file descriptor. Implemented as shim to memfd_create(2).
381+
*
382+
* Returns file descriptor or -1 on failure. Sets errno on failure.
383+
*/
384+
PALEXPORT intptr_t SystemNative_MemfdCreate(const char* name, int32_t isReadonly);
385+
372386
/**
373387
* Open or create a shared memory object. Implemented as shim to shm_open(3).
374388
*

0 commit comments

Comments
 (0)