Austin Schuh | 745610d | 2015-09-06 18:19:50 -0700 | [diff] [blame] | 1 | // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- |
| 2 | // Copyright (c) 2007, Google Inc. |
| 3 | // All rights reserved. |
| 4 | // |
| 5 | // Redistribution and use in source and binary forms, with or without |
| 6 | // modification, are permitted provided that the following conditions are |
| 7 | // met: |
| 8 | // |
| 9 | // * Redistributions of source code must retain the above copyright |
| 10 | // notice, this list of conditions and the following disclaimer. |
| 11 | // * Redistributions in binary form must reproduce the above |
| 12 | // copyright notice, this list of conditions and the following disclaimer |
| 13 | // in the documentation and/or other materials provided with the |
| 14 | // distribution. |
| 15 | // * Neither the name of Google Inc. nor the names of its |
| 16 | // contributors may be used to endorse or promote products derived from |
| 17 | // this software without specific prior written permission. |
| 18 | // |
| 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 | |
| 31 | // --- |
| 32 | // Author: Arun Sharma |
| 33 | // |
| 34 | // A tcmalloc system allocator that uses a memory based filesystem such as |
| 35 | // tmpfs or hugetlbfs |
| 36 | // |
| 37 | // Since these only exist on linux, we only register this allocator there. |
| 38 | |
| 39 | #ifdef __linux |
| 40 | |
| 41 | #include <config.h> |
| 42 | #include <errno.h> // for errno, EINVAL |
| 43 | #include <inttypes.h> // for PRId64 |
| 44 | #include <limits.h> // for PATH_MAX |
| 45 | #include <stddef.h> // for size_t, NULL |
| 46 | #ifdef HAVE_STDINT_H |
| 47 | #include <stdint.h> // for int64_t, uintptr_t |
| 48 | #endif |
| 49 | #include <stdio.h> // for snprintf |
| 50 | #include <stdlib.h> // for mkstemp |
| 51 | #include <string.h> // for strerror |
| 52 | #include <sys/mman.h> // for mmap, MAP_FAILED, etc |
| 53 | #include <sys/statfs.h> // for fstatfs, statfs |
| 54 | #include <unistd.h> // for ftruncate, off_t, unlink |
| 55 | #include <new> // for operator new |
| 56 | #include <string> |
| 57 | |
| 58 | #include <gperftools/malloc_extension.h> |
| 59 | #include "base/basictypes.h" |
| 60 | #include "base/googleinit.h" |
| 61 | #include "base/sysinfo.h" |
| 62 | #include "internal_logging.h" |
| 63 | |
| 64 | // TODO(sanjay): Move the code below into the tcmalloc namespace |
| 65 | using tcmalloc::kLog; |
| 66 | using tcmalloc::kCrash; |
| 67 | using tcmalloc::Log; |
| 68 | using std::string; |
| 69 | |
| 70 | DEFINE_string(memfs_malloc_path, EnvToString("TCMALLOC_MEMFS_MALLOC_PATH", ""), |
| 71 | "Path where hugetlbfs or tmpfs is mounted. The caller is " |
| 72 | "responsible for ensuring that the path is unique and does " |
| 73 | "not conflict with another process"); |
| 74 | DEFINE_int64(memfs_malloc_limit_mb, |
| 75 | EnvToInt("TCMALLOC_MEMFS_LIMIT_MB", 0), |
| 76 | "Limit total allocation size to the " |
| 77 | "specified number of MiB. 0 == no limit."); |
| 78 | DEFINE_bool(memfs_malloc_abort_on_fail, |
| 79 | EnvToBool("TCMALLOC_MEMFS_ABORT_ON_FAIL", false), |
| 80 | "abort() whenever memfs_malloc fails to satisfy an allocation " |
| 81 | "for any reason."); |
| 82 | DEFINE_bool(memfs_malloc_ignore_mmap_fail, |
| 83 | EnvToBool("TCMALLOC_MEMFS_IGNORE_MMAP_FAIL", false), |
| 84 | "Ignore failures from mmap"); |
| 85 | DEFINE_bool(memfs_malloc_map_private, |
| 86 | EnvToBool("TCMALLOC_MEMFS_MAP_PRIVATE", false), |
| 87 | "Use MAP_PRIVATE with mmap"); |
| 88 | |
| 89 | // Hugetlbfs based allocator for tcmalloc |
| 90 | class HugetlbSysAllocator: public SysAllocator { |
| 91 | public: |
| 92 | explicit HugetlbSysAllocator(SysAllocator* fallback) |
| 93 | : failed_(true), // To disable allocator until Initialize() is called. |
| 94 | big_page_size_(0), |
| 95 | hugetlb_fd_(-1), |
| 96 | hugetlb_base_(0), |
| 97 | fallback_(fallback) { |
| 98 | } |
| 99 | |
| 100 | void* Alloc(size_t size, size_t *actual_size, size_t alignment); |
| 101 | bool Initialize(); |
| 102 | |
| 103 | bool failed_; // Whether failed to allocate memory. |
| 104 | |
| 105 | private: |
| 106 | void* AllocInternal(size_t size, size_t *actual_size, size_t alignment); |
| 107 | |
| 108 | int64 big_page_size_; |
| 109 | int hugetlb_fd_; // file descriptor for hugetlb |
| 110 | off_t hugetlb_base_; |
| 111 | |
| 112 | SysAllocator* fallback_; // Default system allocator to fall back to. |
| 113 | }; |
| 114 | static char hugetlb_space[sizeof(HugetlbSysAllocator)]; |
| 115 | |
| 116 | // No locking needed here since we assume that tcmalloc calls |
| 117 | // us with an internal lock held (see tcmalloc/system-alloc.cc). |
| 118 | void* HugetlbSysAllocator::Alloc(size_t size, size_t *actual_size, |
| 119 | size_t alignment) { |
| 120 | if (failed_) { |
| 121 | return fallback_->Alloc(size, actual_size, alignment); |
| 122 | } |
| 123 | |
| 124 | // We don't respond to allocation requests smaller than big_page_size_ unless |
| 125 | // the caller is ok to take more than they asked for. Used by MetaDataAlloc. |
| 126 | if (actual_size == NULL && size < big_page_size_) { |
| 127 | return fallback_->Alloc(size, actual_size, alignment); |
| 128 | } |
| 129 | |
| 130 | // Enforce huge page alignment. Be careful to deal with overflow. |
| 131 | size_t new_alignment = alignment; |
| 132 | if (new_alignment < big_page_size_) new_alignment = big_page_size_; |
| 133 | size_t aligned_size = ((size + new_alignment - 1) / |
| 134 | new_alignment) * new_alignment; |
| 135 | if (aligned_size < size) { |
| 136 | return fallback_->Alloc(size, actual_size, alignment); |
| 137 | } |
| 138 | |
| 139 | void* result = AllocInternal(aligned_size, actual_size, new_alignment); |
| 140 | if (result != NULL) { |
| 141 | return result; |
| 142 | } |
| 143 | Log(kLog, __FILE__, __LINE__, |
| 144 | "HugetlbSysAllocator: (failed, allocated)", failed_, hugetlb_base_); |
| 145 | if (FLAGS_memfs_malloc_abort_on_fail) { |
| 146 | Log(kCrash, __FILE__, __LINE__, |
| 147 | "memfs_malloc_abort_on_fail is set"); |
| 148 | } |
| 149 | return fallback_->Alloc(size, actual_size, alignment); |
| 150 | } |
| 151 | |
| 152 | void* HugetlbSysAllocator::AllocInternal(size_t size, size_t* actual_size, |
| 153 | size_t alignment) { |
| 154 | // Ask for extra memory if alignment > pagesize |
| 155 | size_t extra = 0; |
| 156 | if (alignment > big_page_size_) { |
| 157 | extra = alignment - big_page_size_; |
| 158 | } |
| 159 | |
| 160 | // Test if this allocation would put us over the limit. |
| 161 | off_t limit = FLAGS_memfs_malloc_limit_mb*1024*1024; |
| 162 | if (limit > 0 && hugetlb_base_ + size + extra > limit) { |
| 163 | // Disable the allocator when there's less than one page left. |
| 164 | if (limit - hugetlb_base_ < big_page_size_) { |
| 165 | Log(kLog, __FILE__, __LINE__, "reached memfs_malloc_limit_mb"); |
| 166 | failed_ = true; |
| 167 | } |
| 168 | else { |
| 169 | Log(kLog, __FILE__, __LINE__, |
| 170 | "alloc too large (size, bytes left)", size, limit-hugetlb_base_); |
| 171 | } |
| 172 | return NULL; |
| 173 | } |
| 174 | |
| 175 | // This is not needed for hugetlbfs, but needed for tmpfs. Annoyingly |
| 176 | // hugetlbfs returns EINVAL for ftruncate. |
| 177 | int ret = ftruncate(hugetlb_fd_, hugetlb_base_ + size + extra); |
| 178 | if (ret != 0 && errno != EINVAL) { |
| 179 | Log(kLog, __FILE__, __LINE__, |
| 180 | "ftruncate failed", strerror(errno)); |
| 181 | failed_ = true; |
| 182 | return NULL; |
| 183 | } |
| 184 | |
| 185 | // Note: size + extra does not overflow since: |
| 186 | // size + alignment < (1<<NBITS). |
| 187 | // and extra <= alignment |
| 188 | // therefore size + extra < (1<<NBITS) |
| 189 | void *result; |
| 190 | result = mmap(0, size + extra, PROT_WRITE|PROT_READ, |
| 191 | FLAGS_memfs_malloc_map_private ? MAP_PRIVATE : MAP_SHARED, |
| 192 | hugetlb_fd_, hugetlb_base_); |
| 193 | if (result == reinterpret_cast<void*>(MAP_FAILED)) { |
| 194 | if (!FLAGS_memfs_malloc_ignore_mmap_fail) { |
| 195 | Log(kLog, __FILE__, __LINE__, |
| 196 | "mmap failed (size, error)", size + extra, strerror(errno)); |
| 197 | failed_ = true; |
| 198 | } |
| 199 | return NULL; |
| 200 | } |
| 201 | uintptr_t ptr = reinterpret_cast<uintptr_t>(result); |
| 202 | |
| 203 | // Adjust the return memory so it is aligned |
| 204 | size_t adjust = 0; |
| 205 | if ((ptr & (alignment - 1)) != 0) { |
| 206 | adjust = alignment - (ptr & (alignment - 1)); |
| 207 | } |
| 208 | ptr += adjust; |
| 209 | hugetlb_base_ += (size + extra); |
| 210 | |
| 211 | if (actual_size) { |
| 212 | *actual_size = size + extra - adjust; |
| 213 | } |
| 214 | |
| 215 | return reinterpret_cast<void*>(ptr); |
| 216 | } |
| 217 | |
| 218 | bool HugetlbSysAllocator::Initialize() { |
| 219 | char path[PATH_MAX]; |
| 220 | const int pathlen = FLAGS_memfs_malloc_path.size(); |
| 221 | if (pathlen + 8 > sizeof(path)) { |
| 222 | Log(kCrash, __FILE__, __LINE__, "XX fatal: memfs_malloc_path too long"); |
| 223 | return false; |
| 224 | } |
| 225 | memcpy(path, FLAGS_memfs_malloc_path.data(), pathlen); |
| 226 | memcpy(path + pathlen, ".XXXXXX", 8); // Also copies terminating \0 |
| 227 | |
| 228 | int hugetlb_fd = mkstemp(path); |
| 229 | if (hugetlb_fd == -1) { |
| 230 | Log(kLog, __FILE__, __LINE__, |
| 231 | "warning: unable to create memfs_malloc_path", |
| 232 | path, strerror(errno)); |
| 233 | return false; |
| 234 | } |
| 235 | |
| 236 | // Cleanup memory on process exit |
| 237 | if (unlink(path) == -1) { |
| 238 | Log(kCrash, __FILE__, __LINE__, |
| 239 | "fatal: error unlinking memfs_malloc_path", path, strerror(errno)); |
| 240 | return false; |
| 241 | } |
| 242 | |
| 243 | // Use fstatfs to figure out the default page size for memfs |
| 244 | struct statfs sfs; |
| 245 | if (fstatfs(hugetlb_fd, &sfs) == -1) { |
| 246 | Log(kCrash, __FILE__, __LINE__, |
| 247 | "fatal: error fstatfs of memfs_malloc_path", strerror(errno)); |
| 248 | return false; |
| 249 | } |
| 250 | int64 page_size = sfs.f_bsize; |
| 251 | |
| 252 | hugetlb_fd_ = hugetlb_fd; |
| 253 | big_page_size_ = page_size; |
| 254 | failed_ = false; |
| 255 | return true; |
| 256 | } |
| 257 | |
| 258 | REGISTER_MODULE_INITIALIZER(memfs_malloc, { |
| 259 | if (FLAGS_memfs_malloc_path.length()) { |
| 260 | SysAllocator* alloc = MallocExtension::instance()->GetSystemAllocator(); |
| 261 | HugetlbSysAllocator* hp = new (hugetlb_space) HugetlbSysAllocator(alloc); |
| 262 | if (hp->Initialize()) { |
| 263 | MallocExtension::instance()->SetSystemAllocator(hp); |
| 264 | } |
| 265 | } |
| 266 | }); |
| 267 | |
| 268 | #endif /* ifdef __linux */ |