From 2b8ea38174dea68f56f0e809f05c9d1867a93621 Mon Sep 17 00:00:00 2001 From: Tom Jackson Date: Wed, 20 Feb 2013 16:13:00 -0800 Subject: [PATCH] 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 --- folly/MemoryMapping.cpp | 216 +++++++++++++++++++++++++++++++ folly/MemoryMapping.h | 163 +++++++++++++++++++++++ folly/test/MemoryMappingTest.cpp | 57 ++++++++ 3 files changed, 436 insertions(+) create mode 100644 folly/MemoryMapping.cpp create mode 100644 folly/MemoryMapping.h create mode 100644 folly/test/MemoryMappingTest.cpp diff --git a/folly/MemoryMapping.cpp b/folly/MemoryMapping.cpp new file mode 100644 index 00000000..617d8006 --- /dev/null +++ b/folly/MemoryMapping.cpp @@ -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 +#include +#include +#include + +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( + 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 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(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 diff --git a/folly/MemoryMapping.h b/folly/MemoryMapping.h new file mode 100644 index 00000000..244727b4 --- /dev/null +++ b/folly/MemoryMapping.h @@ -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 +#include + +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 + Range asRange() const { + CHECK(mapStart_); + size_t count = data_.size() / sizeof(T); + return Range(static_cast( + static_cast(data_.data())), + count); + } + + /** + * A range of bytes mapped by this mapping. + */ + Range 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 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 + Range asWritableRange() const { + CHECK(mapStart_); + 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. + */ + Range writableRange() const { + return data_; + } +}; + +} // namespace folly + +#endif /* FOLLY_MEMORYMAPPING_H_ */ diff --git a/folly/test/MemoryMappingTest.cpp b/folly/test/MemoryMappingTest.cpp new file mode 100644 index 00000000..662c54c3 --- /dev/null +++ b/folly/test/MemoryMappingTest.cpp @@ -0,0 +1,57 @@ +/* + * 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 +#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().data(); + *d = 37 * M_PI; + } + { + MemoryMapping m(f.fd(), 0, 3); + EXPECT_EQ(0, m.asRange().size()); //not big enough + } + { + MemoryMapping m(f.fd(), 0, sizeof(double)); + const double volatile* d = m.asRange().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().data(); + const double volatile* dr = mr.asRange().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 -- 2.34.1