For this assignment, we will write a safer and more efficient version of the memory allocator from Assignment 5. Our goal is to get performance closer to that of the actual malloc, while making it safe for use by multiple threads.

Task 1: Use mmap to Allocate Page-sized Blocks

Understanding mmap

For the basic allocator implemented in Assignment 5, you were asked to use the sbrk system call to change the size of the heap. This call is not the only way to request memory for our process. In fact, the sbrk syscall is considered deprecated and its use is discouraged. We have used it in our first allocator because it provides a simple and easy-to-use interface for requesting heap memory from the OS.

So what does a modern Unix/Linux want us to use for memory allocation? The modern way to allocate memory is to directly request pages for a process using the mmap system call. In general, mmap allows us to map a file or a device into memory, meaning that any reads/writes within the memory region returned from a successful mmap call are reflected in the file (more on this in the file system project). In other words, mmap allows us to allocate memory from the operating system, backed by a particular file. On Linux and some other operating systems, we can even request “anonymous” mappings, meaning that the returned region is not backed by any file. In this mode, mmap can behave like malloc.

An “mmapped” region’s size is always aligned along the boundary of a page. This means that one constraint is that allocations with mmap should be made in multiples the size of a page. Even if you specify a size that is not a multiple of the system’s page size, mmap will round up to the nearest page. For example, if our page size is 4KB (4096 bytes), and we only use 12 bytes in total during our program’s execution, we have quite a bit of waste! In practice, for large desktop applications this is not a major issue.

Task 1:

The first task is to update our allocator implementation to use mmap instead of sbrk. Remember that, unlike sbrk, mmap will return a pointer to the beginning of a page of memory. Here is the overall updated strategy for mymalloc and myfree:

mymalloc

a) For requests of size < PAGE_SIZE:

b) For requests of size >= PAGE_SIZE:

myfree

a) For a block of memory of size < PAGE_SIZE: add the block to your free list and coalesce (see below) if needed. b) For a block of size >= PAGE_SIZE: use munmap to unmap it.

Coalescing Free Blocks

When adding a block into your free list, keep the list sorted by the memory address of the blocks. This will allow coalescing: whenever two blocks in the free list form a continuous area of memory, they should be merged into one block (coalesced)

Since you insert into the free list and need to handle this in two different places, a helper function is a good idea.

Task 2: A Thread-safe Allocator

Data races can affect memory allocators too. In a multi-threaded environment, we cannot simply make requests to our malloc and free functions based on our previous implementation. We could have a scenario where two or more threads request memory at the same time, and potentially all allocate the same block of memory in the heap. This would certainly be unlucky!

Luckily, we have mutexes to enforce mutual exclusion and help protect against data races. Remember, when we use pthread_mutex_lock and pthread_mutex_unlock, this creates a critical section where only one thread that has acquired the lock can execute a section of code, thus enforcing sequential execution over shared data.

Task

Implement locking mechanisms such that, whenever there is an allocation (malloc or calloc) or deallocation (free), a lock protects that section of code from being run by another thread.

Assignment Strategy

  1. You have a previous implementation of an allocator. Structurally many pieces will look the same, so you do not need to build things from scratch.
  2. You do not have to work on this assignment in the order of the tasks. Task 1 and Task 2 are independent of each other. Start with Task 2 if you have no idea where to begin with Task 1.
  3. One way to approach the assignment: a) Start single threaded, with your code from Assignment 5 b) Initially, ignore splitting and coalescing blocks, and just accept wasted memory when using mmap c) Move to multiple threads, adding locks around all allocations and frees d) Add splitting of blocks e) Add coalescing of blocks

Deliverables

All Tasks ~ Implement your memory allocator in mymalloc.c and include any additional .c and .h files your implementation relies on. For example, you might want to compile your helper data structure separately.

Commit the code to your repository. Do not include any executables, .o files, or other binary, temporary, or hidden files.

Once you are done, remember to submit your solution to Gradescope and do not forget to include your partner.

Going Further and Additional Resources


Ben Weintraub © 2025
Site Last Updated June 27, 2025 at 13:20:40 UTC