xref: /aosp_15_r20/external/perfetto/src/tracing/core/shared_memory_abi.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2017 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
6  * License. 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,
11  * software distributed under the License is distributed on an "AS
12  * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
13  * express or implied. See the License for the specific language
14  * governing permissions and limitations under the License.
15  */
16 #include "perfetto/ext/tracing/core/shared_memory_abi.h"
17 
18 #include "perfetto/base/build_config.h"
19 #include "perfetto/base/time.h"
20 
21 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
22 #include <sys/mman.h>
23 #endif
24 
25 #include "perfetto/ext/base/utils.h"
26 #include "perfetto/ext/tracing/core/basic_types.h"
27 
28 namespace perfetto {
29 
30 namespace {
31 
32 constexpr int kRetryAttempts = 64;
33 
WaitBeforeNextAttempt(int attempt)34 inline void WaitBeforeNextAttempt(int attempt) {
35   if (attempt < kRetryAttempts / 2) {
36     std::this_thread::yield();
37   } else {
38     base::SleepMicroseconds((unsigned(attempt) / 10) * 1000);
39   }
40 }
41 
42 // Returns the largest 4-bytes aligned chunk size <= |page_size| / |divider|
43 // for each divider in PageLayout.
GetChunkSize(size_t page_size,size_t divider)44 constexpr size_t GetChunkSize(size_t page_size, size_t divider) {
45   return ((page_size - sizeof(SharedMemoryABI::PageHeader)) / divider) & ~3UL;
46 }
47 
48 // Initializer for the const |chunk_sizes_| array.
InitChunkSizes(size_t page_size)49 std::array<uint16_t, SharedMemoryABI::kNumPageLayouts> InitChunkSizes(
50     size_t page_size) {
51   static_assert(SharedMemoryABI::kNumPageLayouts ==
52                     base::ArraySize(SharedMemoryABI::kNumChunksForLayout),
53                 "kNumPageLayouts out of date");
54   std::array<uint16_t, SharedMemoryABI::kNumPageLayouts> res = {};
55   for (size_t i = 0; i < SharedMemoryABI::kNumPageLayouts; i++) {
56     size_t num_chunks = SharedMemoryABI::kNumChunksForLayout[i];
57     size_t size = num_chunks == 0 ? 0 : GetChunkSize(page_size, num_chunks);
58     PERFETTO_CHECK(size <= std::numeric_limits<uint16_t>::max());
59     res[i] = static_cast<uint16_t>(size);
60   }
61   return res;
62 }
63 
ClearChunkHeader(SharedMemoryABI::ChunkHeader * header)64 inline void ClearChunkHeader(SharedMemoryABI::ChunkHeader* header) {
65   header->writer_id.store(0u, std::memory_order_relaxed);
66   header->chunk_id.store(0u, std::memory_order_relaxed);
67   header->packets.store({}, std::memory_order_release);
68 }
69 
70 }  // namespace
71 
72 SharedMemoryABI::SharedMemoryABI() = default;
73 
SharedMemoryABI(uint8_t * start,size_t size,size_t page_size,ShmemMode mode)74 SharedMemoryABI::SharedMemoryABI(uint8_t* start,
75                                  size_t size,
76                                  size_t page_size,
77                                  ShmemMode mode) {
78   Initialize(start, size, page_size, mode);
79 }
80 
Initialize(uint8_t * start,size_t size,size_t page_size,ShmemMode mode)81 void SharedMemoryABI::Initialize(uint8_t* start,
82                                  size_t size,
83                                  size_t page_size,
84                                  ShmemMode mode) {
85   start_ = start;
86   size_ = size;
87   page_size_ = page_size;
88   use_shmem_emulation_ = mode == ShmemMode::kShmemEmulation;
89   num_pages_ = size / page_size;
90   chunk_sizes_ = InitChunkSizes(page_size);
91   static_assert(sizeof(PageHeader) == 8, "PageHeader size");
92   static_assert(sizeof(ChunkHeader) == 8, "ChunkHeader size");
93   static_assert(sizeof(ChunkHeader::chunk_id) == sizeof(ChunkID),
94                 "ChunkID size");
95 
96   static_assert(sizeof(ChunkHeader::Packets) == 2, "ChunkHeader::Packets size");
97   static_assert(alignof(ChunkHeader) == kChunkAlignment,
98                 "ChunkHeader alignment");
99 
100   // In theory std::atomic does not guarantee that the underlying type
101   // consists only of the actual atomic word. Theoretically it could have
102   // locks or other state. In practice most implementations just implement
103   // them without extra state. The code below overlays the atomic into the
104   // SMB, hence relies on this implementation detail. This should be fine
105   // pragmatically (Chrome's base makes the same assumption), but let's have a
106   // check for this.
107   static_assert(sizeof(std::atomic<uint32_t>) == sizeof(uint32_t) &&
108                     sizeof(std::atomic<uint16_t>) == sizeof(uint16_t),
109                 "Incompatible STL <atomic> implementation");
110 
111   // Chec that the kAllChunks(Complete,Free) are consistent with the
112   // ChunkState enum values.
113 
114   // These must be zero because rely on zero-initialized memory being
115   // interpreted as "free".
116   static_assert(kChunkFree == 0 && kAllChunksFree == 0,
117                 "kChunkFree/kAllChunksFree and must be 0");
118 
119   static_assert((kAllChunksComplete & kChunkMask) == kChunkComplete,
120                 "kAllChunksComplete out of sync with kChunkComplete");
121 
122   // Check the consistency of the kMax... constants.
123   static_assert(sizeof(ChunkHeader::writer_id) == sizeof(WriterID),
124                 "WriterID size");
125   ChunkHeader chunk_header{};
126   chunk_header.chunk_id.store(static_cast<uint32_t>(-1));
127   PERFETTO_CHECK(chunk_header.chunk_id.load() == kMaxChunkID);
128 
129   chunk_header.writer_id.store(static_cast<uint16_t>(-1));
130   PERFETTO_CHECK(kMaxWriterID <= chunk_header.writer_id.load());
131 
132   PERFETTO_CHECK(page_size >= kMinPageSize);
133   PERFETTO_CHECK(page_size <= kMaxPageSize);
134   PERFETTO_CHECK(page_size % kMinPageSize == 0);
135   PERFETTO_CHECK(reinterpret_cast<uintptr_t>(start) % kMinPageSize == 0);
136   PERFETTO_CHECK(size % page_size == 0);
137 }
138 
GetChunkUnchecked(size_t page_idx,uint32_t header_bitmap,size_t chunk_idx)139 SharedMemoryABI::Chunk SharedMemoryABI::GetChunkUnchecked(
140     size_t page_idx,
141     uint32_t header_bitmap,
142     size_t chunk_idx) {
143   const size_t num_chunks = GetNumChunksFromHeaderBitmap(header_bitmap);
144   PERFETTO_DCHECK(chunk_idx < num_chunks);
145   // Compute the chunk virtual address and write it into |chunk|.
146   const uint16_t chunk_size = GetChunkSizeFromHeaderBitmap(header_bitmap);
147   size_t chunk_offset_in_page = sizeof(PageHeader) + chunk_idx * chunk_size;
148 
149   Chunk chunk(page_start(page_idx) + chunk_offset_in_page, chunk_size,
150               static_cast<uint8_t>(chunk_idx));
151   PERFETTO_DCHECK(chunk.end() <= end());
152   return chunk;
153 }
154 
TryAcquireChunk(size_t page_idx,size_t chunk_idx,ChunkState desired_chunk_state,const ChunkHeader * header)155 SharedMemoryABI::Chunk SharedMemoryABI::TryAcquireChunk(
156     size_t page_idx,
157     size_t chunk_idx,
158     ChunkState desired_chunk_state,
159     const ChunkHeader* header) {
160   PERFETTO_DCHECK(desired_chunk_state == kChunkBeingRead ||
161                   desired_chunk_state == kChunkBeingWritten);
162   PageHeader* phdr = page_header(page_idx);
163   for (int attempt = 0; attempt < kRetryAttempts; attempt++) {
164     uint32_t header_bitmap =
165         phdr->header_bitmap.load(std::memory_order_acquire);
166     const size_t num_chunks = GetNumChunksFromHeaderBitmap(header_bitmap);
167 
168     // The page layout has changed (or the page is free).
169     if (chunk_idx >= num_chunks)
170       return Chunk();
171 
172     // Verify that the chunk is still in a state that allows the transition to
173     // |desired_chunk_state|. The only allowed transitions are:
174     // 1. kChunkFree -> kChunkBeingWritten (Producer).
175     // 2. kChunkComplete -> kChunkBeingRead (Service).
176     ChunkState expected_chunk_state =
177         desired_chunk_state == kChunkBeingWritten ? kChunkFree : kChunkComplete;
178     auto cur_chunk_state =
179         GetChunkStateFromHeaderBitmap(header_bitmap, chunk_idx);
180     if (cur_chunk_state != expected_chunk_state)
181       return Chunk();
182 
183     uint32_t next_header_bitmap = header_bitmap;
184     next_header_bitmap &= ~(kChunkMask << (chunk_idx * kChunkShift));
185     next_header_bitmap |= (desired_chunk_state << (chunk_idx * kChunkShift));
186     if (phdr->header_bitmap.compare_exchange_strong(
187             header_bitmap, next_header_bitmap, std::memory_order_acq_rel)) {
188       // Compute the chunk virtual address and write it into |chunk|.
189       Chunk chunk = GetChunkUnchecked(page_idx, header_bitmap, chunk_idx);
190       if (desired_chunk_state == kChunkBeingWritten) {
191         PERFETTO_DCHECK(header);
192         ChunkHeader* new_header = chunk.header();
193         new_header->writer_id.store(header->writer_id,
194                                     std::memory_order_relaxed);
195         new_header->chunk_id.store(header->chunk_id, std::memory_order_relaxed);
196         new_header->packets.store(header->packets, std::memory_order_release);
197       }
198       return chunk;
199     }
200     WaitBeforeNextAttempt(attempt);
201   }
202   return Chunk();  // All our attempts failed.
203 }
204 
TryPartitionPage(size_t page_idx,PageLayout layout)205 bool SharedMemoryABI::TryPartitionPage(size_t page_idx, PageLayout layout) {
206   PERFETTO_DCHECK(layout >= kPageDiv1 && layout <= kPageDiv14);
207   uint32_t expected_bitmap = 0;  // Free page.
208   uint32_t next_bitmap = (layout << kLayoutShift) & kLayoutMask;
209   PageHeader* phdr = page_header(page_idx);
210   if (!phdr->header_bitmap.compare_exchange_strong(expected_bitmap, next_bitmap,
211                                                    std::memory_order_acq_rel)) {
212     return false;
213   }
214   return true;
215 }
216 
GetFreeChunks(size_t page_idx)217 uint32_t SharedMemoryABI::GetFreeChunks(size_t page_idx) {
218   uint32_t bitmap = GetPageHeaderBitmap(page_idx, std::memory_order_relaxed);
219   const uint32_t num_chunks = GetNumChunksFromHeaderBitmap(bitmap);
220   uint32_t res = 0;
221 
222   for (uint32_t i = 0; i < num_chunks; i++) {
223     res |=
224         (GetChunkStateFromHeaderBitmap(bitmap, i) == kChunkFree) ? (1 << i) : 0;
225   }
226   return res;
227 }
228 
ReleaseChunk(Chunk chunk,ChunkState desired_chunk_state)229 size_t SharedMemoryABI::ReleaseChunk(Chunk chunk,
230                                      ChunkState desired_chunk_state) {
231   PERFETTO_DCHECK(desired_chunk_state == kChunkComplete ||
232                   desired_chunk_state == kChunkFree);
233 
234   size_t page_idx;
235   size_t chunk_idx;
236   std::tie(page_idx, chunk_idx) = GetPageAndChunkIndex(chunk);
237 
238   // Reset header fields, so that the service can identify when the chunk's
239   // header has been initialized by the producer.
240   if (desired_chunk_state == kChunkFree)
241     ClearChunkHeader(chunk.header());
242 
243   for (int attempt = 0; attempt < kRetryAttempts; attempt++) {
244     PageHeader* phdr = page_header(page_idx);
245     uint32_t bitmap = phdr->header_bitmap.load(std::memory_order_relaxed);
246     const size_t page_chunk_size = GetChunkSizeFromHeaderBitmap(bitmap);
247 
248     // TODO(primiano): this should not be a CHECK, because a malicious producer
249     // could crash us by putting the chunk in an invalid state. This should
250     // gracefully fail. Keep a CHECK until then.
251     PERFETTO_CHECK(chunk.size() == page_chunk_size);
252     const uint32_t chunk_state =
253         GetChunkStateFromHeaderBitmap(bitmap, chunk_idx);
254 
255     // Verify that the chunk is still in a state that allows the transition to
256     // |desired_chunk_state|. The only allowed transitions are:
257     // 1. kChunkBeingWritten -> kChunkComplete (Producer).
258     // 2. kChunkBeingRead -> kChunkFree (Service).
259     // Or in the emulation mode, the allowed transitions are:
260     // 1. kChunkBeingWritten -> kChunkComplete (Producer).
261     // 2. kChunkComplete -> kChunkFree (Producer).
262     ChunkState expected_chunk_state;
263     if (desired_chunk_state == kChunkComplete) {
264       expected_chunk_state = kChunkBeingWritten;
265     } else {
266       expected_chunk_state =
267           use_shmem_emulation_ ? kChunkComplete : kChunkBeingRead;
268     }
269 
270     // TODO(primiano): should not be a CHECK (same rationale of comment above).
271     PERFETTO_CHECK(chunk_state == expected_chunk_state);
272     uint32_t next_bitmap = bitmap;
273     next_bitmap &= ~(kChunkMask << (chunk_idx * kChunkShift));
274     next_bitmap |= (desired_chunk_state << (chunk_idx * kChunkShift));
275 
276     // If we are freeing a chunk and all the other chunks in the page are free
277     // we should de-partition the page and mark it as clear.
278     if ((next_bitmap & kAllChunksMask) == kAllChunksFree)
279       next_bitmap = 0;
280 
281     if (phdr->header_bitmap.compare_exchange_strong(
282             bitmap, next_bitmap, std::memory_order_acq_rel)) {
283       return page_idx;
284     }
285     WaitBeforeNextAttempt(attempt);
286   }
287   // Too much contention on this page. Give up. This page will be left pending
288   // forever but there isn't much more we can do at this point.
289   PERFETTO_DFATAL("Too much contention on page.");
290   return kInvalidPageIdx;
291 }
292 
293 SharedMemoryABI::Chunk::Chunk() = default;
294 
Chunk(uint8_t * begin,uint16_t size,uint8_t chunk_idx)295 SharedMemoryABI::Chunk::Chunk(uint8_t* begin, uint16_t size, uint8_t chunk_idx)
296     : begin_(begin), size_(size), chunk_idx_(chunk_idx) {
297   PERFETTO_CHECK(reinterpret_cast<uintptr_t>(begin) % kChunkAlignment == 0);
298   PERFETTO_CHECK(size > 0);
299 }
300 
Chunk(Chunk && o)301 SharedMemoryABI::Chunk::Chunk(Chunk&& o) noexcept {
302   *this = std::move(o);
303 }
304 
operator =(Chunk && o)305 SharedMemoryABI::Chunk& SharedMemoryABI::Chunk::operator=(Chunk&& o) {
306   begin_ = o.begin_;
307   size_ = o.size_;
308   chunk_idx_ = o.chunk_idx_;
309   o.begin_ = nullptr;
310   o.size_ = 0;
311   o.chunk_idx_ = 0;
312   return *this;
313 }
314 
GetPageAndChunkIndex(const Chunk & chunk)315 std::pair<size_t, size_t> SharedMemoryABI::GetPageAndChunkIndex(
316     const Chunk& chunk) {
317   PERFETTO_DCHECK(chunk.is_valid());
318   PERFETTO_DCHECK(chunk.begin() >= start_);
319   PERFETTO_DCHECK(chunk.end() <= start_ + size_);
320 
321   // TODO(primiano): The divisions below could be avoided if we cached
322   // |page_shift_|.
323   const uintptr_t rel_addr = static_cast<uintptr_t>(chunk.begin() - start_);
324   const size_t page_idx = rel_addr / page_size_;
325   const size_t offset = rel_addr % page_size_;
326   PERFETTO_DCHECK(offset >= sizeof(PageHeader));
327   PERFETTO_DCHECK(offset % kChunkAlignment == 0);
328   PERFETTO_DCHECK((offset - sizeof(PageHeader)) % chunk.size() == 0);
329   const size_t chunk_idx = (offset - sizeof(PageHeader)) / chunk.size();
330   PERFETTO_DCHECK(chunk_idx < kMaxChunksPerPage);
331   PERFETTO_DCHECK(chunk_idx <
332                   GetNumChunksFromHeaderBitmap(GetPageHeaderBitmap(page_idx)));
333   return std::make_pair(page_idx, chunk_idx);
334 }
335 
336 }  // namespace perfetto
337