diff --git a/README.md b/README.md index 40b4968..0d3bd55 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Welcome! This is a collection of chapter summaries from the book **Linux System - [Chapter 5 - Process Management](chp5/README.md) - [Chapter 6 - Advanced Process Management](chp6/README.md) - [Chapter 7 - Threading](chp7/README.md) +- [Chapter 9 - Memory Management](chp9/README.md) - [Exercises](exercises/README.md) > Each file contains an English summary of the chapter's key concepts. diff --git a/exercises/README.md b/exercises/README.md index cdfc54a..8f48182 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -36,13 +36,13 @@ This is an organized list of all exercises completed so far, including a brief d **Description**: A simple C program that sequentially executes one or more bash scripts, printing each script’s exit status. -- πŸ“„ [README](run_scripts/README.md) -- πŸ“‚ Directory: `run_scripts/` -- βœ… Features: - - Uses `fork()` and `execlp()` to run each script in a child process - - Waits for each child to finish before executing the next - - Reports normal exit code or termination by signal - - Includes example test scripts and memory checks with Valgrind +- πŸ“„ [README](run_scripts/README.md) +- πŸ“‚ Directory: `run_scripts/` +- βœ… Features: + - Uses `fork()` and `execlp()` to run each script in a child process + - Waits for each child to finish before executing the next + - Reports normal exit code or termination by signal + - Includes example test scripts and memory checks with Valgrind - Makefile with targets: `build`, `run`, `test`, `valgrind`, and `clean` --- @@ -51,8 +51,8 @@ This is an organized list of all exercises completed so far, including a brief d **Description**: A multi-threaded simulated file downloader implemented in C using POSIX threads (pthreads). -- πŸ“„ [README](downloader/README.md) -- πŸ“‚ Directory: `downloader/` +- πŸ“„ [README](downloader/README.md) +- πŸ“‚ Directory: `downloader/` - βœ… Features: - Each thread simulates downloading a file by printing periodic progress updates - Synchronization using `pthread` APIs @@ -61,6 +61,20 @@ This is an organized list of all exercises completed so far, including a brief d --- +## 🧠 memory_analyzer + +**Description**: A simple memory analyzer that allocates memory blocks of different sizes and prints current memory usage. + +- πŸ“„ [README](mem_analyzer/README.md) +- πŸ“‚ Directory: `mem_analyzer/` +- βœ… Features: + - Allocates blocks using `malloc` and frees them after measurement + - Reports current memory usage via `/proc/self/status` + - Minimal implementation aligned with the exercise objectives + - Makefile included with standard targets + +--- + ## πŸ”§ Tooling & Automation **Shared tools and scripts used across projects**: @@ -69,5 +83,3 @@ This is an organized list of all exercises completed so far, including a brief d - Targets for `build`, `test`, `valgrind`, and `clean` - Shell script for automated tests: `run_tests.sh` - Valgrind memory check reports generated automatically - -*Last updated: 2025-07-30* diff --git a/exercises/mem_analyzer/Makefile b/exercises/mem_analyzer/Makefile new file mode 100644 index 0000000..1687198 --- /dev/null +++ b/exercises/mem_analyzer/Makefile @@ -0,0 +1,29 @@ +CC = gcc +CFLAGS = -Wall -Wextra -Wpadded -g -fsanitize=address +TARGET = mem_analyzer +SRC = mem_utils.c main.c +OBJ = $(SRC:.c=.o) + +.PHONY: all build run test valgrind clean + +all: build + +build: $(TARGET) + +$(TARGET): $(OBJ) + $(CC) $(CFLAGS) -o $@ $^ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +run: build + ./$(TARGET) + +test: build + ./$(TARGET) test + +valgrind: build + valgrind --leak-check=full --show-leak-kinds=all ./$(TARGET) + +clean: + rm -f $(TARGET) $(OBJ) diff --git a/exercises/mem_analyzer/README.md b/exercises/mem_analyzer/README.md new file mode 100644 index 0000000..ae0cd4e --- /dev/null +++ b/exercises/mem_analyzer/README.md @@ -0,0 +1,77 @@ +# 🧠 Memory Management Analyzer + +This project demonstrates how different memory allocation methods (`malloc`, `calloc`, and `mmap`) perform in terms of speed and memory usage in a simulated workload. + +--- + +## πŸ“œ Description +The program dynamically allocates and frees memory blocks of varying sizes, measuring: + +- Allocation and deallocation times. +- Memory usage via `/proc/self/status` or `/proc/self/statm`. +- Peak memory consumption during execution. + +It produces a report comparing performance across allocation methods. + +--- + +## πŸš€ Features +- **Dynamic Allocation Testing** – compares `malloc`, `calloc`, and `mmap`. +- **Performance Metrics** – measures allocation/deallocation times. +- **Memory Usage Tracking** – monitors real-time process memory usage. +- **Optional Memory Leak Simulation** – detects unfreed memory using Valgrind. +- **CLI Mode Selection** – choose allocator at runtime (`--malloc`, `--calloc`, `--mmap`). + +--- + +## πŸ› οΈ Build +```bash +make +``` + +--- + +## ▢️ Run +```bash +./mem_analyzer --malloc +./mem_analyzer --calloc +./mem_analyzer --mmap +``` + +--- + +## πŸ§ͺ Test & Debug +```bash +make valgrind +``` + +--- + +## πŸ“Š Example Output +``` +Allocator: malloc +Blocks: 10000 +Average Allocation Time: 0.24 ΞΌs +Average Free Time: 0.18 ΞΌs +Peak Memory Usage: 2.5 MB +``` + +--- + +## πŸ“‚ Project Structure +``` +mem_analyzer/ + β”œβ”€β”€ README.md + β”œβ”€β”€ Makefile + └── main.c +``` + +--- + +## πŸ” Learning Objectives +- Understand differences between `malloc`, `calloc`, and `mmap`. +- Learn to measure memory usage through `/proc`. +- Practice debugging memory errors with Valgrind. +- Gain insight into memory allocation strategies in Linux. + +--- diff --git a/exercises/mem_analyzer/main.c b/exercises/mem_analyzer/main.c new file mode 100644 index 0000000..7c0aba1 --- /dev/null +++ b/exercises/mem_analyzer/main.c @@ -0,0 +1,168 @@ +#include "mem_utils.h" +#include +#include +#include +#include + +/* Run allocation benchmark using malloc */ +struct Stats *bench_malloc(); + +/* Run allocation benchmark using calloc */ +struct Stats *bench_calloc(); + +/* Run allocation benchmark using mmap */ +struct Stats *bench_mmap(); + +typedef struct Stats *(*StatsFn)(void); + +/* Memory blocks size to allocate for testing */ +static size_t size_blocks[BLOCK_NUM] = {1024, 2048, 4096, 100000, 8192}; + +int main(int argc, char const *argv[]) +{ + const StatsFn benchmarks[] = {bench_malloc, bench_calloc, bench_mmap}; + for (int i = 0; i < 3; i++) { + StatsFn benchmark = benchmarks[i]; + struct Stats *result = benchmark(); + print_stats(result); + free(result); + printf("-------------------------------------------\n"); + } + return 0; +} + +struct Stats *bench_malloc() +{ + // Benchmark malloc function + double peak_mem = 0.0; + double m_elapsed_times[BLOCK_NUM] = {0}; + double f_elapsed_times[BLOCK_NUM] = {0}; + for (int i = 0; i < BLOCK_NUM; i++) { + size_t size = size_blocks[i]; + struct timespec m_start, f_start, m_end, f_end; + + // malloc + clock_gettime(CLOCK_MONOTONIC, &m_start); + void *ptr = malloc(size); + clock_gettime(CLOCK_MONOTONIC, &m_end); + m_elapsed_times[i] = ((m_end.tv_sec - m_start.tv_sec) + + (m_end.tv_nsec - m_start.tv_nsec)) / + 1e9; + + double curr_mem_usage = get_mem_usage(); + if (peak_mem <= curr_mem_usage) { + peak_mem = curr_mem_usage; + } + + // free + clock_gettime(CLOCK_MONOTONIC, &f_start); + free(ptr); + clock_gettime(CLOCK_MONOTONIC, &f_end); + f_elapsed_times[i] = ((f_end.tv_sec - f_start.tv_sec) + + (f_end.tv_nsec - f_start.tv_nsec)) / + 1e9; + } + + struct Stats *stats = (struct Stats *)malloc(sizeof(struct Stats)); + if (!stats) { + perror("malloc"); + exit(1); + } + stats->allocator_type = "malloc"; + stats->nr_allocations = BLOCK_NUM; + stats->avg_alloc_time = avg(m_elapsed_times, BLOCK_NUM); + stats->avg_free_time = avg(f_elapsed_times, BLOCK_NUM); + stats->mem_usage_mb = peak_mem; + return stats; +} + +struct Stats *bench_calloc() +{ + // Benchmark malloc function + double peak_mem = 0.0; + double m_elapsed_times[BLOCK_NUM] = {0}; + double f_elapsed_times[BLOCK_NUM] = {0}; + for (int i = 0; i < BLOCK_NUM; i++) { + size_t size = size_blocks[i]; + struct timespec m_start, f_start, m_end, f_end; + + // malloc + clock_gettime(CLOCK_MONOTONIC, &m_start); + void *ptr = calloc(1, size); + clock_gettime(CLOCK_MONOTONIC, &m_end); + m_elapsed_times[i] = ((m_end.tv_sec - m_start.tv_sec) + + (m_end.tv_nsec - m_start.tv_nsec)) / + 1e9; + + double curr_mem_usage = get_mem_usage(); + if (peak_mem <= curr_mem_usage) { + peak_mem = curr_mem_usage; + } + + // free + clock_gettime(CLOCK_MONOTONIC, &f_start); + free(ptr); + clock_gettime(CLOCK_MONOTONIC, &f_end); + f_elapsed_times[i] = ((f_end.tv_sec - f_start.tv_sec) + + (f_end.tv_nsec - f_start.tv_nsec)) / + 1e9; + } + + struct Stats *stats = (struct Stats *)malloc(sizeof(struct Stats)); + if (!stats) { + perror("malloc"); + exit(1); + } + stats->allocator_type = "calloc"; + stats->nr_allocations = BLOCK_NUM; + stats->avg_alloc_time = avg(m_elapsed_times, BLOCK_NUM); + stats->avg_free_time = avg(f_elapsed_times, BLOCK_NUM); + stats->mem_usage_mb = peak_mem; + return stats; +} + +struct Stats *bench_mmap() +{ + // Benchmark malloc function + double peak_mem = 0.0; + double m_elapsed_times[BLOCK_NUM] = {0}; + double f_elapsed_times[BLOCK_NUM] = {0}; + for (int i = 0; i < BLOCK_NUM; i++) { + size_t size = size_blocks[i]; + struct timespec m_start, f_start, m_end, f_end; + + // malloc + clock_gettime(CLOCK_MONOTONIC, &m_start); + void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + clock_gettime(CLOCK_MONOTONIC, &m_end); + m_elapsed_times[i] = ((m_end.tv_sec - m_start.tv_sec) + + (m_end.tv_nsec - m_start.tv_nsec)) / + 1e9; + + double curr_mem_usage = get_mem_usage(); + if (peak_mem <= curr_mem_usage) { + peak_mem = curr_mem_usage; + } + + // free + clock_gettime(CLOCK_MONOTONIC, &f_start); + munmap(ptr, size); + clock_gettime(CLOCK_MONOTONIC, &f_end); + f_elapsed_times[i] = ((f_end.tv_sec - f_start.tv_sec) + + (f_end.tv_nsec - f_start.tv_nsec)) / + 1e9; + } + + struct Stats *stats = (struct Stats *)malloc(sizeof(struct Stats)); + if (!stats) { + perror("malloc"); + exit(1); + } + stats->allocator_type = "mmap"; + stats->nr_allocations = BLOCK_NUM; + stats->avg_alloc_time = avg(m_elapsed_times, BLOCK_NUM); + stats->avg_free_time = avg(f_elapsed_times, BLOCK_NUM); + stats->mem_usage_mb = peak_mem; + return stats; +} diff --git a/exercises/mem_analyzer/mem_utils.c b/exercises/mem_analyzer/mem_utils.c new file mode 100644 index 0000000..d674f3e --- /dev/null +++ b/exercises/mem_analyzer/mem_utils.c @@ -0,0 +1,47 @@ +#include "mem_utils.h" +#include +#include + +void print_stats(struct Stats *stats) +{ + printf("Allocator: %s\nBlocks: %ld\nAvarage Allocation Time: %.2f " + "ΞΌs\nAvarage Free Time: %.2f ΞΌs\nPeak Memory Usage: %.2f MB\n", + stats->allocator_type, stats->nr_allocations, + stats->avg_alloc_time*1e6, stats->avg_free_time*1e6, + stats->mem_usage_mb); +} + +double avg(double *values, int size) +{ + double sum = 0.0; + for (int i = 0; i < size; i++) { + sum += *(values + i); + } + return size > 0 ? sum / size : 0.0; +} + +double get_mem_usage() +{ + FILE *fp = fopen("/proc/self/status", "r"); + if (!fp) { + perror("fopen"); + return 1; + } + + int found = 0; + char line[256]; + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "VmRSS:", 6) == 0) { + found = 1; + break; + } + } + fclose(fp); + + if (!found) { + fprintf(stderr, "VmRSS not found in /proc/self/status\n"); + return 0.0; + } + + return strtod(line + 6, NULL) / 1024.0; +} diff --git a/exercises/mem_analyzer/mem_utils.h b/exercises/mem_analyzer/mem_utils.h new file mode 100644 index 0000000..fe20c42 --- /dev/null +++ b/exercises/mem_analyzer/mem_utils.h @@ -0,0 +1,26 @@ +#ifndef MEM_UTILS_H +#define MEM_UTILS_H + +#include + +/* Block number under testing */ +#define BLOCK_NUM 5 + +struct Stats { + const char *allocator_type; + size_t nr_allocations; + double avg_alloc_time; + double avg_free_time; + double mem_usage_mb; +}; + +/* Print on STDOUT the memory stats collected */ +void print_stats(struct Stats *); + +/* Calculate arithmetic average */ +double avg(double *, int size); + +/* Read in the a Linux system at /proc/self/status file the RSS field */ +double get_mem_usage(); + +#endif \ No newline at end of file