297 lines
7.9 KiB
C
297 lines
7.9 KiB
C
|
/*
|
||
|
* 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 <cassert>
|
||
|
|
||
|
#include <folly/File.h>
|
||
|
#include <folly/Range.h>
|
||
|
|
||
|
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 <class T>
|
||
|
Range<const T*> asRange() const {
|
||
|
size_t count = data_.size() / sizeof(T);
|
||
|
return Range<const T*>(
|
||
|
static_cast<const T*>(static_cast<const void*>(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 <class T>
|
||
|
Range<T*> asWritableRange() const {
|
||
|
assert(options_.writable); // you'll segfault anyway...
|
||
|
size_t count = data_.size() / sizeof(T);
|
||
|
return Range<T*>(static_cast<T*>(static_cast<void*>(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<const char>();
|
||
|
}
|
||
|
|
||
|
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
|