Adding support for signed integers
[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
20 #include <fcntl.h>
21 #include <sys/mman.h>
22 #include <sys/types.h>
23 #include <system_error>
24 #include <gflags/gflags.h>
25
26 DEFINE_int64(mlock_chunk_size, 1 << 20,  // 1MB
27              "Maximum bytes to mlock/munlock/munmap at once "
28              "(will be rounded up to PAGESIZE)");
29
30 namespace folly {
31
32 /* protected constructor */
33 MemoryMapping::MemoryMapping()
34   : mapStart_(nullptr)
35   , mapLength_(0)
36   , pageSize_(0)
37   , locked_(false) {
38 }
39
40 MemoryMapping::MemoryMapping(File file, off_t offset, off_t length,
41                              off_t pageSize)
42   : mapStart_(nullptr)
43   , mapLength_(0)
44   , pageSize_(0)
45   , locked_(false) {
46
47   init(std::move(file), offset, length, pageSize, PROT_READ, false);
48 }
49
50 MemoryMapping::MemoryMapping(const char* name, off_t offset, off_t length,
51                              off_t pageSize)
52   : MemoryMapping(File(name), offset, length, pageSize) { }
53
54 MemoryMapping::MemoryMapping(int fd, off_t offset, off_t length,
55                              off_t pageSize)
56   : MemoryMapping(File(fd), offset, length, pageSize) { }
57
58 void MemoryMapping::init(File file,
59                          off_t offset, off_t length,
60                          off_t pageSize,
61                          int prot,
62                          bool grow) {
63   if (pageSize == 0) {
64     pageSize = sysconf(_SC_PAGESIZE);
65   }
66   CHECK_GT(pageSize, 0);
67   CHECK_EQ(pageSize & (pageSize - 1), 0);  // power of two
68   CHECK_GE(offset, 0);
69   pageSize_ = pageSize;
70
71   // Round down the start of the mapped region
72   size_t skipStart = offset % pageSize;
73   offset -= skipStart;
74
75   file_ = std::move(file);
76   mapLength_ = length;
77   if (mapLength_ != -1) {
78     mapLength_ += skipStart;
79
80     // Round up the end of the mapped region
81     mapLength_ = (mapLength_ + pageSize - 1) / pageSize * pageSize;
82   }
83
84   // stat the file
85   struct stat st;
86   CHECK_ERR(fstat(file_.fd(), &st));
87   off_t remaining = st.st_size - offset;
88   if (mapLength_ == -1) {
89     length = mapLength_ = remaining;
90   } else {
91     if (length > remaining) {
92       if (grow) {
93         PCHECK(0 == ftruncate(file_.fd(), offset + length))
94           << "ftructate() failed, couldn't grow file";
95         remaining = length;
96       } else {
97         length = remaining;
98       }
99     }
100     if (mapLength_ > remaining) mapLength_ = remaining;
101   }
102
103   if (length == 0) {
104     mapLength_ = 0;
105     mapStart_ = nullptr;
106   } else {
107     unsigned char* start = static_cast<unsigned char*>(
108       mmap(nullptr, mapLength_, prot, MAP_SHARED, file_.fd(), offset));
109     PCHECK(start != MAP_FAILED)
110       << " offset=" << offset
111       << " length=" << mapLength_;
112     mapStart_ = start;
113     data_.reset(start + skipStart, length);
114   }
115 }
116
117 namespace {
118
119 off_t memOpChunkSize(off_t length, off_t pageSize) {
120   off_t chunkSize = length;
121   if (FLAGS_mlock_chunk_size <= 0) {
122     return chunkSize;
123   }
124
125   chunkSize = FLAGS_mlock_chunk_size;
126   off_t r = chunkSize % pageSize;
127   if (r) {
128     chunkSize += (pageSize - r);
129   }
130   return chunkSize;
131 }
132
133 /**
134  * Run @op in chunks over the buffer @mem of @bufSize length.
135  *
136  * Return:
137  * - success: true + amountSucceeded == bufSize (op success on whole buffer)
138  * - failure: false + amountSucceeded == nr bytes on which op succeeded.
139  */
140 bool memOpInChunks(std::function<int(void*, size_t)> op,
141                    void* mem, size_t bufSize, off_t pageSize,
142                    size_t& amountSucceeded) {
143   // unmap/mlock/munlock take a kernel semaphore and block other threads from
144   // doing other memory operations. If the size of the buffer is big the
145   // semaphore can be down for seconds (for benchmarks see
146   // http://kostja-osipov.livejournal.com/42963.html).  Doing the operations in
147   // chunks breaks the locking into intervals and lets other threads do memory
148   // operations of their own.
149
150   size_t chunkSize = memOpChunkSize(bufSize, pageSize);
151
152   char* addr = static_cast<char*>(mem);
153   amountSucceeded = 0;
154
155   while (amountSucceeded < bufSize) {
156     size_t size = std::min(chunkSize, bufSize - amountSucceeded);
157     if (op(addr + amountSucceeded, size) != 0) {
158       return false;
159     }
160     amountSucceeded += size;
161   }
162
163   return true;
164 }
165
166 }  // anonymous namespace
167
168 bool MemoryMapping::mlock(LockMode lock) {
169   size_t amountSucceeded = 0;
170   locked_ = memOpInChunks(::mlock, mapStart_, mapLength_, pageSize_,
171                           amountSucceeded);
172   if (locked_) {
173     return true;
174   }
175
176   auto msg(folly::format(
177     "mlock({}) failed at {}",
178     mapLength_, amountSucceeded).str());
179
180   if (lock == LockMode::TRY_LOCK && (errno == EPERM || errno == ENOMEM)) {
181     PLOG(WARNING) << msg;
182   } else {
183     PLOG(FATAL) << msg;
184   }
185
186   // only part of the buffer was mlocked, unlock it back
187   if (!memOpInChunks(::munlock, mapStart_, amountSucceeded, pageSize_,
188                      amountSucceeded)) {
189     PLOG(WARNING) << "munlock()";
190   }
191
192   return false;
193 }
194
195 void MemoryMapping::munlock(bool dontneed) {
196   if (!locked_) return;
197
198   size_t amountSucceeded = 0;
199   if (!memOpInChunks(::munlock, mapStart_, mapLength_, pageSize_,
200                      amountSucceeded)) {
201     PLOG(WARNING) << "munlock()";
202   }
203   if (mapLength_ && dontneed &&
204       ::madvise(mapStart_, mapLength_, MADV_DONTNEED)) {
205     PLOG(WARNING) << "madvise()";
206   }
207   locked_ = false;
208 }
209
210 void MemoryMapping::hintLinearScan() {
211   advise(MADV_SEQUENTIAL);
212 }
213
214 MemoryMapping::~MemoryMapping() {
215   if (mapLength_) {
216     size_t amountSucceeded = 0;
217     if (!memOpInChunks(::munmap, mapStart_, mapLength_, pageSize_,
218                        amountSucceeded)) {
219       PLOG(FATAL) << folly::format(
220         "munmap({}) failed at {}",
221         mapLength_, amountSucceeded).str();
222     }
223   }
224 }
225
226 void MemoryMapping::advise(int advice) const {
227   if (mapLength_ && ::madvise(mapStart_, mapLength_, advice)) {
228     PLOG(WARNING) << "madvise()";
229   }
230 }
231
232 WritableMemoryMapping::WritableMemoryMapping(
233     File file, off_t offset, off_t length, off_t pageSize) {
234   init(std::move(file), offset, length, pageSize, PROT_READ | PROT_WRITE, true);
235 }
236
237 }  // namespace folly