Serenity Operating System
1/*
2 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/BuiltinWrappers.h>
8#include <AK/Format.h>
9#include <Kernel/Memory/MemoryManager.h>
10#include <Kernel/Memory/PhysicalPage.h>
11#include <Kernel/Memory/PhysicalZone.h>
12
13namespace Kernel::Memory {
14
15PhysicalPageEntry& PhysicalZone::get_freelist_entry(ChunkIndex index) const
16{
17 return MM.get_physical_page_entry(m_base_address.offset(index * ZONE_CHUNK_SIZE));
18}
19
20PhysicalZone::PhysicalZone(PhysicalAddress base_address, size_t page_count)
21 : m_base_address(base_address)
22 , m_page_count(page_count)
23 , m_used_chunks(0)
24{
25 size_t const chunk_count = page_count * 2;
26 for (int order = max_order; order >= 0; --order) {
27 auto& bucket = m_buckets[order];
28 size_t block_size = 2u << order;
29 size_t bitmap_size_for_order = ceil_div((size_t)(chunk_count / block_size), (size_t)2);
30 bucket.order = order;
31 if (bitmap_size_for_order)
32 bucket.bitmap.grow(bitmap_size_for_order, false);
33 }
34
35 auto first_order = count_trailing_zeroes(page_count);
36 size_t block_size = 2u << first_order;
37 auto& bucket = m_buckets[first_order];
38 size_t remaining_chunk_count = chunk_count;
39 size_t initial_bundle_count = remaining_chunk_count / block_size;
40
41 size_t offset = 0;
42 for (size_t i = 0; i < initial_bundle_count; ++i) {
43 ChunkIndex index = offset + i;
44 bucket.set_buddy_bit(index, true);
45
46 auto& freelist_entry = get_freelist_entry(index).freelist;
47 freelist_entry.next_index = bucket.freelist;
48 freelist_entry.prev_index = -1;
49 bucket.freelist = index;
50
51 remaining_chunk_count -= block_size;
52 offset += block_size;
53 }
54}
55
56Optional<PhysicalAddress> PhysicalZone::allocate_block(size_t order)
57{
58 size_t block_size = 2u << order;
59 auto result = allocate_block_impl(order);
60 if (!result.has_value())
61 return {};
62 m_used_chunks += block_size;
63 VERIFY(!(result.value() & 1));
64 return m_base_address.offset(result.value() * ZONE_CHUNK_SIZE);
65}
66
67Optional<PhysicalZone::ChunkIndex> PhysicalZone::allocate_block_impl(size_t order)
68{
69 if (order > max_order)
70 return {};
71 size_t block_size = 2u << order;
72 auto& bucket = m_buckets[order];
73 if (bucket.freelist == -1) {
74 // The freelist for this order is empty, try to allocate a block from one order higher, and split it.
75 auto buddies = allocate_block_impl(order + 1);
76
77 if (!buddies.has_value()) {
78 // Looks like we're unable to satisfy this allocation request.
79 return {};
80 }
81
82 // Split the block from order+1 into two parts.
83 // We keep one (in the freelist for this order) and return the other.
84
85 ChunkIndex index = buddies.value();
86
87 // First half goes in the freelist
88 auto& freelist_entry = get_freelist_entry(index).freelist;
89 freelist_entry.next_index = -1;
90 freelist_entry.prev_index = -1;
91 bucket.freelist = index;
92
93 VERIFY(bucket.get_buddy_bit(index) == false);
94
95 // Set buddy bit to 1 (one used, one unused).
96 bucket.set_buddy_bit(index, true);
97
98 // Second half is returned.
99 return index + block_size;
100 }
101
102 // Freelist has at least one entry, return that.
103 ChunkIndex index = bucket.freelist;
104
105 bucket.freelist = get_freelist_entry(bucket.freelist).freelist.next_index;
106 if (bucket.freelist != -1) {
107 get_freelist_entry(bucket.freelist).freelist.prev_index = -1;
108 }
109
110 VERIFY(bucket.get_buddy_bit(index) == true);
111 bucket.set_buddy_bit(index, false);
112
113 return index;
114}
115
116void PhysicalZone::deallocate_block(PhysicalAddress address, size_t order)
117{
118 size_t block_size = 2u << order;
119 ChunkIndex index = (address.get() - m_base_address.get()) / ZONE_CHUNK_SIZE;
120 deallocate_block_impl(index, order);
121 m_used_chunks -= block_size;
122}
123
124void PhysicalZone::deallocate_block_impl(ChunkIndex index, size_t order)
125{
126 size_t block_size = 2u << order;
127
128 // Basic algorithm:
129 // If the buddy block is free (buddy bit is 1 -- because this block was the only used one):
130 // Then,
131 // 1. Merge with buddy.
132 // 2. Return the merged block to order+1.
133 // Else (buddy bit is 0 -- because both blocks are used)
134 // 1. Add the block to the freelist.
135 // 2. Set buddy bit to 1.
136 auto& bucket = m_buckets[order];
137
138 if (bucket.get_buddy_bit(index)) {
139 // Buddy is free! Merge with buddy and coalesce upwards to the next order.
140 auto buddy_bit_index = bucket.buddy_bit_index(index);
141 ChunkIndex buddy_base_index = (buddy_bit_index << 1) << (1 + order);
142
143 if (index == buddy_base_index)
144 remove_from_freelist(bucket, buddy_base_index + block_size);
145 else
146 remove_from_freelist(bucket, buddy_base_index);
147
148 bucket.set_buddy_bit(index, false);
149 deallocate_block_impl(buddy_base_index, order + 1);
150 } else {
151 // Buddy is in use. Add freed block to freelist and set buddy bit to 1.
152
153 if (bucket.freelist != -1) {
154 get_freelist_entry(bucket.freelist).freelist.prev_index = index;
155 }
156
157 auto& freelist_entry = get_freelist_entry(index).freelist;
158 freelist_entry.next_index = bucket.freelist;
159 freelist_entry.prev_index = -1;
160 bucket.freelist = index;
161
162 bucket.set_buddy_bit(index, true);
163 }
164}
165
166void PhysicalZone::remove_from_freelist(BuddyBucket& bucket, ChunkIndex index)
167{
168 auto& freelist_entry = get_freelist_entry(index).freelist;
169 VERIFY(freelist_entry.prev_index >= -1);
170 VERIFY(freelist_entry.next_index >= -1);
171 if (freelist_entry.prev_index != -1) {
172 auto& prev_entry = get_freelist_entry(freelist_entry.prev_index).freelist;
173 prev_entry.next_index = freelist_entry.next_index;
174 }
175 if (freelist_entry.next_index != -1) {
176 auto& next_entry = get_freelist_entry(freelist_entry.next_index).freelist;
177 next_entry.prev_index = freelist_entry.prev_index;
178 }
179 if (bucket.freelist == index)
180 bucket.freelist = freelist_entry.next_index;
181 freelist_entry.next_index = -1;
182 freelist_entry.prev_index = -1;
183}
184
185void PhysicalZone::dump() const
186{
187 dbgln("(( {} used, {} available, page_count: {} ))", m_used_chunks, available(), m_page_count);
188 for (size_t i = 0; i <= max_order; ++i) {
189 auto const& bucket = m_buckets[i];
190 dbgln("[{:2} / {:4}] ", i, (size_t)(2u << i));
191 auto entry = bucket.freelist;
192 while (entry != -1) {
193 dbgln(" {}", entry);
194 entry = get_freelist_entry(entry).freelist.next_index;
195 }
196 }
197}
198
199}