Operating System — Memory Management

Understand Memory Management in just 5 minutes

Matthew Wong
Level Up Coding

--

Photo by Jeremy Zero on Unsplash

Memory Management is the process of managing computer memory, moving processes between main memory and disk to boost the overall performance of the system. It is vitally important as it helps OS to keep track of every memory location, including allocating memory and freeing it.

In this article, I am going to focus on userspace memory only as it directly affects the execution of application software and every softare engineer should know about it.

User-space memory management

Address Space

Address space

One address is mapped to one byte. On a 32-bit system, the maximum amount of memory in a process is 2^32 bytes, which means 4GB. That means the memory of a process on a 64-bit system can be 16EB.

More detail about address space

Each process has its own address space and it should have 2^32 bytes (4GB) of memory. (I am talking about a 32-bit system)

Address space consists of 4 main kinds of memory. They are:

  1. Program code & segments
  2. Data segment
  3. Stack
  4. Heap

Program code & constants

Let’s have a look at an example of code segment:

int main(void) {
char *string = "hello"; //string constant
printf("\"hello\" = %p\n", "hello"); // "hello" is used
printf("String pointer = %p\n", string); // same constant is used
return 0;
}

A computer is very clever. In this example, the program is always using the same string constant as long as it is unique.

Most of the developers or software engineers with basic programming knowledge definitly know it. Codes and constants are both read-only.

  • We cannot change the values of the codes nor the constants during runtime.
  • Constants are stored in code segment and Only unique constants are stored.

Data Segment & BSS

Global and static variables are stored in data segment and BSS(Block Started by Symbol). Data Segment is for storing initialized variables while BSS is for storing uninitialized variables.

Let’s take a look at the example below:

int global_int = 1; // globalint main(void) {
int local_int = 1;
static int static_int = 1; // static
printf("local_int addr = %p\n", &local_int ); // e.g. 0xbf8bb8ac
printf("static_int addr = %p\n", &static_int ); // e.g. 0xxxxxx18
printf("global_int addr = %p\n", &global_int ); // e.g. 0xxxxxx14
return 0;
}

If you try to run the c program on your own machine. We will find that the global and static variable are just stored next to each other. That means they are in the same segment.

Let’s take a look at another example:

int global_bss; // uninitialized
int global_data = 1; // initialized
int main(void) {
static int static_bss; // uninitialized
static int static_data = 1; // uninitialized
printf("global bss = %p\n", &global_bss ); // e.g. 0xxxxxx88
printf("static bss = %p\n", &static_bss ); // e.g. 0xxxxxx84
printf("global data = %p\n", &global_data ); // e.g. 0xxxxxx14
printf("static data = %p\n", &static_data ); // e.g. 0xxxxxx18
}

This shows that data and BSS are stored separately.

Stack

The stack is an area which stores:

  • local variables,
  • function parameters, and
  • environment variable

created by a function.

Example of stack

When a function is called, a stack frame will be pushed. When a function returns, the stack frame will be poped. This job is not done by the kernel but instead it is done by the compiler. It hardcodes this mechanism into our programs.

A function can read and write anywhere in the stack, not restricted to its own stack frame.

However, the compiler cannot estimate the stack’s size in compile time.
The number of function calls depends on a lof of things like the program state, the user inputs, etc. The kernel can only reserve certain space for the stack.

Let’s thing a bit more. What if there is no more space left in the stack? What will the OS do? Enlarge the stack? What if it is a chain of endless
recursive function calls?

What will happen?

  • Exception caught by the CPU
  • Stack overflow exception
  • Program terminated

Solutions

  • Minimizing the number of arguments
  • Minimizing the number of local variables
  • Minimizing the number of calls
  • Use global variables
  • Use malloc

Heap

When a program is just running, the entire heap space is unallocated or empty.

Heap is growing when malloc is called

The allocated space growing or shrinking depends on malloc() and free() system calls. malloc() grows the heap space while free() shrinks the heap space.

When “malloc()” or “free()” is called, it may call the “brk()” system call. The “brk()” system call mark where the address of the heap ends.

free()” de-allocates any allocated memory. When a program calls “free(ptr)”, then the address “ptr” must be the start of memory obtained by previous “malloc()” call.

Next Steps

If you’re reading this line, congratulations!!! You made it. You have learnt the basics of user space memory management. Of course there are more complex problems about the memory management like segmantation fault. Hopefully I can discuss some of them in the future.

Feel free to check other articles about operating system below:

To know more about my backend learning path, check out my journey here:

--

--