From: Tudor Bosman Date: Wed, 22 Aug 2012 00:16:02 +0000 (-0700) Subject: Huge page library. X-Git-Tag: v0.22.0~1191 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=569f5f14ab8dcbdcb8dd2a0cd4fb7ccf5da769f0;p=folly.git Huge page library. Test Plan: by hand Reviewed By: lucian@fb.com FB internal diff: D555164 --- diff --git a/folly/experimental/io/HugePageUtil.cpp b/folly/experimental/io/HugePageUtil.cpp new file mode 100644 index 00000000..1506adb8 --- /dev/null +++ b/folly/experimental/io/HugePageUtil.cpp @@ -0,0 +1,103 @@ +/* + * Copyright 2012 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 +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "folly/experimental/io/HugePages.h" +#include "folly/Format.h" +#include "folly/Range.h" +#include "folly/ScopeGuard.h" + +DEFINE_bool(cp, false, "Copy file"); + +using namespace folly; + +namespace { + +void usage(const char* name) __attribute__((noreturn)); + +void usage(const char* name) { + std::cerr << folly::format( + "Usage: {0}\n" + " list all huge page sizes and their mount points\n" + " {0} -cp \n" + " copy src_file to a huge page file\n", + name); + exit(1); +} + +void copy(const char* srcFile, const char* destPrefix) { + int srcfd = open(srcFile, O_RDONLY); + if (srcfd == -1) { + throw std::system_error(errno, std::system_category(), "open failed"); + } + SCOPE_EXIT { + close(srcfd); + }; + struct stat st; + if (fstat(srcfd, &st) == -1) { + throw std::system_error(errno, std::system_category(), "fstat failed"); + } + + void* start = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, srcfd, 0); + if (start == MAP_FAILED) { + throw std::system_error(errno, std::system_category(), "mmap failed"); + } + + SCOPE_EXIT { + munmap(start, st.st_size); + }; + + HugePages hp; + auto f = hp.create(ByteRange(static_cast(start), + st.st_size), + destPrefix); + std::cout << f.path << "\n"; +} + +void list() { + HugePages hp; + for (auto& p : hp.sizes()) { + std::cout << p.first << " " << p.second << "\n"; + } +} + +} // namespace + + +int main(int argc, char *argv[]) { + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_cp) { + if (argc != 3) usage(argv[0]); + copy(argv[1], argv[2]); + } else { + if (argc != 1) usage(argv[0]); + list(); + } + return 0; +} + diff --git a/folly/experimental/io/HugePages.cpp b/folly/experimental/io/HugePages.cpp new file mode 100644 index 00000000..3591410d --- /dev/null +++ b/folly/experimental/io/HugePages.cpp @@ -0,0 +1,331 @@ +/* + * Copyright 2012 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/experimental/io/HugePages.h" + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "folly/Conv.h" +#include "folly/Format.h" +#include "folly/Range.h" +#include "folly/ScopeGuard.h" +#include "folly/String.h" +#include "folly/experimental/io/Stream.h" + +namespace fs = ::boost::filesystem; + +namespace folly { + +namespace { + +// Get the default huge page size +size_t getDefaultHugePageSize() { + // We need to parse /proc/meminfo + static const boost::regex regex(R"!(Hugepagesize:\s*(\d+)\s*kB)!"); + boost::cmatch match; + for (auto& byteLine : byLine("/proc/meminfo")) { + StringPiece line(byteLine); + if (boost::regex_match(line.begin(), line.end(), match, regex)) { + StringPiece numStr(line.begin() + match.position(1), match.length(1)); + return to(numStr) * 1024; // in KiB + } + } + throw std::runtime_error("Can't find default huge page size"); +} + +// Get raw huge page sizes (without mount points, they'll be filled later) +HugePageSizeVec getRawHugePageSizes() { + // We need to parse file names from /sys/kernel/mm/hugepages + static const boost::regex regex(R"!(hugepages-(\d+)kB)!"); + boost::smatch match; + HugePageSizeVec vec; + fs::path path("/sys/kernel/mm/hugepages"); + for (fs::directory_iterator it(path); it != fs::directory_iterator(); ++it) { + std::string filename(it->path().filename().native()); + if (boost::regex_match(filename, match, regex)) { + StringPiece numStr(filename.data() + match.position(1), match.length(1)); + vec.emplace_back(to(numStr) * 1024, ""); + } + } + return vec; +} + +// Parse the value of a pagesize mount option +// Format: number, optional K/M/G/T suffix, trailing junk allowed +size_t parsePageSizeValue(StringPiece value) { + static const boost::regex regex(R"!((\d+)([kmgt])?.*)!", boost::regex::icase); + boost::cmatch match; + if (!boost::regex_match(value.begin(), value.end(), match, regex)) { + throw std::runtime_error("Invalid pagesize option"); + } + char c = '\0'; + if (match.length(2) != 0) { + c = tolower(value[match.position(2)]); + } + StringPiece numStr(value.data() + match.position(1), match.length(1)); + size_t size = to(numStr); + switch (c) { + case 't': size *= 1024; + case 'g': size *= 1024; + case 'm': size *= 1024; + case 'k': size *= 1024; + } + return size; +} + +/** + * Get list of supported huge page sizes and their mount points, if + * hugetlbfs file systems are mounted for those sizes. + */ +HugePageSizeVec getHugePageSizes() { + HugePageSizeVec sizeVec = getRawHugePageSizes(); + if (sizeVec.empty()) { + return sizeVec; // nothing to do + } + std::sort(sizeVec.begin(), sizeVec.end()); + + size_t defaultHugePageSize = getDefaultHugePageSize(); + + struct PageSizeLess { + bool operator()(const std::pair& a, size_t b) const { + return a.first < b; + } + bool operator()(size_t a, const std::pair& b) const { + return a < b.first; + } + }; + + // Read and parse /proc/mounts + std::vector parts; + std::vector options; + for (auto& byteLine : byLine("/proc/mounts")) { + StringPiece line(byteLine); + parts.clear(); + split(" ", line, parts); + // device path fstype options uid gid + if (parts.size() != 6) { + throw std::runtime_error("Invalid /proc/mounts line"); + } + if (parts[2] != "hugetlbfs") { + continue; // we only care about hugetlbfs + } + + options.clear(); + split(",", parts[3], options); + size_t pageSize = defaultHugePageSize; + // Search for the "pagesize" option, which must have a value + for (auto& option : options) { + // key=value + const char* p = static_cast( + memchr(option.data(), '=', option.size())); + if (!p) { + continue; + } + if (StringPiece(option.data(), p) != "pagesize") { + continue; + } + pageSize = parsePageSizeValue(StringPiece(p + 1, option.end())); + break; + } + + auto pos = std::lower_bound(sizeVec.begin(), sizeVec.end(), pageSize, + PageSizeLess()); + if (pos == sizeVec.end() || pos->first != pageSize) { + throw std::runtime_error("Mount page size not found"); + } + if (pos->second.empty()) { + // Store mount point + pos->second.assign(parts[1].data(), parts[1].size()); + } + } + + return sizeVec; +} + +// RAII wrapper around an open file, closes on exit unless you call release() +class ScopedFd : private boost::noncopyable { + public: + explicit ScopedFd(int fd) : fd_(fd) { } + int fd() const { return fd_; } + + void release() { + fd_ = -1; + } + + void close() { + if (fd_ == -1) { + return; + } + int r = ::close(fd_); + fd_ = -1; + if (r == -1) { + throw std::system_error(errno, std::system_category(), "close failed"); + } + } + + ~ScopedFd() { + try { + close(); + } catch (...) { + PLOG(ERROR) << "close failed!"; + } + } + + private: + int fd_; +}; + +// RAII wrapper that deletes a file upon destruction unless you call release() +class ScopedDeleter : private boost::noncopyable { + public: + explicit ScopedDeleter(std::string name) : name_(std::move(name)) { } + void release() { + name_.clear(); + } + + ~ScopedDeleter() { + if (name_.empty()) { + return; + } + int r = ::unlink(name_.c_str()); + if (r == -1) { + PLOG(ERROR) << "unlink failed"; + } + } + private: + std::string name_; +}; + +// RAII wrapper around a mmap mapping, munmaps upon destruction unless you +// call release() +class ScopedMmap : private boost::noncopyable { + public: + ScopedMmap(void* start, size_t size) : start_(start), size_(size) { } + + void* start() const { return start_; } + size_t size() const { return size_; } + + void release() { + start_ = MAP_FAILED; + } + + void munmap() { + if (start_ == MAP_FAILED) { + return; + } + int r = ::munmap(start_, size_); + start_ = MAP_FAILED; + if (r == -1) { + throw std::system_error(errno, std::system_category(), "munmap failed"); + } + } + + ~ScopedMmap() { + try { + munmap(); + } catch (...) { + PLOG(ERROR) << "munmap failed!"; + } + } + private: + void* start_; + size_t size_; +}; + +} // namespace + +HugePages::HugePages() : sizes_(getHugePageSizes()) { } + +HugePages::File HugePages::create(ByteRange data, + StringPiece baseName, + size_t hugePageSize, + mode_t mode) const { + // Pick an appropriate size. + StringPiece mountPath; + if (hugePageSize == 0) { + for (auto& p : sizes_) { + if (p.second.empty()) { + continue; // not mounted + } + hugePageSize = p.first; + mountPath = StringPiece(p.second); + break; + } + if (hugePageSize == 0) { + throw std::runtime_error("No huge page filesystem mounted"); + } + } else { + // Linear search is just fine + for (auto& p : sizes_) { + if (p.first == hugePageSize) { + if (p.second.empty()) { + throw std::runtime_error( + "No huge page filesystem mounted with requested page size"); + } + mountPath = StringPiece(p.second); + } + } + if (mountPath.empty()) { + throw std::runtime_error("Requested huge page size not found"); + } + } + + // Round size up + File file; + file.size = data.size() / hugePageSize * hugePageSize; + if (file.size != data.size()) { + file.size += hugePageSize; + } + + file.path = folly::format("{}/{}", mountPath, baseName).str(); + ScopedFd fd(open(file.path.c_str(), O_RDWR | O_CREAT | O_TRUNC, mode)); + if (fd.fd() == -1) { + throw std::system_error(errno, std::system_category(), "open failed"); + } + + ScopedDeleter deleter(file.path); + + ScopedMmap map(mmap(nullptr, file.size, PROT_READ | PROT_WRITE, MAP_SHARED, + fd.fd(), 0), + file.size); + if (map.start() == MAP_FAILED) { + throw std::system_error(errno, std::system_category(), "mmap failed"); + } + + memcpy(map.start(), data.data(), data.size()); + + map.munmap(); + deleter.release(); + fd.close(); + + return file; +} + +} // namespace folly + diff --git a/folly/experimental/io/HugePages.h b/folly/experimental/io/HugePages.h new file mode 100644 index 00000000..6ec6a1fa --- /dev/null +++ b/folly/experimental/io/HugePages.h @@ -0,0 +1,77 @@ +/* + * Copyright 2012 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_IO_HUGEPAGES_H_ +#define FOLLY_IO_HUGEPAGES_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#include "folly/Range.h" + +namespace folly { + +/** + * Vector of (huge_page_size, mount_point), sorted by huge_page_size. + * mount_point might be empty if no hugetlbfs file system is mounted for + * that size. + */ +typedef std::vector> HugePageSizeVec; + +/** + * Class to interface with Linux huge pages (hugetlbfs). + */ +class HugePages { + public: + HugePages(); + + /** + * Get list of supported huge page sizes and their mount points, if + * hugetlbfs file systems are mounted for those sizes. + */ + const HugePageSizeVec& sizes() const { return sizes_; } + + /** + * Create a file on a huge page filesystem containing a copy of the data + * from data. If multiple huge page sizes are allowed, we + * pick the smallest huge page size available, unless you request one + * explicitly with the hugePageSize argument. + * + * We return a struct File structure containing the full path and size + * (rounded up to a multiple of the huge page size) + */ + struct File { + std::string path; + size_t size; + }; + File create( + ByteRange data, StringPiece baseName, size_t hugePageSize = 0, + mode_t mode = 0644) const; + + private: + HugePageSizeVec sizes_; +}; + +} // namespace folly + +#endif /* FOLLY_IO_HUGEPAGES_H_ */ +