1 /*
2  * Copyright (C) 2023 The Android Open Source Project
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 <string.h>
18 #include <sys/mman.h>
19 #include <sys/syscall.h>
20 
21 #include <android-base/file.h>
22 #include <android-base/stringprintf.h>
23 #include <benchmark/benchmark.h>
24 
25 #include "util.h"
26 
27 enum BenchmarkType : uint8_t {
28   kBenchmarkMmapOnly,
29   kBenchmarkMunmapOnly,
30   kBenchmarkAll,
31 };
32 
33 static size_t page_sz = getpagesize();
34 
35 struct MmapParams {
36   int prot;
37   int flags;
38   int64_t size;
39 };
40 
41 struct MprotectParams {
42   int initial_prot;
43   int mprotect_prot;
44   int64_t size;
45 };
46 
47 template <BenchmarkType type>
MmapBenchmarkImpl(benchmark::State & state,const struct MmapParams & params,int fd,void * area=nullptr)48 void MmapBenchmarkImpl(benchmark::State& state, const struct MmapParams& params, int fd,
49                        void* area = nullptr) {
50   for (auto _ : state) {
51     if (type == kBenchmarkMunmapOnly) state.PauseTiming();
52     void* addr = mmap(area, params.size, params.prot, params.flags, fd, 0);
53     if (addr == MAP_FAILED) {
54       state.SkipWithError(android::base::StringPrintf("mmap failed: %s", strerror(errno)).c_str());
55       break;
56     }
57 
58     if (type == kBenchmarkMmapOnly) state.PauseTiming();
59 
60     if (params.prot & PROT_WRITE) {
61       MakeAllocationResident(addr, params.size, page_sz);
62     }
63 
64     if (type == kBenchmarkMunmapOnly) state.ResumeTiming();
65 
66     if (munmap(addr, params.size) != 0) {
67       state.SkipWithError(
68           android::base::StringPrintf("munmap failed: %s", strerror(errno)).c_str());
69       break;
70     }
71     if (type == kBenchmarkMmapOnly) state.ResumeTiming();
72   }
73 }
74 
MmapBenchmark(benchmark::State & state,const struct MmapParams & params,int fd,void * area=nullptr)75 static void MmapBenchmark(benchmark::State& state, const struct MmapParams& params, int fd,
76                           void* area = nullptr) {
77   MmapBenchmarkImpl<kBenchmarkAll>(state, params, fd, area);
78 }
79 
MmapFixedBenchmark(benchmark::State & state,const struct MmapParams & params,int fd,size_t area_size,size_t offs)80 static void MmapFixedBenchmark(benchmark::State& state, const struct MmapParams& params, int fd,
81                                size_t area_size, size_t offs) {
82   if ((params.flags & MAP_FIXED) == 0) {
83     state.SkipWithError("MmapFixedBenchmark called without MAP_FIXED set");
84     return;
85   }
86 
87   // Create the mmap that will be used for the fixed mmaps.
88   uint8_t* area = reinterpret_cast<uint8_t*>(
89       mmap(nullptr, area_size, params.prot, params.flags & ~MAP_FIXED, fd, 0));
90   if (area == MAP_FAILED) {
91     state.SkipWithError(android::base::StringPrintf("mmap failed: %s", strerror(errno)).c_str());
92     return;
93   }
94 
95   MmapBenchmark(state, params, fd, area + offs);
96 
97   if (munmap(area, area_size) != 0) {
98     state.SkipWithError(android::base::StringPrintf("munmap failed: %s", strerror(errno)).c_str());
99     return;
100   }
101 }
102 
MmapFileBenchmark(benchmark::State & state,const struct MmapParams & params,size_t area_size,size_t offs)103 static void MmapFileBenchmark(benchmark::State& state, const struct MmapParams& params,
104                               size_t area_size, size_t offs) {
105   TemporaryFile tf;
106 
107   if (tf.fd < 0) {
108     state.SkipWithError(
109         android::base::StringPrintf("failed to create a temporary file: %s", strerror(errno))
110             .c_str());
111     return;
112   }
113 
114   if (area_size > 0 && ftruncate(tf.fd, area_size)) {
115     state.SkipWithError(
116         android::base::StringPrintf("ftruncate failed: %s", strerror(errno)).c_str());
117     return;
118   }
119 
120   if (params.flags & MAP_FIXED) {
121     MmapFixedBenchmark(state, params, tf.fd, area_size, offs);
122   } else {
123     MmapBenchmark(state, params, tf.fd);
124   }
125 }
126 
127 // anon mmap
BM_syscall_mmap_anon_rw(benchmark::State & state)128 static void BM_syscall_mmap_anon_rw(benchmark::State& state) {
129   struct MmapParams params = {
130       .prot = PROT_READ | PROT_WRITE,
131       .flags = MAP_PRIVATE | MAP_ANONYMOUS,
132       .size = state.range(0),
133   };
134 
135   MmapBenchmark(state, params, 0);
136 }
137 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_rw, "AT_ALL_PAGE_SIZES");
138 
BM_syscall_mmap_anon_noreserve(benchmark::State & state)139 static void BM_syscall_mmap_anon_noreserve(benchmark::State& state) {
140   struct MmapParams params = {
141       .prot = PROT_NONE,
142       .flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE,
143       .size = state.range(0),
144   };
145 
146   MmapBenchmark(state, params, 0);
147 }
148 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_noreserve, "AT_ALL_PAGE_SIZES");
149 
BM_syscall_mmap_anon_none(benchmark::State & state)150 static void BM_syscall_mmap_anon_none(benchmark::State& state) {
151   struct MmapParams params = {
152       .prot = PROT_NONE,
153       .flags = MAP_PRIVATE | MAP_ANONYMOUS,
154       .size = state.range(0),
155   };
156 
157   MmapBenchmark(state, params, 0);
158 }
159 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_none, "AT_ALL_PAGE_SIZES");
160 
161 // anon fixed mmap
BM_syscall_mmap_anon_rw_fixed(benchmark::State & state)162 static void BM_syscall_mmap_anon_rw_fixed(benchmark::State& state) {
163   struct MmapParams params = {
164       .prot = PROT_READ | PROT_WRITE,
165       .flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
166       .size = state.range(0),
167   };
168 
169   MmapFixedBenchmark(state, params, -1, params.size, 0);
170 }
171 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_rw_fixed, "AT_ALL_PAGE_SIZES");
172 
BM_syscall_mmap_anon_none_fixed(benchmark::State & state)173 static void BM_syscall_mmap_anon_none_fixed(benchmark::State& state) {
174   struct MmapParams params = {
175       .prot = PROT_NONE,
176       .flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
177       .size = state.range(0),
178   };
179 
180   MmapFixedBenchmark(state, params, -1, params.size, 0);
181 }
182 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_none_fixed, "AT_ALL_PAGE_SIZES");
183 
184 // file mmap
BM_syscall_mmap_file_rd_priv(benchmark::State & state)185 static void BM_syscall_mmap_file_rd_priv(benchmark::State& state) {
186   struct MmapParams params = {
187       .prot = PROT_READ,
188       .flags = MAP_PRIVATE,
189       .size = state.range(0),
190   };
191 
192   MmapFileBenchmark(state, params, params.size, 0);
193 }
194 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rd_priv, "AT_ALL_PAGE_SIZES");
195 
BM_syscall_mmap_file_rw_shared(benchmark::State & state)196 static void BM_syscall_mmap_file_rw_shared(benchmark::State& state) {
197   struct MmapParams params = {
198       .prot = PROT_READ | PROT_WRITE,
199       .flags = MAP_SHARED,
200       .size = state.range(0),
201   };
202 
203   MmapFileBenchmark(state, params, params.size, 0);
204 }
205 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rw_shared, "AT_ALL_PAGE_SIZES");
206 
207 // file fixed mmap
BM_syscall_mmap_file_rw_priv_fixed_start(benchmark::State & state)208 static void BM_syscall_mmap_file_rw_priv_fixed_start(benchmark::State& state) {
209   struct MmapParams params = {
210       .prot = PROT_READ | PROT_WRITE,
211       .flags = MAP_PRIVATE | MAP_FIXED,
212       .size = state.range(0),
213   };
214 
215   // allocate 3x area and map at the start
216   MmapFileBenchmark(state, params, params.size * 3, 0);
217 }
218 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rw_priv_fixed_start, "AT_ALL_PAGE_SIZES");
219 
BM_syscall_mmap_file_rw_priv_fixed_mid(benchmark::State & state)220 static void BM_syscall_mmap_file_rw_priv_fixed_mid(benchmark::State& state) {
221   struct MmapParams params = {
222       .prot = PROT_READ | PROT_WRITE,
223       .flags = MAP_PRIVATE | MAP_FIXED,
224       .size = state.range(0),
225   };
226 
227   // allocate 3x area and map at the middle
228   MmapFileBenchmark(state, params, params.size * 3, params.size);
229 }
230 // mapping at sub-page size offset is not supported, so run only for AT_MULTI_PAGE_SIZES
231 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rw_priv_fixed_mid, "AT_MULTI_PAGE_SIZES");
232 
BM_syscall_mmap_file_rw_priv_fixed_end(benchmark::State & state)233 static void BM_syscall_mmap_file_rw_priv_fixed_end(benchmark::State& state) {
234   struct MmapParams params = {
235       .prot = PROT_READ | PROT_WRITE,
236       .flags = MAP_PRIVATE | MAP_FIXED,
237       .size = state.range(0),
238   };
239 
240   // allocate 3x area and map at the end
241   MmapFileBenchmark(state, params, params.size * 3, params.size * 2);
242 }
243 // mapping at sub-page size offset is not supported, so run only for AT_MULTI_PAGE_SIZES
244 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rw_priv_fixed_end, "AT_MULTI_PAGE_SIZES");
245 
BM_syscall_mmap_anon_mmap_only(benchmark::State & state)246 static void BM_syscall_mmap_anon_mmap_only(benchmark::State& state) {
247   struct MmapParams params = {
248       .prot = PROT_READ | PROT_WRITE,
249       .flags = MAP_PRIVATE | MAP_ANONYMOUS,
250       .size = state.range(0),
251   };
252   MmapBenchmarkImpl<kBenchmarkMmapOnly>(state, params, 0);
253 }
254 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_mmap_only, "AT_MULTI_PAGE_SIZES");
255 
BM_syscall_mmap_anon_munmap_only(benchmark::State & state)256 static void BM_syscall_mmap_anon_munmap_only(benchmark::State& state) {
257   struct MmapParams params = {
258       .prot = PROT_READ | PROT_WRITE,
259       .flags = MAP_PRIVATE | MAP_ANONYMOUS,
260       .size = state.range(0),
261   };
262   MmapBenchmarkImpl<kBenchmarkMunmapOnly>(state, params, 0);
263 }
264 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_munmap_only, "AT_MULTI_PAGE_SIZES");
265 
MadviseBenchmark(benchmark::State & state,const struct MmapParams & params,int madvise_flags)266 void MadviseBenchmark(benchmark::State& state, const struct MmapParams& params, int madvise_flags) {
267   void* addr = mmap(nullptr, params.size, params.prot, params.flags, 0, 0);
268   if (addr == MAP_FAILED) {
269     state.SkipWithError(android::base::StringPrintf("mmap failed: %s", strerror(errno)).c_str());
270     return;
271   }
272   for (auto _ : state) {
273     state.PauseTiming();
274     if (params.prot & PROT_WRITE) {
275       MakeAllocationResident(addr, params.size, page_sz);
276     }
277     state.ResumeTiming();
278 
279     madvise(addr, params.size, madvise_flags);
280   }
281 
282   if (munmap(addr, params.size) != 0) {
283     state.SkipWithError(android::base::StringPrintf("munmap failed: %s", strerror(errno)).c_str());
284   }
285 }
286 
BM_syscall_mmap_anon_madvise_dontneed(benchmark::State & state)287 static void BM_syscall_mmap_anon_madvise_dontneed(benchmark::State& state) {
288   struct MmapParams params = {
289       .prot = PROT_READ | PROT_WRITE,
290       .flags = MAP_PRIVATE | MAP_ANONYMOUS,
291       .size = state.range(0),
292   };
293   MadviseBenchmark(state, params, MADV_DONTNEED);
294 }
295 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_madvise_dontneed, "AT_MULTI_PAGE_SIZES");
296 
BM_syscall_mmap_anon_madvise_pageout(benchmark::State & state)297 static void BM_syscall_mmap_anon_madvise_pageout(benchmark::State& state) {
298   struct MmapParams params = {
299       .prot = PROT_READ | PROT_WRITE,
300       .flags = MAP_PRIVATE | MAP_ANONYMOUS,
301       .size = state.range(0),
302   };
303   MadviseBenchmark(state, params, MADV_PAGEOUT);
304 }
305 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_madvise_pageout, "AT_MULTI_PAGE_SIZES");
306 
BM_syscall_mmap_anon_madvise_free(benchmark::State & state)307 static void BM_syscall_mmap_anon_madvise_free(benchmark::State& state) {
308   struct MmapParams params = {
309       .prot = PROT_READ | PROT_WRITE,
310       .flags = MAP_PRIVATE | MAP_ANONYMOUS,
311       .size = state.range(0),
312   };
313   MadviseBenchmark(state, params, MADV_FREE);
314 }
315 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_madvise_free, "AT_MULTI_PAGE_SIZES");
316 
MprotectBenchmark(benchmark::State & state,const struct MprotectParams & params,void * addr)317 void MprotectBenchmark(benchmark::State& state, const struct MprotectParams& params, void* addr) {
318   for (auto _ : state) {
319     state.PauseTiming();
320     /*
321      * Guarantee that physical memory pages are allocated for this region to prevent
322      * segmentation fault when using mprotect to change permissions.
323      */
324     if (params.initial_prot & PROT_WRITE) {
325       MakeAllocationResident(addr, params.size, page_sz);
326     }
327     state.ResumeTiming();
328 
329     if (mprotect(addr, params.size, params.mprotect_prot) != 0) {
330       state.SkipWithError(android::base::StringPrintf("mprotect failed: %m"));
331       break;
332     }
333 
334     state.PauseTiming();
335     // Revert back to the original protection
336     int res = mprotect(addr, params.size, params.initial_prot);
337     state.ResumeTiming();
338     if (res != 0) {
339       state.SkipWithError(
340           android::base::StringPrintf("mprotect failed to revert to original prot: %m"));
341       break;
342     }
343   }
344 }
345 
MprotectBenchmarkWithMmapAnon(benchmark::State & state,const struct MprotectParams & params)346 static void MprotectBenchmarkWithMmapAnon(benchmark::State& state,
347                                           const struct MprotectParams& params) {
348   void* addr = mmap(nullptr, params.size, params.initial_prot, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
349   if (addr == MAP_FAILED) {
350     state.SkipWithError(android::base::StringPrintf("mmap failed: %m"));
351     return;
352   }
353 
354   MprotectBenchmark(state, params, addr);
355 
356   if (munmap(addr, params.size) != 0)
357     state.SkipWithError(android::base::StringPrintf("munmap failed: %m"));
358 }
359 
BM_syscall_mmap_anon_mprotect_rw_to_rd(benchmark::State & state)360 static void BM_syscall_mmap_anon_mprotect_rw_to_rd(benchmark::State& state) {
361   struct MprotectParams params = {
362       .initial_prot = PROT_READ | PROT_WRITE,
363       .mprotect_prot = PROT_READ,
364       .size = state.range(0),
365   };
366   MprotectBenchmarkWithMmapAnon(state, params);
367 }
368 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_mprotect_rw_to_rd, "AT_ALL_PAGE_SIZES");
369 
BM_syscall_mmap_anon_mprotect_rw_to_none(benchmark::State & state)370 static void BM_syscall_mmap_anon_mprotect_rw_to_none(benchmark::State& state) {
371   struct MprotectParams params = {
372       .initial_prot = PROT_READ | PROT_WRITE,
373       .mprotect_prot = PROT_NONE,
374       .size = state.range(0),
375   };
376   MprotectBenchmarkWithMmapAnon(state, params);
377 }
378 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_mprotect_rw_to_none, "AT_ALL_PAGE_SIZES");
379 
BM_syscall_mmap_anon_mprotect_rd_to_none(benchmark::State & state)380 static void BM_syscall_mmap_anon_mprotect_rd_to_none(benchmark::State& state) {
381   struct MprotectParams params = {
382       .initial_prot = PROT_READ,
383       .mprotect_prot = PROT_NONE,
384       .size = state.range(0),
385   };
386   MprotectBenchmarkWithMmapAnon(state, params);
387 }
388 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_mprotect_rd_to_none, "AT_ALL_PAGE_SIZES");
389 
MprotectBenchmarkWithMmapFile(benchmark::State & state,const struct MprotectParams & params)390 static void MprotectBenchmarkWithMmapFile(benchmark::State& state,
391                                           const struct MprotectParams& params) {
392   TemporaryFile tf;
393 
394   if (tf.fd < 0) {
395     state.SkipWithError(android::base::StringPrintf("failed to create a temporary file: %m"));
396     return;
397   }
398 
399   if (params.size > 0 && ftruncate(tf.fd, params.size)) {
400     state.SkipWithError(android::base::StringPrintf("ftruncate failed: %m"));
401     return;
402   }
403 
404   void* addr = mmap(nullptr, params.size, params.initial_prot, MAP_PRIVATE, tf.fd, 0);
405   if (addr == MAP_FAILED) {
406     state.SkipWithError(android::base::StringPrintf("mmap failed: %m"));
407     return;
408   }
409 
410   MprotectBenchmark(state, params, addr);
411 
412   if (munmap(addr, params.size) != 0)
413     state.SkipWithError(android::base::StringPrintf("munmap failed: %m"));
414 }
415 
BM_syscall_mmap_file_mprotect_rw_to_rd(benchmark::State & state)416 static void BM_syscall_mmap_file_mprotect_rw_to_rd(benchmark::State& state) {
417   struct MprotectParams params = {
418       .initial_prot = PROT_READ | PROT_WRITE,
419       .mprotect_prot = PROT_READ,
420       .size = state.range(0),
421   };
422   MprotectBenchmarkWithMmapFile(state, params);
423 }
424 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_rw_to_rd, "AT_ALL_PAGE_SIZES");
425 
BM_syscall_mmap_file_mprotect_rw_to_none(benchmark::State & state)426 static void BM_syscall_mmap_file_mprotect_rw_to_none(benchmark::State& state) {
427   struct MprotectParams params = {
428       .initial_prot = PROT_READ | PROT_WRITE,
429       .mprotect_prot = PROT_NONE,
430       .size = state.range(0),
431   };
432   MprotectBenchmarkWithMmapFile(state, params);
433 }
434 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_rw_to_none, "AT_ALL_PAGE_SIZES");
435 
BM_syscall_mmap_file_mprotect_none_to_rw(benchmark::State & state)436 static void BM_syscall_mmap_file_mprotect_none_to_rw(benchmark::State& state) {
437   struct MprotectParams params = {
438       .initial_prot = PROT_NONE,
439       .mprotect_prot = PROT_READ | PROT_WRITE,
440       .size = state.range(0),
441   };
442   MprotectBenchmarkWithMmapFile(state, params);
443 }
444 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_none_to_rw, "AT_ALL_PAGE_SIZES");
445 
BM_syscall_mmap_file_mprotect_none_to_rd(benchmark::State & state)446 static void BM_syscall_mmap_file_mprotect_none_to_rd(benchmark::State& state) {
447   struct MprotectParams params = {
448       .initial_prot = PROT_NONE,
449       .mprotect_prot = PROT_READ,
450       .size = state.range(0),
451   };
452   MprotectBenchmarkWithMmapFile(state, params);
453 }
454 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_none_to_rd, "AT_ALL_PAGE_SIZES");
455 
BM_syscall_mmap_file_mprotect_rd_to_none(benchmark::State & state)456 static void BM_syscall_mmap_file_mprotect_rd_to_none(benchmark::State& state) {
457   struct MprotectParams params = {
458       .initial_prot = PROT_READ,
459       .mprotect_prot = PROT_NONE,
460       .size = state.range(0),
461   };
462   MprotectBenchmarkWithMmapFile(state, params);
463 }
464 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_rd_to_none, "AT_ALL_PAGE_SIZES");
465