SLAB memory management - linux-kernel

I'm confused as to the structuring of the SLAB memory management mechanism.
I get that there are multiple 'caches' that are specific to commonly used data objects, but why does each cache contain multiple 'slabs'?
What differentiates each slab within a cache? Why not simply have the cache filled with the data objects themselves? Why does there need to be this extra layer?

The slab allocator is an abstraction layer to make easier allocation of numerous objects of a same type.
The interface offers the function
struct kmem_cache * kmem_cache_create(const char *name,
size_t size, size_t align, unsigned long flags,
void (*ctor)(void*));
This function creates a new slab allocator that will be able to handle allocation of size-bytes long objects. If the creation is successful, you get your pointer to the related struct kmem_cache. This structures holds information about the slabs it manages.
As it is implied on the Wikipedia description, the purpose of such an extra layer is to prevent memory fragmentation issues that could happen if the memory allocation was made in a simple and intuitive manner. To do so, it introduces the notion of slab through the following data structure :
struct slab {
struct list_head list; /* embedded list structure */
unsigned long colouroff;
void *s_mem; /* first object in the slab */
unsigned int inuse; /* allocated objects in the slab */
kmem_bufctl_t free; /* first free object (if any) */
};
Thus, the kmem_cache object holds 3 lists of its slabs, gathered in 3 flavours :
Empty slabs : these slabs do not contain an in-use object.
Partial slabs : these slabs contain objects currently used but there is still memory area that can hold new objects.
Full slabs : these slabs contains objects being used and cannot host new objects (full ...).
When requesting an object through the slab allocator, it will try to get the required memory area within a partial slab, if it cannot, it will get it from an empty slab.
If you are eager to collect more information about this, you should take a look at Robert Love's Linux Kernel Development

I may be too late to answer this, but this may help others as well.
As I see from Understanding Linux Virtual Memory Manager, having slabs has three major benefits.
Reducing internal fragmentation caused by buddy system. Because we have caches that best suits smaller objects.
Better hardware cache usage - by aligining objects to start at different offsets in different slabs so that interference between cache lines can be reduced. This is based on assumption that we have physically indexed cache.
A slab is primary unit in cache, which is acquired/relinquished at once. This causes reduction in external fragmentation as well.
See section 3.2 from The Slab Allocator: An Object Caching Kernel memory Allocator (1994).

Related

What are other commonly used memory allocators except the slab allocator?

Hi I recently learned that the slab allocator (using an embedded pointer for singly linked list) is able to allocate equal-sized memories to the customer without causing extra waste (for example, cookies in malloc).
What are other commonly used memory allocators in production? I mean, is slab allocator good enough to handle all types of workloads?
Thanks.

How to split one heap into several heaps inside one process?

