In computers, for a process to be executable, it needs to be placed in memory. For this, a field must be assigned to a process in memory. Memory allocation is an important issue to be aware of, especially in kernel and system architectures.

Let's take a look at Linux memory allocation in detail and understand what goes on behind the scenes.

How Is Memory Allocation Done?

Most software engineers do not know the details of this process. But if you are a system programmer candidate, you should know more about it. When looking at the allocation process, it is necessary to go into a little detail on Linux and the glibc library.

When applications need memory, they have to request it from the operating system. This request from the kernel will naturally require a system call. You cannot allocate memory yourself in user mode.

The malloc() family of functions is responsible for memory allocation in the C language. The question to ask here is whether malloc(), as a glibc function, makes a direct system call.

There is no system call called malloc in the Linux kernel. However, there are two system calls for applications memory demands, which are brk and mmap.

Since you will be requesting memory in your application via glibc functions, you may be wondering which of these system calls glibc is using at this point. The answer is both.

A memory allocation diagram

The First System Call: brk

Each process has a contiguous data field. With the brk system call, the program break value, which determines the limit of the data field, is increased and the allocation process is performed.

Although memory allocation with this method is very fast, it is not always possible to return unused space to the system.

For example, consider that you allocate five fields, each 16KB in size, with the brk system call via the malloc() function. When you are done with number two of these fields, it is not possible to return the relevant resource (deallocation) so that the system can use it. Because if you reduce the address value to show the place where your field number two starts, with a call to brk, you will have done deallocation for fields numbers three, four, and five.

To prevent memory loss in this scenario, the malloc implementation in glibc monitors the places allocated in the process data field and then specifies to return it to the system with the free() function, so that the system can use the free space for further memory allocations.

In other words, after five 16KB areas are allocated, if the second area is returned with the free() function and another 16KB area is requested again after a while, instead of enlarging the data area through the brk system call, the previous address is returned.

However, if the newly requested area is larger than 16KB, then the data area will be enlarged by allocating a new area with the brk system call since area two cannot be used. Although area number two is not in use, the application can't use it because of the size difference. Because of scenarios like this, there is a situation called internal fragmentation, and in fact, you can rarely use all parts of the memory to the fullest.

For better understanding, try compiling and running the following sample application:

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

int main(int argc, char* argv[])
{
        char *ptr[7];
        int n;

        printf("Pid of %s: %d", argv[0], getpid());
        printf("Initial program break : %p", sbrk(0));
        for(n=0; n<5; n++) ptr[n] = malloc(16 * 1024);
        printf("After 5 x 16kB malloc : %p", sbrk(0));
        free(ptr[1]);
        printf("After free of second 16kB : %p", sbrk(0));
        ptr[5] = malloc(16 * 1024);
        printf("After allocating 6th of 16kB : %p", sbrk(0));
        free(ptr[5]);
        printf("After freeing last block : %p", sbrk(0));
        ptr[6] = malloc(18 * 1024);
        printf("After allocating a new 18kB : %p", sbrk(0));
        getchar();
        return 0;
}

When you run the application you will get a result similar to the following output:

        Pid of ./a.out: 31990
Initial program break : 0x55ebcadf4000
After 5 x 16kB malloc : 0x55ebcadf4000
After free of second 16kB : 0x55ebcadf4000
After allocating 6th of 16kB : 0x55ebcadf4000
After freeing last block : 0x55ebcadf4000
After allocating a new 18kB : 0x55ebcadf4000

The output for brk with strace will be as follows:

        brk(NULL)                               = 0x5608595b6000
brk(0x5608595d7000) = 0x5608595d7000

As you can see, 0x21000 has been added to the ending address of the data field. You can understand this from the value 0x5608595d7000. So approximately 0x21000, or 132KB of memory was allocated.

There are two important points to consider here. The first is the allocation of more than the amount specified in the sample code. Another is which line of code caused the brk call that provided the allocation.

Address Space Layout Randomization: ASLR

When you run the above example application one after the other, you'll see different address values each time. Making the address space change randomly this way significantly complicates the work of security attacks and increases software security.

However, in 32-bit architectures, eight bits are generally used to randomize the address space. Increasing the number of bits will not be appropriate as the addressable area over the remaining bits will be very low. Also, the use of only 8-bit combinations does not make things difficult enough for the attacker.

In 64-bit architectures, on the other hand, since there are too many bits that can be allocated for ASLR operation, much larger randomness is provided, and the degree of security increases.

Linux kernel also powers Android-based devices and the ASLR feature is fully activated on Android 4.0.3 and later. Even for this reason alone, it would not be wrong to say that a 64-bit smartphone provides a significant security advantage over 32-bit versions.

By temporarily disabling the ASLR feature with the following command, it will appear that the previous test application returns the same address values each time it is run:

        echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
    

To restore it to its previous state, it will be enough to write 2 instead of 0 in the same file.

The Second System Call: mmap

mmap is the second system call used for memory allocation on Linux. With the mmap call, the free space in any area of the memory is mapped to the address space of the calling process.

In a memory allocation done this way, when you want to return the second 16KB partition with the free() function in the previous brk example, there is no mechanism to prevent this operation. The relevant memory segment is removed from the address space of the process. It is marked as no longer used and returned to the system.

Because memory allocations with mmap are very slow compared to those with brk, brk allocation is needed.

With mmap, any free area of memory is mapped to the address space of the process, so the contents of the allocated space are reset before this process is complete. If the reset was not done this way, the data belonging to the process that previously used the relevant memory area could also be accessed by the next unrelated process. This would make it impossible to talk about security in systems.

Importance of Memory Allocation in Linux

Memory allocation is very important, especially in optimization and security issues. As seen in the examples above, not fully understanding this issue can mean destroying your system's security.

Even concepts similar to push and pop that exist in many programming languages are based on memory allocation operations. Being able to use and master system memory well is vital both in embedded system programming and in developing a secure and optimized system architecture.

If you also want to dip your toes in Linux kernel development, consider mastering the C programming language first.