0

So for my C assignment, I need to implement a dynamic memory allocator with a similar interface to the standard library like malloc, free, realloc. I'm implementing the allocator as a library of functions that can be called by other programs. Virtual heap will be managed by a simple buddy allocation algorithm.

My functions given are:

void * virtual_sbrk(int32_t increment);
pretty much the same as the real-world sbrk and brk syscalls. I don't need to implement this.

void init_allocator(void * heapstart, uint8_t initial_size, uint8_t min_size);
This function will be called once at the beginning and initialise the virtual heap.

void * virtual_malloc(void * heapstart, uint32_t size);
mallocs memory

int virtual_free(void * heapstart, void * ptr);
frees memory

void * virtual_realloc(void * heapstart, void * ptr, uint32_t size);
reallocates memory

void virtual_info(void * heapstart);
prints the current state of the buddy allocator to standard output.

This is my current problem: How do you initialise the heap and implement malloc without anything in the first place? Like I can't use malloc or any of the pre existing allocator functions. So far I've tried to use a linked list with nodes containing the memory as a value. Eg if initial size is 3 and min size is 1, I'd have 5 nodes with the root containing 8 bytes, two more containing 4 bytes each , and lastly 2 more contining 2 bytes each. But I'm still confused on how to use sbrk or how the heap is structured in the first place. I've browsed online resources but still confused on how to construct the heap memory.

Following is my code so far:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

struct node{
    size_t memory;
    struct node *nextInLine;
};

void printNode(const struct node *nd, const char *comment){

    if(nd == NULL){

        printf("%s is null\n", comment);
    }
    else{

        printf("%s: memory:%d address:%p nextInLine:%p\n",
            comment,
            nd->memory,
            nd,
            nd->nextInLine);
    }
}



void printList(const struct node *list){
    printf("Printing List:\n");
    const struct node *t;
    t = list;
    if(t == NULL){
        printf("current node is empty\n");
    }
    else{
        while(t){
            printNode(t, "node");
            t = t->nextInLine;
        }
    }
}


void * virtual_sbrk(int32_t increment) {
    void *p = malloc(increment);
    return p;
}


uint8_t return_init_size(uint8_t size){
    return size;
}

struct node *getNewNode(const uint8_t memory_size){

    struct node *newNode = NULL;
    double two = 2;
    size_t m_size = memory_size;
    double result = pow(two, m_size);
    newNode = virtual_sbrk(result);
    if(newNode != NULL){
        newNode->memory = result;
        newNode->nextInLine = NULL;
    } 
    else{
        printf("Allocation error: newNode is still NULL\n");
    }
    return newNode;

}

void init_allocator(void * heapstart, uint8_t initial_size, uint8_t min_size) {

    //error catchers
    if(initial_size == 0){
        printf("Initial size is 0\n");
    }
    if(initial_size < min_size){
        printf("initial_size is smaller than min_size\n");
    }


    //initialising the virtual heap using a linked array with nodes the memory size of 2^some_size 
    uint8_t i = initial_size;
    struct node *first = heapstart;
    heapstart = first;
    struct node *tail = NULL;
    while(i >= min_size){
        if(first == NULL){
            first = getNewNode(i);
            if(first != NULL){
                tail = first;
            }
        }
        else{
            tail->nextInLine = getNewNode(i);
            if(tail->nextInLine != NULL){
                tail = tail->nextInLine;
            }
            tail->nextInLine = getNewNode(i);
            if(tail->nextInLine != NULL){
                tail = tail->nextInLine;
            }
        }
        i -= 1;
    }

    printList(first);

}

void * virtual_malloc(void * heapstart, uint32_t size) {
   
    if(size == 0){
        return NULL;
    }

    

    return NULL;
}

int virtual_free(void * heapstart, void * ptr) {

    return 1;
}

void * virtual_realloc(void * heapstart, void * ptr, uint32_t size) {

    return NULL;
}

void virtual_info(void * heapstart) {
    
}

It would be great if someone could help explain how I would go about doing this, as in the structure I need to follow, if that makes sense.

  • you can use free list. this is actually how malloc works, look in here for more info [how-do-free-and-malloc-work](https://stackoverflow.com/questions/1957099/how-do-free-and-malloc-work-in-c) – Asaf Itach May 02 '21 at 05:04
  • You could use a large global array i.e. 'char pool[1000000];' – tstanisl May 02 '21 at 10:47

1 Answers1

1

You can use both sbrk and mmap as glibc malloc does.

glibc malloc works with threads, with something called arenas.

When malloc is initialized it calls sbrk to extend the mapped memory.

When big allocations happen, or new threads are created malloc ends up calling mmap.

mmap allocates a new mapping in the address space of the process. sbrk extends the current mapping to make it bigger.

Simple example of sbrk:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>

#define HEAP_SZ 0x8000

int main(void) {
    void *p = sbrk(0);
    printf("current break addr = %p\n", p);
    sbrk(HEAP_SZ);
    void *n = sbrk(0);
    printf("new break addr = %p\n", n);
    return 0;
}

The first call (with argument 0) returns the current program break. When specifying a size greater than 0, program break is extended, so on the next call with argument 0, the new program break will be returned.

You can do then something like this:


unsigned long heap_mem_sz = 0;
void *heap_start_addr = NULL;

void init_heap(void) {
    void *p = sbrk(0);
    #if DEBUG
        printf("current break addr = %p\n", p);
    #endif
    sbrk(HEAP_SZ);
    heap_mem_sz = (unsigned long)HEAP_SZ;
    void *n = sbrk(0);
    #if DEBUG
        printf("new break addr = %p\n", n);
    #endif
    heap_start_addr = (void *)n;
    return;
}

Having that information on globals allows you to continue the development of the allocator implementation.

You can call init_heap() the first time an allocation is requested.

Now you can return that allocation and craft a "top chunk".

It will be a chunk with the same structure than the others but containing all the memory from which allocations take memory, and it gets shrinked on allocations.

Also, you will need to do something once the heap memory is full, so consider calling syscalls like mmap or sbrk again.

Linked lists on malloc are used for bins. They are used for searching freed chunks that can satisfy new allocations so you reuse chunks that are not used anymore.

For such linked list, you can create a global:

struct heap_chunk *freed_chain = NULL

When memory is requested, you first check if freed_chain is NULL, if not, traverse the linked list until a block compatible with the user request is found, or the next pointer is NULL.

If any of those chunks is valid, you will need to unlink that chunk from the linked list, and make the previous chunk point to the next one, so no more memory requests access to it as now it is allocated and not freed.

On freeing memory, you would need to link a new chunk to that linked list.

Obviously on malloc, for optimization purposes this is more complex, and some different bins with different size requirements and different properties exist to speed up allocations.