Which ecosystems allow to create multiple heaps right now?
Is it possible to have multiple heaps in java?
garbage collection and memory management in Erlang
Is there any benefit to use multiple heaps for memory management purposes?
AppDomains don't create new heaps (there is still one heap for all domains). So, what one need to do to launch several different GC inside the single process?
Which syntactic primitives does one need to create? How a runtime should support that primitives?
Which ecosystems allow to create multiple heaps right now?
One obvious answer would be "C++" (feel free to fill in surrounding pieces as you see fit, if you don't consider a language to be an "ecosystem" in itself).
C++ allows you to specify heaps along a few different axes. One is by the type of an object--you can specify allocation for a particular type by overloading operator new and operator delete for that type:
class Foo {
static void *operator new(size_t size);
static void operator delete(void *block, size_t size);
};
It's then up to you to connect these heap management functions to an actual source of memory. You might allocate that via ::operator new, or you might (for example) go directly to the OS, such as with something like GlobalAlloc or VirtualAlloc on Windows, sbrk on UNIX-like systems, or just have pre-specified blocks of memory on a bare-metal embedded system.
Along a somewhat different axis, all the containers in the C++ standard library allocate and free memory via Allocator classes. The Allocator for any particular collection is specified as a template parameter, so (for example) a declaration for std::vector looks something like this:
template <class T, class Alloc=std::allocator<T>>
class vector {
// ...
};
This lets you specify a heap that will be used to allocate objects in that collection. Much as with operator new and operator delete, this really only specifies the interface by which the collection will allocate and free memory--it's up to you to connect that to code that actually manages the heap.
Garbage Collection
As far as garbage collection goes: I personally find it annoying, and advise against its use as a general rule. The problem is that it while it can (at least from one perspective) fix some types of problems with memory management, it does nothing to help management of other resources--and (unfortunately) I haven't seen anything like a tracing collector for file handles, network sockets, database connections, and so on. RAII provides a uniform method for dealing with resource management in general.
That said, if you really insist on using GC, C++ does support that as well. Prior to C++11, GC was entirely usable on a practical level, but led to what was technically undefined behavior under a few obscure circumstances, such as:
storing a pointer in a file, and reading it back in, or
modifying the bits of a pointer, later un-doing that modification
...and later taking the re-constituted pointer and dereferencing it. Obviously, while the pointer wasn't visible to the CPU, the pointed-to block of memory became eligible for GC, so the later dereference caused problems. C++11 defined these circumstances, and added a few library calls (e.g., declare_reachable, undeclare_reachable) to deal with them (e.g., if you call decalare_reachable(block);, that block is not eligible for collection, regardless of whether a pointer to it is visible). As such, if you want to use GC with C++ you can, and the bounds of defined behavior are thoroughly specified. The only problem is that essentially no code ever calls declare_reachable and/or undeclare_reachable, so in real use they're likely to be of little or no help (but pointer swizzling and/or storage in a file are sufficiently rare that this is unlikely to pose a real problem).
For a practical example, you might want to look at the Boehm-Demers-Weiser collector (if you haven't already).

Does unordered map frees its memory after calling clear?

I know that unordered map removes the objects that were stored in it when doing clear() but does it also relinquishes the memory (back to OS) that it holds to create itself.
struct A{
A(){std::cout << "constructor called\n";}
~A(){std::cout << "destructor called\n";}
};
// this will reserve size of 1000 buckets
std::unordered_map<int, A> my_map{1000};
// now insert into the map
my_map[1] = "asdf";
my_map[2] = "asdff";
....
then I do my_map.clear(); This will call destructor of A.
So my question is will the 1000 buckets that was reserved also be freed? I tried looking at the size() after doing clear it says zero, so is there something like capacity for unordered_map that is similar to vector that would let me view the reserved size?
will the 1000 buckets that was reserved also be freed?
This is not specified by the standard.
is there something like capacity for unordered_map that is similar to vector that would let me view the reserved size?
A simple "capacity" value doesn't make sense for a hash map. Rehashing happens when load_factor exceeds max_load_factor. You can check the number of buckets using bucket_count.
I know that unordered map removes the objects that were stored in it when doing clear() but does it also relinquishes the memory (back to OS) that it holds to create itself.
There is no guarantee (in particular because the C++11 specification does not know what an OS is), and in practice it often don't release the memory to the OS (that is, your virtual address space might stay unchanged).
What happens is that (without specific Allocator argument to the std::map template, or to std::unordered_map template) the memory is delete-d (or delete[]-d). And if you really care a lot, implement your own Allocator andd pass it appropriately to your container templates. I'm not sure that is worth the trouble.
Usually new calls malloc & delete calls free. In that case in most (but not all) cases the memory is not released to the kernel (e.g. with munmap(2) on Linux...) but just marked as re-usable by future calls to new.
Details are of course implementation specific. Many free (or delete) implementations would for example release with munmap a large enough block (of several megabytes or more). The exact threshold vary from one implementation to the next one.
Notice that if you use free software (e.g. a Linux system with GCC, GNU libc, ....), you could look into the source code of your C++ standard library (the code of std::map and of ::operator delete etc... inside GCC) and of your C standard library (the code of free) and find out by yourself these gory details.

Sized Deallocation Feature In Memory Management in C++1y

Sized Deallocation feature has been proposed to include in C++1y. However I wanted to understand how it would affect/improve the current c++ low-level memory management?
This proposal is in N3778, which states following about the intent of this.
With C++11, programmers may define a static member function operator
delete that takes a size parameter indicating the size of the object
to be deleted. The equivalent global operator delete is not available.
This omission has unfortunate performance consequences.
Modern memory allocators often allocate in size categories, and, for
space efficiency reasons, do not store the size of the object near the
object. Deallocation then requires searching for the size category
store that contains the object. This search can be expensive,
particularly as the search data structures are often not in memory
caches. The solution is to permit implementations and programmers
to define sized versions of the global operator delete. The
compiler shall call the sized version in preference to the unsized
version when the sized version is available.
Well from above paragraph, it look like the size information which operator delete require can be maintained and hence passed by used program. This would avoid any search for the size while deallocation. But as per my understanding, while allocating, memory management store the size information in some sort of header(explained boundary-tag method in dlmalloc), which would be used while deallocation.
T* p = new T();
// Now size information would be stored in the header
// *(char*)(p - 0x4) = size;
// This would be used when we delete the memory????.
delete p;
If size information is stored in the header, why deallocation require searching for it?
It looks like I am missing something obvious and did not understand this concepts completely.
Additionally,how this feature can be used in program while dealing with the low level memory management in C++. Hope that somebody will help me to understand these concept.
As in your quote:
[Modern memory allocators] for space efficiency reasons, do not store the size of the object near the object.
Increasing the size of every allocation in order to add explicit size information is obviously going to use more memory than alternatives such as storing the size information once per allocation pool, or supplying the information upon deallocation.

Understanding the Buddy Allocator

I have a conceptual doubt in understanding the way Linux Kernel manages Free blocks. Here is what I interpreted through reading so far.
The Buddy Allocator implementation is allocation scheme that combines a normal power-of-2 allocation.
At times when we need a block of size which is not available, it divides the large block into two. Those two blocks are Buddies, probably hence it is called the Buddy Allocator.
Through a source I learnt that an array of free_area_t structs are maintained for each order that points to a linked list of blocks of pages that are free.
Which I found in <linux/mm.h>
typedef struct free_area_struct {
struct list_head free_list;
unsigned long *map;
} free_area_t;
The free_list appear to be a linked-list of page blocks? My question is, whether it is a list of Free pages or Used pages?
And map appears to be a bitmap that represents the state of a pair of buddies.
My question is How can it be a single-bit that holds the state bit for a pair of buddies? Because if, I use one of the block in a Buddy-pair to allocats, and the other left free, what would be the state then, and how is that managed to be stored in a single bit? Does it represent the entire block of the size of power-of-two, which can be divided in two parts when we need a block size which is not available, so the allocated half is Buddy of the other half which is free? If this is the case that half is being allocated and half remains free, then what will be status of map ? What if both are free? and what if both are allocated? How can be a binary value representing 3 states of a block?
Edit: After further reading, the first doubt is cleared. Which says: If a free block cannot be found of the requested order, a higher order block
is split into two buddies. One is allocated and the other is placed on the free list for
the lower order. So it is linked list of free pages.
map represents the state of a single memory block at the lowest level.

Resources