226 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
| Copyright (C) 2016-2019 The University of Notre Dame
 | |
| This software is distributed under the GNU General Public License.
 | |
| See the file LICENSE for details.
 | |
| */
 | |
| 
 | |
| #include "kmalloc.h"
 | |
| #include "console.h"
 | |
| #include "kernel/types.h"
 | |
| #include "memorylayout.h"
 | |
| 
 | |
| #define KUNIT sizeof(struct kmalloc_chunk)
 | |
| 
 | |
| #define KMALLOC_STATE_FREE 0xa1a1a1a1
 | |
| #define KMALLOC_STATE_USED 0xbfbfbfbf
 | |
| 
 | |
| struct kmalloc_chunk {
 | |
| 	int state;
 | |
| 	int length;
 | |
| 	struct kmalloc_chunk *next;
 | |
| 	struct kmalloc_chunk *prev;
 | |
| };
 | |
| 
 | |
| static struct kmalloc_chunk *head = 0;
 | |
| 
 | |
| /*
 | |
| Initialize the linked list by creating a single chunk at
 | |
| a given start address and length.  The chunk is initially
 | |
| free and has no next or previous chunks.
 | |
| */
 | |
| 
 | |
| void kmalloc_init(char *start, int length)
 | |
| {
 | |
| 	head = (struct kmalloc_chunk *) start;
 | |
| 	head->state = KMALLOC_STATE_FREE;
 | |
| 	head->length = length;
 | |
| 	head->next = 0;
 | |
| 	head->prev = 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
| Split a large chunk into two, such that the current chunk
 | |
| has the desired length, and the next chunk has the remainder.
 | |
| */
 | |
| 
 | |
| static void ksplit(struct kmalloc_chunk *c, int length)
 | |
| {
 | |
| 	struct kmalloc_chunk *n = (struct kmalloc_chunk *) ((char *) c + length);
 | |
| 
 | |
| 	n->state = KMALLOC_STATE_FREE;
 | |
| 	n->length = c->length - length;
 | |
| 	n->prev = c;
 | |
| 	n->next = c->next;
 | |
| 
 | |
| 	if(c->next)
 | |
| 		c->next->prev = n;
 | |
| 
 | |
| 	c->next = n;
 | |
| 	c->length = length;
 | |
| }
 | |
| 
 | |
| /*
 | |
| Allocate a chunk of memory of the given length.
 | |
| To avoid fragmentation, round up the length to
 | |
| a multiple of the chunk size.  Then, search fo
 | |
| a chunk of the desired size, and split it if necessary.
 | |
| */
 | |
| 
 | |
| void *kmalloc(int length)
 | |
| {
 | |
| 	// round up length to a multiple of KUNIT
 | |
| 	int extra = length % KUNIT;
 | |
| 	if(extra)
 | |
| 		length += (KUNIT - extra);
 | |
| 
 | |
| 	// then add one more unit to accommodate the chunk header
 | |
| 	length += KUNIT;
 | |
| 
 | |
| 	struct kmalloc_chunk *c = head;
 | |
| 
 | |
| 	while(1) {
 | |
| 		if(!c) {
 | |
| 			printf("kmalloc: out of memory!\n");
 | |
| 			return 0;
 | |
| 		}
 | |
| 		if(c->state == KMALLOC_STATE_FREE && c->length >= length)
 | |
| 			break;
 | |
| 		c = c->next;
 | |
| 	}
 | |
| 
 | |
| 	// split the chunk if the remainder is greater than two units
 | |
| 	if((c->length-length) > 2 * KUNIT) {
 | |
| 		ksplit(c, length);
 | |
| 	}
 | |
| 
 | |
| 	c->state = KMALLOC_STATE_USED;
 | |
| 
 | |
| 	// return a pointer to the memory following the chunk header
 | |
| 	return (c + 1);
 | |
| }
 | |
| 
 | |
| /*
 | |
| Attempt to merge a chunk with its successor,
 | |
| if it exists and both are in the free state.
 | |
| */
 | |
| 
 | |
| static void kmerge(struct kmalloc_chunk *c)
 | |
| {
 | |
| 	if(!c)
 | |
| 		return;
 | |
| 
 | |
| 	if(c->state != KMALLOC_STATE_FREE)
 | |
| 		return;
 | |
| 
 | |
| 	if(c->next && c->next->state == KMALLOC_STATE_FREE) {
 | |
| 		c->length += c->next->length;
 | |
| 		if(c->next->next) {
 | |
| 			c->next->next->prev = c;
 | |
| 		}
 | |
| 		c->next = c->next->next;
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| /*
 | |
| Free memory by marking the chunk as de-allocated,
 | |
| then attempting to merge it with the predecessor and successor.
 | |
| */
 | |
| 
 | |
| void kfree(void *ptr)
 | |
| {
 | |
| 	struct kmalloc_chunk *c = (struct kmalloc_chunk *) ptr;
 | |
| 	c--;
 | |
| 
 | |
| 	if(c->state != KMALLOC_STATE_USED) {
 | |
| 		printf("invalid kfree(%x)\n", ptr);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	c->state = KMALLOC_STATE_FREE;
 | |
| 
 | |
| 	kmerge(c);
 | |
| 	kmerge(c->prev);
 | |
| }
 | |
| 
 | |
| void kmalloc_debug()
 | |
| {
 | |
| 	struct kmalloc_chunk *c;
 | |
| 
 | |
| 	printf("state ptr      prev     next     length\n");
 | |
| 
 | |
| 	for(c = head; c; c = c->next) {
 | |
| 		if(c->state == KMALLOC_STATE_FREE) {
 | |
| 			printf("F");
 | |
| 		} else if(c->state == KMALLOC_STATE_USED) {
 | |
| 			printf("U");
 | |
| 		} else {
 | |
| 			printf("kmalloc list corrupted at %x!\n", c);
 | |
| 			return;
 | |
| 		}
 | |
| 		printf("     %x %x %x %d\n", c, c->prev, c->next, c->length);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Testing
 | |
| 
 | |
| static void setup(void)
 | |
| {
 | |
| 	kmalloc_init((char *) KMALLOC_START, KMALLOC_LENGTH);
 | |
| }
 | |
| 
 | |
| static void tear_down(void)
 | |
| {
 | |
| }
 | |
| 
 | |
| static int kmalloc_test_single_alloc(void)
 | |
| {
 | |
| 	char *ptr = kmalloc(128);
 | |
| 	struct kmalloc_chunk *next = 0;
 | |
| 	int res = (unsigned long) ptr == (unsigned long) head + sizeof(struct kmalloc_chunk);
 | |
| 	res &= head->state == KMALLOC_STATE_USED;
 | |
| 	res &= head->length == 128 + sizeof(struct kmalloc_chunk);
 | |
| 	res &= (char *) head->next == (char *) KMALLOC_START + head->length;
 | |
| 	next = head->next;
 | |
| 	res &= next->state == KMALLOC_STATE_FREE;
 | |
| 	res &= next->length == KMALLOC_LENGTH - head->length;
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int kmalloc_test_single_alloc_and_free(void)
 | |
| {
 | |
| 	char *ptr = kmalloc(128);
 | |
| 	int res;
 | |
| 	kfree(ptr);
 | |
| 	res = head->state == KMALLOC_STATE_FREE;
 | |
| 	res &= head->next == 0;
 | |
| 	res &= head->length == KMALLOC_LENGTH;
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| int kmalloc_test(void)
 | |
| {
 | |
| 	int (*tests[]) (void) = {
 | |
| 	kmalloc_test_single_alloc, kmalloc_test_single_alloc_and_free,};
 | |
| 
 | |
| 	int i = 0;
 | |
| 	for(i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
 | |
| 		printf("running test %d...", i);
 | |
| 		int res;
 | |
| 
 | |
| 		setup();
 | |
| 		res = tests[i] ();
 | |
| 		tear_down();
 | |
| 
 | |
| 		if(!res) {
 | |
| 			printf("failed\n");
 | |
| 			printf("\ntest %d failed.\n");
 | |
| 			return 0;
 | |
| 		}
 | |
| 		printf("succeeded\n");
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 |