MonkOS  v0.1
A simple 64-bit operating system (x86_64)
heap.c
Go to the documentation of this file.
1 //============================================================================
2 /// @file heap.c
3 /// @brief A simple heap memory manager
4 //
5 // Copyright 2016 Brett Vickers.
6 // Use of this source code is governed by a BSD-style license
7 // that can be found in the MonkOS LICENSE file.
8 //============================================================================
9 
10 #include <core.h>
11 #include <libc/string.h>
12 #include <kernel/mem/heap.h>
13 #include <kernel/mem/paging.h>
14 
15 // ALLOC_PAGES: The minimum number of pages to allocate each time the heap
16 // is grown.
17 #define ALLOC_PAGES 16
18 
19 // block_header flags
20 #define FLAG_ALLOCATED (1 << 0)
21 
22 // round16 returns the first value x greater than or equal to n that satisfies
23 // (x mod 16) = r
24 #define round16(n, r) (((((n) - (r) + 31) >> 4) << 4) + (r) - 16)
25 
26 // total_bytes returns the total number of bytes contained in the block 'b',
27 // including the block header and footer. 'b' may be a pointer to a
28 // block_header or a block_footer struct.
29 #define total_bytes(b) \
30  ((b)->size + sizeof(block_header_t) + sizeof(block_footer_t))
31 
32 struct heap
33 {
34  pagetable_t *pt; // page table that owns the heap
35  void *vaddr; // address of heap start
36  uint64_t pages; // pages currently alloced to the heap
37  uint64_t maxpages; // max pages used by the heap
38  struct fblock_header *first_fblock; // first free block in the heap
39  uint64_t reserved; // pad to a multiple of 16 bytes
40 };
41 
42 typedef struct block_header
43 {
44  uint64_t size; // size of block in bytes (minus header/footer)
45  uint64_t flags;
47 
48 typedef struct block_footer
49 {
50  uint64_t size; // size of the preceding block
52 
53 typedef struct fblock_header
54 {
55  struct block_header block;
56  struct fblock_header *next_fblock; // next free block in the heap
57  struct fblock_header *prev_fblock; // prev free block in the heap
59 
60 heap_t *
62 {
63  heap_t *heap = (heap_t *)page_alloc(pt, vaddr, ALLOC_PAGES);
64  heap->pt = pt;
65  heap->vaddr = vaddr;
66  heap->pages = ALLOC_PAGES;
67  heap->maxpages = max(ALLOC_PAGES, maxpages);
68  heap->first_fblock = (fblock_header_t *)(heap + 1);
69  heap->reserved = 0;
70 
71  uint64_t block_size = heap->pages * PAGE_SIZE -
72  sizeof(heap_t) -
73  sizeof(block_header_t) -
74  sizeof(block_footer_t);
75 
76  fblock_header_t *fh = heap->first_fblock;
77  fh->block.size = block_size;
78  fh->block.flags = 0;
79  fh->next_fblock = NULL;
80  fh->prev_fblock = NULL;
81 
82  block_footer_t *footer = ptr_add(block_footer_t, fh, block_size +
83  sizeof(block_header_t));
84  footer->size = block_size;
85 
86  return heap;
87 }
88 
89 void
91 {
92  page_free(heap->pt, heap->vaddr, heap->pages);
93  // The heap pointer now points to unpaged memory.
94 }
95 
96 /// Check the next block adjacent to 'b'. If it's free, return a pointer to
97 /// it.
98 static fblock_header_t *
100 {
101  block_header_t *term = ptr_add(block_header_t, heap->vaddr,
102  heap->pages * PAGE_SIZE);
104  if (next >= term)
105  return NULL;
106  if ((next->flags & FLAG_ALLOCATED) == 0)
107  return (fblock_header_t *)next;
108  return NULL;
109 }
110 
111 /// Check the preceding block adjacent to 'b'. If it's free, return a pointer
112 /// to it.
113 static fblock_header_t *
115 {
116  block_header_t *first = ptr_add(block_header_t, heap->vaddr,
117  sizeof(heap_t));
118  if (bh == first)
119  return NULL;
121  sizeof(block_footer_t));
123  if ((prev->flags & FLAG_ALLOCATED) == 0)
124  return (fblock_header_t *)prev;
125  return NULL;
126 }
127 
128 /// Scan all blocks after 'b' until a free block is encountered and return it.
129 /// If no free blocks are found, return NULL.
130 static fblock_header_t *
132 {
133  block_header_t *term = ptr_add(block_header_t, heap->vaddr,
134  heap->pages * PAGE_SIZE);
135  for (;;) {
136  bh = ptr_add(block_header_t, bh, total_bytes(bh));
137  if (bh >= term)
138  return NULL;
139  if ((bh->flags & FLAG_ALLOCATED) == 0)
140  return (fblock_header_t *)bh;
141  }
142  return NULL;
143 }
144 
145 /// Scan all blocks preceding 'b' until a free block is encountered and return
146 /// it. If no free blocks are found, return NULL.
147 static fblock_header_t *
149 {
150  block_header_t *first = ptr_add(block_header_t, heap->vaddr,
151  sizeof(heap_t));
152  for (;;) {
153  if (bh == first)
154  return NULL;
156  sizeof(block_footer_t));
157  bh = ptr_sub(block_header_t, bh, total_bytes(bf));
158  if ((bh->flags & FLAG_ALLOCATED) == 0)
159  return (fblock_header_t *)bh;
160  }
161  return NULL;
162 }
163 
164 /// Grow the heap so that it's big enough to hold at least minsize newly
165 /// allocated bytes (not including headers). Return a pointer to the
166 /// first new free block if successful. Otherwise return NULL.
167 static fblock_header_t *
168 grow_heap(heap_t *heap, uint64_t minsize)
169 {
170  // Account for headers when growing the heap.
171  minsize += sizeof(block_header_t) + sizeof(block_footer_t);
172  uint64_t pages = max(ALLOC_PAGES, div_up(minsize, PAGE_SIZE));
173 
174  // Don't allocate more than maxpages to the heap.
175  if (heap->pages + pages > heap->maxpages) {
176  pages = heap->maxpages - heap->pages;
177  if (pages * PAGE_SIZE < minsize)
178  return NULL;
179  }
180 
181  // Compute the virtual address of the next group of pages and allocate
182  // them from the page table.
183  void *vnext = ptr_add(void, heap->vaddr, heap->pages * PAGE_SIZE);
184  page_alloc(heap->pt, vnext, pages);
185  heap->pages += pages;
186 
187  // Examine the last block in the heap to see if it's free.
189  sizeof(block_footer_t));
190  block_header_t *lh = ptr_sub(block_header_t, lf, lf->size +
191  sizeof(block_header_t));
192 
193  // If the last block in the old heap was in use, mark the newly allocated
194  // pages as a single free block of memory.
195  fblock_header_t *fh;
196  if (lh->flags & FLAG_ALLOCATED) {
197  fh = (fblock_header_t *)vnext;
198  fh->block.size = pages * PAGE_SIZE - sizeof(block_header_t) -
199  sizeof(block_footer_t);
200  fh->block.flags = 0;
201  fh->next_fblock = NULL;
202  fh->prev_fblock = prev_fblock(heap, (block_header_t *)vnext);
203  if (fh->prev_fblock == NULL)
204  heap->first_fblock = fh;
205  }
206 
207  // If the last block in the old heap was free, merge it with the newly
208  // allocated pages.
209  else {
210  fh = (fblock_header_t *)lh;
211  fh->block.size += pages * PAGE_SIZE;
212  }
213 
214  // Update the free block footer.
215  block_footer_t *ff = ptr_add(block_footer_t, fh, fh->block.size +
216  sizeof(block_header_t));
217  ff->size = fh->block.size;
218 
219  return fh;
220 }
221 
222 // Find a free block large enough to hold 'size' bytes. If such a block is not
223 // found, grow the heap and try again.
224 static fblock_header_t *
225 find_fblock(heap_t *heap, uint64_t size)
226 {
227  fblock_header_t *fh = heap->first_fblock;
228  while (fh) {
229  if (fh->block.size >= size)
230  return fh;
231  fh = fh->next_fblock;
232  }
233 
234  // No free blocks large enough were found, so grow the heap.
235  return grow_heap(heap, size);
236 }
237 
238 void *
239 heap_alloc(heap_t *heap, uint64_t size)
240 {
241  // Round the size up to the nearest (mod 16) == 8 value. This way all
242  // returned pointers will remain aligned on 16-byte boundaries.
243  size = round16(size, 16 - sizeof(block_footer_t));
244 
245  // Find a free block big enough to hold the allocation
246  fblock_header_t *fh = find_fblock(heap, size);
247  if (fh == NULL)
248  return NULL;
249 
250  // Copy the original free block's size and pointers, since we'll be
251  // modifying their current storage.
252  uint64_t fsize = fh->block.size;
253  fblock_header_t *next = fh->next_fblock;
254  fblock_header_t *prev = fh->prev_fblock;
255 
256  // If the free block would be filled (or nearly filled) by the
257  // allocation, replace it with an allocated block.
258  block_header_t *ah;
259  if (fsize - size < 8 + sizeof(block_header_t) + sizeof(block_footer_t)) {
260 
261  // Mark the block header as allocated, leaving the size unchanged.
262  ah = &fh->block;
263  ah->flags = FLAG_ALLOCATED;
264 
265  // Ignore the allocated block footer, since the size remains
266  // unchanged.
267 
268  // Fix up the free list.
269  if (prev)
270  prev->next_fblock = next;
271  else
272  heap->first_fblock = next;
273  if (next)
274  next->prev_fblock = prev;
275  }
276 
277  // Otherwise, split the free block into an allocated block and a smaller
278  // free block.
279  else {
280 
281  // Initialize the allocated block header.
282  ah = &fh->block;
283  ah->size = size;
284  ah->flags = FLAG_ALLOCATED;
285 
286  // Initialize the allocated block footer.
287  block_footer_t *af = ptr_add(block_footer_t, ah, size +
288  sizeof(block_header_t));
289  af->size = size;
290 
291  // Initialize the new free block header, which follows the allocated
292  // block.
293  fblock_header_t *fh = (fblock_header_t *)(af + 1);
294  fh->block.size = fsize - size - sizeof(block_header_t) -
295  sizeof(block_footer_t);
296  fh->block.flags = 0;
297 
298  // Initialize the new free block footer.
299  block_footer_t *ff = ptr_add(block_footer_t, fh, fh->block.size +
300  sizeof(block_header_t));
301  ff->size = fh->block.size;
302 
303  // Fix up the free list.
304  fh->prev_fblock = prev;
305  fh->next_fblock = next;
306  if (prev)
307  prev->next_fblock = fh;
308  else
309  heap->first_fblock = fh;
310  if (next)
311  next->prev_fblock = fh;
312  }
313 
314  // Return a pointer just beyond the allocated block header.
315  return ptr_add(void, ah, sizeof(block_header_t));
316 }
317 
318 void
319 heap_free(heap_t *heap, void *ptr)
320 {
322 
323  // Check if adjacent blocks are free.
324  fblock_header_t *fhp = prev_fblock_adj(heap, h);
325  fblock_header_t *fhn = next_fblock_adj(heap, h);
326 
327  // If both adjacent blocks are free, merge all three into a single
328  // free block.
329  if (fhp && fhn) {
330  fhp->block.size += h->size + 2 * sizeof(block_header_t) +
331  2 * sizeof(block_footer_t) + fhn->block.size;
332 
333  fhp->next_fblock = fhn->next_fblock;
334  if (fhn->next_fblock != NULL)
335  fhn->next_fblock->prev_fblock = fhp;
336 
337  block_footer_t *ffp = ptr_add(block_footer_t, fhp, fhp->block.size +
338  sizeof(block_header_t));
339  ffp->size = fhp->block.size;
340  }
341 
342  // If only the previous adjacent block is free, merge it with the newly
343  // free block.
344  else if (fhp && !fhn) {
345  fhp->block.size += total_bytes(h);
346  block_footer_t *ffp = ptr_add(block_footer_t, fhp, fhp->block.size +
347  sizeof(block_header_t));
348  ffp->size = fhp->block.size;
349  }
350 
351  // If only the next adjacent block is free, merge it with the newly free
352  // block.
353  else if (fhn && !fhp) {
354  fblock_header_t *fh = (fblock_header_t *)h;
355  fh->block.size += total_bytes(&fhn->block);
356  fh->block.flags = 0;
357 
358  fh->prev_fblock = fhn->prev_fblock;
359  if (fh->prev_fblock == NULL)
360  heap->first_fblock = fh;
361  else
362  fh->prev_fblock->next_fblock = fh;
363 
364  fh->next_fblock = fhn->next_fblock;
365  if (fh->next_fblock != NULL)
366  fh->next_fblock->prev_fblock = fh;
367 
368  block_footer_t *ff = ptr_add(block_footer_t, fh, fh->block.size +
369  sizeof(block_header_t));
370  ff->size = fh->block.size;
371  }
372 
373  // If neither adjacent block is free, convert the block into a single
374  // free block.
375  else {
376  fblock_header_t *fh = (fblock_header_t *)h;
377  fh->block.flags = 0;
378 
379  fh->next_fblock = next_fblock(heap, h);
380  if (fh->next_fblock == NULL)
381  fh->prev_fblock = prev_fblock(heap, h);
382  else {
383  fh->prev_fblock = fh->next_fblock->prev_fblock;
384  fh->next_fblock->prev_fblock = fh;
385  }
386 
387  if (fh->prev_fblock == NULL)
388  heap->first_fblock = fh;
389  else
390  fh->prev_fblock->next_fblock = fh;
391  }
392 }
uint64_t size
Definition: heap.c:50
#define ptr_sub(t, p, o)
Definition: core.h:42
String and memory operations.
static fblock_header_t * grow_heap(heap_t *heap, uint64_t minsize)
Grow the heap so that it&#39;s big enough to hold at least minsize newly allocated bytes (not including h...
Definition: heap.c:168
uint64_t flags
Definition: heap.c:45
#define round16(n, r)
Definition: heap.c:24
#define total_bytes(b)
Definition: heap.c:29
uint64_t reserved
Definition: heap.c:39
Core include file.
static fblock_header_t * find_fblock(heap_t *heap, uint64_t size)
Definition: heap.c:225
Definition: heap.c:32
void heap_free(heap_t *heap, void *ptr)
Free memory previously allocated with heap_alloc.
Definition: heap.c:319
void * page_alloc(pagetable_t *pt, void *vaddr_in, int count)
Allocate one or more pages contiguous in virtual memory.
Definition: paging.c:415
struct fblock_header * prev_fblock
Definition: heap.c:57
A simple heap memory manager.
pagetable_t * pt
Definition: heap.c:34
A pagetable structure.
Definition: paging.h:66
static fblock_header_t * prev_fblock_adj(heap_t *heap, block_header_t *bh)
Check the preceding block adjacent to &#39;b&#39;.
Definition: heap.c:114
#define ptr_add(t, p, o)
Definition: core.h:41
void * vaddr
Definition: heap.c:35
uint64_t maxpages
Definition: heap.c:37
struct block_header block
Definition: heap.c:55
Paged memory management.
static fblock_header_t * prev_fblock(heap_t *heap, block_header_t *bh)
Scan all blocks preceding &#39;b&#39; until a free block is encountered and return it.
Definition: heap.c:148
struct fblock_header * next_fblock
Definition: heap.c:56
struct fblock_header * first_fblock
Definition: heap.c:38
#define FLAG_ALLOCATED
Definition: heap.c:20
#define PAGE_SIZE
Definition: paging.h:15
#define max(a, b)
Definition: core.h:30
void * heap_alloc(heap_t *heap, uint64_t size)
Allocate memory from a heap.
Definition: heap.c:239
heap_t * heap_create(pagetable_t *pt, void *vaddr, uint64_t maxpages)
Create a new heap from which to allocate virtual memory.
Definition: heap.c:61
uint64_t pages
Definition: heap.c:36
static fblock_header_t * next_fblock_adj(heap_t *heap, block_header_t *bh)
Check the next block adjacent to &#39;b&#39;.
Definition: heap.c:99
#define div_up(a, b)
Definition: core.h:34
#define ALLOC_PAGES
Definition: heap.c:17
void heap_destroy(heap_t *heap)
Destroy a heap, returning its memory to page table.
Definition: heap.c:90
uint64_t size
Definition: heap.c:44
static fblock_header_t * next_fblock(heap_t *heap, block_header_t *bh)
Scan all blocks after &#39;b&#39; until a free block is encountered and return it.
Definition: heap.c:131
void page_free(pagetable_t *pt, void *vaddr_in, int count)
Free one or more contiguous pages from virtual memory.
Definition: paging.c:425