tx_byte_pool_id | 内存字节池id |
tx_byte_pool_name | 内存字节池名字 |
tx_byte_pool_available | 内存字节池可分配字节 |
tx_byte_pool_fragments | 内存字节池块个数 |
tx_byte_pool_list | 内存字节池头list指针 |
tx_byte_pool_search | 内存字节池开始搜索起始地址 |
tx_byte_pool_start | 内存字节池所在内存空间起始地址 |
tx_byte_pool_size | 内存字节池大小 |
tx_byte_pool_owner | 内存字节池所属线程 |
tx_byte_pool_suspension_list | 挂起线程链表 |
tx_byte_pool_suspended_count | |
tx_byte_pool_created_next | 指向下一个内存字节池指针 |
tx_byte_pool_created_previous | 指向前一个内存字节池指针 |
tx_byte_pool_huge_memory_start | |
tx_byte_pool_min_available | |
tx_byte_pool_available_list | |
tx_mutex_ptr | |
tx_huge_memory_watermark |
- 内存池链表
所有创建的内存字节池都插入到created_ptr。tx_byte_pool_created_next指向下一个内存字节池,tx_byte_pool_created_previous指向前一个内存字节池。
- 内存字节池初始化
内存字节池初始化为一个大的块和一个小的控制块两部分,小的控制块只是为了fisrt-fit算法,不会存储数据使用。(first-fit首次适应算法:从链首开始查找,直至找到一个能满足其大小要求的空闲分区为止。然后再按照要求的大小,从该分区中划出一块内存分配给请求者,余下的空闲分区仍留在空闲分区链中。特点: 该算法倾向于使用内存中低地址部分的空闲区,在高地址部分的空闲区很少被利用,从而保留了高地址部分的大空闲区。显然为以后到达的大作业分配大的内存空间创造了条件。缺点:低地址部分不断被划分,留下许多难以利用、很小的空闲区,而每次查找又都从低地址部分开始,会增加查找的开销。)
内存池中不同块(block)之间通过单向链表连接,利用每块内存头部前8个字节作为控制字段,前4个字节指向下一个内存块的起始地址,随后4个字节作为内存是否分配标志,如果已经被分配,指向内存池TX_BYTE_POOL控制块指针,如果空闲设置为TX_BYTE_BLOCK_FREE。
最后一块内存池只有8个字节,作为控制块,前4个字节指向内存池TX_BYTE_POOL控制块指针,后4个字节设置为TX_BYTE_BLOCK_ALLOC。
如下图,pool_ptr指向TX_BYTE_POOL内存池控制块,整个内存分为两块,TX_BYTE_POOL中tx_byte_pool_start指向内存池首地址。第一块中的前4个字节存储第二块(最后一块,控制块)的起始地址,后四个字节设置为TX_BYTE_BLOCK_FREE,因为还没有使用,标记为空闲块。
第二块(最后一块,小的控制块)的前4个字节存放指向内存池起始地址tx_byte_pool_start,后4字节标记为TX_BYTE_BLOCK_ALLOC 标记为已分配,也表示最后一块。
tx_byte_pool_search指向搜索内存时的开始地址。
这样内存块就构成了单向循环链表。
- 内存分配
内存分配使用首次适应(fisrt-fit)算法,从上次操作的空闲内存块开始查找,找到大小合适内存,返回成功。
找到第一个大于请求分配大小的内存块时,并把这个大的内存块分为两个内存块,前一个作为分配到的内存使用,后一个挂入内存管理链表中。
初始化时,内存块只有两个,随着分配内存和释放内存,会出现很多小的内存块,称为内存碎片。
查找过程中,发现两个邻居内存块是地址连续的,那么把这两个内存块合并成为一个内存块,称为内存整理。
如果内存池没有足够可用内存,申请分配内存的线程会挂起。
举例:
下图为第一次分配内存后,最初的第一块内存被分为了两块:第一块和第二块。
第一块作为分配到的内存使用。memptr跨过前八字节的控制字段指向内存的地址,为返回给程序的地址。第一块的前4字节存储第二块内存起始地址,随后4个字节指向了内存池控制块,标志着第一块内存已经被占用,不是空闲内存了。
第二块内存前4个字节指向第三块内存(最后一块内存),继续构成单向链表。随后4个字节存储TX_BYTE_BLOCK_FREE,表示还是空闲内存块。
tx_byte_pool_search指向搜索内存时的开始地址,第一块已经被占用,所以指向了第一个空闲的内存块,即第二块内存起始地址。
如下图,第二次分配内存。第一个空闲的内存块(第二块)开始搜索,找到了合适大小,由于请求大小小于空闲的内存块大小,所以把空闲块再次分为两块,如下图所示。
第二块是分配给应用程序的,memptr为返回地址,第二块内存的前4字节指向第三块内存首地址,随后4个字节指向了内存池控制块,表示这块内存已经被占用。
第三块内存,前4字节指向第四块内存(最后一块内存),继续构成单向链表。随后4个字节存储TX_BYTE_BLOCK_FREE,表示还是空闲内存块。
- 内存释放
内存释放时,根据释放内存首地址,减8,计算出内存块控制字段的首地址,通过前四字节找到内存池控制块指针,可以进行内存释放操作。
释放后,4到8字节存储TX_BYTE_BLOCK_FREE表示为空闲内存块。
这里第二块内存和第三块内存虽然是连续的,但并没有进行合并或内存整理。什么时间合并呢?
等到申请内存分配,发现请求大小大于第二块的大小,开始查找第三块时,这时发现第二块和第三块内存连续,就把第二块和第三块合并为一块。
释放内存后,会检查suspension list中是否有线程,如果有,尝试分配内存并恢复线程执行。线程恢复是按照FIFO顺序恢复,并没有按照线程优先级高低顺序。但是可以在线程释放前调用_tx_byte_pool_prioritize,把最高优先级线程移动到挂起链表最前面,从而先恢复最高优先级线程。
- 内存整理
内存在多次分配和释放后,可能会出现大量小的内存块,这种想象成为内存碎片化。当需要分配一个较大内存时,每次可能需要先遍历大量小内存,这样会使查找开销增加,算法性能下降。由于每个内存块都占用8个字节的控制字段,大量小内存会导致内存的浪费。
查找过程中,发现两个邻居内存块是地址连续的,那么把这两个内存块合并成为一个内存块,称为内存整理。内存整理能够提升算法性能,但提升有限。其它优化方式,可以从查找算法进行优化,如二叉树查找等。
举例:
假设下图为多次内存分配和释放后的结构,第一块被占用,第二块,第三块,第四块为空闲内存块。
第二块大小为64字节,第三块大小为64字节,第四块内存大小为256字节。假如现在申请分配内存大小为128字节,在分配查找过程中,发现第二块太小不满足,但第二块和第三块地址连续,于是把第二块和第三块合并为一块,并且发现合并后正好满足请求,返回给应用程序,如下图。