/* * Copyright (C) 2017 The Android Open Source Project * * 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 "perfetto/base/paged_memory.h" #include <algorithm> #include <cmath> #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) #include <Windows.h> #else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) #include <sys/mman.h> #endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) #include "perfetto/base/logging.h" #include "perfetto/base/utils.h" namespace perfetto { namespace base { namespace { constexpr size_t kGuardSize = kPageSize; #if TRACK_COMMITTED_SIZE() constexpr size_t kCommitChunkSize = kPageSize * 1024; // 4mB #endif // TRACK_COMMITTED_SIZE() } // namespace // static PagedMemory PagedMemory::Allocate(size_t size, int flags) { PERFETTO_DCHECK(size % kPageSize == 0); size_t outer_size = size + kGuardSize * 2; #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) void* ptr = VirtualAlloc(nullptr, outer_size, MEM_RESERVE, PAGE_NOACCESS); if (!ptr && (flags & kMayFail)) return PagedMemory(); PERFETTO_CHECK(ptr); char* usable_region = reinterpret_cast<char*>(ptr) + kGuardSize; #else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) void* ptr = mmap(nullptr, outer_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (ptr == MAP_FAILED && (flags & kMayFail)) return PagedMemory(); PERFETTO_CHECK(ptr && ptr != MAP_FAILED); char* usable_region = reinterpret_cast<char*>(ptr) + kGuardSize; int res = mprotect(ptr, kGuardSize, PROT_NONE); res |= mprotect(usable_region + size, kGuardSize, PROT_NONE); PERFETTO_CHECK(res == 0); #endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) auto memory = PagedMemory(usable_region, size); #if TRACK_COMMITTED_SIZE() size_t initial_commit = size; if (flags & kDontCommit) initial_commit = std::min(initial_commit, kCommitChunkSize); memory.EnsureCommitted(initial_commit); #endif // TRACK_COMMITTED_SIZE() return memory; } PagedMemory::PagedMemory() {} PagedMemory::PagedMemory(char* p, size_t size) : p_(p), size_(size) { ANNOTATE_NEW_BUFFER(p_, size_, committed_size_); } PagedMemory::PagedMemory(PagedMemory&& other) noexcept { *this = other; other.p_ = nullptr; } PagedMemory& PagedMemory::operator=(PagedMemory&& other) { *this = other; other.p_ = nullptr; return *this; } PagedMemory::~PagedMemory() { if (!p_) return; PERFETTO_CHECK(size_); char* start = p_ - kGuardSize; #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) BOOL res = VirtualFree(start, 0, MEM_RELEASE); PERFETTO_CHECK(res != 0); #else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) const size_t outer_size = size_ + kGuardSize * 2; int res = munmap(start, outer_size); PERFETTO_CHECK(res == 0); #endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) ANNOTATE_DELETE_BUFFER(p_, size_, committed_size_); } bool PagedMemory::AdviseDontNeed(void* p, size_t size) { PERFETTO_DCHECK(p_); PERFETTO_DCHECK(p >= p_); PERFETTO_DCHECK(static_cast<char*>(p) + size <= p_ + size_); #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) // Discarding pages on Windows has more CPU cost than is justified for the // possible memory savings. return false; #else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) // http://man7.org/linux/man-pages/man2/madvise.2.html int res = madvise(p, size, MADV_DONTNEED); PERFETTO_DCHECK(res == 0); return true; #endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) } #if TRACK_COMMITTED_SIZE() void PagedMemory::EnsureCommitted(size_t committed_size) { PERFETTO_DCHECK(committed_size > 0u); PERFETTO_DCHECK(committed_size <= size_); #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) if (committed_size_ >= committed_size) return; // Rounding up. size_t delta = committed_size - committed_size_; size_t num_additional_chunks = (delta + kCommitChunkSize - 1) / kCommitChunkSize; PERFETTO_DCHECK(num_additional_chunks * kCommitChunkSize >= delta); // Don't commit more than the total size. size_t commit_size = std::min(num_additional_chunks * kCommitChunkSize, size_ - committed_size_); void* res = VirtualAlloc(p_ + committed_size_, commit_size, MEM_COMMIT, PAGE_READWRITE); PERFETTO_CHECK(res); ANNOTATE_CHANGE_SIZE(p_, size_, committed_size_, committed_size_ + commit_size); committed_size_ += commit_size; #else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) // mmap commits automatically as needed, so we only track here for ASAN. committed_size = std::max(committed_size_, committed_size); ANNOTATE_CHANGE_SIZE(p_, size_, committed_size_, committed_size); committed_size_ = committed_size; #endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) } #endif // TRACK_COMMITTED_SIZE() } // namespace base } // namespace perfetto