diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c061998 --- /dev/null +++ b/.clang-format @@ -0,0 +1,189 @@ +Language: Cpp +# BasedOnStyle: Microsoft +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: false + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 1000 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME diff --git a/.github/actions/building/action.yml b/.github/actions/building/action.yml new file mode 100644 index 0000000..9af2669 --- /dev/null +++ b/.github/actions/building/action.yml @@ -0,0 +1,36 @@ +name: "Build" +description: "Executes a compilation." + +runs: + using: "composite" + steps: + - name: Compile + shell: bash + run: | + + # Ensure the script exits on any command failure + set -e + + # Define the root directory of the project (adjust if necessary) + PROJECT_ROOT="/home/runner/work/so-i-24-chp3-iledesma08/so-i-24-chp3-iledesma08" + + # Update package list and install necessary dependencies + echo "Updating package list and installing dependencies..." + sudo apt update + sudo apt install -y python3-pip + + # Install Conan and detect profile + echo "Installing Conan and setting up dependencies..." + pip install --upgrade conan + conan profile detect + + # Set up and build the main project + echo "Setting up and building the main project..." + cd "$PROJECT_ROOT" + mkdir -p build + conan install . --build=missing + cd build + mkdir tests + cmake .. -DCMAKE_TOOLCHAIN_FILE="$PROJECT_ROOT/build/Release/generators/conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release + make + cd "$PROJECT_ROOT" diff --git a/.github/actions/coverage_and_test/action.yml b/.github/actions/coverage_and_test/action.yml new file mode 100644 index 0000000..af552da --- /dev/null +++ b/.github/actions/coverage_and_test/action.yml @@ -0,0 +1,59 @@ +name: "Test and coverage check" +description: "Test and coverage check with gcovr, pass if coverage is greater than 20%" + +runs: + using: "composite" + steps: + - name: "Run coverage" + shell: bash + run: | + echo "Showing current directory" + echo $(pwd) + + PROJECT_PATH=$(pwd) + + ERROR_FILE_FLAG=$(pwd)/tests_errors.txt + + CTEST_ERROR_FILE_FLAG=$(pwd)/ctest_errors.txt + # run tests built with cmake + # --output-on-faileure: prints everythin outputted by the tests if it fails + # -VV verbose output + CTEST_COMMAND=$(ctest --test-dir build/tests --output-on-failure -VV 2> $CTEST_ERROR_FILE_FLAG 1>$ERROR_FILE_FLAG) + + if [ -s $CTEST_ERROR_FILE_FLAG ]; then + echo "Error: Unit Tests Failed" + exit 1 + else + echo "All tests where passed!" + fi + + # run coverage of those tests and oputput to file + gcovr -r $PROJECT_PATH . >> $ERROR_FILE_FLAG + + echo "Runnig: gcovr -r $PROJECT_PATH ." + + cat $ERROR_FILE_FLAG + + # Find the value of correct coverage + # parse coverage output file and extracts coverage percentage + # grep pulls out the line of the total + # awk prints only the last field (NF = number of fields of the grepped line) + # cut finnaly creates a list using '%' as a delimiter (-d '%') and keeps first value (-f 1) + COVERAGE_RESULT=$(grep "TOTAL" $ERROR_FILE_FLAG | awk '{print $NF}' | cut -d '%' -f 1) + + # Coverage lines GT 5 + if [ "$(echo "$COVERAGE_RESULT > 5" | bc)" -eq 1 ]; then + echo "Coverage is greater than 5%. Nice!" + exit 0 + else + echo "Error: Coverage is less than or equal to 5%" + exit 1 + fi + + # Upload errors as an artifact, when failed + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: Tests or coverage errors!!! + path: ./tests_errors.txt + retention-days: 1 diff --git a/.github/actions/documentation/action.yml b/.github/actions/documentation/action.yml new file mode 100644 index 0000000..0aa80a9 --- /dev/null +++ b/.github/actions/documentation/action.yml @@ -0,0 +1,44 @@ +name: "Doc check" +description: "Code documentation generation with Doxygen" + +runs: + using: "composite" + steps: + - name: "Run doxygen command" + shell: bash + run: | + # We only make check, not changes + DOX_CONF_FILE=$(pwd)/Doxyfile + + # Append to DOX_CONF_FILE input source directories, if you have libs, add $(pwd)/lib + { + cat $DOX_CONF_FILE + echo "INPUT" = $(pwd)/src $(pwd)/include + } > $DOX_CONF_FILE + + # Generate documentation + # dot -c clears Graphviz configuration, doxygen uses Graphviz for generating graphical representations + sudo dot -c + + ERROR_FILE_FLAG=$(pwd)/dox_errors.txt + + # create documentation: -s specifies comments of configurations items will be omitted. + # pipe stderr to error file + DOXYGEN_COMMAND=$(doxygen -s $DOX_CONF_FILE 2> $ERROR_FILE_FLAG) + + # if error file not empty fail + if [ -s $ERROR_FILE_FLAG ]; then + echo "Error: There are some files that are not documented correctly" + exit 1 + else + echo "All files are documented correctly. Niiiceee" + exit 0 + fi + + # Upload errors as an artifact, when failed + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: Doxygen errors!!! + path: ./dox_errors.txt + retention-days: 1 diff --git a/.github/actions/style/action.yml b/.github/actions/style/action.yml new file mode 100644 index 0000000..426728a --- /dev/null +++ b/.github/actions/style/action.yml @@ -0,0 +1,40 @@ +name: "Style check" +description: "Style check using clang-format" + +runs: + using: "composite" + steps: + - name: "Run clang-format" + shell: bash + run: | + # We only make check, not changes + # Use the find command with the variable + PROJECT_PATH=$(pwd) + + SOURCE_FILES=$(find $PROJECT_PATH/src -type f \( -name "*.cpp" -or -name "*.hpp" -or -name "*.h" -or -name "*.c" \) | tr "\n" " ") + SOURCE_FILES+=$(find $PROJECT_PATH/include -type f \( -name "*.cpp" -or -name "*.hpp" -or -name "*.h" -or -name "*.c" \) | tr "\n" " ") + # If you have a lib, uncomment the following line + # SOURCE_FILES+=$(find $PROJECT_PATH/lib -type f \( -name "*.cpp" -or -name "*.hpp" -or -name "*.h" -or -name "*.c" \) | tr "\n" " ") + + ERROR_FILE_FLAG=$PROJECT_PATH/clang-format_errors.txt + + echo "Running: clang-format -n $SOURCE_FILES" + + CLANG_COMMAND=$(clang-format -n $SOURCE_FILES 2> ${ERROR_FILE_FLAG}) + + if [ -s $ERROR_FILE_FLAG ]; then + echo "Error: There are some files that are not formatted correctly" + cat $ERROR_FILE_FLAG + exit 1 + else + echo "All files are formatted correctly. Niiiceee" + exit 0 + fi + + # Upload errors as an artifact, when failed + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: Clang-format errors!!! + path: ./clang-format_errors.txt + retention-days: 1 diff --git a/.github/workflows/QAWorkflow.yml b/.github/workflows/QAWorkflow.yml new file mode 100644 index 0000000..892c63a --- /dev/null +++ b/.github/workflows/QAWorkflow.yml @@ -0,0 +1,35 @@ +name: QA Workflow + +on: + workflow_dispatch: + pull_request: + # Pull request events + types: [synchronize, opened, reopened, ready_for_review] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install dependencies + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: doxygen gcovr lcov cppcheck graphviz clang-format valgrind bc + version: 1.0 + + - name: Run style check + uses: ./.github/actions/style + + - name: Run documentation check + uses: ./.github/actions/documentation + + - name: Build project + uses: ./.github/actions/building + + - name: Run tests and coverage + uses: ./.github/actions/coverage_and_test diff --git a/.gitignore b/.gitignore index c6127b3..7b269a8 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,19 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# VSCode files +.vscode/ +build/ + +# Personal files +used-cmds.md + +# Enviroment +venv/ +.env + +# CMake +CMakeUserPresets.json + +test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..90670c5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.10) + +# Set the project name +project( + memory + LANGUAGES C +) + +include_directories(include) + +# Flags for compiling +set(CMAKE_C_STANDARD 17) +set(CMAKE_C_FLAGS_RELEASE "-O0 -Wall -Wextra -Wpedantic -Werror -Wunused-parameter -Wmissing-prototypes -Wstrict-prototypes") +set(CMAKE_C_FLAGS_DEBUG "-g -O0 -fprofile-arcs -ftest-coverage -Wall -Wextra -Wpedantic -Werror -Wunused-parameter -Wmissing-prototypes -Wstrict-prototypes") + +# Add executable for the main project +add_executable(${PROJECT_NAME} + src/main.c + src/memory.c + src/malloc.c + src/free.c + src/realloc.c + src/calloc.c + src/mem_logging.c + src/mem_metrics.c + tests/memory_test.c + tests/heap_test.c +) + +add_subdirectory(tests) diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..0e9f99d --- /dev/null +++ b/Doxyfile @@ -0,0 +1 @@ +INPUT = /home/ignacio/Facultad/Operativos1/Practico/lab3/so-i-24-chp3-iledesma08/src /home/ignacio/Facultad/Operativos1/Practico/lab3/so-i-24-chp3-iledesma08/include diff --git a/conanfile.txt b/conanfile.txt new file mode 100644 index 0000000..8a4807d --- /dev/null +++ b/conanfile.txt @@ -0,0 +1,9 @@ +[requires] +unity/2.6.0 + +[generators] +CMakeDeps +CMakeToolchain + +[layout] +cmake_layout \ No newline at end of file diff --git a/include/calloc.h b/include/calloc.h new file mode 100644 index 0000000..2fcc132 --- /dev/null +++ b/include/calloc.h @@ -0,0 +1,26 @@ +/** + * @file calloc.h + * @brief Header file for zero-initialized memory allocation. + * + * This header provides the prototype for the `calloc` function, which allocates + * memory for an array of elements and initializes all bytes to zero. It leverages + * the custom memory allocation system defined in the custom memory allocator. + */ + +#pragma once // Prevents multiple inclusions of this header file during compilation. + +#include "malloc.h" ///< Includes memory allocation functionality for custom allocator. +#include "memory.h" ///< Includes definitions for memory management structures and constants. + +/** + * @brief Allocates memory for an array of elements and initializes all bytes to zero. + * + * This function calculates the total memory required for an array with the given + * number of elements and element size, allocates the memory, and sets all bytes + * in the allocated memory to zero. + * + * @param number Number of elements to allocate memory for. + * @param size Size of each element in bytes. + * @return void* Pointer to the allocated and zero-initialized memory, or NULL if the allocation fails. + */ +void* my_calloc(size_t number, size_t size); diff --git a/include/free.h b/include/free.h new file mode 100644 index 0000000..800cb9b --- /dev/null +++ b/include/free.h @@ -0,0 +1,48 @@ +/** + * @file free.h + * @brief Header file for memory deallocation and validation functions. + * + * This header provides function prototypes for freeing allocated memory, + * validating memory addresses, and merging adjacent free memory blocks. + * These functions are part of the custom memory allocator. + */ + +#pragma once // Prevents multiple inclusions of this header file during compilation. + +#include "mem_logging.h" ///< Provides functionality for logging memory operations. +#include "memory.h" ///< Includes definitions for memory management structures and constants. + +/** + * @brief Merges a free memory block with adjacent free blocks. + * + * This function combines a given free block with its neighboring free blocks, + * if available, to form a larger contiguous free block. The resulting block + * is updated in the memory heap. + * + * @param b Pointer to the memory block to merge. + * @return t_block Pointer to the merged memory block. + */ +t_block fusion(t_block b); + +/** + * @brief Checks if a given memory address is valid. + * + * Validates whether the provided pointer corresponds to a valid memory block + * in the heap managed by the custom memory allocator. + * + * @param p Pointer to the memory address to validate. + * @return int Returns 1 if the address is valid, 0 otherwise. + */ +int valid_addr(void* p); + +/** + * @brief Frees a previously allocated memory block. + * + * Marks the specified memory block as free, merges it with adjacent free blocks + * if possible, and optionally unmaps it from the heap if it is the last block + * and `unmap_flag` is enabled. + * + * @param ptr Pointer to the memory block to be freed. + * @param unmap_flag Flag indicating whether to unmap the memory block if possible. + */ +void my_free(void* ptr, int unmap_flag); diff --git a/include/heap_test.h b/include/heap_test.h new file mode 100644 index 0000000..f981d99 --- /dev/null +++ b/include/heap_test.h @@ -0,0 +1,13 @@ +/** + * @file heap_test.h + * @brief Header file for memory heap testing functions. + * + * This header file provides the prototypes for functions that test the memory + */ + +#include "mem_metrics.h" // Include the header for memory metrics functionality. + +/** + * @brief Test the memory usage function. + */ +void test_check_heap(void); diff --git a/include/malloc.h b/include/malloc.h new file mode 100644 index 0000000..656d439 --- /dev/null +++ b/include/malloc.h @@ -0,0 +1,62 @@ +/** + * @file malloc.h + * @brief Header file for custom memory allocation functions. + * + * This header defines the function prototypes for memory allocation, + * including finding, splitting, and expanding memory blocks, as well + * as allocating a block of memory using the custom allocator. + */ + +#pragma once // Prevents multiple inclusions of this header file during compilation. + +#include "mem_logging.h" ///< Includes functionality for logging memory operations. +#include "memory.h" ///< Includes custom memory management definitions. + +/** + * @brief Finds a free memory block that can fit the requested size. + * + * Searches the heap for a free block of memory that is at least as large + * as the requested size. If a suitable block is found, it is returned. + * Otherwise, NULL is returned. + * + * @param last Pointer to the last block traversed during the search. Updated to point to the final block checked. + * @param size Requested size in bytes. + * @return t_block Pointer to the found memory block, or NULL if no suitable block is available. + */ +t_block find_block(t_block* last, size_t size); + +/** + * @brief Splits a memory block into two if it is larger than the requested size. + * + * Adjusts the size of the current block to match the requested size, and creates + * a new block with the remaining space. The new block is marked as free and linked + * to the current block. + * + * @param b Pointer to the block to split. + * @param s Size of the new block in bytes. + */ +void split_block(t_block b, size_t s); + +/** + * @brief Expands the heap by creating a new memory block. + * + * Extends the heap by allocating a new block of memory and linking it to the + * existing heap structure. If the allocation fails, NULL is returned. + * + * @param last Pointer to the last block in the current heap. + * @param s Size of the new block in bytes. + * @return t_block Pointer to the newly created block, or NULL if allocation fails. + */ +t_block extend_heap(t_block last, size_t s); + +/** + * @brief Allocates a block of memory of the requested size. + * + * Attempts to find a suitable free block in the heap or extends the heap if + * no suitable block is available. Returns a pointer to the allocated memory, + * or NULL if the allocation fails. + * + * @param size Requested size in bytes for the memory block. + * @return void* Pointer to the allocated memory block, or NULL if allocation fails. + */ +void* my_malloc(size_t size); diff --git a/include/mem_logging.h b/include/mem_logging.h new file mode 100644 index 0000000..e157f57 --- /dev/null +++ b/include/mem_logging.h @@ -0,0 +1,114 @@ +/** + * @file mem_logging.h + * @brief Header file for memory operation logging. + * + * This file defines structures, constants, and function prototypes for logging + * memory allocation and deallocation operations in a custom memory management system. + * It tracks memory usage and logs operations such as malloc, calloc, realloc, and free. + */ + +#pragma once // Prevents multiple inclusions of this header file during compilation. + +#include ///< Provides fixed-width integer types for consistency. +#include ///< Includes standard I/O functions for logging operations. +#include ///< Provides memory manipulation functions like memset. +#include ///< Includes memory mapping functions for dynamic memory management. +#include ///< Provides utilities for time-stamping operations. + +/** + * @def GRAY + * ANSI escape code for gray-colored text in terminal output. + */ +#define GRAY "\033[1;30m" + +/** + * @def RED + * ANSI escape code for red-colored text in terminal output. + */ +#define RED "\033[1;31m" + +/** + * @def GREEN + * ANSI escape code for green-colored text in terminal output. + */ +#define GREEN "\033[1;32m" + +/** + * @def YELLOW + * ANSI escape code for yellow-colored text in terminal output. + */ +#define YELLOW "\033[1;33m" + +/** + * @def BLUE + * ANSI escape code for blue-colored text in terminal output. + */ +#define BLUE "\033[1;34m" + +/** + * @def RESET + * ANSI escape code to reset terminal text formatting. + */ +#define RESET "\033[0m" + +/** + * @enum alloc_op + * @brief Enumeration of memory operation types. + * + * Represents the type of memory operation being logged. Includes allocation, + * reallocation, and deallocation operations. + */ +typedef enum +{ + MALLOC, ///< Memory allocated using malloc. + CALLOC, ///< Memory allocated using calloc. + REALLOC, ///< Memory reallocated using realloc. + FREE ///< Memory deallocated using free. +} alloc_op; + +/** + * @brief Represents a log entry for a memory operation. + * + * A linked list structure that stores details of a memory operation, including + * the type of operation, memory block pointer, size, and operation-specific data. + */ +typedef struct log_entry +{ + alloc_op op; ///< Type of the memory operation (e.g., MALLOC, FREE). + void* ptr; ///< Pointer to the memory block involved in the operation. + size_t size; ///< Size of the memory block. + size_t total_allocated; ///< Total memory allocated at the time of the operation. + size_t total_freed; ///< Total memory freed at the time of the operation. + struct log_entry* next; ///< Pointer to the next log entry in the list. + unsigned long op_id; ///< Unique identifier for the memory operation. +} t_log_entry; + +/** + * @brief Logs a memory operation. + * + * Creates a new log entry for a memory operation and adds it to the log list. + * Each entry stores information about the operation type, memory block, size, + * and current memory usage statistics. + * + * @param op The type of memory operation (e.g., MALLOC, FREE). + * @param ptr Pointer to the memory block involved in the operation. + * @param size Size of the memory block in bytes. + * @param op_ctr Pointer to the operation counter, used to assign a unique ID. + */ +void log_mem_operation(alloc_op op, void* ptr, size_t size, unsigned long* op_ctr); + +/** + * @brief Clears all memory operation logs. + * + * Frees all log entries in the log list and resets the log state. This function + * is used to clean up memory and remove all records of logged operations. + */ +void clear_logs(void); + +/** + * @brief Displays the memory operation log. + * + * Prints the memory operation log to the console, showing details of each + * memory allocation and deallocation operation in chronological order. + */ +void show_logs(void); diff --git a/include/mem_metrics.h b/include/mem_metrics.h new file mode 100644 index 0000000..dd02fe8 --- /dev/null +++ b/include/mem_metrics.h @@ -0,0 +1,91 @@ +/** + * @file mem_metrics.h + * @brief Header file for memory allocator performance and fragmentation metrics. + * + * This file provides constants and function prototypes for calculating memory + * fragmentation, measuring allocator efficiency, and managing the state of a custom + * memory allocator. It includes functions for analyzing performance and fragmentation + * for various memory allocation methods. + */ + +#pragma once // Prevents multiple inclusions of this header file during compilation. + +#include "calloc.h" ///< Includes functionality for zero-initialized memory allocation. +#include "free.h" ///< Includes functionality for freeing memory blocks. +#include "malloc.h" ///< Includes functionality for memory allocation. +#include "memory.h" ///< Includes core memory management structures and definitions. +#include "realloc.h" ///< Includes functionality for memory reallocation. +#include ///< Provides standard library functions like `exit`. + +/** + * @def ALLOCATION_SIZE_MAX + * Maximum size for a single memory allocation in performance tests. + */ +#define ALLOCATION_SIZE_MAX 1024 + +/** + * @def ALLOCATION_SIZE_MIN + * Minimum size for a single memory allocation in performance tests. + */ +#define ALLOCATION_SIZE_MIN 8 + +/** + * @def NUM_ALLOCATIONS + * Number of memory allocations to perform during the efficiency test. + */ +#define NUM_ALLOCATIONS 1000 + +/** + * @def TO_PERCENTAGE_MULTIPLIER + * Multiplier used to convert fragmentation values to percentages. + */ +#define TO_PERCENTAGE_MULTIPLIER 100 + +/** + * @def MILLISECONDS_IN_SECOND + * Defines the number of milliseconds in one second, used for time-related calculations. + */ +#define MILLISECONDS_IN_SECOND 1000.0 + +/** + * @def NANOSECONDS_IN_MILLISECOND + * Defines the number of nanoseconds in one millisecond, used for time-related calculations. + */ +#define NANOSECONDS_IN_MILLISECOND 1e6 + +/** + * @brief Calculates memory fragmentation for each allocation method. + * + * Analyzes the memory heap to calculate external fragmentation for each supported + * allocation method (e.g., First Fit, Best Fit, Worst Fit). Stores the calculated + * fragmentation rates in the provided array. + * + * @param fragmentation_rates Array to store the fragmentation percentage for each method. + */ +void calculate_fragmentation_all_methods(double* fragmentation_rates); + +/** + * @brief Retrieves the current time in seconds. + * + * Combines seconds and nanoseconds into a single floating-point value to represent + * the current time in seconds. Used for measuring performance metrics. + * + * @return double Current time in seconds. + */ +double get_time_in_milliseconds(void); + +/** + * @brief Runs efficiency tests for all supported allocation methods. + * + * Measures the time taken for memory allocations and deallocations, as well as + * fragmentation rates, for all available allocation methods (e.g., First Fit, Best Fit, Worst Fit). + */ +void efficiency_test_all_methods(void); + +/** + * @brief Clears all memory blocks and resets the memory allocator. + * + * Frees all allocated blocks, clears fragmentation data, and resets the memory allocator + * state. Ensures that no residual data remains in the heap. + */ +void clear_memory(void); diff --git a/include/memory.h b/include/memory.h new file mode 100644 index 0000000..ed19593 --- /dev/null +++ b/include/memory.h @@ -0,0 +1,175 @@ +/** + * @file memory.h + * @brief Custom memory management library. + * + * This header file defines the structures, constants, and function prototypes + * required for implementing a custom memory allocator. It provides tools for + * dynamic memory allocation, freeing, and diagnostic checks of the heap to ensure + * efficient memory usage. + */ + +#pragma once // Prevents multiple inclusions of this header file during compilation. + +/* Required headers for memory management */ +#include "mem_logging.h" ///< Provides functionality for logging memory operations. +#include ///< Enables thread safety with mutex support. +#include ///< Defines standard types like `size_t`. +#include ///< Provides fixed-width integer types. +#include ///< Includes I/O utilities for debugging. +#include ///< Includes memory manipulation utilities like `memset`. +#include ///< Provides memory mapping functions for dynamic memory allocation. +#include ///< Defines basic data types. +#include ///< Provides POSIX API functions for memory management. + +/** + * @def INVALID_ADDRESS + * Indicates that a memory address is invalid or inaccessible. + */ +#define INVALID_ADDRESS 0 + +/** + * @def ALIGNMENT + * Minimum padding required for block alignment. + */ +#define ALIGNMENT 8 + +/** + * @def align + * Aligns a size value to the next multiple of 8 bytes. + * + * Ensures memory allocations conform to alignment requirements, which + * can improve performance and prevent hardware errors. + * + * @param x The size to align. + * @return The aligned size. + */ +#define align(x) (((((x)-1) >> 3) << 3) + ALIGNMENT) + +/** + * @def BLOCK_MIN_SIZE + * The minimum size of a memory block, including metadata. + */ +#define BLOCK_MIN_SIZE 32 + +/** + * @def PAGESIZE + * Defines the size of a memory page, typically used in mmap allocations. + */ +#define PAGESIZE 4096 + +/** + * @def FIRST_FIT + * Memory allocation strategy: finds the first free block that fits the request. + */ +#define FIRST_FIT 0 + +/** + * @def BEST_FIT + * Memory allocation strategy: finds the smallest block that fits the request. + */ +#define BEST_FIT 1 + +/** + * @def WORST_FIT + * Memory allocation strategy: finds the largest available block. + */ +#define WORST_FIT 2 + +/** + * @def ALLOC_METHODS + * Total number of memory allocation strategies supported. + */ +#define ALLOC_METHODS 3 + +/** + * @def DATA_START + * Offset for the start of the data section in a memory block. + */ +#define DATA_START 1 + +/** + * @def TRUE + * Boolean true value, used for readability in memory block operations. + */ +#define TRUE 1 + +/** + * @def FALSE + * Boolean false value, used for readability in memory block operations. + */ +#define FALSE 0 + +/** + * @brief t_block + * Alias for a pointer to the `s_block` structure, representing a memory block. + */ +typedef struct s_block* t_block; + +/** + * @brief Represents a block of memory managed by the allocator. + * + * This structure contains metadata about a memory block, including its size, + * pointers to adjacent blocks, and allocation status. It is part of a linked + * list that enables the allocator to manage the heap efficiently. + */ +struct s_block +{ + size_t size; /**< Size of the memory block's data area. */ + struct s_block* next; /**< Pointer to the next block in the heap. */ + struct s_block* prev; /**< Pointer to the previous block in the heap. */ + int free; /**< Status flag: 1 if the block is free, 0 if allocated. */ + void* ptr; /**< Pointer to the start of the data section. */ + int alloc_method; /**< Allocation strategy used for this block. */ + char data[DATA_START]; /**< Start of the block's data section. */ +}; + +/** + * @brief Initializes the memory manager. + * + * Sets up the necessary data structures and locks for managing memory allocations. + */ +void memory_manager_init(void); + +/** + * @brief Cleans up resources used by the memory manager. + * + * Releases any locks or resources held by the memory manager, preparing it for shutdown. + */ +void memory_manager_cleanup(void); + +/** + * @brief Retrieves the metadata block for a given data pointer. + * + * Computes the starting address of the metadata structure that corresponds to + * the given data pointer, enabling access to block metadata for allocation or deallocation. + * + * @param p Pointer to a data block allocated by the custom allocator. + * @return t_block Pointer to the metadata block associated with the data pointer. + */ +t_block get_block(void* p); + +/** + * @brief Sets the allocation method for the memory manager. + * + * Configures the allocation strategy used by the memory manager. Supported + * strategies include First Fit, Best Fit, and Worst Fit. + * + * @param m Allocation method to set (FIRST_FIT, BEST_FIT, or WORST_FIT). + */ +void set_method(int m); + +/** + * @brief Performs a diagnostic check of the heap. + * + * Scans the heap for issues such as adjacent free blocks that can be merged or + * invalid block sizes. Outputs diagnostic information for debugging purposes. + */ +void check_heap(void); + +/** + * @brief Displays memory usage statistics. + * + * Prints details about the total allocated and freed memory, as well as the + * current state of the heap. + */ +void memory_usage(void); diff --git a/include/memory_test.h b/include/memory_test.h new file mode 100644 index 0000000..b066810 --- /dev/null +++ b/include/memory_test.h @@ -0,0 +1,13 @@ +/** + * @file memory_test.h + * @brief Header file for memory testing functions. + * + * This header file provides the prototypes for functions that test the memory + */ + +#include "mem_metrics.h" // Include the header for memory metrics functionality. + +/** + * @brief Test the memory usage function. + */ +void test_memory_usage(void); diff --git a/include/realloc.h b/include/realloc.h new file mode 100644 index 0000000..194008d --- /dev/null +++ b/include/realloc.h @@ -0,0 +1,40 @@ +/** + * @file realloc.h + * @brief Header file for memory reallocation functions. + * + * This header defines the prototypes for functions that handle resizing + * memory blocks and copying data between them. These functions are part + * of the custom memory allocator and ensure efficient memory management. + */ + +#pragma once // Prevents multiple inclusions of this header file during compilation. + +#include "free.h" ///< Includes functionality for freeing memory blocks. +#include "malloc.h" ///< Includes functionality for memory allocation. +#include "memory.h" ///< Includes definitions for memory structures and constants. + +/** + * @brief Copies the contents of a source memory block to a destination block. + * + * Transfers the data from the source block to the destination block, ensuring + * that no more data is copied than the size of either block. This function is + * useful during memory reallocation when the block size changes. + * + * @param src Pointer to the source memory block. + * @param dst Pointer to the destination memory block. + */ +void copy_block(t_block src, t_block dst); + +/** + * @brief Resizes a previously allocated memory block. + * + * Changes the size of an allocated memory block, preserving the existing data + * up to the smaller of the old and new sizes. If the block needs to be moved, + * a new block is allocated, and the old block's data is copied. The old block + * is then freed. + * + * @param p Pointer to the memory block to be resized. + * @param size New size in bytes for the memory block. + * @return void* Pointer to the resized memory block, or NULL if reallocation fails. + */ +void* my_realloc(void* p, size_t size); diff --git a/include/test_mem.h b/include/test_mem.h new file mode 100644 index 0000000..2337d00 --- /dev/null +++ b/include/test_mem.h @@ -0,0 +1,31 @@ +#ifndef TEST_COMMANDS_H +#define TEST_COMMANDS_H + +#include + +#include "calloc.h" +#include "free.h" +#include "malloc.h" +#include "realloc.h" + +void setUp(void); + +void tearDown(void); + +void test_my_malloc_allocates_memory(void); + +void test_my_free_frees_memory(void); + +void test_my_calloc_zeroes_memory(void); + +void test_my_realloc_increases_memory(void); + +void test_split_block_creates_new_block(void); + +void test_fusion_merges_adjacent_blocks(void); + +void test_valid_addr_validates_pointer(void); + +void test_extend_heap_adds_memory(void); + +#endif // TEST_COMMANDS_H diff --git a/lib/memory/CMakeLists.txt b/lib/memory/CMakeLists.txt deleted file mode 100644 index f7bf7cc..0000000 --- a/lib/memory/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -cmake_minimum_required(VERSION 3.10) - -# Set the project name -project(memory) - -include_directories(include) - -# Add the library -add_library(memory STATIC - src/memory.c -) - -# Set C++ standard -set_target_properties(memory PROPERTIES - C_STANDARD 17 -) \ No newline at end of file diff --git a/lib/memory/include/memory.h b/lib/memory/include/memory.h deleted file mode 100644 index 26b7a43..0000000 --- a/lib/memory/include/memory.h +++ /dev/null @@ -1,156 +0,0 @@ -/** - * @file memory.h - * @brief Memory management library with custom allocation functions. - * - * Esta biblioteca define funciones de asignación de memoria dinámica - * y gestión de bloques para crear un asignador de memoria personalizado. - */ - -#pragma once - -#include -#include -#include -#include - -/** - * @brief Macro para alinear una cantidad de bytes al siguiente múltiplo de 8. - * - * @param x Cantidad de bytes a alinear. - */ -#define align(x) (((((x)-1) >> 3) << 3) + 8) - -/** Tamaño mínimo de un bloque de memoria. */ -#define BLOCK_SIZE 40 -/** Tamaño de página en memoria. */ -#define PAGESIZE 4096 -/** Política de asignación First Fit. */ -#define FIRST_FIT 0 -/** Política de asignación Best Fit. */ -#define BEST_FIT 1 -/** Tamaño del bloque */ -#define DATA_START 1 - -/** - * @struct s_block - * @brief Estructura para representar un bloque de memoria. - * - * Contiene la información necesaria para gestionar la asignación y - * liberación de un bloque de memoria. - */ -struct s_block { - size_t size; /**< Tamaño del bloque de datos. */ - struct s_block *next; /**< Puntero al siguiente bloque en la lista enlazada. */ - struct s_block *prev; /**< Puntero al bloque anterior en la lista enlazada. */ - int free; /**< Indicador de si el bloque está libre (1) o ocupado (0). */ - void *ptr; /**< Puntero a la dirección de los datos almacenados. */ - char data[DATA_START]; /**< Área donde comienzan los datos del bloque. */ -}; - -/** Tipo de puntero para un bloque de memoria. */ -typedef struct s_block *t_block; - -/** - * @brief Obtiene el bloque que contiene una dirección de memoria dada. - * - * @param p Puntero a la dirección de datos. - * @return t_block Puntero al bloque de memoria correspondiente. - */ -t_block get_block(void *p); - -/** - * @brief Verifica si una dirección de memoria es válida. - * - * @param p Dirección de memoria a verificar. - * @return int Retorna 1 si la dirección es válida, 0 en caso contrario. - */ -int valid_addr(void *p); - -/** - * @brief Encuentra un bloque libre que tenga al menos el tamaño solicitado. - * - * @param last Puntero al último bloque. - * @param size Tamaño solicitado. - * @return t_block Puntero al bloque encontrado, o NULL si no se encuentra ninguno. - */ -t_block find_block(t_block *last, size_t size); - -/** - * @brief Expande el heap para crear un nuevo bloque de memoria. - * - * @param last Último bloque del heap. - * @param s Tamaño del nuevo bloque. - * @return t_block Puntero al nuevo bloque creado. - */ -t_block extend_heap(t_block last, size_t s); - -/** - * @brief Divide un bloque de memoria en dos, si el tamaño solicitado es menor que el bloque disponible. - * - * @param b Bloque a dividir. - * @param s Tamaño del nuevo bloque. - */ -void split_block(t_block b, size_t s); - -/** - * @brief Fusiona un bloque libre con su siguiente bloque si también está libre. - * - * @param b Bloque a fusionar. - * @return t_block Puntero al bloque fusionado. - */ -t_block fusion(t_block b); - -/** - * @brief Copia el contenido de un bloque de origen a un bloque de destino. - * - * @param src Bloque de origen. - * @param dst Bloque de destino. - */ -void copy_block(t_block src, t_block dst); - -/** - * @brief Asigna un bloque de memoria del tamaño solicitado. - * - * @param size Tamaño en bytes del bloque a asignar. - * @return void* Puntero al área de datos asignada. - */ -void *malloc(size_t size); - -/** - * @brief Libera un bloque de memoria previamente asignado. - * - * @param p Puntero al área de datos a liberar. - */ -void free(void *p); - -/** - * @brief Asigna un bloque de memoria para un número de elementos, inicializándolo a cero. - * - * @param number Número de elementos. - * @param size Tamaño de cada elemento. - * @return void* Puntero al área de datos asignada e inicializada. - */ -void *calloc(size_t number, size_t size); - -/** - * @brief Cambia el tamaño de un bloque de memoria previamente asignado. - * - * @param p Puntero al área de datos a redimensionar. - * @param size Nuevo tamaño en bytes. - * @return void* Puntero al área de datos redimensionada. - */ -void *realloc(void *p, size_t size); - -/** - * @brief Verifica el estado del heap y detecta bloques libres consecutivos. - * - * @param data Información adicional para la verificación. - */ -void check_heap(void *data); - -/** - * @brief Configura el modo de asignación de memoria (First Fit o Best Fit). - * - * @param mode Modo de asignación (0 para First Fit, 1 para Best Fit). - */ -void malloc_control(int mode); \ No newline at end of file diff --git a/lib/memory/src/memory.c b/lib/memory/src/memory.c deleted file mode 100644 index 4e976cc..0000000 --- a/lib/memory/src/memory.c +++ /dev/null @@ -1,270 +0,0 @@ -#include -#include - -typedef struct s_block *t_block; -void *base = NULL; -int method = 0; - -t_block find_block(t_block *last, size_t size){ - t_block b = base; - - if (method == FIRST_FIT){ - while (b && !(b->free && b->size >= size)){ - *last = b; - b = b->next; - } - - return (b); - } else { - size_t dif = PAGESIZE; - t_block best = NULL; - - while (b){ - if (b->free){ - if (b->size == size){ - return b; - } - if (b->size > size && (b->size - size) < dif){ - dif = b->size - size; - best = b; - } - } - *last = b; - b = b->next; - } - return best; - } -} - -void split_block(t_block b, size_t s){ - if (b->size <= s + BLOCK_SIZE){ - return; - } - - t_block new; - new = (t_block)(b->data + s); - new->size = b->size - s - BLOCK_SIZE; - new->next = b->next; - new->free = 1; - b->size = s; - b->next = new; -} - -void copy_block(t_block src, t_block dst){ - int *sdata, *ddata; - size_t i; - sdata = src->ptr; - ddata = dst->ptr; - - for (i = 0; (i * 4) < src->size && (i * 4) < dst->size; i++) - ddata[i] = sdata[i]; -} - -t_block get_block(void *p){ - char *tmp; - tmp = p; - - if (tmp >= (char *)base + BLOCK_SIZE){ - tmp -= BLOCK_SIZE; - } - return (t_block)(tmp); -} - -int valid_addr(void *p){ - if (base){ - if (p > base && p < sbrk(0)){ - t_block b = get_block(p); - return b && (p == b->ptr); - } - } - return (0); -} - -t_block fusion(t_block b){ - if (b->next && b->next->free){ - b->size += BLOCK_SIZE + b->next->size; - b->next = b->next->next; - - if (b->next) - b->next->prev = b; - } - return b; -} - -t_block extend_heap(t_block last, size_t s){ - t_block b; - b = mmap(0, s, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - - if (b == MAP_FAILED){ - return NULL; - } - b->size = s; - b->next = NULL; - b->prev = last; - b->ptr = b->data; - - if (last) - last->next = b; - - b->free = 0; - return b; -} - -void get_method(int m){ - method = m; -} - -void set_method(int m){ - method = m; -} - -void malloc_control(int m){ - if (m == 0) { - set_method(0); - } else if (m == 1) { - set_method(1); - } else { - printf("Error: invalid method\n"); - } -} - -void *malloc(size_t size){ - t_block b, last; - size_t s; - s = align(size); - - if (base){ - last = base; - b = find_block(&last, s); - if (b) { - if ((b->size - s) >= (BLOCK_SIZE + 4)) - split_block(b, s); - b->free = 0; - } else { - b = extend_heap(last, s); - if (!b) - return (NULL); - } - } else { - b = extend_heap(NULL, s); - if (!b) - return (NULL); - base = b; - } - return (b->data); -} - -void free(void *ptr){ - t_block b; - - if (valid_addr(ptr)){ - b = get_block(ptr); - b->free = 1; - - if (b->next && b->next->free) - fusion(b); - if (b->prev && b->prev->free) - fusion(b->prev); - else{ - if (b->next) - b->next->prev = b; - if (b->prev) - b->prev->next = b; - else - base = b; - b->free = 1; - b->prev = NULL; - } - } -} - -void *calloc(size_t number, size_t size){ - size_t *new; - size_t s4, i; - - if (!number || !size){ - return (NULL); - } - new = malloc(number * size); - if (new){ - s4 = align(number * size) << 2; - for (i = 0; i < s4; i++) - new[i] = 0; - } - return (new); -} - -void *realloc(void *ptr, size_t size){ - size_t s; - t_block b, new; - void *newp; - - if (!ptr) - return (malloc(size)); - - if (valid_addr(ptr)){ - s = align(size); - b = get_block(ptr); - - if (b->size >= s){ - if (b->size - s >= (BLOCK_SIZE + 4)) - split_block(b, s); - } else { - if (b->next && b->next->free && (b->size + BLOCK_SIZE + b->next->size) >= s){ - fusion(b); - if (b->size - s >= (BLOCK_SIZE + 4)) - split_block(b, s); - } else { - newp = malloc(s); - if (!newp) - return (NULL); - new = get_block(newp); - copy_block(b, new); - free(ptr); - return (newp); - } - } - return (ptr); - } - return (NULL); -} - -void check_heap(void *data){ - if (data == NULL){ - printf("Data is NULL\n"); - return; - } - - t_block block = get_block(data); - - if (block == NULL){ - printf("Block is NULL\n"); - return; - } - - printf("\033[1;33mHeap check\033[0m\n"); - printf("Size: %zu\n", block->size); - - if (block->next != NULL) { - printf("Next block: %p\n", (void *)(block->next)); - } else { - printf("Next block: NULL\n"); - } - - if (block->prev != NULL){ - printf("Prev block: %p\n", (void *)(block->prev)); - } else { - printf("Prev block: NULL\n"); - } - - printf("Free: %d\n", block->free); - - if (block->ptr != NULL){ - printf("Beginning data address: %p\n", block->ptr); - printf("Last data address: %p\n", (void *)((char *)(block->ptr) + block->size)); - } else { - printf("Data address: NULL\n"); - } - - printf("Heap address: %p\n", sbrk(0)); -} \ No newline at end of file diff --git a/src/calloc.c b/src/calloc.c new file mode 100644 index 0000000..ee90735 --- /dev/null +++ b/src/calloc.c @@ -0,0 +1,39 @@ +#include "calloc.h" + +extern pthread_mutex_t memory_mutex; // Mutex for thread-safe memory operations. +unsigned long calloc_ctr = 0; // Counter to track the number of calloc operations performed. + +void* my_calloc(size_t number, size_t size) +{ + pthread_mutex_lock(&memory_mutex); // Lock the mutex to ensure thread safety. + + // Calculate the total size of memory required. + size_t total_size = number * size; + if (total_size == 0) // If the calculated size is zero, return NULL. + { + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before returning. + return NULL; + } + + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before my_malloc starts. + // Allocate the memory using malloc, leveraging the existing allocation logic. + void* ptr = my_malloc(total_size); + if (ptr == NULL) // Check if the allocation was successful. + { + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before returning. + return NULL; // Return NULL if allocation failed. + } + + // Initialize the allocated memory to zero byte by byte. + char* char_ptr = (char*)ptr; // Cast the pointer to a character pointer for byte-wise access. + for (size_t i = 0; i < total_size; i++) + { + char_ptr[i] = 0; // Set each byte to zero. + } + + // Log the calloc operation for tracking and debugging purposes. + log_mem_operation(CALLOC, ptr, total_size, &calloc_ctr); + + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex after completing the operation. + return ptr; // Return the pointer to the zero-initialized memory. +} diff --git a/src/free.c b/src/free.c new file mode 100644 index 0000000..40af4f1 --- /dev/null +++ b/src/free.c @@ -0,0 +1,122 @@ +#include "free.h" // Include header for free-related functions and definitions. + +extern void* base; // Pointer to the start of the heap. +extern pthread_mutex_t memory_mutex; // Mutex for thread-safe memory operations. +extern int enable_unmapping; // Flag to enable or disable unmapping of memory blocks. +extern size_t total_freed_memory; // Tracks the total memory freed by the allocator. +unsigned long free_ctr = 0; // Counter for the number of free operations performed. + +t_block fusion(t_block b) +{ + // Merge with subsequent free blocks. + while (b->next && b->next->free) + { + t_block next_block = b->next; // Get the next block. + b->size += BLOCK_MIN_SIZE + next_block->size; // Increase the current block size. + b->next = next_block->next; // Update the link to skip the merged block. + if (b->next) + b->next->prev = b; // Update the previous pointer of the next block. + } + + // Merge with previous free blocks. + while (b->prev && b->prev->free) + { + t_block prev_block = b->prev; // Get the previous block. + prev_block->size += BLOCK_MIN_SIZE + b->size; // Increase the previous block size. + prev_block->next = b->next; // Update the link to skip the merged block. + if (b->next) + b->next->prev = prev_block; // Update the previous pointer of the next block. + b = prev_block; // Move to the previous block as the new reference. + } + return b; // Return the fused block. +} + +int valid_addr(void* p) +{ + if (p == NULL || base == NULL) // Check if the pointer or heap base is null. + { + return INVALID_ADDRESS; // Invalid address. + } + t_block b = get_block(p); // Retrieve the block metadata for the pointer. + t_block current = base; // Start checking from the base of the heap. + + // Traverse the heap to find the block. + while (current) + { + if (current == b) // If the block matches the metadata. + { + return (current->ptr == p); // Validate that the pointer matches the block's data pointer. + } + current = current->next; // Move to the next block. + } + return INVALID_ADDRESS; // Return invalid if the block is not found. +} + +void my_free(void* ptr, int unmap_flag) +{ + pthread_mutex_lock(&memory_mutex); // Lock the mutex for thread safety. + + if (ptr == NULL) // If the pointer is null, there's nothing to free. + { + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before returning. + return; + } + + t_block b; + + if (valid_addr(ptr)) // Check if the pointer is valid. + { + b = get_block(ptr); // Retrieve the block metadata. + b->free = TRUE; // Mark the block as free. + + // Log the free operation. + log_mem_operation(FREE, ptr, b->size, &free_ctr); + + // Update the total freed memory. + total_freed_memory += b->size; + + // Merge with adjacent free blocks. + b = fusion(b); + + // If unmap_flag is set and the block is at the end of the heap. + if (unmap_flag && b->next == NULL) + { + if (b->prev) // If there is a previous block, update its next pointer. + { + b->prev->next = NULL; + } + else + { + base = NULL; // If this is the only block, reset the heap base. + } + + // Check if the block should be unmapped. + if (enable_unmapping && b->free) + { + size_t total_size = b->size + BLOCK_MIN_SIZE; // Calculate the total size of the block. + printf("Unmapping block at %p with size: %zu\n", (void*)b, total_size); + + // Attempt to unmap the block from memory. + if (munmap(b, total_size) == -1) + { + perror("munmap"); // Print an error if unmapping fails. + } + else + { + // Reset the base pointer if it was pointing to the unmapped block. + if (base == b) + { + base = NULL; + } + } + } + } + } + else + { + // Print an error message for an invalid pointer. + printf("Failed attempt to free - %sInvalid Address%s: %p\n", RED, RESET, ptr); + } + + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex after completing the operation. +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ad46931 --- /dev/null +++ b/src/main.c @@ -0,0 +1,15 @@ +#include "heap_test.h" // Include the header for heap test functionality. +#include "memory_test.h" // Include the header for memory test functionality. + +int main(void) +{ + printf("\n%s====== Running Empty CHECK_HEAP Test ======%s\n", BLUE, RESET); + check_heap(); // Test empty heap + test_memory_usage(); + test_check_heap(); + printf("\n%s--------------------------------%s\n", BLUE, RESET); + show_logs(); + printf("\n%s====== Running EFFICIENCY and FRAGMENTATION Tests ======%s\n\n", BLUE, RESET); + efficiency_test_all_methods(); + return 0; +} diff --git a/src/malloc.c b/src/malloc.c new file mode 100644 index 0000000..38d36ec --- /dev/null +++ b/src/malloc.c @@ -0,0 +1,174 @@ +#include "malloc.h" // Include the header file for memory allocation functions. + +extern void* base; // Global pointer to the beginning of the heap. +extern int method; // Global variable indicating the memory allocation strategy. +extern pthread_mutex_t memory_mutex; // Mutex for thread-safe memory operations. + +extern size_t total_allocated_memory; // Tracks the total memory allocated by the custom allocator. + +unsigned long malloc_ctr = 0; // Counter for the number of malloc operations performed. + +t_block find_block(t_block* last, size_t size) +{ + t_block b = base; // Start searching from the base of the heap. + + if (method == FIRST_FIT) // First Fit strategy: find the first block that fits. + { + while (b && !(b->free && b->size >= size)) // Traverse until a suitable block is found. + { + *last = b; // Keep track of the last block traversed. + b = b->next; // Move to the next block. + } + return b; // Return the found block or NULL if none is suitable. + } + else if (method == BEST_FIT) // Best Fit strategy: find the smallest block that fits. + { + size_t diff = (size_t)-1; // Initialize difference to the largest possible value. + t_block best = NULL; // Pointer to the best-fit block. + + while (b) + { + if (b->free && b->size >= size) // Check if the block is free and fits the requested size. + { + if (b->size - size < diff) // Update best fit if the current block is a closer match. + { + diff = b->size - size; + best = b; + } + if (diff == 0) // If the block perfectly fits, stop searching. + { + break; + } + } + *last = b; + b = b->next; + } + return best; // Return the best-fit block or NULL. + } + else if (method == WORST_FIT) // Worst Fit strategy: find the largest block that fits. + { + size_t max_size = 0; // Initialize the maximum size to 0. + t_block worst = NULL; // Pointer to the worst-fit block. + + while (b) + { + if (b->free && b->size >= size) // Check if the block is free and fits the requested size. + { + if (b->size > max_size) // Update worst fit if the current block is larger. + { + max_size = b->size; + worst = b; + } + } + *last = b; + b = b->next; + } + return worst; // Return the worst-fit block or NULL. + } + else + { + // Default to First Fit if the method is invalid or unrecognized. + while (b && !(b->free && b->size >= size)) + { + *last = b; + b = b->next; + } + return b; // Return the found block or NULL. + } +} + +void split_block(t_block b, size_t s) +{ + if (b->size > s + BLOCK_MIN_SIZE) // Ensure there is enough space to split. + { + t_block new_block = (t_block)(b->data + s); // Calculate the address of the new block. + new_block->size = b->size - s - BLOCK_MIN_SIZE; // Adjust the size of the new block. + new_block->next = b->next; // Link the new block to the next block. + new_block->prev = b; // Set the previous block of the new block. + new_block->free = TRUE; // Mark the new block as free. + new_block->ptr = new_block->data; // Initialize the data pointer for the new block. + + b->size = s; // Update the size of the current block. + b->next = new_block; // Link the current block to the new block. + if (new_block->next) + { + new_block->next->prev = new_block; // Update the previous link of the next block. + } + } +} + +t_block extend_heap(t_block last, size_t s) +{ + t_block b = + mmap(0, s + BLOCK_MIN_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // Allocate memory. + + if (b == MAP_FAILED) // Check if memory allocation failed. + { + return NULL; + } + b->size = s; // Set the size of the new block. + b->next = NULL; // New block is at the end, so no next block. + b->prev = last; // Link the new block to the previous block. + b->ptr = b->data; // Initialize the data pointer. + b->free = FALSE; // Mark the block as allocated. + b->alloc_method = method; // Record the allocation method. + + if (last) + last->next = b; // Link the previous block to the new block. + + return b; // Return the new block. +} + +void* my_malloc(size_t size) +{ + pthread_mutex_lock(&memory_mutex); // Lock the mutex for thread safety. + + t_block b, last; // Variables for the current and last blocks. + size_t s; // Aligned size of the requested memory. + struct timespec start; // Variables for timing the operation. + + clock_gettime(CLOCK_MONOTONIC, &start); // Start timing. + + s = align(size); // Align the requested size. + + if (base) // Check if the heap has been initialized. + { + last = base; + b = find_block(&last, s); // Try to find a suitable block. + if (b) + { + if ((b->size - s) >= (BLOCK_MIN_SIZE + ALIGNMENT)) // Check if the block can be split. + { + split_block(b, s); // Split the block if necessary. + } + b->free = FALSE; // Mark the block as allocated. + } + else + { + b = extend_heap(last, s); // Extend the heap if no suitable block was found. + if (!b) + { + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before returning. + return NULL; // Return NULL if the heap extension failed. + } + } + } + else + { + b = extend_heap(NULL, s); // Initialize the heap if it is empty. + if (!b) + { + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before returning. + return NULL; // Return NULL if the heap extension failed. + } + base = b; // Set the base of the heap. + } + + b->alloc_method = method; // Record the allocation method for the block. + total_allocated_memory += b->size; // Update the total allocated memory. + void* ptr = b->data; // Get the pointer to the allocated data. + log_mem_operation(MALLOC, ptr, size, &malloc_ctr); // Log the memory allocation operation. + + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex after completing the allocation. + return ptr; // Return the pointer to the allocated memory. +} diff --git a/src/mem_logging.c b/src/mem_logging.c new file mode 100644 index 0000000..09fb88b --- /dev/null +++ b/src/mem_logging.c @@ -0,0 +1,90 @@ +#include "mem_logging.h" // Include the header file for memory logging functionality. + +extern size_t total_allocated_memory; // Tracks the total amount of memory allocated. +extern size_t total_freed_memory; // Tracks the total amount of memory freed. + +t_log_entry* log_head = NULL; // Pointer to the head of the linked list for log entries. + +void log_mem_operation(alloc_op op, void* ptr, size_t size, unsigned long* op_ctr) +{ + // Allocate memory for a new log entry using mmap. + t_log_entry* entry = mmap(NULL, sizeof(t_log_entry), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (entry == MAP_FAILED) // Check if the memory allocation failed. + { + // Print an error message to stderr if the allocation failed. + fprintf(stderr, "Failed to allocate memory for log entry.\n"); + return; // Exit the function without logging. + } + + // Populate the log entry with details about the memory operation. + entry->op = op; // Type of memory operation (e.g., MALLOC, FREE). + entry->ptr = ptr; // Pointer to the memory block involved in the operation. + entry->size = size; // Size of the memory block. + entry->total_allocated = total_allocated_memory; // Total allocated memory at the time of the operation. + entry->total_freed = total_freed_memory; // Total freed memory at the time of the operation. + entry->next = log_head; // Link the new entry to the current head of the log list. + entry->op_id = *op_ctr; // Assign a unique operation ID to the log entry. + (*op_ctr)++; // Increment the operation counter. + log_head = entry; // Update the head of the log list to the new entry. +} + +void clear_logs(void) +{ + t_log_entry* entry = log_head; // Start at the head of the log list. + + // Traverse the log list and unmap each entry. + while (entry) + { + t_log_entry* next = entry->next; // Save the pointer to the next log entry. + munmap(entry, sizeof(t_log_entry)); // Unmap the current log entry from memory. + entry = next; // Move to the next entry in the list. + } + + log_head = NULL; // Reset the head of the log list to indicate it is empty. +} + +void show_logs(void) +{ + // Print the memory operation log. + printf(YELLOW "\nOPERATIONS LOG:\n\n" RESET); + + t_log_entry* entry = log_head; // Start at the head of the log list. + if (entry == NULL) // If the log is empty, print a message and return. + { + printf("\tNo memory operations logged.\n"); + return; + } + + // Reverse the log list to print in chronological order. + t_log_entry* reversed_log = NULL; + while (entry) + { + t_log_entry* next = entry->next; + entry->next = reversed_log; + reversed_log = entry; + entry = next; + } + + entry = reversed_log; // Traverse the reversed list. + while (entry) + { + // Print each log entry with appropriate formatting based on operation type. + switch (entry->op) + { + case MALLOC: + printf("%smalloc [%lu]%s of %zu bytes at %p\n", RED, entry->op_id, RESET, entry->size, entry->ptr); + break; + case CALLOC: + printf("%scalloc [%lu]%s of %zu bytes at %p\n", RED, entry->op_id, RESET, entry->size, entry->ptr); + break; + case REALLOC: + printf("%srealloc [%lu]%s to %zu bytes at %p\n", RED, entry->op_id, RESET, entry->size, entry->ptr); + break; + case FREE: + printf("%sfree [%lu]%s of %zu bytes from %p\n", GREEN, entry->op_id, RESET, entry->size, entry->ptr); + break; + } + entry = entry->next; // Move to the next log entry. + } +} diff --git a/src/mem_metrics.c b/src/mem_metrics.c new file mode 100644 index 0000000..9e59360 --- /dev/null +++ b/src/mem_metrics.c @@ -0,0 +1,208 @@ +#include "mem_metrics.h" // Include the header for memory metrics functionality. + +// External variables from memory.c for accessing heap state and memory statistics. +extern void* base; // Pointer to the start of the heap. +extern int method; // Memory allocation method. +extern size_t total_allocated_memory; // Total memory allocated during program execution. +extern size_t total_freed_memory; // Total memory freed during program execution. +extern int enable_unmapping; // Flag to enable or disable unmapping of memory blocks. + +/** + * @brief Calculates fragmentation for all allocation methods. + */ +void calculate_fragmentation_all_methods(double* fragmentation_rates) +{ + if (base == NULL) // If the heap is empty, there's nothing to calculate. + { + printf("No blocks to calculate fragmentation.\n"); + for (int m = 0; m < ALLOC_METHODS; m++) // Set fragmentation rates to 0 for all methods. + { + fragmentation_rates[m] = 0.0; // Initialize fragmentation as 0%. + } + return; + } + + size_t total_free[ALLOC_METHODS] = {0}; // Total free memory for each method. + size_t largest_free_block[ALLOC_METHODS] = {0}; // Largest free block for each method. + size_t total_allocated[ALLOC_METHODS] = {0}; // Total allocated memory for each method. + + t_block current_block = base; // Start from the base of the heap. + while (current_block) + { + int m = current_block->alloc_method; // Get the allocation method for the block. + if (m < 0 || m >= ALLOC_METHODS) // Skip invalid methods (Free blocks created on purpose for example). + { + current_block = current_block->next; + continue; // Move to the next block. + } + + if (current_block->free) // If the block is free, update free memory stats. + { + total_free[m] += current_block->size; // Add the block size to the free memory total. + if (current_block->size > largest_free_block[m]) // Update the largest free block. + { + largest_free_block[m] = current_block->size; + } + } + else // If the block is allocated, update allocated memory stats. + { + total_allocated[m] += current_block->size; + } + current_block = current_block->next; // Move to the next block. + } + + for (int m = 0; m < ALLOC_METHODS; m++) + { + size_t total_memory = total_free[m] + total_allocated[m]; // Total memory for the method. + double external_fragmentation = 0.0; + + if (total_free[m] > 0) // Calculate fragmentation if there's free memory. + { + external_fragmentation = + ((double)(total_free[m] - largest_free_block[m]) / (double)total_free[m]) * TO_PERCENTAGE_MULTIPLIER; + } + + fragmentation_rates[m] = external_fragmentation; // Store the calculated fragmentation rate. + + // Print fragmentation details for the current allocation method. + const char* method_name = (m == FIRST_FIT) ? "First Fit" + : (m == BEST_FIT) ? "Best Fit" + : (m == WORST_FIT) ? "Worst Fit" + : "Unknown"; + + printf("%sFragmentation Report for %s%s:\n", YELLOW, method_name, RESET); + printf("\tTotal memory: %zu bytes\n", total_memory); + printf("\tTotal allocated memory: %zu bytes\n", total_allocated[m]); + printf("\tTotal free memory: %zu bytes\n", total_free[m]); + printf("\tLargest free block: %zu bytes\n", largest_free_block[m]); + printf("\tExternal fragmentation: %.2f%%\n\n", external_fragmentation); + } +} + +void efficiency_test_all_methods(void) +{ + double fragmentation_rates[ALLOC_METHODS]; // Array to store fragmentation rates for each method. + + void* pointers[ALLOC_METHODS][NUM_ALLOCATIONS]; // Array to hold allocated blocks for all methods. + size_t sizes[ALLOC_METHODS][NUM_ALLOCATIONS]; // Array to hold sizes for all methods. + + double allocation_times[ALLOC_METHODS] = {0}; // Array to store allocation times for each method. + double deallocation_times[ALLOC_METHODS] = {0}; // Array to store deallocation times for each method. + + enable_unmapping = FALSE; // Disable unmapping during allocation. + + srand(time(NULL)); // Seed the random number generator. + + // Generate random sizes for all methods. + for (int m = FIRST_FIT; m < ALLOC_METHODS; m++) + { + for (int i = 0; i < NUM_ALLOCATIONS; i++) + { + sizes[m][i] = rand() % ALLOCATION_SIZE_MAX + ALLOCATION_SIZE_MIN; + } + } + + // Interleave allocations for all methods and measure times. + for (int m = FIRST_FIT; m < ALLOC_METHODS; m++) + { + set_method(m); // Set the allocation method. + + double start_time = get_time_in_milliseconds(); // Start timing allocation. + + for (int i = 0; i < NUM_ALLOCATIONS; i++) + { + pointers[m][i] = my_malloc(sizes[m][i]); // Allocate memory for this method. + if (!pointers[m][i]) + { + fprintf(stderr, "Allocation failed for method %d at iteration %d.\n", m, i); + exit(1); + } + + // Periodically free some blocks to simulate fragmentation. + if (i % 5 == 0 && pointers[m][i]) + { + my_free(pointers[m][i], FALSE); // Free every fifth block. + pointers[m][i] = NULL; + } + } + + allocation_times[m] = get_time_in_milliseconds() - start_time; // End timing allocation. + } + + // Calculate fragmentation after interleaved allocations and frees. + calculate_fragmentation_all_methods(fragmentation_rates); + + // Deallocate all remaining memory blocks and measure times. + for (int m = FIRST_FIT; m < ALLOC_METHODS; m++) + { + double start_time = get_time_in_milliseconds(); // Start timing deallocation. + + for (int i = 0; i < NUM_ALLOCATIONS; i++) + { + if (pointers[m][i]) + { + my_free(pointers[m][i], TRUE); // Free the memory block. + pointers[m][i] = NULL; // Nullify the pointer to prevent double free. + } + } + + deallocation_times[m] = get_time_in_milliseconds() - start_time; // End timing deallocation. + } + + // Print allocation and deallocation times for each method. + for (int m = FIRST_FIT; m < ALLOC_METHODS; m++) + { + const char* method_name = (m == FIRST_FIT) ? "First Fit" + : (m == BEST_FIT) ? "Best Fit" + : (m == WORST_FIT) ? "Worst Fit" + : "Unknown"; + printf(BLUE "Efficiency Test Results for %s:\n" RESET, method_name); + printf("\tAllocation time: %.6f [ms]\n", allocation_times[m]); + printf("\tDeallocation time: %.6f [ms]\n\n", deallocation_times[m]); + } + + enable_unmapping = TRUE; // Re-enable unmapping after the test. +} + +double get_time_in_milliseconds(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); // Obtén el tiempo actual. + return (ts.tv_sec * MILLISECONDS_IN_SECOND) + + (ts.tv_nsec / NANOSECONDS_IN_MILLISECOND); // Convierte a milisegundos. +} + +void clear_memory(void) +{ + enable_unmapping = TRUE; // Enable unmapping to release memory. + + if (base == NULL) // If the heap is empty, nothing to clear. + { + printf("No blocks to clear. The allocator is empty.\n"); + return; + } + + t_block current_block = base; // Start at the base of the heap. + while (current_block) // Traverse the heap to unmap all blocks. + { + t_block next = current_block->next; // Save the next block pointer. + size_t total_size = current_block->size + BLOCK_MIN_SIZE; + if (munmap(current_block, total_size) == INVALID_ADDRESS) // Attempt to unmap the current block. + { + perror("munmap failed"); + } + else + { + printf("Unmapped block at %p, size: %zu\n", (void*)current_block, total_size); + } + current_block = next; // Move to the next block. + } + + // Reset global variables to their initial state. + base = NULL; + total_allocated_memory = 0; + total_freed_memory = 0; + + clear_logs(); // Clear the memory operation logs. + printf("Cleared Heap and Logs\n"); +} diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..7e02ef3 --- /dev/null +++ b/src/memory.c @@ -0,0 +1,92 @@ +#include "memory.h" // Includes the custom memory management library header. + +extern t_log_entry* log_head; // External variable pointing to the head of the memory operation log list. + +void* base = NULL; // Pointer to the beginning of the memory heap. +int method = FIRST_FIT; // Memory allocation method; default is First Fit (0). +int enable_unmapping = FALSE; // Flag to enable or disable unmapping of freed memory blocks. +typedef struct s_block* t_block; // Alias for a pointer to the `s_block` structure. +pthread_mutex_t memory_mutex = PTHREAD_MUTEX_INITIALIZER; // Mutex for synchronizing memory operations across threads. + +// Memory usage statistics. +size_t total_allocated_memory = 0; // Tracks the total allocated memory. +size_t total_freed_memory = 0; // Tracks the total freed memory. + +void memory_manager_init() +{ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); // Initialize mutex attributes. + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // Set mutex to recursive type. + pthread_mutex_init(&memory_mutex, &attr); // Initialize the mutex with the configured attributes. + pthread_mutexattr_destroy(&attr); // Destroy the mutex attributes as they are no longer needed. +} + +void memory_manager_cleanup() +{ + pthread_mutex_destroy(&memory_mutex); // Destroy the mutex to release resources. +} + +t_block get_block(void* p) +{ + if (p == NULL) // If the pointer is null, return NULL. + { + return NULL; + } + // Calculate the start of the block structure using the offset of the `data` field. + return (t_block)((char*)p - offsetof(struct s_block, data)); +} + +void set_method(int m) +{ + if (m == FIRST_FIT || m == BEST_FIT || m == WORST_FIT) // Check if the method is valid. + { + method = m; // Set the allocation method. + } + else + { + printf("Error: invalid method\n"); // Print an error if the method is invalid. + } +} + +void check_heap(void) +{ + if (base == NULL) // If the heap is empty, print a message and return. + { + printf("Heap is empty.\n"); + return; + } + + t_block current = base; // Start at the base of the heap. + while (current != NULL) // Iterate through all blocks in the heap. + { + // Check for adjacent free blocks that are not fused together. + if (current->free && current->next && current->next->free) + { + printf("%sWarning:%s Free blocks at %p and %p are adjacent but not fused.\n", RED, RESET, (void*)current, + (void*)current->next); + } + // Check if the block size is invalid. + if (current->size <= 0) + { + printf("%sWarning:%sBlock at %p has invalid size %zu.\n", RED, RESET, (void*)current, current->size); + } + // Print details of the current block. + printf("%s--------------------------------%s\n", YELLOW, RESET); + printf("%sBlock at %p%s\n", GRAY, (void*)current, RESET); + printf("\tSize: %zu\n", current->size); + printf("\tFree: %d\n", current->free); + printf("\tNext block: %p\n", (void*)(current->next)); + printf("\tPrevious block: %p\n", (void*)(current->prev)); + printf("\tData address: %p\n", current->ptr); + current = current->next; // Move to the next block. + } +} + +void memory_usage(void) +{ + printf(YELLOW "\nMemory Usage:\n" RESET); + printf("\tTotal allocated memory (since start): %zu bytes\n", total_allocated_memory); + printf("\tTotal freed memory (since start): %zu bytes\n", total_freed_memory); + size_t current_allocated = total_allocated_memory - total_freed_memory; // Calculate currently allocated memory. + printf("\tCurrently allocated memory: %zu bytes\n", current_allocated); +} diff --git a/src/realloc.c b/src/realloc.c new file mode 100644 index 0000000..54c4d1a --- /dev/null +++ b/src/realloc.c @@ -0,0 +1,65 @@ +#include "realloc.h" // Include header file for realloc-related functions and definitions. + +extern pthread_mutex_t memory_mutex; // Mutex for thread-safe memory operations. +unsigned long realloc_ctr = 0; // Counter to track the number of realloc operations performed. + +void copy_block(t_block src, t_block dst) +{ + size_t *sdata, *ddata; // Pointers for source and destination data. + size_t i; // Loop index for copying data. + + sdata = src->ptr; // Get the pointer to the source data. + ddata = dst->ptr; // Get the pointer to the destination data. + + // Copy data byte by byte while ensuring it fits within both blocks' sizes. + for (i = 0; i * sizeof(size_t) < src->size && i * sizeof(size_t) < dst->size; i++) + ddata[i] = sdata[i]; +} + +void* my_realloc(void* ptr, size_t size) +{ + pthread_mutex_lock(&memory_mutex); // Lock the mutex for thread-safe operation. + + if (ptr == NULL) // If the pointer is NULL, realloc behaves like malloc. + { + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before returning. + return my_malloc(size); // Allocate a new block of the requested size. + } + + t_block b = get_block(ptr); // Retrieve the block metadata for the pointer. + + // Check if the pointer is valid and belongs to the heap. + if (!valid_addr(ptr) || b == NULL) + { + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before returning. + return NULL; // Return NULL for invalid address or metadata issues. + } + + if (b->size >= size) // If the current block size is already sufficient. + { + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before returning. + return ptr; // Return the original pointer. + } + + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex before my_malloc starts. + // Allocate a new block with the requested size. + void* new_ptr = my_malloc(size); + if (new_ptr) // Check if the allocation was successful. + { + char *src = (char*)ptr, *dest = (char*)new_ptr; // Cast pointers for byte-wise copying. + + // Copy data from the original block to the new block. + for (size_t i = 0; i < b->size; i++) + { + dest[i] = src[i]; + } + + my_free(ptr, FALSE); // Free the old block without unmapping it. + + // Log the realloc operation for debugging and tracking. + log_mem_operation(REALLOC, new_ptr, size, &realloc_ctr); + } + + pthread_mutex_unlock(&memory_mutex); // Unlock the mutex after completing the operation. + return new_ptr; // Return the pointer to the newly allocated memory. +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..4834843 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,42 @@ +# Verify cmake version +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +# Create project for tests +project(tests LANGUAGES C) + +# Enable testing +include(CTest) + +# Find the unity package +find_package(unity REQUIRED) + +# Include directories for tests +include_directories(${CMAKE_SOURCE_DIR}/include) + +# Add executable for tests +add_executable(tests + ${CMAKE_SOURCE_DIR}/tests/test_mem.c + ${CMAKE_SOURCE_DIR}/src/memory.c + ${CMAKE_SOURCE_DIR}/src/malloc.c + ${CMAKE_SOURCE_DIR}/src/free.c + ${CMAKE_SOURCE_DIR}/src/realloc.c + ${CMAKE_SOURCE_DIR}/src/calloc.c + ${CMAKE_SOURCE_DIR}/src/mem_logging.c + ${CMAKE_SOURCE_DIR}/src/mem_metrics.c + ${CMAKE_SOURCE_DIR}/tests/memory_test.c + ${CMAKE_SOURCE_DIR}/tests/heap_test.c +) + +# Specify the output directory for the tests binary within build/tests +set_target_properties(tests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" +) + +# Link libraries for tests +target_link_libraries(tests PRIVATE unity::unity) + +# Add test command +add_test(NAME imemTests COMMAND tests) + +# Add coverage flags unconditionally for the tests binary +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -fprofile-arcs -ftest-coverage --coverage") diff --git a/tests/heap_test.c b/tests/heap_test.c new file mode 100644 index 0000000..81f5103 --- /dev/null +++ b/tests/heap_test.c @@ -0,0 +1,25 @@ +#include "heap_test.h" // Include the header for heap test functionality. + +void test_check_heap(void) +{ + printf("\n%s====== Running CHECK_HEAP Tests ======%s\n", BLUE, RESET); + + printf("%s\nTest: NON-EMPTY HEAP%s\n", YELLOW, RESET); + void* block1 = my_malloc(64); + void* block2 = my_malloc(128); + void* block3 = my_malloc(256); + my_free(block2, FALSE); + my_free(block3, FALSE); + check_heap(); + memory_usage(); + my_free(block1, TRUE); + + printf("%s\nTest: FREING WITH UNMAPING%s", YELLOW, RESET); + void* block4 = my_malloc(32); + void* block5 = my_malloc(48); + printf("%s (Freing 32 bytes block at %p and unmaping memory)%s\n", GREEN, block4, RESET); + my_free(block4, TRUE); + check_heap(); + memory_usage(); + my_free(block5, TRUE); +} diff --git a/tests/memory_test.c b/tests/memory_test.c new file mode 100644 index 0000000..845efe4 --- /dev/null +++ b/tests/memory_test.c @@ -0,0 +1,25 @@ +#include "memory_test.h" // Include the header for memory metrics functionality. + +void test_memory_usage(void) +{ + printf("\n%s====== Running MEMORY_USAGE Tests ======%s\n", BLUE, RESET); + printf("%s\nTest: EMPTY USAGE%s\n", YELLOW, RESET); + memory_usage(); // Test empty usage + + printf("\n%s--------------------------------%s\n\n", YELLOW, RESET); + + printf("%sTest: ALLOCATIONS AND FREES%s\n\n", YELLOW, RESET); + + void* block6 = my_malloc(64); + void* block7 = my_malloc(128); + printf("%sAllocating blocks at %p and %p%s\n", RED, block6, block7, RESET); + memory_usage(); + + printf("%s\nFreing 64 bytes block at %p%s\n", GREEN, block6, RESET); + my_free(block6, TRUE); // Free without merging + memory_usage(); + + printf("%s\nFreing 128 bytes block at %p%s\n", GREEN, block7, RESET); + my_free(block7, TRUE); // Free without merging + memory_usage(); +} diff --git a/tests/test_mem.c b/tests/test_mem.c new file mode 100644 index 0000000..a2d6956 --- /dev/null +++ b/tests/test_mem.c @@ -0,0 +1,165 @@ +#include "test_mem.h" + +// Mock global variables +extern void* base; +extern int method; +extern pthread_mutex_t memory_mutex; +extern size_t total_allocated_memory; +extern size_t total_freed_memory; +extern unsigned long malloc_ctr; +extern unsigned long free_ctr; +extern unsigned long calloc_ctr; +extern unsigned long realloc_ctr; + +void setUp(void) +{ + // Reset global state before each test + base = NULL; + total_allocated_memory = 0; + total_freed_memory = 0; + malloc_ctr = 0; + free_ctr = 0; + calloc_ctr = 0; + realloc_ctr = 0; +} + +void tearDown(void) +{ + // Clean up global state after each test + if (base) + { + munmap(base, total_allocated_memory); + base = NULL; + } +} + +// Test for `my_malloc` +void test_my_malloc_allocates_memory(void) +{ + size_t size = 64; + void* ptr = my_malloc(size); + + TEST_ASSERT_NOT_NULL(ptr); + TEST_ASSERT_EQUAL(size, total_allocated_memory); + + my_free(ptr, FALSE); // Clean up +} + +// Test for `my_free` +void test_my_free_frees_memory(void) +{ + size_t size = 64; + void* ptr = my_malloc(size); + + my_free(ptr, FALSE); + + TEST_ASSERT_EQUAL(size, total_freed_memory); +} + +// Test for `my_calloc` +void test_my_calloc_zeroes_memory(void) +{ + size_t count = 10; + size_t size = 4; + void* ptr = my_calloc(count, size); + + printf("test_my_calloc_zeroes_memory\n"); + + TEST_ASSERT_NOT_NULL(ptr); + + char* data = (char*)ptr; + for (size_t i = 0; i < count * size; i++) + { + TEST_ASSERT_EQUAL(0, data[i]); + } + + my_free(ptr, FALSE); // Clean up +} + +// Test for `my_realloc` +void test_my_realloc_increases_memory(void) +{ + size_t initial_size = 64; + size_t new_size = 128; + void* ptr = my_malloc(initial_size); + + void* new_ptr = my_realloc(ptr, new_size); + + TEST_ASSERT_NOT_NULL(new_ptr); + TEST_ASSERT_EQUAL(initial_size + new_size, total_allocated_memory); + + my_free(new_ptr, FALSE); // Clean up +} + +// Test for `split_block` +void test_split_block_creates_new_block(void) +{ + size_t size = 128; + void* ptr = my_malloc(size); + + t_block block = get_block(ptr); + size_t split_size = 64; + split_block(block, split_size); + + TEST_ASSERT_EQUAL(split_size, block->size); + TEST_ASSERT_NOT_NULL(block->next); + TEST_ASSERT_EQUAL(size - split_size - BLOCK_MIN_SIZE, block->next->size); + + my_free(ptr, FALSE); // Clean up +} + +// Test for `fusion` +void test_fusion_merges_adjacent_blocks(void) +{ + size_t size = 64; + void* ptr1 = my_malloc(size); + void* ptr2 = my_malloc(size); + + my_free(ptr1, FALSE); + my_free(ptr2, FALSE); + + t_block block = get_block(ptr1); + block = fusion(block); + + TEST_ASSERT_EQUAL(size * 2 + BLOCK_MIN_SIZE, block->size); +} + +// Test for `valid_addr` +void test_valid_addr_validates_pointer(void) +{ + size_t size = 64; + void* ptr = my_malloc(size); + + TEST_ASSERT_TRUE(valid_addr(ptr)); + TEST_ASSERT_FALSE(valid_addr((void*)0x12345678)); // Invalid address + + my_free(ptr, FALSE); // Clean up +} + +// Test for `extend_heap` +void test_extend_heap_adds_memory(void) +{ + size_t size = 128; + t_block block = extend_heap(NULL, size); + + TEST_ASSERT_NOT_NULL(block); + TEST_ASSERT_EQUAL(size, block->size); + + munmap(block, size + BLOCK_MIN_SIZE); // Clean up +} + +int main(void) +{ + UNITY_BEGIN(); + + RUN_TEST(test_my_malloc_allocates_memory); + RUN_TEST(test_my_free_frees_memory); + RUN_TEST(test_my_calloc_zeroes_memory); + RUN_TEST(test_my_realloc_increases_memory); + RUN_TEST(test_split_block_creates_new_block); + RUN_TEST(test_fusion_merges_adjacent_blocks); + RUN_TEST(test_valid_addr_validates_pointer); + RUN_TEST(test_extend_heap_adds_memory); + + return UNITY_END(); +}