In the previous parts, we covered system call basics, file I/O, process management and even built a mini-shell. Now, we turn our attention to memory management, a critical area for systems programming.
Memory management system calls allow programs to request, map and protect memory in a controlled way. This gives you the foundation for advanced topics like shared memory, memory-mapped files and performance optimization.
1. Memory Layout of a Process
A Linux process has several memory regions:
- Text segment: contains the compiled program instructions
- Data segment: contains global and static variables
- Heap: dynamic memory allocated at runtime
- Stack: local variables and function calls
- Memory-mapped region: for files and shared memory
System calls primarily interact with the heap and memory-mapped regions.
2. Adjusting Heap Size: brk() and sbrk()
Traditionally, dynamic memory is managed using brk() and sbrk().
brk()
#include <unistd.h>
int brk(void *end_data_segment);
- Sets the end of the data segment (heap).
- Returns 0 on success, -1 on failure.
sbrk()
#include <unistd.h>
void *sbrk(intptr_t increment);
- Moves the program break by increment bytes.
- Returns the previous end of the heap.
Example:
#include <unistd.h>
#include <stdio.h>
int main()
{
void *heap_start = sbrk(0);
printf("Heap starts at %p\n", heap_start);
sbrk(1024);
printf("Heap increased by 1024 bytes\n");
return 0;
}
Note: Most programs use malloc() instead of calling these directly. malloc() internally uses brk() or mmap().
3. Mapping Memory: mmap() and munmap()
mmap() maps files or anonymous memory into the process’s address space.
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- addr: suggested start address (usually NULL)
- length: size of mapping
- prot: memory protection (PROT_READ, PROT_WRITE)
- flags: MAP_PRIVATE, MAP_SHARED, MAP_ANONYMOUS
- fd: file descriptor (ignored if MAP_ANONYMOUS)
- offset: offset in file
munmap() unmaps pages of memory.
#include <sys/mmap>
int munmap(void *addr, size_t len);
- addr: remove process memory starting from this address
- len: remove len bytes of memory starting from addr
Example of mapping a file into memory:
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd = open("example.txt", O_RDONLY);
if (fd < 0) return 1;
size_t size = 4096;
char *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED)
{
perror("mmap");
return 1;
}
printf("First byte: %c\n", data[0]);
munmap(data, size);
close(fd);
return 0;
}
4. Controlling Memory Protections: mprotect()
mprotect() allows you to change the access permissions of a memory region.
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
Example: making a memory page read-only:
mprotect(addr, 4096, PROT_READ);
Returns 0 on success, -1 on failure.
5. Shared Memory via mmap()
Memory can be shared between processes using mmap() with MAP_SHARED and a file descriptor:
int fd = open("shared.bin", O_CREAT | O_RDWR, 0644);
ftruncate(fd, 4096);
char *shared = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Now, multiple processes that map this file can read and write the same memory.
6. Example: Modifying a File via Memory Mapping
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main()
{
int fd = open("file.txt", O_RDWR);
size_t size = 4096;
char *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
{
perror("mmap");
return 1;
}
strcpy(data, "Hello mmap!");
msync(data, size, MS_SYNC);
munmap(data, size);
close(fd);
return 0;
}
7. TL/DR
- brk() and sbrk() adjust the heap size (low-level dynamic memory).
- mmap() maps files or anonymous memory into a process’s address space.
- munmap() unmaps pages of memory.
- mprotect() changes memory protections dynamically.
- Shared memory can be implemented via mmap() with MAP_SHARED.
- Memory-mapped files allow direct modification of file contents in memory.
At this point, you understand the low-level memory management system calls in Linux and how they support dynamic memory, file mapping and shared memory.