storage/allocator/ssd/ssd_buddy_allocator.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | #include <cstring> | ||
| 5 | #include <mutex> | ||
| 6 | #include <stdexcept> | ||
| 7 | #include <unordered_set> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "storage/allocator/ssd/ssd_block_allocator.h" | ||
| 11 | #include "storage/io_backend/io_backend.h" | ||
| 12 | |||
| 13 | class SsdBuddyAllocator : public SsdBlockAllocator { | ||
| 14 | public: | ||
| 15 | 678 | SsdBuddyAllocator(IOBackend* backend, | |
| 16 | int min_block_size, | ||
| 17 | int num_levels, | ||
| 18 | uint64_t capacity_bytes, | ||
| 19 | uint64_t base_byte_offset) | ||
| 20 | 1356 | : backend_(backend), | |
| 21 | 678 | min_block_size_(min_block_size), | |
| 22 | 678 | num_levels_(num_levels), | |
| 23 | 678 | total_min_blocks_(capacity_bytes / min_block_size), | |
| 24 | 678 | base_byte_offset_(base_byte_offset) { | |
| 25 | 678 | if (backend_ == nullptr || min_block_size_ <= 0 || num_levels_ <= 0) { | |
| 26 | ✗ | throw std::invalid_argument("invalid SsdBuddyAllocator config"); | |
| 27 | } | ||
| 28 | 678 | free_lists_.resize(num_levels_); | |
| 29 | 678 | const uint64_t max_block_min_units = uint64_t{1} << (num_levels_ - 1); | |
| 30 | 1829592 | for (uint64_t block = 1; block + max_block_min_units <= total_min_blocks_; | |
| 31 | 1828914 | block += max_block_min_units) { | |
| 32 | 1828914 | free_lists_.back().insert(block); | |
| 33 | } | ||
| 34 | 678 | } | |
| 35 | |||
| 36 | 8487472 | uint64_t Alloc(size_t data_size) override { | |
| 37 | 8487472 | std::lock_guard<std::mutex> lock(mu_); | |
| 38 | 8487472 | const int level = LevelFor(data_size); | |
| 39 | 16974940 | return AllocAtLevel(level); | |
| 40 | 8487472 | } | |
| 41 | |||
| 42 | 41986 | void Free(uint64_t block_addr) override { | |
| 43 | 41986 | if (block_addr == kInvalidHandle || block_addr == 0) { | |
| 44 | ✗ | return; | |
| 45 | } | ||
| 46 | 41986 | uint32_t data_size = 0; | |
| 47 | 41986 | uint32_t level = 0; | |
| 48 | 41986 | ReadHeader(block_addr, data_size, level); | |
| 49 | 41986 | std::lock_guard<std::mutex> lock(mu_); | |
| 50 | 41986 | FreeAtLevel(block_addr, static_cast<int>(level)); | |
| 51 | 41986 | } | |
| 52 | |||
| 53 | 98768 | void Write(uint64_t block_addr, const void* data, size_t data_size) override { | |
| 54 | 98768 | const int level = LevelFor(data_size); | |
| 55 | 98768 | WriteWithLevel(block_addr, level, data, data_size); | |
| 56 | 98768 | } | |
| 57 | |||
| 58 | 73602 | size_t Read(uint64_t block_addr, void* out_buf, size_t buf_size) override { | |
| 59 | 73602 | std::lock_guard<std::mutex> lock(io_mu_); | |
| 60 | 73602 | uint32_t data_size = 0; | |
| 61 | 73602 | uint32_t level = 0; | |
| 62 | 73602 | ReadHeaderUnlocked(block_addr, data_size, level); | |
| 63 | 73602 | const uint64_t block_size = static_cast<uint64_t>(min_block_size_) << level; | |
| 64 | 73602 | const PageID_t start = BlockByteOffset(block_addr) / PAGE_SIZE; | |
| 65 | 73602 | const uint64_t page_off = BlockByteOffset(block_addr) % PAGE_SIZE; | |
| 66 | 73602 | const uint64_t pages = (page_off + block_size + PAGE_SIZE - 1) / PAGE_SIZE; | |
| 67 | 73602 | char* buf = backend_->AllocateBuffer(pages); | |
| 68 | 73602 | backend_->BatchReadPages({{start, buf, pages}}); | |
| 69 | 73602 | const size_t actual = std::min(buf_size, static_cast<size_t>(data_size)); | |
| 70 | 73602 | std::memcpy(out_buf, buf + page_off + kHeaderSize, actual); | |
| 71 | 73602 | backend_->FreeBuffer(buf); | |
| 72 | 73602 | return actual; | |
| 73 | 73602 | } | |
| 74 | |||
| 75 | 98768 | uint64_t AllocAndWrite(const void* data, size_t data_size) override { | |
| 76 | 98768 | const uint64_t handle = Alloc(data_size); | |
| 77 | 98768 | if (handle != kInvalidHandle) { | |
| 78 | 98768 | Write(handle, data, data_size); | |
| 79 | } | ||
| 80 | 98768 | return handle; | |
| 81 | } | ||
| 82 | |||
| 83 | 95216 | size_t SlotCapacity(uint64_t block_addr) const override { | |
| 84 | 95216 | if (block_addr == 0 || block_addr == kInvalidHandle) { | |
| 85 | ✗ | return 0; | |
| 86 | } | ||
| 87 | 95216 | std::lock_guard<std::mutex> lock(io_mu_); | |
| 88 | 95216 | uint32_t data_size = 0; | |
| 89 | 95216 | uint32_t level = 0; | |
| 90 | 95216 | const_cast<SsdBuddyAllocator*>(this)->ReadHeaderUnlocked( | |
| 91 | block_addr, data_size, level); | ||
| 92 | 95216 | return (static_cast<uint64_t>(min_block_size_) << level) - kHeaderSize; | |
| 93 | 95216 | } | |
| 94 | |||
| 95 | static constexpr int kHeaderSize = 8; | ||
| 96 | |||
| 97 | private: | ||
| 98 | 8586240 | int LevelFor(size_t data_size) const { | |
| 99 | 8586240 | const uint64_t needed = static_cast<uint64_t>(data_size) + kHeaderSize; | |
| 100 | 17172068 | for (int level = 0; level < num_levels_; ++level) { | |
| 101 | 17172066 | if ((static_cast<uint64_t>(min_block_size_) << level) >= needed) { | |
| 102 | 8586238 | return level; | |
| 103 | } | ||
| 104 | } | ||
| 105 | 2 | throw std::invalid_argument("SsdBuddyAllocator value too large"); | |
| 106 | } | ||
| 107 | |||
| 108 | 8487470 | uint64_t AllocAtLevel(int level) { | |
| 109 | 16410094 | for (int cur = level; cur < num_levels_; ++cur) { | |
| 110 | 16410088 | if (free_lists_[cur].empty()) { | |
| 111 | 7922624 | continue; | |
| 112 | } | ||
| 113 | 8487464 | uint64_t block = *free_lists_[cur].begin(); | |
| 114 | 8487464 | free_lists_[cur].erase(block); | |
| 115 | 16410066 | while (cur > level) { | |
| 116 | 7922602 | --cur; | |
| 117 | 7922602 | const uint64_t buddy = block + (uint64_t{1} << cur); | |
| 118 | 7922602 | free_lists_[cur].insert(buddy); | |
| 119 | } | ||
| 120 | 8487464 | return block; | |
| 121 | } | ||
| 122 | 6 | return kInvalidHandle; | |
| 123 | } | ||
| 124 | |||
| 125 | 41986 | void FreeAtLevel(uint64_t block, int level) { | |
| 126 | 42126 | while (level + 1 < num_levels_) { | |
| 127 | 42126 | const uint64_t buddy = block ^ (uint64_t{1} << level); | |
| 128 | 42126 | auto it = free_lists_[level].find(buddy); | |
| 129 | 42126 | if (it == free_lists_[level].end()) { | |
| 130 | 41986 | break; | |
| 131 | } | ||
| 132 | 140 | free_lists_[level].erase(it); | |
| 133 | 140 | block = std::min(block, buddy); | |
| 134 | 140 | ++level; | |
| 135 | } | ||
| 136 | 41986 | free_lists_[level].insert(block); | |
| 137 | 41986 | } | |
| 138 | |||
| 139 | 456776 | uint64_t BlockByteOffset(uint64_t block_addr) const { | |
| 140 | 456776 | return base_byte_offset_ + block_addr * min_block_size_; | |
| 141 | } | ||
| 142 | |||
| 143 | 98768 | void WriteWithLevel( | |
| 144 | uint64_t block_addr, int level, const void* data, size_t data_size) { | ||
| 145 | 98768 | std::lock_guard<std::mutex> lock(io_mu_); | |
| 146 | 98768 | const uint64_t block_size = static_cast<uint64_t>(min_block_size_) << level; | |
| 147 | 98768 | const uint64_t byte_offset = BlockByteOffset(block_addr); | |
| 148 | 98768 | const PageID_t start = byte_offset / PAGE_SIZE; | |
| 149 | 98768 | const uint64_t page_off = byte_offset % PAGE_SIZE; | |
| 150 | 98768 | const uint64_t pages = (page_off + block_size + PAGE_SIZE - 1) / PAGE_SIZE; | |
| 151 | 98768 | char* buf = backend_->AllocateBuffer(pages); | |
| 152 | 98768 | backend_->BatchReadPages({{start, buf, pages}}); | |
| 153 | 98768 | const uint32_t n = static_cast<uint32_t>(data_size); | |
| 154 | 98768 | const uint32_t l = static_cast<uint32_t>(level); | |
| 155 | 98768 | std::memcpy(buf + page_off, &n, sizeof(n)); | |
| 156 | 98768 | std::memcpy(buf + page_off + sizeof(n), &l, sizeof(l)); | |
| 157 | 98768 | std::memcpy(buf + page_off + kHeaderSize, data, data_size); | |
| 158 | 98768 | backend_->BatchWritePages({{start, buf, pages}}); | |
| 159 | 98768 | backend_->FreeBuffer(buf); | |
| 160 | 98768 | } | |
| 161 | |||
| 162 | 41986 | void ReadHeader(uint64_t block_addr, uint32_t& data_size, uint32_t& level) { | |
| 163 | 41986 | std::lock_guard<std::mutex> lock(io_mu_); | |
| 164 | 41986 | ReadHeaderUnlocked(block_addr, data_size, level); | |
| 165 | 41986 | } | |
| 166 | |||
| 167 | 210804 | void ReadHeaderUnlocked( | |
| 168 | uint64_t block_addr, uint32_t& data_size, uint32_t& level) { | ||
| 169 | 210804 | const uint64_t byte_offset = BlockByteOffset(block_addr); | |
| 170 | 210804 | const PageID_t start = byte_offset / PAGE_SIZE; | |
| 171 | 210804 | const uint64_t page_off = byte_offset % PAGE_SIZE; | |
| 172 | 210804 | char* buf = backend_->AllocateBuffer(); | |
| 173 | 210804 | backend_->ReadPage(start, buf); | |
| 174 | 210804 | std::memcpy(&data_size, buf + page_off, sizeof(data_size)); | |
| 175 | 210804 | std::memcpy(&level, buf + page_off + sizeof(data_size), sizeof(level)); | |
| 176 | 210804 | backend_->FreeBuffer(buf); | |
| 177 | 210804 | } | |
| 178 | |||
| 179 | IOBackend* backend_; | ||
| 180 | const int min_block_size_; | ||
| 181 | const int num_levels_; | ||
| 182 | const uint64_t total_min_blocks_; | ||
| 183 | const uint64_t base_byte_offset_; | ||
| 184 | std::mutex mu_; | ||
| 185 | mutable std::mutex io_mu_; | ||
| 186 | std::vector<std::unordered_set<uint64_t>> free_lists_; | ||
| 187 | }; | ||
| 188 | |||
| 189 | FACTORY_REGISTER( | ||
| 190 | SsdBlockAllocator, | ||
| 191 | SSD_BUDDY, | ||
| 192 | SsdBuddyAllocator, | ||
| 193 | IOBackend*, | ||
| 194 | int, | ||
| 195 | int, | ||
| 196 | uint64_t, | ||
| 197 | uint64_t); | ||
| 198 |