// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2023 Red Hat */ #include <linux/delay.h> #include <linux/mm.h> #include <linux/sched/mm.h> #include <linux/slab.h> #include <linux/vmalloc.h> #include "logger.h" #include "memory-alloc.h" #include "permassert.h" /* * UDS and VDO keep track of which threads are allowed to allocate memory freely, and which threads * must be careful to not do a memory allocation that does an I/O request. The 'allocating_threads' * thread_registry and its associated methods implement this tracking. */ static struct thread_registry allocating_threads; static inline bool allocations_allowed(void) { … } /* * Register the current thread as an allocating thread. * * An optional flag location can be supplied indicating whether, at any given point in time, the * threads associated with that flag should be allocating storage. If the flag is false, a message * will be logged. * * If no flag is supplied, the thread is always allowed to allocate storage without complaint. * * @new_thread: registered_thread structure to use for the current thread * @flag_ptr: Location of the allocation-allowed flag */ void vdo_register_allocating_thread(struct registered_thread *new_thread, const bool *flag_ptr) { … } /* Unregister the current thread as an allocating thread. */ void vdo_unregister_allocating_thread(void) { … } /* * We track how much memory has been allocated and freed. When we unload the module, we log an * error if we have not freed all the memory that we allocated. Nearly all memory allocation and * freeing is done using this module. * * We do not use kernel functions like the kvasprintf() method, which allocate memory indirectly * using kmalloc. * * These data structures and methods are used to track the amount of memory used. */ /* * We allocate very few large objects, and allocation/deallocation isn't done in a * performance-critical stage for us, so a linked list should be fine. */ struct vmalloc_block_info { … }; static struct { … } memory_stats __cacheline_aligned; static void update_peak_usage(void) { … } static void add_kmalloc_block(size_t size) { … } static void remove_kmalloc_block(size_t size) { … } static void add_vmalloc_block(struct vmalloc_block_info *block) { … } static void remove_vmalloc_block(void *ptr) { … } /* * Determine whether allocating a memory block should use kmalloc or __vmalloc. * * vmalloc can allocate any integral number of pages. * * kmalloc can allocate any number of bytes up to a configured limit, which defaults to 8 megabytes * on some systems. kmalloc is especially good when memory is being both allocated and freed, and * it does this efficiently in a multi CPU environment. * * kmalloc usually rounds the size of the block up to the next power of two, so when the requested * block is bigger than PAGE_SIZE / 2 bytes, kmalloc will never give you less space than the * corresponding vmalloc allocation. Sometimes vmalloc will use less overhead than kmalloc. * * The advantages of kmalloc do not help out UDS or VDO, because we allocate all our memory up * front and do not free and reallocate it. Sometimes we have problems using kmalloc, because the * Linux memory page map can become so fragmented that kmalloc will not give us a 32KB chunk. We * have used vmalloc as a backup to kmalloc in the past, and a follow-up vmalloc of 32KB will work. * But there is no strong case to be made for using kmalloc over vmalloc for these size chunks. * * The kmalloc/vmalloc boundary is set at 4KB, and kmalloc gets the 4KB requests. There is no * strong reason for favoring either kmalloc or vmalloc for 4KB requests, except that tracking * vmalloc statistics uses a linked list implementation. Using a simple test, this choice of * boundary results in 132 vmalloc calls. Using vmalloc for requests of exactly 4KB results in an * additional 6374 vmalloc calls, which is much less efficient for tracking. * * @size: How many bytes to allocate */ static inline bool use_kmalloc(size_t size) { … } /* * Allocate storage based on memory size and alignment, logging an error if the allocation fails. * The memory will be zeroed. * * @size: The size of an object * @align: The required alignment * @what: What is being allocated (for error logging) * @ptr: A pointer to hold the allocated memory * * Return: VDO_SUCCESS or an error code */ int vdo_allocate_memory(size_t size, size_t align, const char *what, void *ptr) { … } /* * Allocate storage based on memory size, failing immediately if the required memory is not * available. The memory will be zeroed. * * @size: The size of an object. * @what: What is being allocated (for error logging) * * Return: pointer to the allocated memory, or NULL if the required space is not available. */ void *vdo_allocate_memory_nowait(size_t size, const char *what __maybe_unused) { … } void vdo_free(void *ptr) { … } /* * Reallocate dynamically allocated memory. There are no alignment guarantees for the reallocated * memory. If the new memory is larger than the old memory, the new space will be zeroed. * * @ptr: The memory to reallocate. * @old_size: The old size of the memory * @size: The new size to allocate * @what: What is being allocated (for error logging) * @new_ptr: A pointer to hold the reallocated pointer * * Return: VDO_SUCCESS or an error code */ int vdo_reallocate_memory(void *ptr, size_t old_size, size_t size, const char *what, void *new_ptr) { … } int vdo_duplicate_string(const char *string, const char *what, char **new_string) { … } void vdo_memory_init(void) { … } void vdo_memory_exit(void) { … } void vdo_get_memory_stats(u64 *bytes_used, u64 *peak_bytes_used) { … } /* * Report stats on any allocated memory that we're tracking. Not all allocation types are * guaranteed to be tracked in bytes (e.g., bios). */ void vdo_report_memory_usage(void) { … }