Permalink
Browse files

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
  • Loading branch information...
1 parent 88b49f3 commit 2b8ea38174dea68f56f0e809f05c9d1867a93621 Tom Jackson committed with jdelong Feb 21, 2013
Showing with 436 additions and 0 deletions.
  1. +216 −0 folly/MemoryMapping.cpp
  2. +163 −0 folly/MemoryMapping.h
  3. +57 −0 folly/test/MemoryMappingTest.cpp
View
@@ -0,0 +1,216 @@
+/*
+ * 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
View
@@ -0,0 +1,163 @@
+/*
+ * 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_ */
Oops, something went wrong.

0 comments on commit 2b8ea38

Please sign in to comment.