/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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. */ #pragma once #include #include #include namespace folly { /** * Maps files in memory (read-only). * * @author Tudor Bosman (tudorb@fb.com) */ class MemoryMapping { public: /** * Lock the pages in memory? * TRY_LOCK = try to lock, log warning if permission denied * MUST_LOCK = lock, fail assertion if permission denied. */ enum class LockMode { TRY_LOCK, MUST_LOCK, }; struct LockFlags { LockFlags() {} bool operator==(const LockFlags& other) const; /** * Instead of locking all the pages in the mapping before the call returns, * only lock those that are currently resident and mark the others to be * locked at the time they're populated by their first page fault. * * Uses mlock2(flags=MLOCK_ONFAULT). Requires Linux >= 4.4. */ bool lockOnFault = false; }; /** * Map a portion of the file indicated by filename in memory, causing SIGABRT * on error. * * By default, map the whole file. length=-1: map from offset to EOF. * Unlike the mmap() system call, offset and length don't need to be * page-aligned. length is clipped to the end of the file if it's too large. * * The mapping will be destroyed (and the memory pointed-to by data() will * likely become inaccessible) when the MemoryMapping object is destroyed. */ struct Options { Options() {} // Convenience methods; return *this for chaining. Options& setPageSize(off_t v) { pageSize = v; return *this; } Options& setShared(bool v) { shared = v; return *this; } Options& setPrefault(bool v) { prefault = v; return *this; } Options& setReadable(bool v) { readable = v; return *this; } Options& setWritable(bool v) { writable = v; return *this; } Options& setGrow(bool v) { grow = v; return *this; } // Page size. 0 = use appropriate page size. // (On Linux, we use a huge page size if the file is on a hugetlbfs // file system, and the default page size otherwise) off_t pageSize = 0; // If shared (default), the memory mapping is shared with other processes // mapping the same file (or children); if not shared (private), each // process has its own mapping. Changes in writable, private mappings are // not reflected to the underlying file. See the discussion of // MAP_PRIVATE vs MAP_SHARED in the mmap(2) manual page. bool shared = true; // Populate page tables; subsequent accesses should not be blocked // by page faults. This is a hint, as it may not be supported. bool prefault = false; // Map the pages readable. Note that mapping pages without read permissions // is not universally supported (not supported on hugetlbfs on Linux, for // example) bool readable = true; // Map the pages writable. bool writable = false; // When mapping a file in writable mode, grow the file to the requested // length (using ftruncate()) before mapping; if false, truncate the // mapping to the actual file size instead. bool grow = false; // Fix map at this address, if not nullptr. Must be aligned to a multiple // of the appropriate page size. void* address = nullptr; }; // Options to emulate the old WritableMemoryMapping: readable and writable, // allow growing the file if mapping past EOF. static Options writable() { return Options().setWritable(true).setGrow(true); } enum AnonymousType { kAnonymous, }; /** * Create an anonymous mapping. */ MemoryMapping(AnonymousType, off_t length, Options options = Options()); explicit MemoryMapping( File file, off_t offset = 0, off_t length = -1, Options options = Options()); explicit MemoryMapping( const char* name, off_t offset = 0, off_t length = -1, Options options = Options()); explicit MemoryMapping( int fd, off_t offset = 0, off_t length = -1, Options options = Options()); MemoryMapping(const MemoryMapping&) = delete; MemoryMapping(MemoryMapping&&) noexcept; ~MemoryMapping(); MemoryMapping& operator=(const MemoryMapping&) = delete; MemoryMapping& operator=(MemoryMapping&&); void swap(MemoryMapping& other) noexcept; /** * Lock the pages in memory */ bool mlock(LockMode mode, LockFlags flags = {}); /** * Unlock the pages. * If dontneed is true, the kernel is instructed to release these pages * (per madvise(MADV_DONTNEED)). */ void munlock(bool dontneed = false); /** * Hint that these pages will be scanned linearly. * madvise(MADV_SEQUENTIAL) */ void hintLinearScan(); /** * Advise the kernel about memory access. */ void advise(int advice) const; void advise(int advice, size_t offset, size_t length) const; /** * A bitwise cast of the mapped bytes as range of values. Only intended for * use with POD or in-place usable types. */ template Range asRange() const { size_t count = data_.size() / sizeof(T); return Range( static_cast(static_cast(data_.data())), count); } /** * A range of bytes mapped by this mapping. */ ByteRange range() const { return data_; } /** * A bitwise cast of the mapped bytes as range of mutable values. Only * intended for use with POD or in-place usable types. */ template Range asWritableRange() const { assert(options_.writable); // you'll segfault anyway... size_t count = data_.size() / sizeof(T); return Range(static_cast(static_cast(data_.data())), count); } /** * A range of mutable bytes mapped by this mapping. */ MutableByteRange writableRange() const { assert(options_.writable); // you'll segfault anyway... return data_; } /** * Return the memory area where the file was mapped. * Deprecated; use range() instead. */ StringPiece data() const { return asRange(); } bool mlocked() const { return locked_; } int fd() const { return file_.fd(); } private: MemoryMapping(); enum InitFlags { kGrow = 1 << 0, kAnon = 1 << 1, }; void init(off_t offset, off_t length); File file_; void* mapStart_ = nullptr; off_t mapLength_ = 0; Options options_; bool locked_ = false; MutableByteRange data_; }; void swap(MemoryMapping&, MemoryMapping&) noexcept; /** * A special case of memcpy() that always copies memory forwards. * (libc's memcpy() is allowed to copy memory backwards, and will do so * when using SSSE3 instructions). * * Assumes src and dest are aligned to alignof(unsigned long). * * Useful when copying from/to memory mappings after hintLinearScan(); * copying backwards renders any prefetching useless (even harmful). */ void alignedForwardMemcpy(void* dst, const void* src, size_t size); /** * Copy a file using mmap(). Overwrites dest. */ void mmapFileCopy(const char* src, const char* dest, mode_t mode = 0666); /** * mlock2 is Linux-only and exists since Linux 4.4 * On Linux pre-4.4 and other platforms fail with ENOSYS. * glibc added the mlock2 wrapper in 2.27 * https://lists.gnu.org/archive/html/info-gnu/2018-02/msg00000.html */ int mlock2wrapper(const void* addr, size_t len, MemoryMapping::LockFlags flags); } // namespace folly