Fix folly on OSX and BSD in prep for FastCGI on HHVM
[folly.git] / folly / MemoryMapping.cpp
1 /*
2  * Copyright 2014 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "folly/MemoryMapping.h"
18 #include "folly/Format.h"
19 #include "folly/Portability.h"
20
21 #ifdef __linux__
22 #include "folly/experimental/io/HugePages.h"
23 #endif
24
25 #include <fcntl.h>
26 #include <sys/mman.h>
27 #include <sys/types.h>
28 #include <system_error>
29 #include <gflags/gflags.h>
30
31 DEFINE_int64(mlock_chunk_size, 1 << 20,  // 1MB
32              "Maximum bytes to mlock/munlock/munmap at once "
33              "(will be rounded up to PAGESIZE)");
34
35 #ifndef MAP_POPULATE
36 #define MAP_POPULATE 0
37 #endif
38
39 namespace folly {
40
41 MemoryMapping::MemoryMapping(MemoryMapping&& other) {
42   swap(other);
43 }
44
45 MemoryMapping::MemoryMapping(File file, off_t offset, off_t length,
46                              Options options)
47   : file_(std::move(file)),
48     options_(std::move(options)) {
49   CHECK(file_);
50   init(offset, length);
51 }
52
53 MemoryMapping::MemoryMapping(const char* name, off_t offset, off_t length,
54                              Options options)
55   : MemoryMapping(File(name), offset, length, options) { }
56
57 MemoryMapping::MemoryMapping(int fd, off_t offset, off_t length,
58                              Options options)
59   : MemoryMapping(File(fd), offset, length, options) { }
60
61 MemoryMapping::MemoryMapping(AnonymousType, off_t length, Options options)
62   : options_(std::move(options)) {
63   init(0, length);
64 }
65
66 namespace {
67
68 #ifdef __linux__
69 void getDeviceOptions(dev_t device, off_t& pageSize, bool& autoExtend) {
70   auto ps = getHugePageSizeForDevice(device);
71   if (ps) {
72     pageSize = ps->size;
73     autoExtend = true;
74   }
75 }
76 #else
77 inline void getDeviceOptions(dev_t device, off_t& pageSize,
78                              bool& autoExtend) { }
79 #endif
80
81 }  // namespace
82
83 void MemoryMapping::init(off_t offset, off_t length) {
84   const bool grow = options_.grow;
85   const bool anon = !file_;
86   CHECK(!(grow && anon));
87
88   off_t& pageSize = options_.pageSize;
89
90   struct stat st;
91
92   // On Linux, hugetlbfs file systems don't require ftruncate() to grow the
93   // file, and (on kernels before 2.6.24) don't even allow it. Also, the file
94   // size is always a multiple of the page size.
95   bool autoExtend = false;
96
97   if (!anon) {
98     // Stat the file
99     CHECK_ERR(fstat(file_.fd(), &st));
100
101     if (pageSize == 0) {
102       getDeviceOptions(st.st_dev, pageSize, autoExtend);
103     }
104   } else {
105     DCHECK(!file_);
106     DCHECK_EQ(offset, 0);
107     CHECK_EQ(pageSize, 0);
108     CHECK_GE(length, 0);
109   }
110
111   if (pageSize == 0) {
112     pageSize = sysconf(_SC_PAGESIZE);
113   }
114
115   CHECK_GT(pageSize, 0);
116   CHECK_EQ(pageSize & (pageSize - 1), 0);  // power of two
117   CHECK_GE(offset, 0);
118
119   // Round down the start of the mapped region
120   size_t skipStart = offset % pageSize;
121   offset -= skipStart;
122
123   mapLength_ = length;
124   if (mapLength_ != -1) {
125     mapLength_ += skipStart;
126
127     // Round up the end of the mapped region
128     mapLength_ = (mapLength_ + pageSize - 1) / pageSize * pageSize;
129   }
130
131   off_t remaining = anon ? length : st.st_size - offset;
132
133   if (mapLength_ == -1) {
134     length = mapLength_ = remaining;
135   } else {
136     if (length > remaining) {
137       if (grow) {
138         if (!autoExtend) {
139           PCHECK(0 == ftruncate(file_.fd(), offset + length))
140             << "ftruncate() failed, couldn't grow file to "
141             << offset + length;
142           remaining = length;
143         } else {
144           // Extend mapping to multiple of page size, don't use ftruncate
145           remaining = mapLength_;
146         }
147       } else {
148         length = remaining;
149       }
150     }
151     if (mapLength_ > remaining) {
152       mapLength_ = remaining;
153     }
154   }
155
156   if (length == 0) {
157     mapLength_ = 0;
158     mapStart_ = nullptr;
159   } else {
160     int flags = options_.shared ? MAP_SHARED : MAP_PRIVATE;
161     if (anon) flags |= MAP_ANONYMOUS;
162     if (options_.prefault) flags |= MAP_POPULATE;
163
164     // The standard doesn't actually require PROT_NONE to be zero...
165     int prot = PROT_NONE;
166     if (options_.readable || options_.writable) {
167       prot = ((options_.readable ? PROT_READ : 0) |
168               (options_.writable ? PROT_WRITE : 0));
169     }
170
171     unsigned char* start = static_cast<unsigned char*>(
172       mmap(options_.address, mapLength_, prot, flags, file_.fd(), offset));
173     PCHECK(start != MAP_FAILED)
174       << " offset=" << offset
175       << " length=" << mapLength_;
176     mapStart_ = start;
177     data_.reset(start + skipStart, length);
178   }
179 }
180
181 namespace {
182
183 off_t memOpChunkSize(off_t length, off_t pageSize) {
184   off_t chunkSize = length;
185   if (FLAGS_mlock_chunk_size <= 0) {
186     return chunkSize;
187   }
188
189   chunkSize = FLAGS_mlock_chunk_size;
190   off_t r = chunkSize % pageSize;
191   if (r) {
192     chunkSize += (pageSize - r);
193   }
194   return chunkSize;
195 }
196
197 /**
198  * Run @op in chunks over the buffer @mem of @bufSize length.
199  *
200  * Return:
201  * - success: true + amountSucceeded == bufSize (op success on whole buffer)
202  * - failure: false + amountSucceeded == nr bytes on which op succeeded.
203  */
204 bool memOpInChunks(std::function<int(void*, size_t)> op,
205                    void* mem, size_t bufSize, off_t pageSize,
206                    size_t& amountSucceeded) {
207   // unmap/mlock/munlock take a kernel semaphore and block other threads from
208   // doing other memory operations. If the size of the buffer is big the
209   // semaphore can be down for seconds (for benchmarks see
210   // http://kostja-osipov.livejournal.com/42963.html).  Doing the operations in
211   // chunks breaks the locking into intervals and lets other threads do memory
212   // operations of their own.
213
214   size_t chunkSize = memOpChunkSize(bufSize, pageSize);
215
216   char* addr = static_cast<char*>(mem);
217   amountSucceeded = 0;
218
219   while (amountSucceeded < bufSize) {
220     size_t size = std::min(chunkSize, bufSize - amountSucceeded);
221     if (op(addr + amountSucceeded, size) != 0) {
222       return false;
223     }
224     amountSucceeded += size;
225   }
226
227   return true;
228 }
229
230 }  // anonymous namespace
231
232 bool MemoryMapping::mlock(LockMode lock) {
233   size_t amountSucceeded = 0;
234   locked_ = memOpInChunks(::mlock, mapStart_, mapLength_, options_.pageSize,
235                           amountSucceeded);
236   if (locked_) {
237     return true;
238   }
239
240   auto msg(folly::format(
241     "mlock({}) failed at {}",
242     mapLength_, amountSucceeded).str());
243
244   if (lock == LockMode::TRY_LOCK && (errno == EPERM || errno == ENOMEM)) {
245     PLOG(WARNING) << msg;
246   } else {
247     PLOG(FATAL) << msg;
248   }
249
250   // only part of the buffer was mlocked, unlock it back
251   if (!memOpInChunks(::munlock, mapStart_, amountSucceeded, options_.pageSize,
252                      amountSucceeded)) {
253     PLOG(WARNING) << "munlock()";
254   }
255
256   return false;
257 }
258
259 void MemoryMapping::munlock(bool dontneed) {
260   if (!locked_) return;
261
262   size_t amountSucceeded = 0;
263   if (!memOpInChunks(::munlock, mapStart_, mapLength_, options_.pageSize,
264                      amountSucceeded)) {
265     PLOG(WARNING) << "munlock()";
266   }
267   if (mapLength_ && dontneed &&
268       ::madvise(mapStart_, mapLength_, MADV_DONTNEED)) {
269     PLOG(WARNING) << "madvise()";
270   }
271   locked_ = false;
272 }
273
274 void MemoryMapping::hintLinearScan() {
275   advise(MADV_SEQUENTIAL);
276 }
277
278 MemoryMapping::~MemoryMapping() {
279   if (mapLength_) {
280     size_t amountSucceeded = 0;
281     if (!memOpInChunks(::munmap, mapStart_, mapLength_, options_.pageSize,
282                        amountSucceeded)) {
283       PLOG(FATAL) << folly::format(
284         "munmap({}) failed at {}",
285         mapLength_, amountSucceeded).str();
286     }
287   }
288 }
289
290 void MemoryMapping::advise(int advice) const {
291   if (mapLength_ && ::madvise(mapStart_, mapLength_, advice)) {
292     PLOG(WARNING) << "madvise()";
293   }
294 }
295
296 MemoryMapping& MemoryMapping::operator=(MemoryMapping other) {
297   swap(other);
298   return *this;
299 }
300
301 void MemoryMapping::swap(MemoryMapping& other) {
302   using std::swap;
303   swap(this->file_, other.file_);
304   swap(this->mapStart_, other.mapStart_);
305   swap(this->mapLength_, other.mapLength_);
306   swap(this->options_, other.options_);
307   swap(this->locked_, other.locked_);
308   swap(this->data_, other.data_);
309 }
310
311 void swap(MemoryMapping& a, MemoryMapping& b) { a.swap(b); }
312
313 void alignedForwardMemcpy(void* dst, const void* src, size_t size) {
314   assert(reinterpret_cast<uintptr_t>(src) % alignof(unsigned long) == 0);
315   assert(reinterpret_cast<uintptr_t>(dst) % alignof(unsigned long) == 0);
316
317   auto srcl = static_cast<const unsigned long*>(src);
318   auto dstl = static_cast<unsigned long*>(dst);
319
320   while (size >= sizeof(unsigned long)) {
321     *dstl++ = *srcl++;
322     size -= sizeof(unsigned long);
323   }
324
325   auto srcc = reinterpret_cast<const unsigned char*>(srcl);
326   auto dstc = reinterpret_cast<unsigned char*>(dstl);
327
328   while (size != 0) {
329     *dstc++ = *srcc++;
330     --size;
331   }
332 }
333
334 void mmapFileCopy(const char* src, const char* dest, mode_t mode) {
335   MemoryMapping srcMap(src);
336   srcMap.hintLinearScan();
337
338   MemoryMapping destMap(
339       File(dest, O_RDWR | O_CREAT | O_TRUNC, mode),
340       0,
341       srcMap.range().size(),
342       MemoryMapping::writable());
343
344   alignedForwardMemcpy(destMap.writableRange().data(),
345                        srcMap.range().data(),
346                        srcMap.range().size());
347 }
348
349 }  // namespace folly