Commit 2b8ea381 authored by Tom Jackson's avatar Tom Jackson Committed by Jordan DeLong

MemoryMapping

Summary: MemoryMapping is a C++ wrapper object around mmap. It works with `folly::File`s, and provides bitwise-range access for reading and writing files.

Test Plan: Unit test

Reviewed By: lucian@fb.com

FB internal diff: D452384
parent 88b49f3e
/*
* Copyright 2013 Facebook, Inc.
*
* 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 "folly/MemoryMapping.h"
#include "folly/Format.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <system_error>
DEFINE_int64(mlock_max_size_at_once, 1 << 20, // 1MB
"Maximum bytes to mlock/munlock/munmap at once "
"(will be rounded up to PAGESIZE)");
namespace folly {
/* protected constructor */
MemoryMapping::MemoryMapping()
: mapStart_(nullptr)
, mapLength_(0)
, locked_(false) {
}
MemoryMapping::MemoryMapping(File file, off_t offset, off_t length)
: mapStart_(nullptr)
, mapLength_(0)
, locked_(false) {
init(std::move(file), offset, length, PROT_READ, false);
}
void MemoryMapping::init(File file,
off_t offset, off_t length,
int prot,
bool grow) {
off_t pageSize = sysconf(_SC_PAGESIZE);
CHECK_GE(offset, 0);
// Round down the start of the mapped region
size_t skipStart = offset % pageSize;
offset -= skipStart;
file_ = std::move(file);
mapLength_ = length;
if (mapLength_ != -1) {
mapLength_ += skipStart;
// Round up the end of the mapped region
mapLength_ = (mapLength_ + pageSize - 1) / pageSize * pageSize;
}
// stat the file
struct stat st;
CHECK_ERR(fstat(file_.fd(), &st));
off_t remaining = st.st_size - offset;
if (mapLength_ == -1) {
length = mapLength_ = remaining;
} else {
if (length > remaining) {
if (grow) {
PCHECK(0 == ftruncate(file_.fd(), offset + length))
<< "ftructate() failed, couldn't grow file";
remaining = length;
} else {
length = remaining;
}
}
if (mapLength_ > remaining) mapLength_ = remaining;
}
if (length == 0) {
mapLength_ = 0;
mapStart_ = nullptr;
} else {
unsigned char* start = static_cast<unsigned char*>(
mmap(nullptr, mapLength_, prot, MAP_SHARED, file_.fd(), offset));
PCHECK(start != MAP_FAILED)
<< " offset=" << offset
<< " length=" << mapLength_;
mapStart_ = start;
data_.reset(start + skipStart, length);
}
}
namespace {
off_t memOpChunkSize(off_t length) {
off_t chunkSize = length;
if (FLAGS_mlock_max_size_at_once <= 0) {
return chunkSize;
}
chunkSize = FLAGS_mlock_max_size_at_once;
off_t pageSize = sysconf(_SC_PAGESIZE);
off_t r = chunkSize % pageSize;
if (r) {
chunkSize += (pageSize - r);
}
return chunkSize;
}
/**
* Run @op in chunks over the buffer @mem of @bufSize length.
*
* Return:
* - success: true + amountSucceeded == bufSize (op success on whole buffer)
* - failure: false + amountSucceeded == nr bytes on which op succeeded.
*/
bool memOpInChunks(std::function<int(void*, size_t)> op,
void* mem, size_t bufSize,
size_t& amountSucceeded) {
// unmap/mlock/munlock take a kernel semaphore and block other threads from
// doing other memory operations. If the size of the buffer is big the
// semaphore can be down for seconds (for benchmarks see
// http://kostja-osipov.livejournal.com/42963.html). Doing the operations in
// chunks breaks the locking into intervals and lets other threads do memory
// operations of their own.
size_t chunkSize = memOpChunkSize(bufSize);
size_t chunkCount = bufSize / chunkSize;
char* addr = static_cast<char*>(mem);
amountSucceeded = 0;
while (amountSucceeded < bufSize) {
size_t size = std::min(chunkSize, bufSize - amountSucceeded);
if (op(addr + amountSucceeded, size) != 0) {
return false;
}
amountSucceeded += size;
}
return true;
}
} // anonymous namespace
bool MemoryMapping::mlock(LockMode lock) {
size_t amountSucceeded = 0;
locked_ = memOpInChunks(::mlock, mapStart_, mapLength_, amountSucceeded);
if (locked_) {
return true;
}
auto msg(folly::format(
"mlock({}) failed at {}",
mapLength_, amountSucceeded).str());
if (lock == LockMode::TRY_LOCK && (errno == EPERM || errno == ENOMEM)) {
PLOG(WARNING) << msg;
} else {
PLOG(FATAL) << msg;
}
// only part of the buffer was mlocked, unlock it back
if (!memOpInChunks(::munlock, mapStart_, amountSucceeded, amountSucceeded)) {
PLOG(WARNING) << "munlock()";
}
return false;
}
void MemoryMapping::munlock(bool dontneed) {
if (!locked_) return;
size_t amountSucceeded = 0;
if (!memOpInChunks(::munlock, mapStart_, mapLength_, amountSucceeded)) {
PLOG(WARNING) << "munlock()";
}
if (mapLength_ && dontneed &&
::madvise(mapStart_, mapLength_, MADV_DONTNEED)) {
PLOG(WARNING) << "madvise()";
}
locked_ = false;
}
void MemoryMapping::hintLinearScan() {
advise(MADV_SEQUENTIAL);
}
MemoryMapping::~MemoryMapping() {
if (mapLength_) {
size_t amountSucceeded = 0;
if (!memOpInChunks(::munmap, mapStart_, mapLength_, amountSucceeded)) {
PLOG(FATAL) << folly::format(
"munmap({}) failed at {}",
mapLength_, amountSucceeded).str();
}
}
}
void MemoryMapping::advise(int advice) const {
if (mapLength_ && ::madvise(mapStart_, mapLength_, advice)) {
PLOG(WARNING) << "madvise()";
}
}
WritableMemoryMapping::WritableMemoryMapping(File file, off_t offset, off_t length) {
init(std::move(file), offset, length, PROT_READ | PROT_WRITE, true);
}
} // namespace folly
/*
* Copyright 2013 Facebook, Inc.
*
* 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 FOLLY_MEMORYMAPPING_H_
#define FOLLY_MEMORYMAPPING_H_
#include "folly/FBString.h"
#include "folly/File.h"
#include "folly/Range.h"
#include <glog/logging.h>
#include <boost/noncopyable.hpp>
namespace folly {
/**
* Maps files in memory (read-only).
*
* @author Tudor Bosman (tudorb@fb.com)
*/
class MemoryMapping : boost::noncopyable {
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
};
/**
* Map a portion of the file indicated by filename in memory, causing a CHECK
* failure 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.
*/
explicit MemoryMapping(File file,
off_t offset=0,
off_t length=-1);
virtual ~MemoryMapping();
/**
* Lock the pages in memory
*/
bool mlock(LockMode lock);
/**
* 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;
/**
* 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 {
CHECK(mapStart_);
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.
*/
Range<const uint8_t*> range() const {
return {data_.begin(), data_.end()};
}
/**
* Return the memory area where the file was mapped.
*/
ByteRange data() const {
return range();
}
bool mlocked() const {
return locked_;
}
protected:
MemoryMapping();
void init(File file,
off_t offset, off_t length,
int prot,
bool grow);
File file_;
void* mapStart_;
off_t mapLength_;
bool locked_;
Range<uint8_t*> data_;
};
/**
* Maps files in memory for writing.
*
* @author Tom Jackson (tjackson@fb.com)
*/
class WritableMemoryMapping : public MemoryMapping {
public:
explicit WritableMemoryMapping(File file,
off_t offset = 0,
off_t length = -1);
/**
* 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 {
CHECK(mapStart_);
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.
*/
Range<uint8_t*> writableRange() const {
return data_;
}
};
} // namespace folly
#endif /* FOLLY_MEMORYMAPPING_H_ */
/*
* Copyright 2013 Facebook, Inc.
*
* 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 <gtest/gtest.h>
#include "folly/MemoryMapping.h"
namespace folly {
TEST(MemoryMapping, Basic) {
File f = File::temporary();
{
WritableMemoryMapping m(f.fd(), 0, sizeof(double));
double volatile* d = m.asWritableRange<double>().data();
*d = 37 * M_PI;
}
{
MemoryMapping m(f.fd(), 0, 3);
EXPECT_EQ(0, m.asRange<int>().size()); //not big enough
}
{
MemoryMapping m(f.fd(), 0, sizeof(double));
const double volatile* d = m.asRange<double>().data();
EXPECT_EQ(*d, 37 * M_PI);
}
}
TEST(MemoryMapping, DoublyMapped) {
File f = File::temporary();
// two mappings of the same memory, different addresses.
WritableMemoryMapping mw(f.fd(), 0, sizeof(double));
MemoryMapping mr(f.fd(), 0, sizeof(double));
double volatile* dw = mw.asWritableRange<double>().data();
const double volatile* dr = mr.asRange<double>().data();
// Show that it's truly the same value, even though the pointers differ
EXPECT_NE(dw, dr);
*dw = 42 * M_PI;
EXPECT_EQ(*dr, 42 * M_PI);
*dw = 43 * M_PI;
EXPECT_EQ(*dr, 43 * M_PI);
}
} // namespace folly
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