The Linux Kernel performs virtual memory allocations on behalf of processes, providing each process its own unique virtual memory that's unable to
Segments of memory
Memory is segmented into different sections.
In order of low to high:
The text segment contains a code segment containing machine instructions and a text segment, often made read-only to prevent accidental modifications through memory mismanagement. This segment is a fixed size, as its size is known at process start time.
Sharable, so only a single instance needs to be kept in memory, speeding up launch time.
This segment contains global and static variables initialised by the programmer. This section is not read-only, as it is possible to modify these values at run-time.
Further broken into read-only and read-write for constant values.
Uninitialised Data (BSS)
This segment contains any uninitialised global and static variables, which the kernel will initialise to integer 0 prior to program start. It's named after an ancient assembler operator that stood for "Block Started by Symbol".
Traditionally, the heap and stack adjoined one another and grew in opposing directions into a shared free memory space. In modern operating systems and with such large available address spaces the two can now be placed almost anywhere, but usually still grow in opposite directions.
The heap, or the free store, supports dynamic, developer-managed, allocation of memory. It is shared by all shared objects and dynamically loaded modules within a process.
In the kernel memory is managed using
vfree(). In userspace this is achieved using
Positioned in high memory, and grows into the free space shared with the heap.
The stack stores the program stack, a LIFO data structure containing stack frames, comprising:
- A return address, declaring where to go once execution completes.
- Local variables declared in this frame.
- Caller's environment, register values.
A separate stack pointer register stores the address of the top of the stack, and is adjusted each time a new stack frame is pushed onto the stack.
Space is freed when exiting a stack frame, allowing automatic allocation and deallocation of local values.
In C, recursive function calls always push a new frame onto the stack, preventing erroneous manipulation of values in the parent frame.
Concurrency allows access to the same section of memory to be shared between processes or threads. Multiple concurrent readers with zero writers is a common pattern for processing messages, but problems will occur when writing to memory others are reading from.
Race conditions can occur when multiple threads are allowed uncontrolled access to data, and can lead to memory leaks as shared data is overwritten.
Where possible, avoid resource sharing. Use mutual exclusion locks (mutex locks) or semaphores to synchronise access where not.