入门篇介绍了zone在初始化时规定了nanozone管理256字节以内内存,其余内存由scalablezone管理,上篇介绍了nanozone的机制,本篇继续介绍scalablezone的内存机制。
整体策略与szone
对于256字节以上的内存,使用scalablezone分配,由于待分配内存的大小差异较大,scalablezone在分配时不采用同一套逻辑,而是先将分配大小划分成tiny、small、medium、large4个级别,针对这4个级别,采用不同的分配逻辑处理,其中tiny、small、medium(iOS下不使用,这里不介绍)的整体逻辑相同,但是具体策略有差异,large有一套自己的机制,iOS上没有medium策略。
首先,调用create_scalable_szone函数创建scalablezone,scalablezone的类型是szone_s,是基础malloc_zone_t结构的扩展,szone_s定义如下:
c
typedef struct szone_s {
malloc_zone_t basic_zone; // first page will be given read-only protection
uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)];
struct rack_s tiny_rack;
struct rack_s small_rack;
struct rack_s medium_rack;
_malloc_lock_s large_szone_lock MALLOC_CACHE_ALIGN; // One customer at a time for large
unsigned num_large_objects_in_use;
unsigned num_large_entries;
large_entry_t *large_entries; // hashed by location; null entries don't count
size_t num_bytes_in_large_objects;
#if CONFIG_LARGE_CACHE
int large_entry_cache_oldest;
int large_entry_cache_newest;
large_entry_t large_entry_cache[LARGE_ENTRY_CACHE_SIZE_HIGH]; // "death row" for large malloc/free
int large_cache_depth;
size_t large_cache_entry_limit;
boolean_t large_legacy_reset_mprotect;
size_t large_entry_cache_reserve_bytes;
size_t large_entry_cache_reserve_limit;
size_t large_entry_cache_bytes; // total size of death row, bytes
#endif
/* The purgeable zone constructed by create_purgeable_zone() would like to hand off tiny and small
* allocations to the default scalable zone. Record the latter as the "helper" zone here. */
struct szone_s *helper_zone;
} szone_t;
包含以下字段:
- basic_zone:基础结构malloc_zone_t,存储内存管理的各入口函数地址,在入门篇文章中已介绍。
- tiny_rack/small_rack/medium_rack:rack_s类型,用于tiny、small、medium内存的具体管理,下文中介绍。
- large类型内存管理相关字段,包括统计信息,管理large内存块,缓存逻辑相关。
- helper_zone:辅助zone。
create_scalable_szone函数创建scalablezone。
ini
szone_t *create_scalable_szone(size_t initial_size, unsigned debug_flags)
{
szone_t *szone;
szone = mvm_allocate_pages(SZONE_PAGED_SIZE, 0, DISABLE_ASLR, VM_MEMORY_MALLOC);
if (!szone) {
return NULL;
}
unsigned int max_mags = mag_max_magazines();
uint32_t num_magazines = (max_mags > 1) ? MIN(max_mags, TINY_MAX_MAGAZINES) : 1;
rack_init(&szone->tiny_rack, RACK_TYPE_TINY, num_magazines, debug_flags);
rack_init(&szone->small_rack, RACK_TYPE_SMALL, num_magazines, debug_flags);
//...
#if CONFIG_LARGE_CACHE
//large cache相关字段初始化
#endif
szone->basic_zone.version = 13;
szone->basic_zone.size = (void *)szone_size;
szone->basic_zone.malloc = (void *)szone_malloc;
szone->basic_zone.calloc = (void *)szone_calloc;
szone->basic_zone.valloc = (void *)szone_valloc;
szone->basic_zone.free = (void *)szone_free;
szone->basic_zone.realloc = (void *)szone_realloc;
szone->basic_zone.destroy = (void *)szone_destroy;
szone->basic_zone.batch_malloc = (void *)szone_batch_malloc;
szone->basic_zone.batch_free = (void *)szone_batch_free;
szone->basic_zone.introspect = (struct malloc_introspection_t *)&szone_introspect;
szone->basic_zone.memalign = (void *)szone_memalign;
//...
return szone;
}
初始化逻辑包含以下部分:
- mvm_allocate_pages创建一个szone_t内存,即scalablezone。
- rack_init函数初始化tiny、small的rack数据,初始化分配逻辑。
- 初始化large cach相关逻辑
- 初始化malloc_zone_t base_zone的字段,作为scalablezone管理内存的入口API。
例如,scalablezone的malloc逻辑由szone_malloc实现:
arduino
void *szone_malloc(szone_t *szone, size_t size)
{
return szone_malloc_should_clear(szone, size, 0);
}
内部调用szone_malloc_should_clear实现malloc,如下:
ini
#if MALLOC_TARGET_64BIT
#define TINY_LIMIT_THRESHOLD (1008)
#else // MALLOC_TARGET_64BIT
#define TINY_LIMIT_THRESHOLD (496)
#endif // MALLOC_TARGET_64BIT
#if MALLOC_TARGET_IOS
#define SMALL_LIMIT_THRESHOLD (15 * 1024)
#else // MALLOC_TARGET_IOS
#define SMALL_LIMIT_THRESHOLD (32 * 1024)
#endif // MALLOC_TARGET_IOS
MALLOC_NOINLINE void *
szone_malloc_should_clear(szone_t *szone, size_t size, boolean_t cleared_requested)
{
void *ptr;
msize_t msize;
if (size <= TINY_LIMIT_THRESHOLD) {
msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1);
if (!msize) {
msize = 1;
}
ptr = tiny_malloc_should_clear(&szone->tiny_rack, msize, cleared_requested);
} else if (size <= SMALL_LIMIT_THRESHOLD) {
msize = SMALL_MSIZE_FOR_BYTES(size + SMALL_QUANTUM - 1);
if (!msize) {
msize = 1;
}
ptr = small_malloc_should_clear(&szone->small_rack, msize, cleared_requested);
} else {
size_t num_kernel_pages = round_large_page_quanta(size) >> large_vm_page_quanta_shift;
if (num_kernel_pages == 0) { /* Overflowed */
ptr = 0;
} else {
ptr = large_malloc(szone, num_kernel_pages, 0, cleared_requested);
}
}
return ptr;
}
可以看到,根据分配size大小,使用不同的分配策略。
- tiny:tiny_malloc_should_clear,1008B以内
- small:small_malloc_should_clear1009B~15KB
- large:15KB以上
又例如,free逻辑由szone_free实现:
arduino
void szone_free(szone_t *szone, void *ptr)
{
_szone_free(szone, ptr, false);
}
static void _szone_free(szone_t *szone, void *ptr, bool try)
{
//...
if ((tiny_region = tiny_region_for_ptr_no_lock(&szone->tiny_rack, ptr)) != NULL) {
if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS) {
malloc_zone_error(szone->debug_flags, true, "Pointer %p to metadata being freed\n", ptr);
return;
}
free_tiny(&szone->tiny_rack, ptr, tiny_region, 0, false);
return;
}
//...
if ((small_region = small_region_for_ptr_no_lock(&szone->small_rack, ptr)) != NULL) {
if (SMALL_META_INDEX_FOR_PTR(ptr) >= NUM_SMALL_BLOCKS) {
malloc_zone_error(szone->debug_flags, true, "Pointer %p to metadata being freed (2)\n", ptr);
return;
}
free_small(&szone->small_rack, ptr, small_region, 0);
return;
}
//...
bool claimed = free_large(szone, ptr, try);
if (!try || claimed) {
return;
}
}
类似malloc逻辑,free也具体调用free_tiny、free_small、free_large处理不同size内存的回收。用一张图表示scalablezone处理内存的逻辑如下:
接下来介绍tiny、small、large的相关内存管理机制。
tiny
tiny主要的实现在magazine_tiny.c文件中,首先结合代码,介绍tiny管理内存的相关数据结构及其概念。
相关概念
size分级
根据要分配的size,按照指定size的倍数计算对应的分级(msize),实际分配指定size的倍数分配,例如tiny类型,将内存划分为16B一级,则1008B的范围一共划分为63级(如下图所示)。 例如,分配一个17B大小的内存,对应的msize是2,实际分配32B的内存。
rack
如上所述,rack_s是管理内存的全局数据结构,查看rack_s的定义,核心字段如下:
arduino
OS_ENUM(rack_type, uint32_t,
RACK_TYPE_NONE = 0,
RACK_TYPE_TINY,
RACK_TYPE_SMALL,
RACK_TYPE_MEDIUM,
);
typedef struct rack_s {
rack_type_t type; //类型
//...
size_t num_regions;
region_t initial_regions[INITIAL_NUM_REGIONS];
int num_magazines;
magazine_t *magazines;
//...
} rack_t;
- type:类型,对应tiny、small、medium三种策略类型。
- num_regions:分配的region内存块个数,region是向scalablezone向系统分配的虚拟内存的基础单位,不同类型分配的region的大小不一致。
- initial_regions:初始化时分配的region数组。
- num_magazines:持有的magazine个数,magazine是管理scalable内存的核心概念,具体负责内存的管理,包括region块的分配与管理、空闲块的使用与管理等。
- magazines:magazine数组,一种策略类型具体由若干个magazine管理。
创建scalable_szone时,通过rack_init初始化rack数据。
magazine
结构关系
magazine是管理内存的核心结构,每一种rack(tiny、small、medium),会创建多个magazine,rack在操作内存时,首先需要选取其中一个magazine具体操作,体现在数据结构上,rack_s下持有若干个magazine。
arduino
typedef struct rack_s {
//...
// array of per-processor magazines
magazine_t *magazines;
}
具体的对应关系如图: rack创建多少个magazines,与设备的cpu核心数相关,具体逻辑追溯到rack初始化的逻辑中。
scss
szone_t *
create_scalable_szone(size_t initial_size, unsigned debug_flags)
{
//...
unsigned int max_mags = mag_max_magazines();
uint32_t num_magazines = (max_mags > 1) ? MIN(max_mags, TINY_MAX_MAGAZINES) : 1;
rack_init(&szone->tiny_rack, RACK_TYPE_TINY, num_magazines, debug_flags);
}
void rack_init(rack_t *rack, rack_type_t type, uint32_t num_magazines, uint32_t debug_flags)
{
//...
rack->num_magazines = num_magazines;
//...
if (num_magazines > 0) {
size_t magsize = round_page_quanta(sizeof(magazine_t) * (num_magazines + 1));
magazine_t *magazines = mvm_allocate_pages(magsize, 0, MALLOC_ADD_GUARD_PAGE_FLAGS|DISABLE_ASLR, VM_MEMORY_MALLOC);
if (!magazines) {
MALLOC_REPORT_FATAL_ERROR(0, "unable to allocate magazine array");
}
rack->magazines = &magazines[1];
}
}
调用create_scalable_szone初始化tiny的rack数据,在rack_init方法中传入num_magazines并创建magazines,num_magazines通过mag_max_magazines方法获取.
arduino
unsigned int mag_max_magazines(void)
{
return max_magazines;
}
static void _malloc_initialize(const char *apple[], const char *bootargs)
{
logical_ncpus = *(uint8_t *)(uintptr_t)_COMM_PAGE_LOGICAL_CPUS;
//...
if (max_magazines) {
max_magazines = MIN(max_magazines, logical_ncpus);
} else {
max_magazines = logical_ncpus;
}
}
max_magazines由_COMM_PAGE_LOGICAL_CPUS获取,含义是number of logical CPUs(逻辑CPU的核心数)。因此magazines的个数由逻辑CPU的核心数决定。可以理解为magazine和CPU存在对应关系,如图,当分配内存时,根据当前线程执行的CPU,选择对应的magazine。 例如设备共4个CPU核心,当前运行的CPU index是1,则返回magazines[1]。
具体代码如下:
ini
mag_index_t mag_index = tiny_mag_get_thread_index() % rack->num_magazines;
magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]);
mag_index_t tiny_mag_get_thread_index(void)
{
if (likely_if(_os_cpu_number_override == -1)) {
return _malloc_cpu_number();
} else {
return _os_cpu_number_override;
}
}
通过tiny_mag_get_thread_index方法获取当前线程的cpu核心对应的mag_index下标,通过magazines[mag_index]返回magazine对象。CPU核心数和magazine对象的关系大致如图,
这样操作内存时,每个cpu核心对应一个magazine,多magazine同时操作内存,可以发挥多cpu核心并行的能力。
内存管理
magazine是如何管理内存的,首先数据结构如下:
arduino
typedef struct magazine_s {
//...
void *mag_last_free;
msize_t mag_last_free_msize;
//...
region_t mag_last_free_rgn;
free_list_t mag_free_list[MAGAZINE_FREELIST_SLOTS]; //257
uint32_t mag_bitmap[MAGAZINE_FREELIST_BITMAP_WORDS]; //9
size_t mag_bytes_free_at_end; //最后申请的heap region中未使用的大小
size_t mag_bytes_free_at_start;
region_t mag_last_region; // 最后一次向内核申请的heap region地址
size_t mag_num_bytes_in_objects;
size_t num_bytes_in_magazine;
unsigned mag_num_objects;
region_trailer_t *firstNode;
region_trailer_t *lastNode;
//...
}
主要字段如下:
- firstNode、lastNode:region链表,待分配内存从region中分配。
- mag_last_free:缓存的前一次回收内存块的地址
- mag_last_free_msize:缓存的前一次回收内存块的size分级
- mag_last_free_rgn:前一次回收内存所在的region
- mag_free_list:空闲链表,记录了之前回收的内存,实现内存复用的核心字段
- mag_bitmap:内存空闲状态数组,判断内存复用的辅助字段。
- mag_bytes_free_at_end:region中剩余内存空间的结束地址。
- mag_bytes_free_at_start:region中剩余内存空间的起始地址。
总体来看,包含2个核心数据结构和一些辅助功能所需的字段,用一张图说明。 字段中涉及的相关概念,例如region、mag_free_list空闲链表,下文具体介绍。
region
上文所述,magazine会管理一系列region空间,其他分配器的逻辑类似,region是实际存储分配内存的物理空间,magazine分配内存时,会一次性分配一块较大的内存区域region(图中黄色区域),该区域大小范围固定。而具体的内存从region中分配,当region剩余空间不足时,分配一块新的region。
内部结构
对于tiny内存,msize按照16B为基本单位,称为一个block内存,region内分配的不同size的内存块本质上由若干地址连续的block构成。因此,region是一块包含若干地址连续的16B内存块的较大物理空间,结构定义如下:
arduino
typedef struct tiny_region {
//meta
region_trailer_t trailer; //链表
tiny_header_inuse_pair_t pairs[CEIL_NUM_TINY_BLOCKS_WORDS]; //bit状态信息
region_free_blocks_t free_blocks_by_slot[NUM_TINY_SLOTS];
uint8_t pad[TINY_REGION_PAD];
region_cookie_t region_cookie;
//block内存
tiny_block_t blocks[NUM_TINY_BLOCKS];
} * tiny_region_t;
其中,tiny_block_t对应一个16B的block内存块,block是tiny内存块的基本单位,blocks字段是block数组,用于实际存储region内的业务数据,NUM_TINY_BLOCKS是数组容量,即region中包含的block块个数,决定了region可供使用的内存容量。
arduino
#define SHIFT_TINY_QUANTUM 4ull
#define TINY_QUANTUM (1 << SHIFT_TINY_QUANTUM)
#define NUM_TINY_BLOCKS 64504
NUM_TINY_BLOCKS是一个固定值64504,TINY_QUANTUM表示单个block大小是16B,因此region内存的容量是NUM_TINY_BLOCKS*TINY_QUANTUM。
除了负责数据存储的blocks字段外,其余字段用于region状态的管理,包括region链表的维护,block内存状态,用于辅助后续region内存操作与管理,这些字段称为region的meta数据字段。
下图反映了region空间的内存结构分布(真实的region是一块物理内存连续的空间,与图中展示的region存在差异,希望不要造成误解),region内存包含blocks区域和meta区域两部分,blocks区域的内存块由16B block基础内存块构成。 因此,region内存的大小(TINY_REGION_SIZE)等于blocks区大小(TINY_HEAP_SIZE)和meta区大小(TINY_METADATA_SIZE)之和。
scss
//blocks大小
#define TINY_HEAP_SIZE (NUM_TINY_BLOCKS * TINY_QUANTUM)
//meta大小
#define TINY_METADATA_SIZE (sizeof(region_trailer_t) + sizeof(tiny_header_inuse_pair_t) * CEIL_NUM_TINY_BLOCKS_WORDS + (sizeof(region_free_blocks_t) * NUM_TINY_SLOTS))
//region大小
#define TINY_REGION_SIZE ((TINY_HEAP_SIZE + TINY_METADATA_SIZE + PAGE_MAX_SIZE - 1) & ~(PAGE_MAX_SIZE - 1))
同时,还提供了一些封装宏访问region内部内存,例如TINY_REGION_METADATA是meta区起始地址,TINY_REGION_HEAP_BASE是blocks区起始地址,TINY_REGION_HEAP_END是blocks区结束地址。
scss
#define TINY_REGION_METADATA(region) ((uintptr_t)&((tiny_region_t)region)->trailer)
#define TINY_REGION_HEAP_BASE(region) ((void *)(((tiny_region_t)region)->blocks))
#define TINY_REGION_HEAP_END(region) ((void *)(((uintptr_t)TINY_REGION_HEAP_BASE(region)) + TINY_HEAP_SIZE))
同时,提供了TINY_INDEX_FOR_PTR宏,根据region中的任意内存地址ptr,返回对应的block内存在region中的index。
scss
#define TINY_HEAP_OFFSET_FOR_PTR(ptr) ((uintptr_t)(ptr) - (uintptr_t)TINY_REGION_HEAP_BASE(TINY_REGION_FOR_PTR(ptr)))
#define TINY_INDEX_FOR_PTR(ptr) ((TINY_HEAP_OFFSET_FOR_PTR(ptr) >> SHIFT_TINY_QUANTUM) & (NUM_TINY_CEIL_BLOCKS - 1))
具体计算逻辑是,首先计算ptr距离blocks起始地址的偏移量,然后除以单个block的size(16字节),再&(NUM_TINY_CEIL_BLOCKS - 1)得到block得index,offset如图: 反之,提供TINY_PTR_FOR_INDEX宏,可以根据block的index,计算对应的block内存地址。
scss
#define TINY_PTR_FOR_INDEX(index, region) (void *)((uintptr_t)TINY_REGION_HEAP_BASE(region) + ((index) << SHIFT_TINY_QUANTUM))
也可以通过block区中的一个block内存块地址,找到其所在region区的meta字段内存地址。
scss
#define TINY_BLOCK_HEADER_FOR_PTR(ptr) ((void *)&(((tiny_region_t)TINY_REGION_FOR_PTR(ptr))->pairs))
接下来介绍meta信息相关字段。
-
trailer,region链表结构字段,维护magazine下分配的所有region内存,对应的数据结构:
arduinotypedef struct region_trailer { struct region_trailer *prev; struct region_trailer *next; //... } region_trailer_t;
- 通过prev、next指针指向前后region。
-
pairs[]数组,记录region内block内存的使用状态,辅助magazine进行内存操作,其含义作用下文详细介绍。
-
free_blocks_by_slot[]数组,记录region内不同msize空闲内存块。
链表结构
如上所述,region通过meta的region_trailer_t结构的trailer字段维护链表,如图所示,通过链表可以遍历查找到magazine下分配的所有region。 magazine的firstNode字段指向的第一块region,lastNode字段指向最后一块region。
-
将region加入链表的末尾
ini//将region加入tiny_mag_ptr的last node static void recirc_list_splice_last(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) { if (NULL == mag_ptr->lastNode) { mag_ptr->firstNode = node; node->prev = NULL; } else { node->prev = mag_ptr->lastNode; mag_ptr->lastNode->next = node; } mag_ptr->lastNode = node; node->next = NULL; node->recirc_suitable = FALSE; }
-
将region加入链表的头部
inistatic void recirc_list_splice_first(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) { if (NULL == mag_ptr->firstNode) { mag_ptr->lastNode = node; node->next = NULL; } else { node->next = mag_ptr->firstNode; mag_ptr->firstNode->prev = node; } mag_ptr->firstNode = node; node->prev = NULL; node->recirc_suitable = FALSE; }
-
region节点从region trailer链表中移除
inistatic void recirc_list_extract(rack_t *rack, magazine_t *mag_ptr, region_trailer_t *node) { // excise node from list if (NULL == node->prev) { mag_ptr->firstNode = node->next; } else { node->prev->next = node->next; } if (NULL == node->next) { mag_ptr->lastNode = node->prev; } else { node->next->prev = node->prev; } node->next = node->prev = NULL; mag_ptr->recirculation_entries--; }
hash存储
除了magazine维护的region链表外,当分配了一块region后,会计算region的一个hash值,并记录到一块hash ring的内存结构中,标识rack下是否存在这块region,内存结构如图:
rack维护的相关字段如下:
arduino
typedef struct rack_s {
//...
region_hash_generation_t *region_generation;
}
typedef struct region_hash_generation {
size_t num_regions_allocated;
size_t num_regions_allocated_shift;
region_t *hashed_regions;
struct region_hash_generation *nextgen;
} region_hash_generation_t;
定义了region_hash_generation_t类型的region_generation字段,数据结构内部维护了hashed_regions字段,存储region的hash index数据。当hashed_regions的容量无法满足region的增长时,会新分配一个region_generation作为当前generation的nextgen,存储扩容的hashed_regions,并将数据同步到新的hashed_regions。
相关实现
结合图示,分析具体实现。
hash添加
当新分配一个region时,调用rack_region_insert加入region的hash index到hash ring中。
rust
void rack_region_insert(rack_t *rack, region_t region)
{
//rack的regions个数大于的2被大于当前generation的记录分配的region个数时,新增generation,并迁移数据。
if (rack->region_generation->num_regions_allocated < (2 * rack->num_regions)) {
region_t *new_regions;
size_t new_size;
size_t new_shift = rack->region_generation->num_regions_allocated_shift; // In/Out parameter
new_regions = hash_regions_grow_no_lock(rack->region_generation->hashed_regions,
rack->region_generation->num_regions_allocated, &new_shift, &new_size);
rack->region_generation->nextgen->hashed_regions = new_regions;
rack->region_generation->nextgen->num_regions_allocated = new_size;
rack->region_generation->nextgen->num_regions_allocated_shift = new_shift;
//新generation作为当前generation
rack->region_generation = rack->region_generation->nextgen;
}
//ragion的hash index加入generation的hashed_regions中
hash_region_insert_no_lock(rack->region_generation->hashed_regions,
rack->region_generation->num_regions_allocated,
rack->region_generation->num_regions_allocated_shift,
region);
rack->num_regions++;
}
-
判断rack中regions个数(num_regions),是否超过当前generation的记录分配的region个数的一半,如果超过,则需要扩容hashed_regions,新建一个generation,指向扩容后的new_hashed_regions,并作为当前generation。
-
扩容逻辑如下:
inistatic region_t *hash_regions_grow_no_lock(region_t *regions, size_t old_size, size_t *mutable_shift, size_t *new_size) { *new_size = old_size + old_size; *mutable_shift = *mutable_shift + 1; region_t *new_regions = hash_regions_alloc_no_lock(*new_size); size_t index; for (index = 0; index < old_size; ++index) { region_t r = regions[index]; if (r != HASHRING_OPEN_ENTRY && r != HASHRING_REGION_DEALLOCATED) { hash_region_insert_no_lock(new_regions, *new_size, *mutable_shift, r); } } return new_regions; }
分配一块新内存new_regions,容量是旧的2倍,并将旧region中存储的hash index数据迁移到新的hashed_regions内存中。如图所示:
-
将region的hash index加入generation的hashed_regions中。加入的具体实现是:
inistatic void hash_region_insert_no_lock(region_t *regions, size_t num_entries, size_t shift, region_t r) { size_t index, hash_index; rgnhdl_t entry; index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 11400714819323198549ULL) >> (64 - shift); do { entry = regions + index; if (*entry == HASHRING_OPEN_ENTRY || *entry == HASHRING_REGION_DEALLOCATED) { *entry = r; return; } if (++index == num_entries) { index = 0; } } while (index != hash_index); }
首先计算region的hash,以hash作为index,查找index位是否存储数据,如果已存储数据,则不断++index偏移至下一位查找是否有空位,直到找到空位,当index位于buffer数组末尾,则从0继续遍历,整个遍历查找过程是一个环,如果查找回到最初的index,则终止。其中,这里判断是当前hashed_regions[index]的值是否是枚举值HASHRING_OPEN_ENTRY和HASHRING_REGION_DEALLOCATED,HASHRING_OPEN_ENTRY是默认值0,表示没有存过数据,HASHRING_REGION_DEALLOCATED是-1表示之前存储的值被清空了。 如图,计算出region的hash index,由于对应的hashed_regions[hash index]上已经存储数据了,则偏移直到找到空位存储。
hash查找
可以通过查询hash_regions中的index,判断rack中是否存在region。
ini
static rgnhdl_t hash_lookup_region_no_lock(region_t *regions, size_t num_entries, size_t shift, region_t r)
{
size_t index, hash_index;
rgnhdl_t entry;
if (!num_entries) {
return 0;
}
index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 11400714819323198549ULL) >> (64 - shift);
do {
entry = regions + index;
if (*entry == 0) {
return 0;
}
if (*entry == r) {
return entry;
}
if (++index == num_entries) {
index = 0;
}
} while (index != hash_index);
return 0;
}
查找和添加的规则是相同的。
hash删除
当一个region被回收时,调用rack_region_remove方法删除对应的hash index。
rust
bool rack_region_remove(rack_t *rack, region_t region, region_trailer_t *trailer)
{
bool rv = true;
//...
rgnhdl_t pSlot = hash_lookup_region_no_lock(
rack->region_generation->hashed_regions,
rack->region_generation->num_regions_allocated,
rack->region_generation->num_regions_allocated_shift,
region);
//...
*pSlot = HASHRING_REGION_DEALLOCATED;
return rv;
}
其中,pSlot是查找到的hash index,置位HASHRING_REGION_DEALLOCATED,这样下次可以复用。这里不会回收hashed_regions内存,而是置空数据给后续region的hash index复用。
内存状态管理
上文所述,magazine中分配的内存实际从region中分配,并回收复用。为了管理magazine中的内存,首先需要记录这些内存的状态,因为只有知道每块内存的状态,才能有效判断,实现复用的逻辑,可以分为以下3种:
- 未分配:未被分配的内存空间,即region中的剩余内存空间。
- 分配并被使用:从region中分配了一块指定size的内存块,并且正在使用中。
- 未使用:被回收的内存块,可以被复用。
用一张图来说明: magazine使用了一些数据结构和机制记录这些状态。
bit位状态
使用mag_bitmap[]数组记录每一级msize是否存在内存,例如下图:
slot是msize的值,例如16B的slot是0,32B的slot是1,bitmap数组中每个index对应的值bitmap[index]可以存储4字节32bit位的内容,则mag_bitmap[0]可以表示slot0~31范围内的状态。图中,bit位0、1、2、3、8、12、17、18、26为1,则对应msize的空闲链表存在空闲内存块,对于tiny内存,存在16B、32B、48B、64B、144B、208B、288B、304B、432B的空闲内存块。
magazine定义了一些宏来操作bit位,以64位为例:
scss
#define BITMAPV_SET(bitmap, slot) (bitmap[(slot) >> 5] |= 1 << ((slot)&31))
#define BITMAPV_CLR(bitmap, slot) (bitmap[(slot) >> 5] &= ~(1 << ((slot)&31)))
#define BITMAPV_BIT(bitmap, slot) ((bitmap[(slot) >> 5] >> ((slot)&31)) & 1)
block状态管理
mag_bitmap是magazine记录的magazine全局的内存块状态信息,同时对于每一块region,也会维护其内部内存块的状态。
region中内存块由若干个16B大小的block构成,如图所示,淡黄色是msize=1的内存块,由1个block组成,黄色是msize=2的内存块,由2个block组成。
通过记录这些block的状态,来实现记录内存块状态的例如一个msize=2的内存块,共包含2个block,则region会记录这2个block的状态,是否被分配在使用中,或者是否被回收,同时记录哪几个block属于同一个内存块,同时还要能够访问前后内存地址相邻的内存块地址,方便回收时的合并操作。
block的状态信息存储在pairs字段中,定义如下:
arduino
typedef struct tiny_header_inuse_pair {
uint32_t header;
uint32_t inuse;
} tiny_header_inuse_pair_t;
tiny_header_inuse_pair_t pairs[CEIL_NUM_TINY_BLOCKS_WORDS];
pairs字段可以理解为region的Header信息,是tiny_header_inuse_pair_t(简称pair)结构体数组,数组长度通过CEIL_NUM_TINY_BLOCKS_WORDS宏表示。
arduino
#define CEIL_NUM_TINY_BLOCKS_WORDS (((NUM_TINY_BLOCKS + 31) & ~31) >> 5)
每个pair结构体元素包含header和inuse2个字段,长度分别是32bit,因此最多可以存储32个block内存块的状态信息。而一块tiny region共有64504个block,CEIL_NUM_TINY_BLOCKS_WORDS表示存储这些block共需要的pair个数。
而每个pair结构体的字段通过记录这些内存块中block的bit值,来存储当前内存块的状态。其中header字段的bit位记录某个内存块是否被分配以及分配的msize长度,inuse字段记录该内存块的使用状态,是正在使用还是被回收未使用。
未分配
初始化情况下,region内未分配任何内存块,则所有block的bit均为0,下图是一个pair结构体下32个block的bit值,均为0(黄色表示)。
其中header和inuse上的bit位均为0.
分配使用
当分配内存块时,bit位更新,分为2种情况,如果分配的内存msize是1,执行set_tiny_meta_header_in_use_1函数逻辑。如果分配的内存msize大于1,执行set_tiny_meta_header_in_use函数逻辑。
ini
static MALLOC_INLINE void set_tiny_meta_header_in_use_1(const void *ptr) // As above with msize == 1
{
uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
msize_t index = TINY_INDEX_FOR_PTR(ptr);
msize_t midx = (index >> 5) << 1;
uint32_t val = (1 << (index & 31));
block_header[midx] |= val; // BITARRAY_SET(block_header, index);
block_header[midx + 1] |= val; // BITARRAY_SET(in_use, index);
index++;
midx = (index >> 5) << 1;
val = (1 << (index & 31));
block_header[midx] |= val; // BITARRAY_SET(block_header, (index+clr_msize))
}
static MALLOC_INLINE void set_tiny_meta_header_in_use(const void *ptr, msize_t msize)
{
uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
msize_t index = TINY_INDEX_FOR_PTR(ptr);
msize_t clr_msize = msize - 1;
msize_t midx = (index >> 5) << 1;
uint32_t val = (1 << (index & 31));
block_header[midx] |= val; // BITARRAY_SET(block_header, index);
block_header[midx + 1] |= val; // BITARRAY_SET(in_use, index);
index++;
midx = (index >> 5) << 1;
unsigned start = index & 31;
unsigned end = start + clr_msize;
#if defined(__LP64__)
if (end > 63) {
unsigned mask0 = (0xFFFFFFFFU >> (31 - start)) >> 1;
unsigned mask1 = (0xFFFFFFFFU << (end - 64));
block_header[midx + 0] &= mask0; // clear header
block_header[midx + 1] &= mask0; // clear in_use
block_header[midx + 2] = 0; // clear header
block_header[midx + 3] = 0; // clear in_use
block_header[midx + 4] &= mask1; // clear header
block_header[midx + 5] &= mask1; // clear in_use
} else
#endif
if (end > 31) {
unsigned mask0 = (0xFFFFFFFFU >> (31 - start)) >> 1;
unsigned mask1 = (0xFFFFFFFFU << (end - 32));
block_header[midx + 0] &= mask0;
block_header[midx + 1] &= mask0;
block_header[midx + 2] &= mask1;
block_header[midx + 3] &= mask1;
} else {
unsigned mask = (0xFFFFFFFFU >> (31 - start)) >> 1;
mask |= (0xFFFFFFFFU << end);
block_header[midx + 0] &= mask;
block_header[midx + 1] &= mask;
}
// we set the block_header bit for the following block to reaffirm next block is a block
index += clr_msize;
midx = (index >> 5) << 1;
val = (1 << (index & 31));
block_header[midx] |= val; // BITARRAY_SET(block_header, (index+clr_msize));
}
其中,midx是pair的index,首先>>5定位表示每32个block的bit值存储在同一个pair中,<<1是因为block_header指针是32位,指向的pair长度包含2个32位字段,所以block_header表示的数组长度乘2,如图:
block_header[midx]和block_header[midx+1]对应pair中的header和inuse。val是block index对应的bit位。
设置的规则是:
- 设置header字段的bit位数值,规则是根据当前分配的内存的msize长度,进行设置,内存块包含的第一个block对应的bit位设置为1,其余block对应的bit位设置为0。
- 为了标记内存块的边界范围,需要设置内存块中最后一个block的后一个block的bit为数值为1。因为确定内存块大小的的逻辑是从起始block的bit位开始,直到找到下一个为1的bit位,该bit位表示一个新的内存块的起始位置。
- 设置inuse字段的bit位数值,规则是内存块包含的第一个block对应的bit位设置为1,其余block对应的bit位设置为0。
当分配一个msize=1内存,pair字段对应的bit值如图。 由于这里分配的内存块msize是1,仅包含一个block,则header的bit位数值是1 1,insue的bit位数值是1。
当分配的内存块msize大于1,例如msize=5,分为两种情况:
1)内存块下的5个block位于同一个pair index,从起始block开始,包含5个bit位数据1 0 0 0 0,下一个内存开始的bit设置为1,标识边界,同时inuse的bit位标记为1 0 0 0 0,只需要起始block的bit位标记为1,其余为0. 2)如果包含的block位于不同的pair index,则需要记录在不同的header中。
未使用
当发生回收逻辑时,内存块会被加入回收缓存链表,则对应的bit位需要标记为空闲未使用状态。
ini
static MALLOC_INLINE void
set_tiny_meta_header_free(const void *ptr, msize_t msize)
{
// !msize is acceptable and means 65536
uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
msize_t index = TINY_INDEX_FOR_PTR(ptr);
msize_t midx = (index >> 5) << 1;
uint32_t val = (1 << (index & 31));
block_header[midx] |= val; // BITARRAY_SET(block_header, index);
block_header[midx + 1] &= ~val; // BITARRAY_CLR(in_use, index);
//...
}
inuse字段bit位更新,首个block的bit位更新为0,这里同样需要更新header的bit位,如果该内存之前分配过,则header的bit位没有变化,如图: 如果该内存是从一块大的空闲内存块中分割的,则这是一块新的空闲内存,如图,例如需要分配一块msize=5的内存块,匹配到一块的msize=8的空闲内存块,于是分割成两块,msize=5的内存块标记为使用状态,分割后生成一个msize=3的空闲内存块,header上的bit位值更新成1。
清除
当一块内存块不存在时,相应的bit位需要被清除,例如相邻空闲内存块存在合并的操作,则对应的bit位也需要更新,例如前后msize=5和misze=3的前后内存块合并成一块msize=8的更大空闲内存块,将misze=3的内存块的第一个block对应的bit位设置为0。
bit值变更如图: 设置代码如下:
scss
static MALLOC_INLINE void
set_tiny_meta_header_middle(const void *ptr)
{
// indicates this block is in the middle of an in use block
uint32_t *block_header;
uint32_t *in_use;
msize_t index;
block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
in_use = TINY_INUSE_FOR_HEADER(block_header);
index = TINY_INDEX_FOR_PTR(ptr);
BITARRAY_CLR(block_header, index);
BITARRAY_CLR(in_use, index);
}
#define TINY_BLOCK_HEADER_FOR_REGION(region) ((void *)&(((tiny_region_t)region)->pairs))
#define TINY_INUSE_FOR_HEADER(_h) ((void *)&(((tiny_header_inuse_pair_t *)(_h))->inuse))
in_use和block_header可以通过region的字段直接获取,BITARRAY_CLR宏定义了清空bit位值的方法,同时封装了计算下标的操作,等效于上文的block_header index计算逻辑。
arduino
static void BITARRAY_CLR(uint32_t *bits, msize_t index)
{
bits[(index >> 5) << 1] &= ~(1 << (index & 31));
}
状态判断
更新了bit信息之后,就可以根据header和inuse字段的bit值判断当前内存块的状态,例如判断一块内存是否处于空闲状态。
ini
static boolean_t tiny_meta_header_is_free(const void *ptr)
{
uint32_t *block_header;
uint32_t *in_use;
msize_t index;
block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
in_use = TINY_INUSE_FOR_HEADER(block_header);
index = TINY_INDEX_FOR_PTR(ptr);
if (!BITARRAY_BIT(block_header, index)) {
return 0;
}
return !BITARRAY_BIT(in_use, index);
}
- 获取内存块第一个block的index
- 读取block_header字段对应bit位的数值,如果为0,说明该内存块不存在,返回false
- 如果内存块存在,读取in_use字段对应bit位的数值,如果为0,说明是空闲,返回true,否则返回false。
下图对应上述3种情况。
空闲内存管理
除了记录内存状态,magazine需要使用其他数据结构管理内存,例如回收内存的复用。其中,空闲链表mag_free_list是核心结构,当region中有内存块被回收时,按照size分级,将回收的内存块加入到magazine的空闲链表结构mag_free_list中管理。例如tiny下mag_free_list共63个(如上图),说明图中f1~f5位于不同的Region,由于size分级相同,位于同一个mag_free_list中。下图描述Region中内存的回收过程。
- Region1和Region2中分配若干size不等的内存,Region2中存在部分剩余空间未分配(白色区域)
- Region1和Region2中部分内存被回收,例如先后回收了f1~f5,其中f1、f3、f4的msize相同是1,加入同一个mag_free_list中,f2、f5的msize相同是2,加入同一个mag_free_list中。
- 再次分配内存,首先从对应size级别的mag_free_list中分配,例如分配2次16B的内存,先后取f4、f3,分配1次32B内存,取f5。
- mag_bitmap字段记录了msize下是否存在之被回收的空闲内存,是bit位数组,每个bit位对应一个msize,1表示存在,0表示不存在。这样,分配内存时,首先查看mag_bitmap下对应的bit位是为1,如果为1,则继续从对应的mag_free_list中返回内存。如果为0,则判断更大的bit位是否为1,即是否存在msize更大的空闲内存块。
接下来,结合代码个图示详细讲解内存复用的实现机制。
数据结构
链表指针
mag_free_list链表结构的定义如下:
arduino
typedef struct magazine_s {
free_list_t mag_free_list[MAGAZINE_FREELIST_SLOTS];
} magazine_t;
在tiny中,free_list_t结构被转换成了tiny_free_list_t结构,如下:
arduino
typedef struct {
inplace_union previous;
inplace_union next;
} tiny_free_list_t;
其中,previous字段是指向前一个空闲内存块的指针数据,next字段是指向后一个空闲内存块的指针数据,这样就可以遍历查询链表中所有空闲内存块地址。如上图所示。之所以使用前16字节可以存储链表字段,是因为一个内存块至少包含16字节的长度(msize=1)。当内存块被回收时,会清空原本存储的数据,然后内存块的前16字节存储tiny_free_list_t字段数据。
msize数据
除了存储空闲链表字段外,还会存储空闲内存块的长度msize数据,方便后续操作,例如查找当前空闲块在物理地址相邻的前一个空闲块,如图所示:
当回收msize=1的内存块,则回收的内存块共16字节,前后8字节分别存储链表的prev、next指针。如果回收msize>1的内存块,例如msize=3,共48字节,前16字节分别存储prev、next指针之外,后2字节存储msize,同时最后2字节也存储当前内存块的msize。之所以最后2字节也存储msize,这样,当物理地址相邻的后一个内存块也会回收时,通过内存块地址ptr[-1]可以知道前一个空闲块的msize,快速定位到前一个空闲块的起始地址,辅助内存合并等操作,如图,内存块2被回收时,通过ptr[-1]获取空闲块1的msize。 设置msize信息的具体代码在set_tiny_meta_header_free函数中。
scss
static MALLOC_INLINE void
set_tiny_meta_header_free(const void *ptr, msize_t msize)
{
//...
if (msize > 1) {
void *follower = FOLLOWING_TINY_PTR(ptr, msize);
TINY_PREVIOUS_MSIZE(follower) = msize;
TINY_FREE_SIZE(ptr) = msize;
}
if (msize == 0) {
TINY_FREE_SIZE(ptr) = msize;
}
}
#define TINY_FREE_SIZE(ptr) (((msize_t *)(ptr))[8])
#define TINY_PREVIOUS_MSIZE(ptr) ((msize_t *)(ptr))[-1]
其中FOLLOWING_TINY_PTR(ptr, msize)获取下一个内存块的起始地址,TINY_PREVIOUS_MSIZE获取该地址的前2字节,即当前内存块的最后2字节存储msize,TINY_FREE_SIZE(ptr)是起始地址偏移16字节后的2字节存储msize。
magazine相关
mag_free_list
如上文所述,magazine使用空闲链表mag_free_list[slot]来管理所有空闲的内存块,即magazine下所有region中的相同msize的空闲内存块,都会被维护到同一个空闲链表mag_free_list[slot]中,如图所示,magazine下共分配了region1和region2两块region,其中region1中包含两块msize=3的空闲块,region2中包含一块,3个空闲块组成了。
magazine的mag_free_list[slot]记录的是链表的第一个空闲内存块的地址。
mag_bitmap
如上文所述,和mag_free_list字段对应,magazine使用mag_bitmap字段记录magazine是否存在msize的空闲内存块,每个mag_bitmap[i]是uint32_t类型4字节32个bit位,因此可以存储32个msize的信息,具体可以参考"bit位设置"的介绍。
region相关
free_blocks_by_slot
除了magazine维护的空闲链表,每个region也记录了该region内的所有空闲内存块,存储在free_blocks_by_slot字段中。
arduino
typedef struct tiny_region {
// Indices of the first and last free block in this region. Value is the
// block index + 1 so that 0 indicates no free block in this region for the
// corresponding slot.
region_free_blocks_t free_blocks_by_slot[NUM_TINY_SLOTS];
} * tiny_region_t;
free_blocks_by_slot数组存储的是相同msize的第一个空闲块的block index和最后一个空闲块的block index,index是从1开始,而不是0,如果free_blocks_by_slot[msize]是0,表示region中没有长度是msize的空闲块。数据结构如下:
arduino
typedef struct {
uint16_t first_block;
uint16_t last_block;
} region_free_blocks_t;
region_free_blocks_t结构的first_block存储了region中最近一次被回收的空闲内存块的block index,last_block存储了最先被回收的空闲内存块的block index。
如图所示,例如region中先后回收了f1、f2、f3、f4个内存块,对应的起始block index分别是5、7、10、19,其中f1、f2、f4的msize=1,则region_free_blocks_t[0]的first_block存储f4的block index+1=20,last_block存储f1的block index+1=6。region中msize=3的内存块只有f3被回收,则region_free_blocks_t[2]的first_block和last_block的值都是10+1=11。
之所以region中也记录空闲内存块,是为了方便操作空闲链表的操作,例如快速定位region内最近一次被回收的内存块。
相关操作
分析了数据结构后,接下来结合代码分析空闲链表的相关操作。在梳理逻辑之前,首先介绍一下magazine的多个region中空闲内存块的链表结构。
如图,mag_free_list中维护的空闲内存节点的整体顺序和region内存的前后顺序一致,对于同一块region中的内存,则将最新回收的内存插入最前面。例如上图,内存1~5依次被回收,24位于region1,则24插入链表前部,4回收时机较迟,作为首节点,2位于4后面,13位于region2,位于链表中部,3较晚回收,3在1前面,5位于region3,放在链表最后。region内的空闲内存节点顺序和region_free_blocks_t[msize]的的block index顺序一致,最新回收的内存的block index是first_block,最早回收的内存的block index是last_block。
添加进链表
当一个内存块被回收时,需要将其添加进空闲链表,代码如下:
ini
static void tiny_free_list_add_ptr(rack_t *rack, magazine_t *tiny_mag_ptr, void *ptr, msize_t msize)
{
grain_t slot = (!msize || (msize > NUM_TINY_SLOTS)) ? NUM_TINY_SLOTS : msize - 1;
tiny_free_list_t *free_ptr = ptr;
tiny_free_list_t *free_head = tiny_mag_ptr->mag_free_list[slot].p;
//1.设置空闲状态
set_tiny_meta_header_free(ptr, msize);
//2.设置bit位状态
if (free_head) {
} else {
BITMAPV_SET(tiny_mag_ptr->mag_bitmap, slot);
}
tiny_region_t region = TINY_REGION_FOR_PTR(ptr);
//region内msize对应的空闲内存index
region_free_blocks_t *free_blocks = ®ion->free_blocks_by_slot[slot];
//第一个空闲块
uint16_t first_free_block_index = free_blocks->first_block;
//当前内存块的block index
uint16_t this_block_index = TINY_INDEX_FOR_PTR(ptr);
//存在msize对应的空闲内存index
if (first_free_block_index) {
//firstblock对应的空闲块
tiny_free_list_t *old_first_free = TINY_PTR_FOR_INDEX(first_free_block_index - 1, region);
//前一个内存块,位于其他region内
tiny_free_list_t *prev_ptr = free_list_unchecksum_ptr(rack, &old_first_free->previous);
if (!prev_ptr) {
//没有prev,则作为mag_free_list的首个元素
tiny_mag_ptr->mag_free_list[slot].p = free_ptr;
} else {
//有prev,则插入prev的后面
prev_ptr->next.u = free_list_checksum_ptr(rack, free_ptr); // XXX
}
//设置prev关系
free_ptr->previous.u = old_first_free->previous.u;
//设置next和prev关系,free_ptr成为链表中在region区的首个节点
free_ptr->next.u = free_list_checksum_ptr(rack, old_first_free);
old_first_free->previous.u = free_list_checksum_ptr(rack, free_ptr);
//设置为空闲blocks的第一个节点
free_blocks->first_block = this_block_index + 1;
} else {
//当前region没有firstblock,ptr是region下第一个空闲block
tiny_free_list_t *prev_free = NULL;
tiny_free_list_t *next_free;
mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(region);
//遍历mag下的regions,找出前一个region的lastblock,prev_free是对应的内存块
if (mag_index != DEPOT_MAGAZINE_INDEX
&& tiny_mag_ptr->mag_free_list[slot].p) {
region_trailer_t *trailer = REGION_TRAILER_FOR_TINY_REGION(region);
prev_free = tiny_earlier_region_last_free(tiny_mag_ptr, trailer, slot);
}
if (!prev_free) {
//不存在prev,则作为mag_free_list的首个元素
next_free = tiny_mag_ptr->mag_free_list[slot].p;
tiny_mag_ptr->mag_free_list[slot].p = free_ptr;
} else {
//存在prev,则插入prev之后
next_free = free_list_unchecksum_ptr(rack, &prev_free->next);
prev_free->next.u = free_list_checksum_ptr(rack, free_ptr);
}
free_ptr->previous.u = free_list_checksum_ptr(rack, prev_free);
//重新设置prev和next
if (next_free) {
next_free->previous.u = free_list_checksum_ptr(rack, free_ptr);
}
free_ptr->next.u = free_list_checksum_ptr(rack, next_free);
//成为当前region的firstblock
free_blocks->first_block = free_blocks->last_block =
this_block_index + 1;
}
}
结合图示分析:
-
当内存ptr被回收,首先调用set_tiny_meta_header_free方法设置当前内存为空闲状态,并更新设置msize信息。
-
根据内存的msize定位mag_free_list,如果mag_free_list链表为空,则设置mag_bitmap,标记magazine存在msize的空闲内存。
-
根据region的free_blocks字段,查找region内存是否存在空闲内存块,如果存在,则将ptr插入该内存节点之前,并更新当前region的free_blocks->first_block为ptr的block index。下图中内存块5是当前region的first_block,ptr插入3和5之间。
-
如果不存在,则通过tiny_earlier_region_last_free方法,查找离当前region最近的前一个region中的空闲内存块,将ptr插入该内存节点之后,并更新当前region的free_blocks->first_block和last_block为ptr的block index。下图中,ptr所在的region没有空闲块,则向前查找region,1是最近的空闲块,ptr放在1之后。 其中tiny_earlier_region_last_free方法通过遍历region链表,查找是否存在相同msize的空闲块,直到遍历到当前region为止。
inistatic void * tiny_earlier_region_last_free(magazine_t *tiny_mag_ptr, region_trailer_t *trailer, grain_t slot) { //... while (next_trailer && next_trailer != trailer && count++ < 5) { tiny_region_t r = TINY_REGION_FOR_PTR(next_trailer); uint16_t block = r->free_blocks_by_slot[slot].last_block; if (block) { target_block = block; target_trailer = next_trailer; } next_trailer = next_trailer->next; } }
-
如果前面的region不存在空闲内存,则mag_free_list[msize]的首节点是ptr。
从链表删除
当空闲内存块被重新使用,需要从空闲链表结构中删除。
ini
static void tiny_free_list_remove_ptr(rack_t *rack, magazine_t *tiny_mag_ptr, void *ptr, msize_t msize)
{
grain_t slot = tiny_slot_from_msize(msize);
tiny_free_list_t *free_ptr = ptr, *next, *previous;
//后一个空闲块
next = free_list_unchecksum_ptr(rack, &free_ptr->next);
//前一个空闲块
previous = free_list_unchecksum_ptr(rack, &free_ptr->previous);
if (!previous) { //没有prev,则删除的是链表首节点
//next设置为首节点
tiny_mag_ptr->mag_free_list[slot].p = next;
if (!next) {
BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot);
}
} else {
//设置prev的next指针指向next内存
previous->next = free_ptr->next;
}
if (next) { //存在next内存
//设置next的prev指针指向prev内存
next->previous = free_ptr->previous;
}
tiny_region_t region = TINY_REGION_FOR_PTR(ptr);
region_free_blocks_t *free_blocks = ®ion->free_blocks_by_slot[slot];
uint16_t this_block_index = TINY_INDEX_FOR_PTR(ptr);
//是否是region中first_block或者last_block
boolean_t is_first = free_blocks->first_block == this_block_index + 1;
boolean_t is_last = free_blocks->last_block == this_block_index + 1;
if (is_first && is_last) {
//是first_block和last_block,则region中只有该msize空闲块,移除后,region内不存在msize空闲块,更新为0
free_blocks->first_block = free_blocks->last_block = 0;
} else if (is_first) {
//是first,则存在next内存,移除后first替换为next
free_blocks->first_block = TINY_INDEX_FOR_PTR(next) + 1;
} else if (is_last) {
//是last,则存在prev内存,移除后last替换为previous
free_blocks->last_block = TINY_INDEX_FOR_PTR(previous) + 1;
}
}
结合图示分析:
- 例如空闲内存块ptr被再次使用时,首先通过prev、next指针查找链表上前后空闲块。
- 如果没有prev,则ptr是mag_free_list链表的首节点,则将next设置为首节点,如果next也不存在,则ptr移除后链表中已经不存在msize的空闲块了,调用BITMAPV_CLR清空将mag_bitmap上的对应bit标记位置为0。
- 如果有prev,则prev的next指针指向next内存。这样ptr从链表中删除。
- 更新region的free_blocks字段,如果first_block和last_block均等于ptr的block index,则当前region只存在一个msize的空闲块ptr,移除后,first_block和last_block均置为0。
- 如果first_block和last_block不等,则region中至少包含两个msize的空闲块。
- 如果first_block是ptr的block index,则ptr是链表中在region范围内的第一个空闲节点,ptr移除后,first_block替换为链表中后一个空闲块的block index。
- 如果last_block是ptr的block index,则ptr是链表中在region范围内的最后一个空闲节点,ptr移除后,last_block替换为链表中前一个空闲块的block index。
查看前后空闲块
获取相邻前一个空闲块的内存起始地址。
ini
static void *tiny_previous_preceding_free(void *ptr, msize_t *prev_msize)
{
uint32_t *block_header;
uint32_t *in_use;
msize_t index;
msize_t previous_msize;
msize_t previous_index;
void *previous_ptr;
block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
in_use = TINY_INUSE_FOR_HEADER(block_header);
index = TINY_INDEX_FOR_PTR(ptr);
if (!index) {
return NULL;
}
if ((previous_msize = get_tiny_previous_free_msize(ptr)) > index) {
return NULL;
}
previous_index = index - previous_msize;
previous_ptr = TINY_PTR_FOR_INDEX(previous_index, TINY_REGION_FOR_PTR(ptr));
if (!BITARRAY_BIT(block_header, previous_index)) {
return NULL;
}
if (BITARRAY_BIT(in_use, previous_index)) {
return NULL;
}
if (get_tiny_free_size(previous_ptr) != previous_msize) {
return NULL;
}
// conservative check did match true check
*prev_msize = previous_msize;
return previous_ptr;
}
ptr是当前空闲内存块的地址,步骤如下:
-
index是ptr的block index,使用get_tiny_previous_free_msize尝试获取前一个空闲内存的msize。
scss#define TINY_PREVIOUS_MSIZE(ptr) ((msize_t *)(ptr))[-1] #define TINY_FREE_SIZE(ptr) (((msize_t *)(ptr))[8]) static msize_t get_tiny_previous_free_msize(const void *ptr) { if (ptr != TINY_REGION_HEAP_BASE(TINY_REGION_FOR_PTR(ptr))) { void *prev_block = (void *)((uintptr_t)ptr - TINY_QUANTUM); uint32_t *prev_header = TINY_BLOCK_HEADER_FOR_PTR(prev_block); msize_t prev_index = TINY_INDEX_FOR_PTR(prev_block); if (BITARRAY_BIT(prev_header, prev_index)) { return 1; } msize_t *prev_msize_ptr = &TINY_PREVIOUS_MSIZE(ptr); return _malloc_read_uint16_via_rsp(prev_msize_ptr); } return 0; }
首先判断向前偏移一个block的内存,如果BITARRAY_BIT为1,则存在内存块,msize是1,如果不是,则使用TINY_PREVIOUS_MSIZE宏取ptr地址的向前偏移2字节的内存,因为存储了前一个空闲块的msize信息,返回值为previous_msize。
-
previous_ptr是向前偏移previous_msize长度后,前一个空闲块的起始地址,previous_index是对应的block index。
-
判断previous_index的block_header bit位,如果是1,则存在该内存,进一步判断inuse bit位,如果是0,则是空闲块,调用get_tiny_free_size取8字节后的2字节存储的msize和之前的previous_msize比较,进一步校验。这些通过后,表示存在相邻的前一块空闲块,长度是previous_msize。
相较于获取前一块内存地址,获取相邻地址后一块空闲内存块的地址简单,ptr地址直接加上msize得到next起始地址。
ini
size_t original_size = TINY_BYTES_FOR_MSIZE(msize);
void *next_block = ((unsigned char *)ptr + original_size);
如图所示: 接下来,结合代码分析tiny内存的分配与回收的整体逻辑。
底层内存管理
当分配和回收region时触发系统层的分配操作,实质调用底层接口管理虚拟内存,libmalloc做了封装,支持参数控制。
内存分配
ini
void *mvm_allocate_pages(size_t size, unsigned char align, uint32_t debug_flags, int vm_page_label) {
boolean_t add_prelude_guard_page = debug_flags & MALLOC_ADD_PRELUDE_GUARD_PAGE;
boolean_t add_postlude_guard_page = debug_flags & MALLOC_ADD_POSTLUDE_GUARD_PAGE;
boolean_t purgeable = debug_flags & MALLOC_PURGEABLE;
boolean_t use_entropic_range = !(debug_flags & DISABLE_ASLR);
vm_address_t vm_addr;
uintptr_t addr;
vm_size_t allocation_size = round_page_quanta(size);
mach_vm_offset_t allocation_mask = ((mach_vm_offset_t)1 << align) - 1;
int alloc_flags = VM_FLAGS_ANYWHERE | VM_MAKE_TAG(vm_page_label);
kern_return_t kr;
if (!allocation_size) {
allocation_size = vm_page_quanta_size;
}
if (add_postlude_guard_page || add_prelude_guard_page) {
if (add_prelude_guard_page && align > vm_page_quanta_shift) {
allocation_size += (1 << align) + large_vm_page_quanta_size;
} else {
allocation_size += add_prelude_guard_page && add_postlude_guard_page ?
2 * large_vm_page_quanta_size : large_vm_page_quanta_size;
}
}
if (purgeable) {
alloc_flags |= VM_FLAGS_PURGABLE;
}
if (allocation_size < size) {
return NULL;
}
retry:
vm_addr = use_entropic_range ? entropic_address : vm_page_quanta_size;
kr = mach_vm_map(mach_task_self(), &vm_addr, allocation_size,
allocation_mask, alloc_flags, MEMORY_OBJECT_NULL, 0, FALSE,
VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT);
if (kr == KERN_NO_SPACE && use_entropic_range) {
vm_addr = vm_page_quanta_size;
kr = mach_vm_map(mach_task_self(), &vm_addr, allocation_size,
allocation_mask, alloc_flags, MEMORY_OBJECT_NULL, 0, FALSE,
VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT);
}
if (kr) {
if (kr != KERN_NO_SPACE) {
//...
}
return NULL;
}
//...
if (add_postlude_guard_page || add_prelude_guard_page) {
//...
mvm_protect((void *)addr, size, PROT_NONE, debug_flags);
}
return (void *)addr;
}
核心调用是通过xnu的mach_vm_map分配一块allocation_size大小的虚拟内存,allocation_size是经过对齐后的大小,debug_flags控制分配逻辑,例如add_prelude_guard_page和add_postlude_guard_page,通过mvm_protect方法对指定内存的操作权限进行控制。
内存回收
ini
void mvm_deallocate_pages(void *addr, size_t size, unsigned debug_flags)
{
boolean_t added_prelude_guard_page = debug_flags & MALLOC_ADD_PRELUDE_GUARD_PAGE;
boolean_t added_postlude_guard_page = debug_flags & MALLOC_ADD_POSTLUDE_GUARD_PAGE;
vm_address_t vm_addr = (vm_address_t)addr;
vm_size_t allocation_size = size;
kern_return_t kr;
if (added_prelude_guard_page) {
vm_addr -= large_vm_page_quanta_size;
allocation_size += large_vm_page_quanta_size;
}
if (added_postlude_guard_page) {
allocation_size += large_vm_page_quanta_size;
}
kr = vm_deallocate(mach_task_self(), vm_addr, allocation_size);
if (kr) {
malloc_zone_error(debug_flags, false, "Can't deallocate_pages region at %p\n", addr);
}
}
核心方法是通过vm_deallocate回收对齐后的内存空间。
内存权限
arduino
#define PROT_NONE 0x00 /* [MC2] no permissions */
#define PROT_READ 0x01 /* [MC2] pages can be read */
#define PROT_WRITE 0x02 /* [MC2] pages can be written */
#define PROT_EXEC 0x04 /* [MC2] pages can be executed */
void mvm_protect(void *address, size_t size, unsigned protection, unsigned debug_flags)
{
kern_return_t err;
if ((debug_flags & MALLOC_ADD_PRELUDE_GUARD_PAGE) && !(debug_flags & MALLOC_DONT_PROTECT_PRELUDE)) {
err = mprotect((void *)((uintptr_t)address - large_vm_page_quanta_size), large_vm_page_quanta_size, protection);
}
if ((debug_flags & MALLOC_ADD_POSTLUDE_GUARD_PAGE) && !(debug_flags & MALLOC_DONT_PROTECT_POSTLUDE)) {
err = mprotect((void *)(round_page_quanta(((uintptr_t)address + size))), large_vm_page_quanta_size, protection);
}
}
调用mprotect设置虚拟内存的访问权限,address和size控制了内存范围。
了解tiny相关结构后,接下来分析tiny是如何分配和和回收内存的。
depot magazine
depot magazine是rack下一个特殊的magazine,下标是-1.
arduino
#define DEPOT_MAGAZINE_INDEX -1
rack->magazines[DEPOT_MAGAZINE_INDEX]
depot magazine下维护的region是从原magazine中的region迁移过来的,触发条件是,当内存回收时,region中的空闲内存占比达到一定阈值时,发生迁移,主要包含两部分:
- region从原magazine迁移至depot magazine。
- region内的所有空闲内存块从原mag_free_list空闲链表迁移至depot magazine的mag_free_list。
迁移操作如图,深黄色要迁移的region,regions链表和freelist迁移至depot magazine中。 逻辑整体逻辑如下:
arduino
static MALLOC_INLINE boolean_t
tiny_free_try_recirc_to_depot(rack_t *rack,
magazine_t *tiny_mag_ptr,
mag_index_t mag_index,
region_t region,
void *headptr,
size_t headsize,
void *ptr,
msize_t msize)
{
region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(region);
size_t bytes_used = node->bytes_used;
if (DEPOT_MAGAZINE_INDEX != mag_index) {
//当空闲内存达到一定程度,移到depot magazine
if (tiny_magazine_below_recirc_threshold(tiny_mag_ptr)) {
return tiny_free_do_recirc_to_depot(rack, tiny_mag_ptr, mag_index);
}
}
}
首先进行阈值判断:
scss
static MALLOC_INLINE boolean_t tiny_magazine_below_recirc_threshold(magazine_t *mag_ptr)
size_t a = mag_ptr->num_bytes_in_magazine; // Total bytes allocated to this magazine
size_t u = mag_ptr->mag_num_bytes_in_objects; // In use (malloc'd) from this magaqzine
return a - u > ((3 * TINY_HEAP_SIZE) / 2)
&& u < DENSITY_THRESHOLD(a);
}
#define DENSITY_THRESHOLD(a) ((a) - ((a) >> 2))
条件是当magazine中未使用的内存大小大于总内存的3/4时,发生迁移操作。
调用tiny_free_do_recirc_to_depot方法,代码如下:
ini
static boolean_t tiny_free_do_recirc_to_depot(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index)
{
region_trailer_t *node = tiny_mag_ptr->lastNode;
region_t sparse_region = TINY_REGION_FOR_PTR(node);
//...
recirc_list_extract(rack, tiny_mag_ptr, node);
int objects_in_use = tiny_free_detach_region(rack, tiny_mag_ptr, sparse_region);
magazine_t *depot_ptr = &(rack->magazines[DEPOT_MAGAZINE_INDEX]);
//...
size_t bytes_inplay = tiny_free_reattach_region(rack, depot_ptr, sparse_region);
tiny_mag_ptr->mag_num_bytes_in_objects -= bytes_inplay;
tiny_mag_ptr->num_bytes_in_magazine -= TINY_HEAP_SIZE;
tiny_mag_ptr->mag_num_objects -= objects_in_use;
//...
depot_ptr->mag_num_bytes_in_objects += bytes_inplay;
depot_ptr->num_bytes_in_magazine += TINY_HEAP_SIZE;
depot_ptr->mag_num_objects += objects_in_use;
recirc_list_splice_last(rack, depot_ptr, node);
//...
}
包括以下流程:
- 调用recirc_list_extract方法将region从magazine的region链表中删除
ini
region_trailer_t *node = tiny_mag_ptr->lastNode;
recirc_list_extract(rack, tiny_mag_ptr, node);
- 调用tiny_free_detach_region方法将region中的所有内存块从magazine的freelist中删除。
ini
int tiny_free_detach_region(rack_t *rack, magazine_t *tiny_mag_ptr, region_t r)
{
uintptr_t start = (uintptr_t)TINY_REGION_HEAP_BASE(r);
uintptr_t current = start;
uintptr_t limit = (uintptr_t)TINY_REGION_HEAP_END(r);
boolean_t is_free;
msize_t msize;
region_trailer_t *trailer = REGION_TRAILER_FOR_TINY_REGION(r);
while (current < limit) {
msize = get_tiny_meta_header((void *)current, &is_free);
if (is_free && !msize && (current == start)) {
break;
}
if (!msize) {
break;
}
if (is_free) {
//从空闲链表中删除空闲内存
tiny_free_list_remove_ptr(rack, tiny_mag_ptr, (void *)current, msize);
}
current += TINY_BYTES_FOR_MSIZE(msize);
}
return trailer->objects_in_use;
}
方法内部遍历region中的所有空闲内存块,调用tiny_free_list_remove_ptr将其从mag_free_list中删除。 3. 调用tiny_free_reattach_region方法将region中的所有内存块添加进depot magazine的freelist中。
ini
size_t tiny_free_reattach_region(rack_t *rack, magazine_t *tiny_mag_ptr, region_t r)
{
uintptr_t start = (uintptr_t)TINY_REGION_HEAP_BASE(r);
uintptr_t current = start;
uintptr_t limit = (uintptr_t)TINY_REGION_HEAP_END(r);
boolean_t is_free;
msize_t msize;
size_t bytes_used = REGION_TRAILER_FOR_TINY_REGION(r)->bytes_used;
while (current < limit) {
msize = get_tiny_meta_header((void *)current, &is_free);
if (is_free && !msize && (current == start)) {
// first block is all free
break;
}
if (!msize) {
break;
}
if (is_free) {
//加入free链表
tiny_free_list_add_ptr(rack, tiny_mag_ptr, (void *)current, msize);
}
current += TINY_BYTES_FOR_MSIZE(msize);
}
return bytes_used;
}
方法内部遍历region中的所有空闲内存块,调用tiny_free_reattach_region将其添加进mag_free_list中。
- 更新magazine的统计信息字段,例如mag_num_bytes_in_objects。
- 调用recirc_list_splice_last方法将region节点添加进depot magazine的region trailer链表
scss
recirc_list_splice_last(rack, depot_ptr, node);
recirc_list_extract、recirc_list_splice_last的具体实现上文已经介绍。
核心流程
介绍完相关结构,接下来还是以tiny内存为例,介绍内存分配回收的具体流程。
内存分配
tiny_malloc_should_clear方法是内存分配的核心方法,核心实现步骤如下:
ini
void *tiny_malloc_should_clear(rack_t *rack, msize_t msize, boolean_t cleared_requested)
{
void *ptr;
mag_index_t mag_index = tiny_mag_get_thread_index() % rack->num_magazines;
magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]);
//...
#if CONFIG_TINY_CACHE
ptr = tiny_mag_ptr->mag_last_free;
if (tiny_mag_ptr->mag_last_free_msize == msize) {
tiny_mag_ptr->mag_last_free = NULL;
tiny_mag_ptr->mag_last_free_msize = 0;
tiny_mag_ptr->mag_last_free_rgn = NULL;
tiny_check_zero_and_clear(ptr, msize, cleared_requested);
return ptr;
}
#endif
while (1) {
ptr = tiny_malloc_from_free_list(rack, tiny_mag_ptr, mag_index, msize);
if (ptr) {
tiny_check_zero_and_clear(ptr, msize, cleared_requested);
return ptr;
}
#if CONFIG_RECIRC_DEPOT
if (tiny_get_region_from_depot(rack, tiny_mag_ptr, mag_index, msize)) {
ptr = tiny_malloc_from_free_list(rack, tiny_mag_ptr, mag_index, msize);
if (ptr) {
tiny_check_zero_and_clear(ptr, msize, cleared_requested);
return ptr;
}
}
#endif
if (!tiny_mag_ptr->alloc_underway) {
void *fresh_region;
tiny_mag_ptr->alloc_underway = TRUE;
fresh_region = mvm_allocate_pages(TINY_REGION_SIZE,
TINY_BLOCKS_ALIGN,
MALLOC_FIX_GUARD_PAGE_FLAGS(rack->debug_flags),
VM_MEMORY_MALLOC_TINY);
if (!fresh_region) { // out of memory!
tiny_mag_ptr->alloc_underway = FALSE;
return NULL;
}
ptr = tiny_malloc_from_region_no_lock(rack, tiny_mag_ptr, mag_index, msize, fresh_region);
} else {
yield();
}
}
}
核心步骤整理如下:
- 选取处理内存的magazine对象tiny_mag_ptr。
- cache匹配逻辑,如果命中则直接返回缓存mag_last_free,不命中继续。
- 从缓存链表freeelist和现有的region中查找可用内存,成功返回,失败继续。
- 查找depot magazine是否有空闲内存,如果有,则将region以及region内的空闲内存迁移到tiny_mag_ptr,不存在则继续。
- 分配一块新的region,并从新region中分配内存。如果失败,返回NULL。
每一步骤涉及的内容较多,下面结合代码与图示具体展开讲解。
cache逻辑
为了提升分配时的性能,当最近一次内存被回收时,使用mag_last_free和mag_last_free_msize记录这次被回收的内存和msize,而不添加进空闲链表mag_free_list。当下一次分配内存时,如果size大小匹配缓存大小,则直接取缓存内存mag_last_free,而不从空闲链表mag_free_list中查找。这样提升了分配效率,如果缓存没匹配上,则从mag_free_list中查找。执行cache匹配逻辑,cache会在内存回收时更新。
更新
由于magazine中存储的cache是最近一次被回收的内存,因此每次回收内存时,会触发cache的更新逻辑。
ini
void free_tiny(rack_t *rack, void *ptr, region_t tiny_region, size_t known_size,
boolean_t partial_free)
{
//取当前cache
void *ptr2 = tiny_mag_ptr->mag_last_free;
msize_t msize2 = tiny_mag_ptr->mag_last_free_msize;
region_t rgn2 = tiny_mag_ptr->mag_last_free_rgn;
//更新cache
tiny_mag_ptr->mag_last_free = ptr;
tiny_mag_ptr->mag_last_free_msize = msize;
tiny_mag_ptr->mag_last_free_rgn = tiny_region;
msize = msize2;
ptr = ptr2;
tiny_region = rgn2;
flags |= TINY_FREE_FLAG_FROM_CACHE;
//之前cache回收添加进freelist
if (tiny_free_no_lock(rack, tiny_mag_ptr, mag_index, tiny_region, ptr, msize, flags)) {
}
}
首先取出magazine的cache,cache更新为当前回收的内存,将之前cache回收添加进freelist。更新cache相关的三个字段:
- mag_last_free:回收的内存地址,作为cache内存地址
- mag_last_free_msize:回收的内存的msize,表示缓存内存块的大小,cache匹配时使用
- mag_last_free_rgn:回收内存的region。
无论之前的cache是否被使用,都会被更新成新回收的内存和msize,如图:
使用
分配内存时,首先匹配cache,如果要分配的内存msize和mag_last_free_msize相等,则命中cache,直接返回mag_last_free,并且清空cache相关字段。如果下次内存分配前没有发生内存回收,则下次分配时无缓存,执行后续逻辑。
ini
void *tiny_malloc_should_clear(rack_t *rack, msize_t msize, boolean_t cleared_requested)
{
//...
#if CONFIG_TINY_CACHE
ptr = tiny_mag_ptr->mag_last_free;
//匹配成功,直接返回cache,清空cache字段。
if (tiny_mag_ptr->mag_last_free_msize == msize) {
tiny_mag_ptr->mag_last_free = NULL;
tiny_mag_ptr->mag_last_free_msize = 0;
tiny_mag_ptr->mag_last_free_rgn = NULL;
tiny_check_zero_and_clear(ptr, msize, cleared_requested);
return ptr;
}
#endif
}
如果cache未命中,则调用tiny_malloc_from_free_list方法从已有的region中查找一块合适的内存空间返回。
空闲内存查找
从magazine管理的空闲链表mag_free_list或者当前region内的剩余空间查找可用的内存块返回。
ini
void *tiny_malloc_from_free_list(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize)
{
tiny_free_list_t *ptr;
msize_t this_msize;
//计算msize
grain_t slot = tiny_slot_from_msize(msize);
//查找空闲链表
free_list_t *free_list = tiny_mag_ptr->mag_free_list;
free_list_t *the_slot = free_list + slot;
tiny_free_list_t *next;
free_list_t *limit;
uint64_t bitmap;
//剩余size
msize_t leftover_msize;
tiny_free_list_t *leftover_ptr;
//精确匹配,链表头节点
ptr = the_slot->p;
if (ptr) { //存在空闲节点
next = free_list_unchecksum_ptr(rack, &ptr->next);
if (next) {
next->previous = ptr->previous; //从链表中删除ptr
} else {
BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot); //没有剩余的内存块,slot位对应的bitmap置为0
}
the_slot->p = next; //next变为链表的头节点
this_msize = msize; //记录this_msize
//更新region信息
tiny_update_region_free_list_for_remove(slot, ptr, next);
tiny_check_and_zero_inline_meta_from_freelist(rack, ptr, msize);
//查找成功
goto return_tiny_alloc;
}
//取出表示大于msize的bitmap
bitmap = ((uint64_t *)(tiny_mag_ptr->mag_bitmap))[0] & ~((1ULL << slot) - 1);
//空闲链表中没有任何内存块,则走try_tiny_malloc_from_end逻辑
if (!bitmap) {
goto try_tiny_malloc_from_end;
}
//找到最低一个bit不为0的bitmap的位置
slot = BITMAPV_CTZ(bitmap);
limit = free_list + NUM_TINY_SLOTS;
free_list += slot;
if (free_list < limit) {
ptr = free_list->p;
if (ptr) {
next = free_list_unchecksum_ptr(rack, &ptr->next);
free_list->p = next;
if (next) {
next->previous = ptr->previous;
} else {
//没有剩余的内存块,slot位对应的bitmap置为0
BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot);
}
this_msize = get_tiny_free_size(ptr);
tiny_update_region_free_list_for_remove(slot, ptr, next);
tiny_check_and_zero_inline_meta_from_freelist(rack, ptr, this_msize);
//剩余size加入free链表,返回ptr
goto add_leftover_and_proceed;
}
}
ptr = limit->p;
if (ptr) {
this_msize = get_tiny_free_size(ptr);
next = free_list_unchecksum_ptr(rack, &ptr->next);
//剩余size仍然大于NUM_TINY_SLOTS
if (this_msize - msize > NUM_TINY_SLOTS) {
leftover_msize = this_msize - msize;
leftover_ptr = (tiny_free_list_t *)((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize));
tiny_free_list_t tmp_ptr = *ptr;
tiny_check_and_zero_inline_meta_from_freelist(rack, ptr, this_msize);
//剩余内存仍然加入limit的free链表中
limit->p = leftover_ptr;
if (next) {
next->previous.u = free_list_checksum_ptr(rack, leftover_ptr);
}
leftover_ptr->previous = tmp_ptr.previous;
leftover_ptr->next = tmp_ptr.next;
//对应bitmap设置为free
set_tiny_meta_header_free(leftover_ptr, leftover_msize);
this_msize = msize;
tiny_update_region_free_list_for_remove(NUM_TINY_SLOTS, ptr, leftover_ptr);
goto return_tiny_alloc;
}
if (next) {
next->previous = ptr->previous;
}
limit->p = next;
tiny_update_region_free_list_for_remove(slot, ptr, next);
tiny_check_and_zero_inline_meta_from_freelist(rack, ptr, this_msize);
//剩余size加入free链表,返回ptr
goto add_leftover_and_proceed;
/* NOTREACHED */
}
try_tiny_malloc_from_end:
//最后申请的heap region中未使用的大小大于需要的size
if (tiny_mag_ptr->mag_bytes_free_at_end >= TINY_BYTES_FOR_MSIZE(msize)) {
//mag_last_region是低地址,因此可用区域的地址ptr是mag_last_region-mag_bytes_free_at_end
ptr = (tiny_free_list_t *)((uintptr_t)TINY_REGION_HEAP_END(tiny_mag_ptr->mag_last_region) - tiny_mag_ptr->mag_bytes_free_at_end);
//更新未使用大小
tiny_mag_ptr->mag_bytes_free_at_end -= TINY_BYTES_FOR_MSIZE(msize);
if (tiny_mag_ptr->mag_bytes_free_at_end) {
// let's add an in use block after ptr to serve as boundary
//ptr~ptr+msize对应的block范围标记为使用中
set_tiny_meta_header_in_use_1((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize));
}
this_msize = msize;
goto return_tiny_alloc;
}
return NULL;
add_leftover_and_proceed: //处理剩余内存
if (!this_msize || (this_msize > msize)) {
// XXX This works even when (this_msize == 0) because the unsigned
// subtraction wraps around to the correct result
//剩余esize
leftover_msize = this_msize - msize;
//对应的地址
leftover_ptr = (tiny_free_list_t *)((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize));
//加入free链表
tiny_free_list_add_ptr(rack, tiny_mag_ptr, leftover_ptr, leftover_msize);
this_msize = msize;
}
return_tiny_alloc:
tiny_mag_ptr->mag_num_objects++;
tiny_mag_ptr->mag_num_bytes_in_objects += TINY_BYTES_FOR_MSIZE(this_msize);
// Check that the region cookie is intact and update the region's bytes in use count
tiny_region_t region = TINY_REGION_FOR_PTR(ptr);
region_check_cookie(region, ®ION_COOKIE_FOR_TINY_REGION(region));
region_trailer_t *trailer = REGION_TRAILER_FOR_TINY_REGION(region);
size_t bytes_used = trailer->bytes_used + TINY_BYTES_FOR_MSIZE(this_msize);
trailer->bytes_used = (unsigned int)bytes_used;
trailer->objects_in_use++;
// Emptiness discriminant
if (bytes_used < DENSITY_THRESHOLD(TINY_HEAP_SIZE)) {
} else {
trailer->recirc_suitable = FALSE;
}
if (this_msize > 1) {
set_tiny_meta_header_in_use(ptr, this_msize);
} else {
set_tiny_meta_header_in_use_1(ptr);
}
return ptr;
}
代码较多,但是整体思路以下3点:
- 根据需要的msize,首先从空闲链表mag_free_list中查找一块合适的空闲内存块。
- 如果空闲链表中没有找到,则从region的剩余空间中划分一块msize的内存块
- 如果都没有找到,则返回NULL。 从mag_free_list的查找策略,分为以下2点:
- 首先查找msize大小完全匹配的空闲内存块,找到返回
- 其次查找较大msize的内存块,如果找到,则拆分分2块,前一块内存大小是需要的msize返回,后一块是剩余的内存,继续加入空闲链表中,剩余的内存分为2种情况
- 剩余msize小于limitsize,加入对应mag_free_list[slot]
- 剩余msize大于等于limitsize,仍然放在mag_free_list[limit]里
limitsize是tiny内存的最大(1008字节)尺寸/16字节。
为了直观的介绍,用下图表示region的内存情况。 如图,mag_free_list[]中存储了各个msize的空闲链表,tiny的范围是1008字节以内,共63个空闲个mag_free_list,对于mag_free_list[msize](msize<63)存储的各节点的内存size相等,是msize*16,对于最大的mag_free_list[msize](msize=63),存储的各节点的内存size不一定相等,可能存在大于1008的内存块,因为空闲块在回收时会进行合并操作,导致合并后的空闲块大于1008B。mag_bytes_free_at_end表示region内从未分配出去的剩余内存空间的范围大小,因为分配的原则是首先查找分配后回收的内存,在没有找到的情况下才会找region的剩余空间。
接下来具体分析:
-
例如msize是需要分配的内存size,ptr用于存找到的内存块地址,根据msize计算其在mag_free_list的下标slot。
-
首先精确匹配,查找mag_free_list[slot]中是否有空闲内存块,如果有,则查找成功,通过更新next、prev指针将ptr从空闲链表中删除。如果删除后链表为空,则将mag_bitmap对应的标记位清除,表示magazine没有msize的空闲内存。同时调用tiny_update_region_free_list_for_remove方法更新region的free_blocks_by_slot字段。操作结束后,进入return_tiny_alloc流程。
-
如果精确匹配没找到,则从mag_free_list[i](i>slot)存储较大空闲块的链表中查找。具体实现如下:
-
读取mag_bitmap上存储的数据,返回所有bit位是1且高于msize的bit位的下标,这些下标对应的size级别均大于msize。如果没有找到bit位,则不继续处理,进入try_tiny_malloc_from_end流程。
-
如果找到,则通过BITMAPV_CTZ(bitmap)从中取出最低的bit位下标,对应的size级别最小。如图,例如需要分配msize=2(32B的)内存,对应的bit位如下,取出大于该bit位的所有值为1的bit位数据,取出其中最低的bit位,对应的msize=4。
-
定位到对应的slot下标和mag_free_list[slot]链表入口,如果slot小于limit(limit是tiny内存mag_free_list[]最大的下标63),则将mag_free_list[slot]的空闲内存块拆分成两块,前半部分作为分配的内存返回,后半部分作为一块新的空闲内存重新加入对应的mag_free_list中,如图所示: 例如,找到msize=4的内存块,首先修改前后指针将内存块从原链表删除,然后进入add_leftover_and_proceed流程,进入拆分逻辑,例如需要msize=2的内存,则取前32B的内存块返回,同时剩余的32B的内存块加入对应的mag_free_list[slot]中。
-
如果没找到,则查找最后一个链表mag_free_list[limit]中的空闲内存块,如果找到,修改前后指针将内存块从原链表删除,拆分成两块,前msize部分作为ptr返回,针对剩余内存块(leftover_ptr)判断,如果块leftover_ptr的size仍然大于limit size(1008),则仍然将leftover_ptr加入mag_free_list[limit]中。如图所示,内存块1056B,取前32B返回,剩余部分1024B,仍然加入mag_free_list[limit]。 同时将leftover_ptr加入pairs状态标记位中,leftover_ptr是新的内存块。如下图,拆分后,pairs状态标记位多记录了一个leftover_ptr内存信息。然后走return_tiny_alloc流程。
-
如果leftover_ptr的size小于等于limit size,则和步骤c一样进入add_leftover_and_proceed流程。
-
-
当前面的空闲链表逻辑均没有可用的内存块时,则进入try_tiny_malloc_from_end流程,即查看magazine的当前region(mag_last_region)剩余的空间大小mag_bytes_free_at_end是否满足分配msize需求,如果满足,则分配后进入return_tiny_alloc流程,否则直接返回NULL,表示内存查找失败。如图,mag_bytes_free_at_end满足分配msize,则分配一块msize的内存,mag_bytes_free_at_end-=msize。
-
add_leftover_and_proceed流程,处理拆分大内存块后的逻辑,将剩余内存leftover_ptr加入对应size的空闲链表中,并在pairs中设置状态标记为空闲,进入return_tiny_alloc流程
-
return_tiny_alloc流程,最后流程,将当前分配到的ptr,设置pairs中设置状态标记为使用中,并更新magezine和region相关的统计信息,包括
initiny_mag_ptr->mag_num_objects++; tiny_mag_ptr->mag_num_bytes_in_objects trailer->bytes_used = (unsigned int)bytes_used; trailer->objects_in_use++;
- mag_num_objects:magazine分配的总内存块数
- mag_num_bytes_in_objects:magazine分配的总内存大小
- bytes_used:region使用的总内存大小
- objects_in_use:region使用的总内存块数
整体逻辑可以用一张流程图来表示:
depot查找
如果调用tiny_malloc_from_free_list方法没有找到可用的内存块,则调用tiny_get_region_from_depot方法查看depot magazine中的region中是否有合适的内存块。
ini
if (tiny_get_region_from_depot(rack, tiny_mag_ptr, mag_index, msize)) {
//迁移后接着查找
ptr = tiny_malloc_from_free_list(rack, tiny_mag_ptr, mag_index, msize);
if (ptr) {
return ptr;
}
}
static boolean_t tiny_get_region_from_depot(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize)
{
magazine_t *depot_ptr = &(rack->magazines[DEPOT_MAGAZINE_INDEX]);
region_trailer_t *node;
region_t sparse_region;
msize_t try_msize = msize;
while (1) {
//寻找一个大于等于申请大小msize的节点,返回该节点的heap region
sparse_region = tiny_find_msize_region(rack, depot_ptr, DEPOT_MAGAZINE_INDEX, try_msize);
if (NULL == sparse_region) { // Depot empty?
return 0;
}
node = REGION_TRAILER_FOR_TINY_REGION(sparse_region);
if (0 == node->pinned_to_depot) {
// Found one!
break;
}
try_msize++;
if (try_msize > NUM_TINY_SLOTS) {
SZONE_MAGAZINE_PTR_UNLOCK(depot_ptr);
return 0;
}
}
//通过trailer,从node链表中移除region
recirc_list_extract(rack, depot_ptr, node);
//将空闲内存从depot_ptr的free链表中删除
int objects_in_use = tiny_free_detach_region(rack, depot_ptr, sparse_region);
// Transfer ownership of the region
MAGAZINE_INDEX_FOR_TINY_REGION(sparse_region) = mag_index;
//将空闲内存加入tiny_mag_ptr的free链表
size_t bytes_inplay = tiny_free_reattach_region(rack, tiny_mag_ptr, sparse_region);
depot_ptr->mag_num_bytes_in_objects -= bytes_inplay;
depot_ptr->num_bytes_in_magazine -= TINY_HEAP_SIZE;
depot_ptr->mag_num_objects -= objects_in_use;
tiny_mag_ptr->mag_num_bytes_in_objects += bytes_inplay;
tiny_mag_ptr->num_bytes_in_magazine += TINY_HEAP_SIZE;
tiny_mag_ptr->mag_num_objects += objects_in_use;
//通过trailer,将region加入tiny_mag_ptr的链表
recirc_list_splice_last(rack, tiny_mag_ptr, node);
return 1;
}
整体逻辑如下:
-
遍历depot magazine中的freelist,查找大小满足msize的空闲内存块,如果存在,返回所在的region。try_msize是要匹配的size,初识是msize,如果没有找到,则try_msize++,只要size大于等于msize,都满足条件。如果未找到,返回0,表示从depot magazine中查找失败。查找逻辑如下:
inistatic region_t tiny_find_msize_region(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize) { tiny_free_list_t *ptr; grain_t slot = tiny_slot_from_msize(msize); free_list_t *free_list = tiny_mag_ptr->mag_free_list; free_list_t *the_slot = free_list + slot; free_list_t *limit; #if defined(__LP64__) uint64_t bitmap; #else uint32_t bitmap; #endif ptr = the_slot->p; if (ptr) { return TINY_REGION_FOR_PTR(ptr); } #if defined(__LP64__) bitmap = ((uint64_t *)(tiny_mag_ptr->mag_bitmap))[0] & ~((1ULL << slot) - 1); #else bitmap = tiny_mag_ptr->mag_bitmap[0] & ~((1 << slot) - 1); #endif if (!bitmap) { return NULL; } slot = BITMAPV_CTZ(bitmap); limit = free_list + NUM_TINY_SLOTS; free_list += slot; if (free_list < limit) { ptr = free_list->p; if (ptr) { return TINY_REGION_FOR_PTR(ptr); } } ptr = limit->p; if (ptr) { return TINY_REGION_FOR_PTR(ptr); } return NULL; }
逻辑和tiny_malloc_from_free_list相似,首先计算msize对应的slot,然后先从对应的mag_free_list[slot]中精确匹配,如果失败,则从mag_free_listslot找size更大的空闲内存块,最后查找mag_free_list[limit],如果找到,返回内存块所在的region,否则返回NULL,查找失败。
-
如果查找成功,则将depot中的region从region链表中迁移至tiny_mag_ptr,同时将region中的空闲内存块从freelist中迁移至tiny_mag_ptr的freelist中。如图所示,深黄色是查找到的region,从depot的regions链表和freelist中迁移至新的magazine下。
-
更新depot magazine和tiny_mag_ptr的统计信息。
当调用tiny_get_region_from_depot查好成功时,再次调用tiny_malloc_from_free_list方法从magazine中查找,因为上述逻辑只是把内存迁移至magazine的相应结构中,所以需要再查找一次。
region分配
如果查找失败,说明magazine中的没有可用的空闲内存块,则从新的region中分配内存,包含两部分:
- 新分配一块region空间
- 从region中分配msize的内存,将region加入magazine结构中管理。
虚拟内存分配
首先调用mvm_allocate_pages方法分配一块region虚拟内存。
ini
fresh_region = mvm_allocate_pages(TINY_REGION_SIZE,
TINY_BLOCKS_ALIGN,
MALLOC_FIX_GUARD_PAGE_FLAGS(rack->debug_flags),
VM_MEMORY_MALLOC_TINY);
TINY_REGION_SIZE是region的大小范围,按照TINY_BLOCKS_ALIGN大小对齐,同时支持传递一些flags参数,控制分配行为。
内存块分配
新region分配后,调用tiny_malloc_from_region_no_lock方法,从region中分配一块内存块,并将region加入rack和magazine的结构中维护。
ini
ptr = tiny_malloc_from_region_no_lock(rack, tiny_mag_ptr, mag_index, msize, fresh_region);
return ptr;
static void *tiny_malloc_from_region_no_lock(rack_t *rack,
magazine_t *tiny_mag_ptr,
mag_index_t mag_index,
msize_t msize,
void *aligned_address)
{
void *ptr;
//处理之前未使用的剩余空间
if (tiny_mag_ptr->mag_bytes_free_at_end || tiny_mag_ptr->mag_bytes_free_at_start) {
tiny_finalize_region(rack, tiny_mag_ptr);
}
tiny_region_t region = (tiny_region_t)aligned_address;
// We set the unused bits of the header in the last pair to be all ones, and those of the inuse to zeroes.
#if NUM_TINY_BLOCKS & 31
const uint32_t header = 0xFFFFFFFFU << (NUM_TINY_BLOCKS & 31);
#else
const uint32_t header = 0;
#endif
region->pairs[CEIL_NUM_TINY_BLOCKS_WORDS - 1].header = header;
region->pairs[CEIL_NUM_TINY_BLOCKS_WORDS - 1].inuse = 0;
//设置mag_index
MAGAZINE_INDEX_FOR_TINY_REGION(region) = mag_index;
// Insert the new region into the hash ring
rack_region_insert(rack, region);
tiny_mag_ptr->mag_last_region = region;
BYTES_USED_FOR_TINY_REGION(region) = TINY_BYTES_FOR_MSIZE(msize);
OBJECTS_IN_USE_FOR_TINY_REGION(region) = 1;
int offset_msize = 0;
//内存分配地址ptr
ptr = (void *)(TINY_REGION_HEAP_BASE(region) + TINY_BYTES_FOR_MSIZE(offset_msize));
set_tiny_meta_header_in_use(ptr, msize);
tiny_mag_ptr->mag_num_objects++;
tiny_mag_ptr->mag_num_bytes_in_objects += TINY_BYTES_FOR_MSIZE(msize);
tiny_mag_ptr->num_bytes_in_magazine += TINY_HEAP_SIZE;
set_tiny_meta_header_in_use_1((void *)((uintptr_t)ptr + TINY_BYTES_FOR_MSIZE(msize)));
tiny_mag_ptr->mag_bytes_free_at_end = TINY_BYTES_FOR_MSIZE(NUM_TINY_BLOCKS - msize - offset_msize);
tiny_mag_ptr->mag_bytes_free_at_start = 0;
//将region加入tiny_mag_ptr的last node
recirc_list_splice_last(rack, tiny_mag_ptr, REGION_TRAILER_FOR_TINY_REGION(region));
return ptr;
}
整体流程如下:
-
首先判断,如果magazine还有一些剩余size从未使用,则将这部分内存更新为空闲块加入空闲链表。具体逻辑如图: 白色区域是从未分配过的区域,会将这部分区域更新成回收状态,并将入mag_free_list中,加入之前会判断一下是否可以和空间连续的前一个内存块合并,如果合并,则更新mag_free_list中内存。代码如下:
iniif (tiny_mag_ptr->mag_bytes_free_at_end || tiny_mag_ptr->mag_bytes_free_at_start) { tiny_finalize_region(rack, tiny_mag_ptr); } void tiny_finalize_region(rack_t *rack, magazine_t *tiny_mag_ptr) { void *last_block, *previous_block; uint32_t *last_header; msize_t last_msize, previous_msize, last_index; //如果还有一些剩余size,尝试合并前一个内存块 if (tiny_mag_ptr->mag_bytes_free_at_end) { last_block = (void *)((uintptr_t)TINY_REGION_HEAP_END(tiny_mag_ptr->mag_last_region) - tiny_mag_ptr->mag_bytes_free_at_end); last_msize = TINY_MSIZE_FOR_BYTES(tiny_mag_ptr->mag_bytes_free_at_end); last_header = TINY_BLOCK_HEADER_FOR_PTR(last_block); last_index = TINY_INDEX_FOR_PTR(last_block); if (last_index != (NUM_TINY_BLOCKS - 1)) { BITARRAY_CLR(last_header, (last_index + 1)); } //前一个空闲的block previous_block = tiny_previous_preceding_free(last_block, &previous_msize); if (previous_block) { set_tiny_meta_header_middle(last_block); tiny_free_list_remove_ptr(rack, tiny_mag_ptr, previous_block, previous_msize); zero_tiny_free_inline_meta_following(previous_block, previous_msize); last_block = previous_block; last_msize += previous_msize; } tiny_free_list_add_ptr(rack, tiny_mag_ptr, last_block, last_msize); tiny_mag_ptr->mag_bytes_free_at_end = 0; } tiny_mag_ptr->mag_last_region = NULL; }
-
调用rack_region_insert方法将region注册进rack的hash ring结构中。
-
从region取偏移meta字段后的首地址作为内存块地址ptr返回,并且调用set_tiny_meta_header_in_use更新block状态。这里有一个特殊逻辑,设置内存块ptr后一个16字节的内存块为use状态,防止回收合并操作。同时更新相关统计信息,包括mag_num_objects等,更新mag_bytes_free_at_end、mag_bytes_free_at_start的值。
-
调用recirc_list_splice_last方法将region对应的trailer_t节点加入region链表中。
内存回收
free_tiny是tiny内存回收的入口方法,代码如下:
ini
void free_tiny(rack_t *rack, void *ptr, region_t tiny_region, size_t known_size,
boolean_t partial_free)
{
msize_t msize;
boolean_t is_free;
mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(tiny_region);
magazine_t *tiny_mag_ptr = &(rack->magazines[mag_index]);
uint32_t flags = 0;
//cache
#if CONFIG_TINY_CACHE
if (msize < TINY_QUANTUM) {
void *ptr2 = tiny_mag_ptr->mag_last_free;
msize_t msize2 = tiny_mag_ptr->mag_last_free_msize;
region_t rgn2 = tiny_mag_ptr->mag_last_free_rgn;
if (ptr == ptr2) {
free_tiny_botch(rack, ptr);
return;
}
//...
tiny_mag_ptr->mag_last_free = ptr;
tiny_mag_ptr->mag_last_free_msize = msize;
tiny_mag_ptr->mag_last_free_rgn = tiny_region;
//...
msize = msize2;
ptr = ptr2;
tiny_region = rgn2;
flags |= TINY_FREE_FLAG_FROM_CACHE;
}
#endif
//...
if (tiny_free_no_lock(rack, tiny_mag_ptr, mag_index, tiny_region, ptr,
msize, flags)) {
}
}
主要包含两部分逻辑:
- cache更新逻辑,更新tiny_mag_ptr->mag_last_free,更新mag_last_free_msize、mag_last_free_rgn,将原有的cache加入回收逻辑,关于cache的逻辑,上文有具体介绍。
- 调用tiny_free_no_lock进行内存回收操作
核心逻辑
tiny_free_no_lock是回收的核心实现,代码如下:
ini
boolean_t tiny_free_no_lock(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index,
region_t region, void *ptr, msize_t msize, uint32_t flags)
{
void *original_ptr = ptr;
size_t original_size = TINY_BYTES_FOR_MSIZE(msize);
void *next_block = ((unsigned char *)ptr + original_size);
msize_t previous_msize, next_msize;
void *previous;
tiny_free_list_t *big_free_block;
tiny_free_list_t *after_next_block;
tiny_free_list_t *before_next_block;
//尝试合并前一个空闲的block
previous = tiny_previous_preceding_free(ptr, &previous_msize);
if (previous) {
set_tiny_meta_header_middle(ptr);
tiny_free_list_remove_ptr(rack, tiny_mag_ptr, previous, previous_msize);
zero_tiny_free_inline_meta_following(previous, previous_msize);
ptr = previous;
msize += previous_msize;
}
//尝试合并后一个空闲的block
if ((next_block < TINY_REGION_HEAP_END(region)) && tiny_meta_header_is_free(next_block)) {
next_msize = get_tiny_free_size(next_block);
if (next_msize > NUM_TINY_SLOTS) {
msize += next_msize;
big_free_block = (tiny_free_list_t *)next_block;
after_next_block = free_list_unchecksum_ptr(rack, &big_free_block->next);
before_next_block = free_list_unchecksum_ptr(rack, &big_free_block->previous);
if (!before_next_block) {
tiny_mag_ptr->mag_free_list[NUM_TINY_SLOTS].p = ptr;
} else {
before_next_block->next.u = free_list_checksum_ptr(rack, ptr);
}
if (after_next_block) {
after_next_block->previous.u = free_list_checksum_ptr(rack, ptr);
}
((tiny_free_list_t *)ptr)->previous = big_free_block->previous;
((tiny_free_list_t *)ptr)->next = big_free_block->next;
set_tiny_meta_header_middle(big_free_block);
zero_tiny_free_inline_meta(big_free_block, next_msize);
set_tiny_meta_header_free(ptr, msize);
uint16_t next_block_index = TINY_INDEX_FOR_PTR(big_free_block) + 1;
uint16_t ptr_index = TINY_INDEX_FOR_PTR(ptr) + 1;
const grain_t slot = NUM_TINY_SLOTS;
region_free_blocks_t *free_blocks = &((tiny_region_t)region)->free_blocks_by_slot[slot];
if (free_blocks->first_block == next_block_index) {
free_blocks->first_block = ptr_index;
}
if (free_blocks->last_block == next_block_index) {
free_blocks->last_block = ptr_index;
}
goto tiny_free_ending;
}
tiny_free_list_remove_ptr(rack, tiny_mag_ptr, next_block, next_msize);
set_tiny_meta_header_middle(next_block); // clear the meta_header to enable coalescing backwards
zero_tiny_free_inline_meta(next_block, next_msize);
msize += next_msize;
}
//重新添加进free链表和freeblock区
tiny_free_list_add_ptr(rack, tiny_mag_ptr, ptr, msize);
tiny_free_ending:
tiny_mag_ptr->mag_num_bytes_in_objects -= original_size;
region_trailer_t *trailer = REGION_TRAILER_FOR_TINY_REGION(region);
size_t bytes_used = trailer->bytes_used - original_size;
trailer->bytes_used = (unsigned int)bytes_used;
//...
boolean_t needs_unlock = TRUE;
#if CONFIG_RECIRC_DEPOT
needs_unlock = tiny_free_try_recirc_to_depot(rack, tiny_mag_ptr, mag_index, region, original_ptr, original_size, ptr, msize);
#endif // CONFIG_RECIRC_DEPOT
return needs_unlock;
}
具体步骤如下:
-
尝试查找相邻的内存是否空闲,如果空闲,可以进行合并操作,首先调用tiny_previous_preceding_free方法查找前一块空闲内存previous,如果找到则合并。如下图1,合并的操作如下:
- 由于合并后内存块ptr不存在了,因此更新ptr的pair状态标记为空,set_tiny_meta_header_middle。
- 调用tiny_free_list_remove_ptr方法将previous从原空闲链表mag_free_list中删除。
- 更新后的空闲块是合并后的大小,是msize+previous_msize。
-
尝试合并后一块内存块next,基本流程和合并previous相似,更新next的pair状态标记为空,将next从mag_free_list中删除,如上图2。这里包含一个特殊逻辑,当next的size超过limit,则合并后的空闲内存块一定会被加入最后一个链表mag_free_list[limit]中,这里单独处理,将next从原来的链表中删除,然后将合并后的ptr加入mag_free_list[limit]的next原位置中,更新next的pair状态标记为空,其余操作一致,如下图。
-
调用tiny_free_list_add_ptr方法将合并后的空闲块加入mag_free_list。
-
更新统计信息,例如mag_num_bytes_in_objects、bytes_used。
-
调用tiny_free_try_recirc_to_depot,判断当前magzine的空闲块比例是否达到阈值,如果达到,需要将相应的region和空闲内存块迁移至depot magazine的结构中。
-
如果回收的是depot magazine中内存,则进一步判断内存块所在的region是否内存全部被回收,如是则调用系统方法会说该region内存。逻辑如下:
arduinostatic boolean_t tiny_free_try_recirc_to_depot(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, region_t region, void *headptr, size_t headsize, void *ptr, msize_t msize) { if (DEPOT_MAGAZINE_INDEX != mag_index) { //回收给depot region的逻辑 } else { if (0 < bytes_used || 0 < node->pinned_to_depot) { } else { //region的bytes_used为0,回收内存给系统 region_t r_dealloc = tiny_free_try_depot_unmap_no_lock(rack, tiny_mag_ptr, node); SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr); if (r_dealloc) { mvm_deallocate_pages(r_dealloc, TINY_REGION_SIZE, MALLOC_FIX_GUARD_PAGE_FLAGS(rack->debug_flags)); } return FALSE; } } return TRUE; }
- 调用了tiny_free_try_depot_unmap_no_lock将depot magzine下的region从结构中删除
- 调用mvm_deallocate_pages方法回收内存。
以上是tiny内存分配的介绍,由于字数限制,small和large在另外一篇文章中介绍。