Abstract


  • A list of memory locations from 0 to some maximum, which Process (进程) can access
  • Address space is powered by Virtual Memory, so everything in address space is dedicated to that particular process
  • 4 main components
    • Data in Text Segment (the orange block shown above) and Data Segment (the red block shown above) are shipped with the program
    • Data in Heap Segment (the blue block shown above) and Stack Segment (the green block shown above) are allocated dynamically when the program is running

Heap Segment


  • Data is added into heap segment explicitly by Process (进程) during runtime using System Call (系统调用) like brk() for Kernel that follow POSIX. The data in heap can live indefinitely even when function returns, the data is only removed when the process make the related System Call (系统调用) explicitly
  • Grows and shrinks as the process allocates and deallocates memory. The heap segment grows upwards, because the Memory Address is increasing as we have more data is allocated inside the heap segment

Bigbrain

When assigning one variable to another variable, data is not duplicated, instead the Pointer to the data inside the heap segment is duplicated and assigned. A nice visualisation on heap memory allocation can be found here.

Manual memory management from the process needed!

Process is responsible of allocating memory and deallocating in the heap segment. See language examples below.

Java

  • Memory allocation with new keyword, and memory deallocation is done by Garbage Collector automatically

Rust

C

  • Memory allocation with malloc() function, and manual memory deallocation is done with free() function

Manual Memory Deallocation is Dangerous!

After we manually deallocated the heap memory associated with a Pointer, then try to read data from the same pointer, it will lead to undefined behaviours. Thus, resulting in Poor memory safety.

Refer to this section of article for more details.

Heap fragmentation

  • There are 2 types of heap fragmentation - Internal Fragmentation & External Fragmentation(show in the diagram above)
Internal FragmentationExternal Fragmentation
DefinitionWhen allocated Memory frames have unused memory space, since each memory frame comes with a fixed size.When free memory frames in the heap are scattered throughout the memory space after a series of allocations and deallocations of memory blocks of varying sizes.
RiskIncreased memory consumption than actual demand.

For example, the memory frame size is 10MB, and my program needs 10.00001MB. We will get 2 memory frames that are 10MB each, this means 9.99999MB is wasted. Imagine I have a lot of programs with 10.00001MB in my computer, that means we waste almost half of the available Main Memory!
Difficulty to allocate contiguous blocks of memory reduced performance.

Small gaps between allocated blocks can also become unusable if Virtual Memory isn’t used, even though the total amount of free memory might be sufficient to fulfil a request.

Ways to handle memory fragmentation

Compaction - rearranging memory blocks to eliminate gaps.

And many other techniques like Dyanmic memory allocation - Wikipedia and Memory pool - Wikipedia.

Memory leak

  • Happens when we forget to release data in Heap Segment using free() in the example of C
  • This can eventually lead to the exhaustion of available Main Memory, resulting in degraded performance or even program crashes

Attention

We should always deallocate the chucks of data when we don’t need it anymore to avoid memory leak!

Stack Segment


  • Also known as Call Stack
  • Dynamically allocated region used to store function calls, local variables, and temporary data etc
  • Made up of Stack Frame, following a Stack structure
  • Expands as functions are called and shrinks as they return

Obtain stack size

ulimit -s

Grows Downwards

Stack Segment starts at a higher Memory Address, then memory address decreases as we add in Stack Frame, thus growing downwards in terms of Memory Address, so to remove stack frame, we need to increment the Stack Pointer.

When fn1() calls fn2(), fn1()’s stack frame is created first. Then, fn2()’s stack frame is placed on top of fn1()’s on the call stack, although it may appear below the older frame in the diagram because the stack grows downwards.

Variables live in the stack

When assigning one variable to another variable, data is duplicated.

For example, a=1 and b=a, the value 1 is duplicated and assigned to b. A nice visualisation can be found here

Data management in Stack Segment is more efficient than Heap Segment

  1. Stack memory is allocated and deallocated in a Last In, First Out (LIFO) manner, making it faster than heap memory. This is because all it needs to do is move the Stack Pointer up or down, while heap memory requires more complex memory management
  2. No overhead of complex Synchronisation (同步), unlike data inside heap segment, data inside the stack segment is dedicated to that particular Thread. Thus, manipulation of data inside the stack segment doesn’t require the complex synchronisation

Attention

The size of variable on the stack is defined before Compilation

Stack Frame

  • A section of the Stack Segment dedicated to a specific function call
f()
void f() {
	c = g(2, 90);
}
g()
void g(int i, int j) {
	int a;
}

Stack frame setup

This is just one of the many ways to setup a stack frame!

Caller f():

  1. Allocate space for the new stack frame, including space for parameters and the return address for the PC.
  2. Pass parameters (i, j) onto the new stack frame.
  3. Save the return address (PC) onto the new stack frame.

Transfer control to the callee: Jump to the function g() to hand over control from f() to g().

Callee g():

  1. Save the old stack pointer onto the new stack frame.
  2. Save the old Frame Pointer in the new stack frame.
  3. Save the values in registers that are going to be used by the callee new stack frame.
  4. Determine the amount of memory needed for local variables (a) and allocate the necessary space in the new stack frame.
  5. Adjust the stack pointer to point to the top of the newly allocated stack frame.

Stack frame teardown

This is just one of the many ways to setup a stack frame!

Callee g():

  1. Place return results onto the current stack frame (if applicable).
  2. Restore the saved old stack pointer, old frame pointer and saved registers.
  3. Restore the saved return address to transfer control back to the caller, f().

Caller f():

  1. Accessing the return results via displacement addressing mode.

As you can see, the teardown process only involves restoring the stack pointer and the return address. We do not reset or clear the values in the stack frame. This is why, in C, you can never assume a newly created variable has a specific values. Instead, it may contain leftover data (“garbage values”) from previously reused stack space.

Stack Overflow

  • Happens when the size of all the stack frame is over the default fixed size of the stack segment. Usually can be triggered easily without a proper implementation of Recurrence Function

Data Segment


  • This region stores global and static variables and constants used by the program, pre-defined before the execution of the program
  • Can be both read and write, allowing the Process (进程) to manipulate the data as needed

Text Segment


References