1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #define PW_LOG_MODULE_NAME "KVS"
16 #define PW_LOG_LEVEL PW_KVS_LOG_LEVEL
17
18 #include "pw_kvs/internal/sectors.h"
19
20 #include "pw_kvs_private/config.h"
21 #include "pw_log/log.h"
22
23 namespace pw::kvs::internal {
24 namespace {
25
26 // Returns true if the container conatins the value.
27 // TODO(hepler): At some point move this to pw_containers, along with adding
28 // tests.
29 template <typename Container, typename T>
Contains(const Container & container,const T & value)30 bool Contains(const Container& container, const T& value) {
31 return std::find(std::begin(container), std::end(container), value) !=
32 std::end(container);
33 }
34
35 } // namespace
36
Find(FindMode find_mode,SectorDescriptor ** found_sector,size_t size,span<const Address> addresses_to_skip,span<const Address> reserved_addresses)37 Status Sectors::Find(FindMode find_mode,
38 SectorDescriptor** found_sector,
39 size_t size,
40 span<const Address> addresses_to_skip,
41 span<const Address> reserved_addresses) {
42 SectorDescriptor* first_empty_sector = nullptr;
43 bool at_least_two_empty_sectors = (find_mode == kGarbageCollect);
44
45 // Used for the GC reclaimable bytes check
46 SectorDescriptor* non_empty_least_reclaimable_sector = nullptr;
47 const size_t sector_size_bytes = partition_.sector_size_bytes();
48
49 // Build a list of sectors to avoid.
50 //
51 // This is overly strict. reserved_addresses is populated when there are
52 // sectors reserved for a new entry. It is safe to garbage collect into
53 // these sectors, as long as there remains room for the pending entry. These
54 // reserved sectors could also be garbage collected if they have recoverable
55 // space. For simplicitly, avoid both the relocating key's redundant entries
56 // (addresses_to_skip) and the sectors reserved for pending writes
57 // (reserved_addresses).
58 // TODO(hepler): Look into improving garbage collection.
59 size_t sectors_to_skip = 0;
60 for (Address address : addresses_to_skip) {
61 temp_sectors_to_skip_[sectors_to_skip++] = &FromAddress(address);
62 }
63 for (Address address : reserved_addresses) {
64 temp_sectors_to_skip_[sectors_to_skip++] = &FromAddress(address);
65 }
66
67 PW_LOG_DEBUG(
68 "Find sector with %u bytes available, starting with sector %u, %s",
69 unsigned(size),
70 Index(last_new_),
71 (find_mode == kAppendEntry) ? "Append" : "GC");
72 for (size_t i = 0; i < sectors_to_skip; ++i) {
73 PW_LOG_DEBUG(" Skip sector %u", Index(temp_sectors_to_skip_[i]));
74 }
75
76 // last_new_ is the sector that was last selected as the "new empty sector" to
77 // write to. This last new sector is used as the starting point for the next
78 // "find a new empty sector to write to" operation. By using the last new
79 // sector as the start point we will cycle which empty sector is selected
80 // next, spreading the wear across all the empty sectors and get a wear
81 // leveling benefit, rather than putting more wear on the lower number
82 // sectors.
83 SectorDescriptor* sector = last_new_;
84
85 // Look for a sector to use with enough space. The search uses a 3 priority
86 // tier process.
87 //
88 // Tier 1 is sector that already has valid data. During GC only select a
89 // sector that has no reclaimable bytes. Immediately use the first matching
90 // sector that is found.
91 //
92 // Tier 2 is find sectors that are empty/erased. While scanning for a partial
93 // sector, keep track of the first empty sector and if a second empty sector
94 // was seen. If during GC then count the second empty sector as always seen.
95 //
96 // Tier 3 is during garbage collection, find sectors with enough space that
97 // are not empty but have recoverable bytes. Pick the sector with the least
98 // recoverable bytes to minimize the likelyhood of this sector needing to be
99 // garbage collected soon.
100 for (size_t j = 0; j < descriptors_.size(); j++) {
101 sector += 1;
102 if (sector == descriptors_.end()) {
103 sector = descriptors_.begin();
104 }
105
106 // Skip sectors in the skip list.
107 if (Contains(span(temp_sectors_to_skip_, sectors_to_skip), sector)) {
108 continue;
109 }
110
111 if (!sector->Empty(sector_size_bytes) && sector->HasSpace(size)) {
112 if ((find_mode == kAppendEntry) ||
113 (sector->RecoverableBytes(sector_size_bytes) == 0)) {
114 *found_sector = sector;
115 return OkStatus();
116 } else {
117 if ((non_empty_least_reclaimable_sector == nullptr) ||
118 (non_empty_least_reclaimable_sector->RecoverableBytes(
119 sector_size_bytes) <
120 sector->RecoverableBytes(sector_size_bytes))) {
121 non_empty_least_reclaimable_sector = sector;
122 }
123 }
124 }
125
126 if (sector->Empty(sector_size_bytes)) {
127 if (first_empty_sector == nullptr) {
128 first_empty_sector = sector;
129 } else {
130 at_least_two_empty_sectors = true;
131 }
132 }
133 }
134
135 // Tier 2 check: If the scan for a partial sector does not find a suitable
136 // sector, use the first empty sector that was found. Normally it is required
137 // to keep 1 empty sector after the sector found here, but that rule does not
138 // apply during GC.
139 if (first_empty_sector != nullptr && at_least_two_empty_sectors) {
140 PW_LOG_DEBUG(
141 " Found a usable empty sector; returning the first found (%u)",
142 Index(first_empty_sector));
143 last_new_ = first_empty_sector;
144 *found_sector = first_empty_sector;
145 return OkStatus();
146 }
147
148 // Tier 3 check: If we got this far, use the sector with least recoverable
149 // bytes
150 if (non_empty_least_reclaimable_sector != nullptr) {
151 *found_sector = non_empty_least_reclaimable_sector;
152 PW_LOG_DEBUG(
153 " Found a usable sector %u, with %u B recoverable, in GC",
154 Index(*found_sector),
155 unsigned((*found_sector)->RecoverableBytes(sector_size_bytes)));
156 return OkStatus();
157 }
158
159 // No sector was found.
160 PW_LOG_DEBUG(" Unable to find a usable sector");
161 *found_sector = nullptr;
162 return Status::ResourceExhausted();
163 }
164
WearLeveledSectorFromIndex(size_t idx) const165 SectorDescriptor& Sectors::WearLeveledSectorFromIndex(size_t idx) const {
166 return descriptors_[(Index(last_new_) + 1 + idx) % descriptors_.size()];
167 }
168
169 // TODO(hepler): Consider breaking this function into smaller sub-chunks.
FindSectorToGarbageCollect(span<const Address> reserved_addresses) const170 SectorDescriptor* Sectors::FindSectorToGarbageCollect(
171 span<const Address> reserved_addresses) const {
172 const size_t sector_size_bytes = partition_.sector_size_bytes();
173 SectorDescriptor* sector_candidate = nullptr;
174 size_t candidate_bytes = 0;
175
176 // Build a vector of sectors to avoid.
177 for (size_t i = 0; i < reserved_addresses.size(); ++i) {
178 temp_sectors_to_skip_[i] = &FromAddress(reserved_addresses[i]);
179 PW_LOG_DEBUG(" Skip sector %u", Index(reserved_addresses[i]));
180 }
181 const span sectors_to_skip(temp_sectors_to_skip_, reserved_addresses.size());
182
183 // Step 1: Try to find a sectors with stale keys and no valid keys (no
184 // relocation needed). Use the first such sector found, as that will help the
185 // KVS "rotate" around the partition. Initially this would select the sector
186 // with the most reclaimable space, but that can cause GC sector selection to
187 // "ping-pong" between two sectors when updating large keys.
188 for (size_t i = 0; i < descriptors_.size(); ++i) {
189 SectorDescriptor& sector = WearLeveledSectorFromIndex(i);
190 if ((sector.valid_bytes() == 0) &&
191 (sector.RecoverableBytes(sector_size_bytes) > 0) &&
192 !Contains(sectors_to_skip, §or)) {
193 sector_candidate = §or;
194 break;
195 }
196 }
197
198 // Step 2: If step 1 yields no sectors, just find the sector with the most
199 // reclaimable bytes but no addresses to avoid.
200 if (sector_candidate == nullptr) {
201 for (size_t i = 0; i < descriptors_.size(); ++i) {
202 SectorDescriptor& sector = WearLeveledSectorFromIndex(i);
203 if ((sector.RecoverableBytes(sector_size_bytes) > candidate_bytes) &&
204 !Contains(sectors_to_skip, §or)) {
205 sector_candidate = §or;
206 candidate_bytes = sector.RecoverableBytes(sector_size_bytes);
207 }
208 }
209 }
210
211 // Step 3: If no sectors with reclaimable bytes, select the sector with the
212 // most free bytes. This at least will allow entries of existing keys to get
213 // spread to other sectors, including sectors that already have copies of the
214 // current key being written.
215 if (sector_candidate == nullptr) {
216 for (size_t i = 0; i < descriptors_.size(); ++i) {
217 SectorDescriptor& sector = WearLeveledSectorFromIndex(i);
218 if ((sector.valid_bytes() > candidate_bytes) &&
219 !Contains(sectors_to_skip, §or)) {
220 sector_candidate = §or;
221 candidate_bytes = sector.valid_bytes();
222 PW_LOG_DEBUG(" Doing GC on sector with no reclaimable bytes!");
223 }
224 }
225 }
226
227 if (sector_candidate != nullptr) {
228 PW_LOG_DEBUG(
229 "Found sector %u to Garbage Collect, %u recoverable bytes",
230 Index(sector_candidate),
231 unsigned(sector_candidate->RecoverableBytes(sector_size_bytes)));
232 } else {
233 PW_LOG_DEBUG("Unable to find sector to garbage collect!");
234 }
235 return sector_candidate;
236 }
237
238 } // namespace pw::kvs::internal
239