diff --git a/chp9/README.md b/chp9/README.md index 54daeaa..9d6c4a8 100644 --- a/chp9/README.md +++ b/chp9/README.md @@ -1,12 +1,20 @@ # Chapter 9 - Memory Management -This document summarizes the key concepts from **Chapter 9: Memory Management** in *Linux System Programming, 2nd Edition* by Robert Love. +This document summarizes the key concepts from **Chapter 9: Memory Management** in *Linux System Programming, 2nd Edition* by Robert Love*. It provides an overview of how memory is managed in user space, the role of the kernel, common functions for allocation and manipulation, and techniques to optimize and debug memory usage. --- ## Overview -Memory management in Linux involves allocating, using, and freeing memory efficiently while avoiding leaks and fragmentation. This chapter explains how user-space programs interact with the kernel’s memory manager through system calls, library functions, and advanced allocation techniques. +Memory management in Linux is a fundamental component of system programming. It involves allocating, using, and releasing memory safely and efficiently. Proper memory handling avoids fragmentation, memory leaks, and undefined behavior. + +The chapter focuses on: + +- How user-space processes interact with the kernel for memory allocation. +- The process memory layout and its components. +- Allocation strategies in both heap and stack. +- Advanced techniques like memory mapping and page locking. +- Debugging tools for memory issues. --- @@ -14,91 +22,191 @@ Memory management in Linux involves allocating, using, and freeing memory effici ### 1. Memory Layout of a Process -A typical Linux process memory layout includes: +Every Linux process has a well-defined memory layout, divided into segments: -- **Text Segment** – Executable code of the program (read-only). -- **Data Segment** – Initialized global and static variables. -- **BSS Segment** – Uninitialized global and static variables. -- **Heap** – Dynamically allocated memory (via `malloc`, `calloc`, `realloc`). -- **Stack** – Function call frames, local variables. -- **Memory Mappings** – Shared libraries, `mmap` allocations, etc. +- **Text Segment** – Holds the compiled machine code of the program. Typically marked read-only and executable to prevent accidental modification. +- **Data Segment** – Contains initialized global and static variables. +- **BSS Segment** – Holds uninitialized global and static variables, which the kernel initializes to zero at program start. +- **Heap** – Dynamic memory allocated at runtime using `malloc`, `calloc`, or `realloc`. Grows upward as needed. +- **Stack** – Stores function call frames, local variables, and return addresses. Grows downward on most architectures. +- **Memory Mappings** – Regions created with `mmap()`, commonly used for shared libraries and large allocations. + +> Understanding this layout is essential for writing efficient, low-level programs and debugging memory-related issues. --- ### 2. Dynamic Memory Allocation -- **malloc(size_t size)** – Allocates memory but leaves it uninitialized. -- **calloc(size_t nmemb, size_t size)** – Allocates and zeroes memory. -- **realloc(void *ptr, size_t size)** – Resizes a previously allocated block. -- **free(void *ptr)** – Frees allocated memory. +Dynamic allocation functions allow flexible use of memory at runtime: + +- **malloc(size_t size)** – Allocates a memory block of the given size without initialization. +- **calloc(size_t nmemb, size_t size)** – Allocates memory for an array of `nmemb` elements, initializing all bytes to zero. +- **realloc(void \*ptr, size_t size)** – Resizes an existing memory block, preserving its contents up to the smaller of the old and new sizes. +- **free(void \*ptr)** – Releases previously allocated memory. #### Common pitfalls -- **Memory leaks** – Forgetting to free memory. -- **Double free** – Calling `free` twice on the same pointer. -- **Use-after-free** – Accessing memory after it has been freed. +- **Memory leaks** – Occur when allocated memory is not freed before losing all references to it. +- **Double free** – Freeing the same memory block twice, which causes undefined behavior. +- **Use-after-free** – Accessing a memory block after it has been freed, leading to crashes or data corruption. --- ### 3. `brk` and `sbrk` -- Low-level system calls to manage the program break (end of the data segment). -- Rarely used directly; `malloc` and friends handle these internally. +Historically, `brk()` and `sbrk()` were used to manipulate the program break, which defines the end of the process’s data segment. + +- `brk()` sets the end of the data segment. +- `sbrk()` increments (or decrements) the break value. + +Modern applications rarely use these functions directly; instead, `malloc` and related functions manage them internally. These interfaces are considered low-level and non-portable. --- ### 4. Memory Mapping with `mmap` -- **mmap()** maps files or anonymous memory into the process address space. -- Advantages: - - Direct file access without extra copy operations. - - Efficient large memory allocations. -- Common use cases: - - Loading large files. - - Shared memory between processes. -- Paired with `munmap()` to release mappings. +`mmap()` maps files or anonymous memory into the process’s address space. + +#### Advantages + +- Eliminates the need for additional copies when reading files. +- Enables efficient handling of large files and inter-process communication. + +#### Typical use cases + +- Memory-mapped file I/O. +- Shared memory regions for IPC. +- Allocating large anonymous memory regions. + +To release mappings, use **munmap()**. Unlike `malloc`, which uses the heap, `mmap` provides greater flexibility for aligning memory and mapping hardware devices. --- -### 5. Memory Locking +### 5. Advanced Memory Techniques -- **mlock() / mlockall()** – Lock memory pages into RAM to avoid swapping. -- Useful for: - - Real-time applications. - - Security-sensitive data (e.g., cryptographic keys). +Several specialized APIs provide advanced control over memory: + +- **mprotect()** – Changes access permissions (read, write, execute) for a memory region. +- **shm_open() / shm_unlink()** – Creates and removes POSIX shared memory objects for inter-process communication. +- **posix_memalign()** – Allocates memory aligned to a specific boundary, useful for SIMD operations or page-aligned buffers. + +These functions are essential in performance-sensitive and real-time systems. --- -### 6. Advanced Memory Techniques +### 6. Debugging Memory Issues -- **mprotect()** – Change protection (read, write, execute) of memory regions. -- **shm_open() / shm_unlink()** – POSIX shared memory objects. -- **posix_memalign()** – Allocate memory aligned to a specified boundary. +The GNU C Library provides several functions to inspect and analyze memory usage at runtime: + +- **mallinfo()** + Returns a `struct mallinfo` with statistics about memory allocation, such as total allocated space, number of free blocks, and fragmentation levels. +- **malloc_usable_size(void *ptr)** + Returns the actual usable size of an allocated block. This value may be larger than the size requested from `malloc()` due to alignment or internal overhead. +- **malloc_trim(size_t pad)** + Attempts to return unused memory from the heap back to the operating system, retaining `pad` bytes for future allocations. Useful for long-running processes to reduce memory footprint. +- **malloc_stats()** + Prints detailed statistics about memory allocation directly to `stderr`. + This includes information about the heap, allocated blocks, and free space. + +#### Notes + +- These functions are **GNU-specific** and may not be portable across different C libraries or UNIX-like systems. +- They are primarily intended for **debugging and profiling**, not for production code paths that require high performance. +- Use them to analyze fragmentation patterns, memory usage trends, and allocator behavior during development or testing. --- -### 7. Debugging Memory Issues +### 7. Stack-Based Allocation -- Tools like **valgrind** and **AddressSanitizer** help detect: - - Memory leaks. - - Invalid reads/writes. - - Use-after-free errors. -- Good practices: - - Always initialize pointers. - - Free resources in reverse order of allocation. +Stack allocation can be an efficient alternative for temporary buffers: + +- **alloca(size_t size)** – Allocates memory on the calling function’s stack frame. The allocated memory is automatically released when the function returns, so `free()` is unnecessary. + +#### Advantages + +- Extremely fast allocation and deallocation. +- Ideal for short-lived buffers with known size limits. + +#### Limitations + +- Memory is valid only within the function scope. Returning a pointer to stack memory results in undefined behavior. +- Excessive allocation can lead to **stack overflow** and program crashes. +- `alloca()` is not part of the ISO C standard, but it is available as a POSIX or compiler-specific extension. + +**Example**: + +```c +void example(size_t size) { + char *buffer = alloca(size); + // Use buffer safely within this function only +} +``` + +--- + +### 8. Manipulating Memory + +C provides several functions for low-level memory manipulation: + +- **memset(void \*s, int c, size_t n)** + Fills a block of memory with a given byte value. + Example: + + ```c + memset(buffer, 0, sizeof(buffer)); // Zero out buffer + ``` + +- **memcpy(void \*dest, const void \*src, size_t n)** + Copies `n` bytes from `src` to `dest`. Behavior is undefined if regions overlap. + +- **memmove(void \*dest, const void \*src, size_t n)** + Like `memcpy`, but handles overlapping memory correctly. + +- **memcmp(const void \*s1, const void \*s2, size_t n)** + Compares two memory regions byte by byte, returning: + - `< 0` if `s1` < `s2` + - `0` if equal + - `> 0` if `s1` > `s2` + +- **memchr(const void \*s, int c, size_t n)** + Scans the n bytes of memory pointed at by s for the character c, which is interpreted as an unsigned char + +- **memmem(const void \*haystack, size_t haystacklen, const void \*needle, size_t needlelen)** + Returns a pointer to the first occurrence of the subblock needle, of length needlelen bytes, within the block of memory haystack, of length haystacklen bytes. + If the function does not find needle in haystack, it returns NULL. + + > This function is a GNU extension + +These functions do not perform type checking or add null terminators, making them ideal for raw data operations but requiring caution to avoid buffer overruns. + +--- + +### 9. Locking Memory + +Linux provides mechanisms to lock memory pages into RAM: + +- **mlock()** – Locks a specific memory range, preventing it from being swapped out. +- **mlockall()** – Locks all pages mapped by the calling process. + +#### Use cases + +- Real-time systems that must avoid page faults. +- Applications handling sensitive data such as encryption keys, ensuring they never hit disk. + +After use, memory should be unlocked with **munlock()** or **munlockall()**. --- ## Key Takeaways -- Understand the process memory layout to avoid common pitfalls. -- Use the right allocation function for your needs. -- Prefer `mmap` for large or shared allocations. -- Memory debugging tools are essential for writing reliable code. +- Understanding the process memory layout is critical for writing efficient and safe programs. +- Dynamic allocation with `malloc` and its variants is powerful but requires careful management to avoid leaks and corruption. +- Use `mmap` for large or shared allocations and `alloca` for small, temporary buffers. +- Memory manipulation functions provide high performance but require strict attention to bounds and alignment. --- ## References - *Linux System Programming, 2nd Edition* – Robert Love, O’Reilly Media. -- `man malloc`, `man mmap`, `man mprotect`, `man mlock` +- `man malloc`, `man mmap`, `man alloca`, `man mprotect`, `man mlock`. diff --git a/chp9/alignviolation.c b/chp9/alignviolation.c new file mode 100644 index 0000000..0f02a19 --- /dev/null +++ b/chp9/alignviolation.c @@ -0,0 +1,12 @@ +#include + +int main(void) { + char greeting[] = "Ahoy Matey"; + char *c = greeting[1]; + unsigned long badnews = *(unsigned long *)c; + + printf("%s\n", c); + printf("%lu\n", badnews); + + return 0; +} diff --git a/chp9/mallstats.c b/chp9/mallstats.c new file mode 100644 index 0000000..2fadf50 --- /dev/null +++ b/chp9/mallstats.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +int main(void) { + /* Initialize string array */ + char **names = (char **)calloc(5, sizeof(char *)); + for (int i = 0; i < 5; i++) { + names[i] = (char *)malloc(sizeof(char *)); + } + + memcpy(names[0], "Fabio", 5); + memcpy(names[1], "Chiara", 6); + memcpy(names[2], "Valerio", 7); + memcpy(names[3], "Luca", 4); + memcpy(names[4], "Leila", 5); + + printf("\nBefore freed array...\n"); + malloc_stats(); + printf("\n"); + + for (int i = 0; i < 5; i++) { + printf("%s\n", names[i]); + } + + for (int i = 0; i < 5; i++) { + free(names[i]); + } + + printf("\nAfter freed array...\n"); + malloc_stats(); + printf("\n"); + + return 0; +} diff --git a/chp9/stackallocation.c b/chp9/stackallocation.c new file mode 100644 index 0000000..bc979ca --- /dev/null +++ b/chp9/stackallocation.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_LEN 1024 +#define SYSCONF_DIR "/etc" + +int open_sysconf(const char *, int, int); + +int main(void) +{ + int fd = open_sysconf("/passwd", O_RDONLY, 0644); + if (fd == -1) { + perror("open"); + return -1; + } + + int len; + if ((len = lseek(fd, 0, SEEK_END)) == -1) { + perror("lseek"); + return -1; + } + + if (lseek(fd, 0, SEEK_SET) == -1) { + perror("lseek"); + return -1; + } + + char buffer[len + 1]; + memset(buffer, 0, sizeof(buffer)); + + ssize_t ret; + size_t bytes_read_total = 0; + while (bytes_read_total < len && (ret = read(fd, buffer + bytes_read_total, len - bytes_read_total)) != 0) { + if (ret == -1) { + if (errno == EINTR) continue; + perror("read"); + break; + } + bytes_read_total += ret; + } + + buffer[bytes_read_total] = '\0'; + printf("%s", buffer); + + close(fd); + + return 0; +} + +int open_sysconf(const char *file, int flags, int mode) +{ + const char *etc = SYSCONF_DIR; + char *name = alloca(strlen(etc) + strlen(file) + 1); + strcpy(name, etc); + strcat(name, file); + return open(name, flags, mode); +} diff --git a/chp9/test_malloc.c b/chp9/test_malloc.c new file mode 100644 index 0000000..8fe67d0 --- /dev/null +++ b/chp9/test_malloc.c @@ -0,0 +1,16 @@ +#include +#include +#include + +int main(void) { + char *buf = (char *)malloc(256); + if (!buf) { + perror("malloc"); + return 1; + } + + size_t size = malloc_usable_size(buf); + printf("%d\n", size); + free(buf); + return 0; +}