Commit c9fa9fe9 by David 'Digit' Turner Committed by David Turner

[memfd] Create support class for Linux memfd-backed region.

This creates a new helper class to allocate memfd-backed shared memory regions with the Linux kernel. The feature has been supported since at least Linux 3.17, which is sufficient for a large number of desktop Linux and Android devices. However, only recent version of GLibc or the Android C library expose memfd_create(), so keep implementing it as a direct syscall. NOTE: There is a similar function named memfd_create() inside of src/Reactor/ExecutableMemory.cpp, but it is easier to keep them separate for now to avoid introducing subtle dependency issues in the CMakeLists.txt file. This will used in future CLs that implement Vulkan external semaphores and external memory on Linux and Android for SwiftShader. Bug: b/140419396 Change-Id: I378c34760930d8fef7d0659128ededb5227c323f Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/35128Reviewed-by: 's avatarNicolas Capens <nicolascapens@google.com> Tested-by: 's avatarDavid Turner <digit@google.com> Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
parent d94d6a3d
......@@ -203,6 +203,7 @@ if (build_with_chromium) {
data_deps = [
"tests/GLESUnitTests:swiftshader_unittests",
"tests/SystemUnitTests:swiftshader_system_unittests",
]
}
}
\ No newline at end of file
}
......@@ -1763,6 +1763,12 @@ file(GLOB_RECURSE VULKAN_LIST
${CMAKE_CURRENT_SOURCE_DIR}/include/vulkan/*.h}
)
if (LINUX OR ANDROID)
list(APPEND VULKAN_LIST
${SOURCE_DIR}/System/Linux/MemFd.cpp
${SOURCE_DIR}/System/Linux/MemFd.hpp)
endif(LINUX OR ANDROID)
###########################################################
# Append OS specific files to lists
###########################################################
......
......@@ -555,6 +555,7 @@ cc_defaults {
"System/CPUID.cpp",
"System/Configurator.cpp",
"System/Half.cpp",
"System/Linux/MemFd.cpp",
"System/Math.cpp",
"System/Memory.cpp",
"System/Socket.cpp",
......
......@@ -27,6 +27,11 @@ swiftshader_source_set("System_headers") {
"Socket.hpp",
"Timer.hpp",
]
if (is_linux || is_android) {
sources += [
"Linux/MemFd.hpp",
]
}
}
swiftshader_source_set("System") {
......@@ -39,6 +44,11 @@ swiftshader_source_set("System") {
"Memory.cpp",
"Timer.cpp",
]
if (is_linux || is_android) {
sources += [
"Linux/MemFd.cpp",
]
}
include_dirs = [ ".." ]
public_deps = [
......
// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "MemFd.hpp"
#include "../Debug.hpp"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#ifndef MFD_CLOEXEC
#define MFD_CLOEXEC 0x0001U
#endif
#if __aarch64__
#define __NR_memfd_create 279
#elif __arm__
#define __NR_memfd_create 279
#elif __powerpc64__
#define __NR_memfd_create 360
#elif __i386__
#define __NR_memfd_create 356
#elif __x86_64__
#define __NR_memfd_create 319
#endif /* __NR_memfd_create__ */
namespace linux
{
MemFd::~MemFd()
{
close();
}
void MemFd::importFd(int fd)
{
close();
fd_ = fd;
}
int MemFd::exportFd() const
{
if (fd_ < 0)
{
return fd_;
}
// Duplicate file descriptor while setting the clo-on-exec flag (Linux specific).
return ::fcntl(fd_, F_DUPFD_CLOEXEC, 0);
}
bool MemFd::allocate(const char* name, size_t size)
{
close();
#ifndef __NR_memfd_create
sw::trace("memfd_create() not supported on this system!");
return false;
#else
// In the event of no system call this returns -1 with errno set
// as ENOSYS.
fd_ = syscall(__NR_memfd_create, name, MFD_CLOEXEC);
if (fd_ < 0)
{
sw::trace("memfd_create() returned %d: %s", errno, strerror(errno));
return false;
}
// Ensure there is enough space.
if (size > 0 && ::ftruncate(fd_, size) < 0)
{
sw::trace("ftruncate() %lld returned %d: %s", (long long)size, errno, strerror(errno));
close();
return false;
}
#endif
return true;
}
void MemFd::close()
{
if (fd_ >= 0)
{
// WARNING: Never retry on close() failure, even with EINTR, see
// https://lwn.net/Articles/576478/ for example.
int ret = ::close(fd_);
if (ret < 0) {
sw::trace("MemFd::close() failed with: %s", strerror(errno));
assert(false);
}
fd_ = -1;
}
}
void* MemFd::mapReadWrite(size_t offset, size_t size)
{
void* addr = ::mmap(nullptr, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_,
static_cast<off_t>(offset));
return (addr == MAP_FAILED) ? nullptr : addr;
}
bool MemFd::unmap(void* addr, size_t size)
{
return ::munmap(addr, size) == 0;
}
} // namespace linux
// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEMFD_LINUX
#define MEMFD_LINUX
#include <cstddef>
// Implementation of shared-memory regions backed by memfd_create(), which
// unfortunately is not exported by older GLibc versions, though it has been
// supported by the Linux kernel since 3.17 (good enough for Android and desktop
// Linux).
namespace linux
{
class MemFd {
public:
MemFd() = default;
MemFd(const char* name, size_t size) : MemFd() {
allocate(name, size);
}
~MemFd();
// Return true iff the region is valid/allocated.
bool isValid() const { return fd_ >= 0; }
// Return region's internal file descriptor value. Useful for mapping.
int fd() const { return fd_; }
// Set the internal handle to |fd|.
void importFd(int fd);
// Return a copy of this instance's file descriptor (with CLO_EXEC set).
int exportFd() const;
// Implement memfd_create() through direct syscalls if possible.
// On success, return true and sets |fd| accordingly. On failure, return
// false and sets errno.
bool allocate(const char* name, size_t size);
// Map a segment of |size| bytes from |offset| from the region.
// Both |offset| and |size| should be page-aligned. Returns nullptr/errno
// on failure.
void* mapReadWrite(size_t offset, size_t size);
// Unmap a region segment starting at |addr| of |size| bytes.
// Both |addr| and |size| should be page-aligned. Returns true on success
// or false/errno on failure.
bool unmap(void* addr, size_t size);
void close();
private:
int fd_ = -1;
};
} // namespace linux
#endif // MEMFD_LINUX
# Copyright 2019 The SwiftShader Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import("//testing/test.gni")
test("swiftshader_system_unittests") {
deps = [
"//base",
"//base/test:test_support",
"//testing/gmock",
"//testing/gtest",
"../../src/System",
]
sources = [
"//gpu/swiftshader_tests_main.cc",
"unittests.cpp",
]
include_dirs = [
"../../src"
]
}
// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "System/Memory.hpp"
#ifdef __linux__
#include "System/Linux/MemFd.hpp"
#endif
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdlib>
using namespace sw;
#ifdef __linux__
TEST(MemFd, DefaultConstructor) {
linux::MemFd memfd;
ASSERT_FALSE(memfd.isValid());
ASSERT_EQ(-1, memfd.exportFd());
}
TEST(MemFd, AllocatingConstructor) {
const size_t kRegionSize = sw::memoryPageSize() * 8;
linux::MemFd memfd("test-region", kRegionSize);
ASSERT_TRUE(memfd.isValid());
ASSERT_GE(memfd.fd(), 0);
void* addr = memfd.mapReadWrite(0, kRegionSize);
ASSERT_TRUE(addr);
memfd.unmap(addr, kRegionSize);
}
TEST(MemFd, ExplicitAllocation) {
const size_t kRegionSize = sw::memoryPageSize() * 8;
linux::MemFd memfd;
ASSERT_FALSE(memfd.isValid());
ASSERT_EQ(-1, memfd.exportFd());
ASSERT_TRUE(memfd.allocate("test-region", kRegionSize));
ASSERT_TRUE(memfd.isValid());
}
TEST(MemFd, Close) {
const size_t kRegionSize = sw::memoryPageSize() * 8;
linux::MemFd memfd("test-region", kRegionSize);
ASSERT_TRUE(memfd.isValid());
int fd = memfd.exportFd();
memfd.close();
ASSERT_FALSE(memfd.isValid());
ASSERT_EQ(-1, memfd.exportFd());
::close(fd);
}
TEST(MemFd, ExportImportFd) {
const size_t kRegionSize = sw::memoryPageSize() * 8;
linux::MemFd memfd("test-region1", kRegionSize);
auto* addr = reinterpret_cast<uint8_t*>(memfd.mapReadWrite(0, kRegionSize));
ASSERT_TRUE(addr);
for (size_t n = 0; n < kRegionSize; ++n)
{
addr[n] = static_cast<uint8_t>(n);
}
int fd = memfd.exportFd();
ASSERT_TRUE(memfd.unmap(addr, kRegionSize));
memfd.close();
linux::MemFd memfd2;
memfd2.importFd(fd);
ASSERT_TRUE(memfd2.isValid());
addr = reinterpret_cast<uint8_t*>(memfd2.mapReadWrite(0, kRegionSize));
ASSERT_TRUE(addr);
for (size_t n = 0; n < kRegionSize; ++n)
{
ASSERT_EQ(addr[n], static_cast<uint8_t>(n)) << "# " << n;
}
ASSERT_TRUE(memfd2.unmap(addr, kRegionSize));
}
#endif // __linux__
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment