版权声明:本文为本文为博主原创文章,转载请注明出处。若有错误,欢迎指正。博客地址:https://www.cnblogs.com/wsg1100/
linux
一般,操做系统的内存管理,内存分配算法必定要快,不然会影响应用程序的运行效率,另外一个是内存利用率。算法
不管linux仍是xenomai,在服务或管理应用程序过程当中常常须要内存分配,一般linux内存的分配与释放都是时间不肯定的,例如,缺页异常和页面换出会致使大且不可预测的延迟,不适用于受严格时间限制的实时应用程序。数组
xenomai做为硬实时内核,不能使用linux这样的内存分配释放接口,为此xenomai采起的措施是:在xenomai内核初始化时,先调用__vmalloc()
从linux管理的ZONE_NORMAL中分配 一片内存,而后由xenomai本身来管理这片内存,且xenomai提供的内存分配释放时间肯定的,这样就不会由于内存的分配释放影响实时性(该内存管理代码量很是少,有效代码数3百行左右,实现精巧,值得研究利用)。ide
下面代码基于 xenomai-3.0.8。xenomai 3.1开始有所不一样详见文末。svg
xenomai管理的内存池称为xnheap,内存池大小预先配置,如xenomai的系统内存池cobalt_heap,负责内核大多内核数据分配,其大小为sysheap_size_arg* 1024
Byte(sysheap_size_arg KB),sysheap_size_arg可在内核配置时设置,或者经过内核参数xenomai.sysheap_size=<kbytes>
配置。xenomai内核中这样管理的内存池不止一个,其余的make menuconfig
配置以下。函数
[*] Xenomai/cobalt ---> Sizes and static limits ---> (512) Number of registry slots (4096) Size of system heap (Kb) (512) Size of private heap (Kb) (512) Size of shared heap (Kb)
简单介绍一下配置项中的几个内存池的用途:oop
(512) Number of registry slots
,xenomai内核运行中内核资源对象存储槽的大小,用于分配系统使用资源的最大大小,如信号(signal)、互斥对象(mutex)、信号量等.(4096) Size of system heap (Kb)
本节以xenomai的系统内存池cobalt_heap为例来了解xenomai内存池管理。cobalt_heap在xenomai内核初始化过程当中初始化,先调用__vmalloc()从linux管理的ZONE_NORMAL中分配,而后在调用xnheap_init()初始化。优化
static int __init xenomai_init(void) { ...... ret = sys_init(); ...... return ret; } static __init int sys_init(void) { void *heapaddr; int ret, cpu; heapaddr = xnheap_vmalloc(sysheap_size_arg * 1024);/*256 * 1024*/ if (heapaddr == NULL || xnheap_init(&cobalt_heap, heapaddr, sysheap_size_arg * 1024)) {/*初始heap*/ return -ENOMEM; } xnheap_set_name(&cobalt_heap, "system heap");/*set heap name */ .... return 0; }
xenomai要求管理的内存大小必须是PAGE_SIZE的倍数,且至少有2页,其最大值在xenomai3.0.8版本里为2GB(1<<31),其余版本可能有所改变。以sysheap_size_arg默认值256为例,即cobalt_heap大小256KB。this
每一个内存池分配一个对象xnheap来管理,xnheap结构以下。spa
struct xnpagemap { /** PFREE, PCONT, PLIST or log2 */ u32 type : 8; /** Number of active blocks */ u32 bcount : 24; }; struct xnheap { /** SMP lock */ DECLARE_XNLOCK(lock); /** Base address of the page array */ caddr_t membase; /** Memory limit of page array */ caddr_t memlim; /** Number of pages in the freelist */ int npages; /** Head of the free page list */ caddr_t freelist; /** Address of the page map */ struct xnpagemap *pagemap; /** Link to heapq */ struct list_head next; /** log2 bucket list */ struct xnbucket { caddr_t freelist; int fcount; } buckets[XNHEAP_NBUCKETS]; char name[XNOBJECT_NAME_LEN]; /** Size of storage area */ u32 size; /** Used/busy storage size */ u32 used; };
其中,size
标志该内存池的总大小,used
标志已分配使用大小,npages
表示该内存有多少页,membase
管理的内存基地址,memlim
记录内存结束地址.
struct xnpagemap { /** PFREE, PCONT, PLIST or log2 */ u32 type : 8; /** Number of active blocks */ u32 bcount : 24; };
pagemap
管理着每一页,有多少页就须要多少项,pagemap.type
表示该页面的类型,pagemap.bcount
表示页面被分红这类大小的数量,小于1页分配才会将空闲页n分割成多块,才须要pagemap[n]
来记录,pagemap
一般管理着小于PAGE_SIZE的分配。pagemap.type有以下几类:
XNHEAP_PFREE(0) 表示该页面空闲
XNHEAP_PCONT(1)该页为上一页的续,当分配的内存大于1页时,除首页以外的页用该标识。
XNHEAP_PLIST(2) 表示该页是块的开始(每次请求分配的内存称为一个块)
记录确切的子块大小($log_2size$),值为3-20,(页按size大小分割成许多子块);
struct xnbucket { caddr_t freelist; int fcount; } buckets[XNHEAP_NBUCKETS];
buckets[XNHEAP_NBUCKETS]
记录着整个xnheap不一样大小的分配,由于bucket管理的内存分配单元大小最小为8Byte,因此数组下标是$log_2size -3$,bucket[n]管理着分配单元(块)大小为$2^{n+3}$Byte的内存池,freelist
指向该bucket内第一个空闲块,fcount
标识该bucket可剩余空闲块数。
例如请求分配的大小为64Byte,$log_264 -3 = 3$,则buckets[3]记录着请求大小64Byte的分配,若是buckets[3].freelist
不为NULL,则buckets[3].freelist
就是本次请求的内存首地址。
并非任何大小的分配都由buckets[]管理。当请求大小超过两个页时,再也不使用bucket,从空闲页列表直接分配页面会更节省空间。XNHEAP_NBUCKETS=21,表示最大管理8MB($2^{20+3}$)分配信息,普通分页模式下,页大小为4KB,只用到buckets[0-10]
,大页(hupage)模式(页大小为2MB)下才会使用到buckets[11-20]
,如下分析默认页大小为4KB。
buckets与pagemap区别是管理的对象不一样,buckets[n]
管理大小$2^{n+3}$Byte的内存池的分配。而pagemap[n]
记录整个块内存第n页内的使用信息。
当分配到一片内存做为xnheap后,首先调用xnheap_init()对该片内存初始化。
int xnheap_init(struct xnheap *heap, void *membase, u32 size) { spl_t s; secondary_mode_only(); heap->size = size; heap->membase = membase; heap->npages = size / XNHEAP_PAGESZ; if (heap->npages < 2) return -EINVAL; heap->pagemap = kmalloc(sizeof(struct xnpagemap) * heap->npages, GFP_KERNEL);/*map 大小:每页须要一个struct xnpagemap*/ if (heap->pagemap == NULL) return -ENOMEM; xnlock_init(&heap->lock); init_freelist(heap); /* Default name, override with xnheap_set_name() */ ksformat(heap->name, sizeof(heap->name), "(%p)", heap); ..... return 0; }
计算该内存总页数npages,而后为每页分配一个xnpagemap对象,npages页须要分配npages个xnpagemap,而后调用init_freelist()初始化freelist。
static void init_freelist(struct xnheap *heap) { caddr_t freepage; int n, lastpgnum; heap->used = 0; memset(heap->buckets, 0, sizeof(heap->buckets)); lastpgnum = heap->npages - 1; for (n = 0, freepage = heap->membase; n < lastpgnum; n++, freepage += XNHEAP_PAGESZ) { *((caddr_t *)freepage) = freepage + XNHEAP_PAGESZ; heap->pagemap[n].type = XNHEAP_PFREE; heap->pagemap[n].bcount = 0; } *((caddr_t *) freepage) = NULL; heap->pagemap[lastpgnum].type = XNHEAP_PFREE; heap->pagemap[lastpgnum].bcount = 0; heap->memlim = freepage + XNHEAP_PAGESZ; /* The first page starts the free list. */ heap->freelist = heap->membase;/*free list*/ }
先初始化pagemap[],每页记录为未使用(XNHEAP_PFREE)
设置xnheap的结束地址memlim,并将freelist指向第一个空闲页,而后从第一页开始,前一页保存着后一页起始地址。这样作不只将空闲页连起来,方便分配时索引,并且经过内存赋值操做,若是该内存页未映射,会触发内核缺页异常,让linux将未映射到物理内存的页面映射到物理内存,这样后续xenomai使用过程当中就不会再产生缺页中断,避免影响xenomai实时性。初始化后以下图所示

xenomai内存堆初始化完后,下面经过分配与释放来分析分配释放过程,例如向内存池Cobalt_heap()分别分配1Byte、50Byte、1000Byte、5000Byte、10000Byte数据,而后依次释放。
/*向hobalt_heap分配1字节空间*/ ptrt_1 = xnheap_alloc(&hobalt_heap, 1); /*向hobalt_heap分配50字节空间*/ ptr_50 = xnheap_alloc(&hobalt_heap, 50); /*连续向hobalt_heap分配1000字节空间5次*/ ptr_1000 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_1 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_2 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_3 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_4 = xnheap_alloc(&hobalt_heap, 1000); /*向hobalt_heap分配5000字节空间*/ ptr_5000 = xnheap_alloc(&hobalt_heap, 5000); /*向hobalt_heap分配10000字节空间*/ ptr_10000 = xnheap_alloc(&hobalt_heap, 10000);
首先来看分配1Byte。
/*include\cobalt\kernel\heap.h*/ #define XNHEAP_PAGESZ PAGE_SIZE #define XNHEAP_MINLOG2 3 #define XNHEAP_MAXLOG2 22 /* Holds pagemap.bcount blocks */ #define XNHEAP_MINALLOCSZ (1 << XNHEAP_MINLOG2) #define XNHEAP_MINALIGNSZ (1 << 4) /* i.e. 16 bytes */ #define XNHEAP_NBUCKETS (XNHEAP_MAXLOG2 - XNHEAP_MINLOG2 + 2) #define XNHEAP_MAXHEAPSZ (1 << 31) /* i.e. 2Gb */ void *xnheap_alloc(struct xnheap *heap, u32 size) { u32 pagenum, bsize; int log2size, ilog; caddr_t block; spl_t s; ..... /* * Sizes lower or equal to the page size are rounded either to * the minimum allocation size if lower than this value, or to * the minimum alignment size if greater or equal to this * value. */ if (size > XNHEAP_PAGESZ) size = ALIGN(size, XNHEAP_PAGESZ);/*XNHEAP_PAGESZ = */ else if (size <= XNHEAP_MINALIGNSZ) size = ALIGN(size, XNHEAP_MINALLOCSZ); else size = ALIGN(size, XNHEAP_MINALIGNSZ); ...... }
首先根据大小size来向最小分配或最大分配对齐,xenomai分配类型分为3类,对于大于XNHEAP_PAGESZ的向上与XNHEAP_PAGESZ对齐;对于小于8Byte的,向上与8Byte对齐;对于大于8Byte,向上与16Byte对齐;这样是为了与bucket一一对应。
例如分配5000Byte,最终分配到的空间大小为8192 Byte(以PAGE_SIZE为4KB计算),要分配1Byte空间,将会获得8Byte的空间,分配50Byte空间获得64Byte空间。
咱们请求分配1Byte的内存,对齐后size为8 Byte,buckets[XNHEAP_NBUCKETS]
只管理请求大小小于2*PAGE_SZIE的分配池。 当请求的大小大于页大小的2倍时,从空闲页列表直接分配页面会更节省空间。8Byte小于2*PAGE_SZIE,下面看bucket具体的分配流程。
if (likely(size <= XNHEAP_PAGESZ * 2)) { /*小于等于2PAGE_SIZE的从空闲链表中分配*/ /* * Find the first power of two greater or equal to the * rounded size. */ bsize = size < XNHEAP_MINALLOCSZ ? XNHEAP_MINALLOCSZ : size; log2size = order_base_2(bsize); bsize = 1 << log2size; ilog = log2size - XNHEAP_MINLOG2; xnlock_get_irqsave(&heap->lock, s); block = heap->buckets[ilog].freelist; if (block == NULL) { block = get_free_range(heap, bsize, log2size); if (block == NULL) goto out; if (bsize <= XNHEAP_PAGESZ) heap->buckets[ilog].fcount += (XNHEAP_PAGESZ >> log2size) - 1; } else { if (bsize <= XNHEAP_PAGESZ) --heap->buckets[ilog].fcount; pagenum = ((caddr_t)block - heap->membase) / XNHEAP_PAGESZ; ++heap->pagemap[pagenum].bcount; } heap->buckets[ilog].freelist = *((caddr_t *)block); heap->used += bsize; } else { ..... }
第一步先对size求$log_2size$,$log_28$=3,获得bucket索引下标$ilog = log_28-3=0$,再用ilog做为下标获得管理8Byte大小池的bucket,buckets[0].freelist指向首个空闲块,若是buckets[ilog].freelist不为NULL,则将buckets[ilog].freelist指向的块分配出去,buckets[ilog].fcount减一,再根据freelist的地址计算该空闲块位于第几页(pagenum),更新该页的pagemap[pagenum].bcount。再将buckets[ilog].freelist指向下一个空闲页,更新总内存已分配大小heap->used,返回分配到的内存地址block。
但咱们内存池刚初始化,buckets[ilog].freelist 为NULL,进入block==NULL分支,先为该bucket分配空间。
先经过get_free_range()分配,分配后计算bucket的剩余块数buckets[ilog].fcount,XNHEAP_PAGESZ >> log2size就是新页面被分红了多少块,且立刻就要被分配出去耍一块,因此再减一。
下面看如何分配bucket管理的空间,get_free_range()中,先分配空闲页,而后再对空闲页进行分块。先看从整块内存找空闲页部分
static caddr_t get_free_range(struct xnheap *heap, u32 bsize, int log2size) { caddr_t block, eblock, freepage, lastpage, headpage, freehead = NULL; u32 pagenum, pagecont, freecont; freepage = heap->freelist; /*空闲页*/ while (freepage) { headpage = freepage; freecont = 0; do { lastpage = freepage; freepage = *((caddr_t *) freepage); freecont += XNHEAP_PAGESZ; } while (freepage == lastpage + XNHEAP_PAGESZ && freecont < bsize); if (freecont >= bsize) { if (headpage == heap->freelist) heap->freelist = *((caddr_t *)lastpage); else *((caddr_t *)freehead) = *((caddr_t *)lastpage); goto splitpage; } freehead = lastpage; } return NULL; splitpage: ...... return headpage; }

heap->freelist指向xnheap内存中第一个空闲页,10-14行循环迭代freepage并记录大小freecont,直到获得freecont大小的空闲页。咱们传入get_free_range()的bsize=8,$log_2size$ = 3,因此循环1次,分配到4KB空间就够了。以下图所示.

条件freecont >= bsize表示分配到了知足大小的连续空闲页,不然就是连续内存空间不够,看lastpage指向的下一个空闲空间是否连续,直到分配到符合条件的内存页,不然没法知足这次分配条件,返回 NULL。
咱们这里分配到了页0,20行更新heap->freelist指向下一个空闲页 。

跳转splitpage对页0进行切割。
splitpage: if (bsize < XNHEAP_PAGESZ) { for (block = headpage, eblock = headpage + XNHEAP_PAGESZ - bsize; block < eblock; block += bsize) *((caddr_t *)block) = block + bsize; *((caddr_t *)eblock) = NULL; } else *((caddr_t *)headpage) = NULL; pagenum = (headpage - heap->membase) / XNHEAP_PAGESZ; heap->pagemap[pagenum].type = log2size ? : XNHEAP_PLIST; heap->pagemap[pagenum].bcount = 1; for (pagecont = bsize / XNHEAP_PAGESZ; pagecont > 1; pagecont--) { heap->pagemap[pagenum + pagecont - 1].type = XNHEAP_PCONT; heap->pagemap[pagenum + pagecont - 1].bcount = 0; } return headpage;
splitpage操做将一个4K大小的页分红一个个大小为8Byte的块,并将这些块连起来,并更xpagemap[pagenum]的type为块大小3(2的幂$log_2blocksize$),表示该页PLIST。bcount=1是即将分配出去的第一个块。

回到xnheap_alloc(),更新bucket内剩余块数heap->buckets[3].fcount、8字节池空闲地址buckets[3].freelist,整个内存池已分配数heap->used,而后返回内存池分配的到的内存起始地址ptr_1。此时以下:

经过以上分析,咱们分配1字节空间,最终获得8字节的空间,8(1<<3)字节是xenomai内存池的最小管理单位,而且下次再分配8Byte内空间时,直接返回buckets[3].freelist并更新几个成员变量便可,速度极快。
一样,根据以上步骤请求分配50字节空间时,先对50向上向上对齐获得64,计算bucket索引$ilog = log_2 64-3=3$,本次分配请求从bucket[3]管理的内存池中分配,因为首次分配,bucket[3]中没有还管理的空间须要先从xnheap中分配空闲页,最终分配获得64字节大小的空间,分配后以下图所示。

请求分配1000字节空间时,先对1000向上对齐获得1024,计算bucket索引$ilog = log_2 1024-3=7$,本次分配请求从bucket[7]管理的内存池中分配,因为首次分配,bucket[7]中没有还管理的空间须要先从xnheap中分配一个空闲页分红4块交给bucket管理,最终本次分配获得1024字节大小的空间,分配后以下图所示。
以上分配后,buckets[7]中还剩余3个空闲块,若是bucket内的全部块分配完了,再次请求分配大小为1000字节的空间时会怎样?会再去分配一页空闲页进行切割。为了表示这个过程,继续执行如下语句,当ptr_1000_4分配后以下图所示。
ptr_1000_1 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_2 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_3 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_4 = xnheap_alloc(&hobalt_heap, 1000);
当分配ptr_1000_3后bucket中再也不由空闲块,bucket[7].freelist从新指向NULL,分配ptr_1000_4时就会触发再次从总内存分配空闲页来分红1K大小的块,分配ptr_1000_4后bucket[7].freelist指向新的空闲页。
因为请求大小是5000字节,前面说过超过页大小后会与页对齐,也就是8K的空间,且该大小知足<=2*PAGE_SIZE
,会向bucket[13]分配。
与小于页大小(4KB)的分配不一样的是,向页对齐后8K,8K空间占用2个页,因此图中连续的页五、页5分配出去,bucket内没有剩余块,页5对应的xnpagemap[5]的type被设置为XNHEAP_PCONT(1)表示该页与上页是连续的。
因为请求大小是10000字节,前面说过超过页大小后会与页对齐,也就是12K的空间,对于大于8K(2*PAGE)SIZE)大小的分配请求,从空闲页列表直接分配页面会更节省空间。
if (likely(size <= XNHEAP_PAGESZ * 2)) { /*小于8KB*/ ...... } else { if (size > heap->size) return NULL; xnlock_get_irqsave(&heap->lock, s); /* Directly request a free page range. */ block = get_free_range(heap, size, 0); if (block) heap->used += size; }
先判断总大小,而后调用get_free_range()直接从空闲页列表直接分配,参数$log_2size$=0,该状况下get_free_range()函数执行路径以下;
static caddr_t get_free_range(struct xnheap *heap, u32 bsize, int log2size) { caddr_t block, eblock, freepage, lastpage, headpage, freehead = NULL; u32 pagenum, pagecont, freecont; freepage = heap->freelist; while (freepage) { headpage = freepage; freecont = 0; /*在空闲页列表查找知足条件的连续空闲页*/ do { lastpage = freepage; freepage = *((caddr_t *) freepage); freecont += XNHEAP_PAGESZ; } while (freepage == lastpage + XNHEAP_PAGESZ && freecont < bsize); if (freecont >= bsize) { /*获得连续的页*/ if (headpage == heap->freelist) heap->freelist = *((caddr_t *)lastpage); /*更新freelist*/ else ..... goto splitpage; } freehead = lastpage; } return NULL; splitpage: if (bsize < XNHEAP_PAGESZ) { //<4K ..... } else *((caddr_t *)headpage) = NULL; pagenum = (headpage - heap->membase) / XNHEAP_PAGESZ; heap->pagemap[pagenum].type = log2size ? : XNHEAP_PLIST; heap->pagemap[pagenum].bcount = 1; for (pagecont = bsize / XNHEAP_PAGESZ; pagecont > 1; pagecont--) { heap->pagemap[pagenum + pagecont - 1].type = XNHEAP_PCONT; heap->pagemap[pagenum + pagecont - 1].bcount = 0; } return headpage; }
分配后的内存视图以下。
经过以上分析,咱们能够将分配到的内存块分为两类:
内存块释放的过程就是根据这些信息来定位要释放的块,并将它从新放回bucket内存池或空闲页列表。
经过xnheap_alloc()
分配的内存,经过xnheap_free()
释放,固然必须是在同一个xnheap上操做。
void xnheap_free(struct xnheap *heap, void *block) { caddr_t freepage, lastpage, nextpage, tailpage, freeptr, *tailptr; int log2size, npages, nblocks, xpage, ilog; u32 pagenum, pagecont, boffset, bsize; spl_t s; xnlock_get_irqsave(&heap->lock, s); if ((caddr_t)block < heap->membase || (caddr_t)block >= heap->memlim) goto bad_block; /* Compute the heading page number in the page map. */ pagenum = ((caddr_t)block - heap->membase) / XNHEAP_PAGESZ; boffset = ((caddr_t)block - (heap->membase + pagenum * XNHEAP_PAGESZ)); switch (heap->pagemap[pagenum].type) { case XNHEAP_PFREE: /* Unallocated page? */ case XNHEAP_PCONT: /* Not a range heading page? */ bad_block: xnlock_put_irqrestore(&heap->lock, s); XENO_BUG(COBALT); return; case XNHEAP_PLIST: /**/ ..... break; default: ....... } heap->used -= bsize; xnlock_put_irqrestore(&heap->lock, s); }
xnheap_free()中先根据地址判断释放的内存块是否属于指定的xnheap。若是合法的,接着计算要释放的内存所在的页号pagenum,以及页内的偏移量boffset。获得页号后从pagemap[pagenum]判断要释放的内存块属于那种类型,再作相应的释放操做。
将前面分配到的内存按不一样顺序释放,来查看xnheap的释放流程,因为分配的1000字节的几个内存块比较具备表明性,先看他们的释放,释放顺序以下。
/*释放*/ xnheap_free(&hobalt_heap, ptr_1000_1); xnheap_free(&hobalt_heap, ptr_1000); xnheap_free(&hobalt_heap, ptr_1000_3); xnheap_free(&hobalt_heap, ptr_1000_2); xnheap_free(&hobalt_heap, ptr_1000_4);
首先释放ptr_1000_1,ptr_1000_1实际指向的内存块可用空间为1024字节,首先计算ptr_1000_1所在的内存页页号pagenum = 2,以及页内的偏移量boffset = 1024.根据页号获得该页的类型pagemap[2].type=10,表示该已分配给buckets管理,跳转执行具体释放操做:
switch (heap->pagemap[pagenum].type) { case XNHEAP_PFREE: /* Unallocated page? */ case XNHEAP_PCONT: /* Not a range heading page? */ bad_block: xnlock_put_irqrestore(&heap->lock, s); XENO_BUG(COBALT); return; case XNHEAP_PLIST: ..... break; default: log2size = heap->pagemap[pagenum].type; bsize = (1 << log2size); if ((boffset & (bsize - 1)) != 0) /* Not a block start? */ goto bad_block; ilog = log2size - XNHEAP_MINLOG2; if (likely(--heap->pagemap[pagenum].bcount > 0)) { /* Return the block to the bucketed memory space. */ *((caddr_t *)block) = heap->buckets[ilog].freelist; heap->buckets[ilog].freelist = block; ++heap->buckets[ilog].fcount; break; } ..... } heap->used -= bsize;
从pagemap[2].type获得log2size = 10,反算出咱们释放的指针指向的内存块大小bsize = 1024字节。知道要释放的内存大小后,验证该地址是不是合法的内存块起始地址,验证方法就是看该地址是否与bsize对齐 。
验证合法后开始释放,要释放的内存属于bucket管理,计算buckets[]下标$ilog =10-3=7$,属于buckets[7]管理。先将页信息pagemap[pagenum].bcount减一,判断是否是页内要释放的最后一个内存块,若是是另行处理。22-24行将该该块内存放回bucket[7],将释放的内存指向原来的freelist,freelist指向释放的块,更新fcount值,完成ptr_1000_1的释放。更新整个xnheap内存使用量。释放ptr_1000_1后的内存视图以下。
接着依次释放ptr_1000、ptr_1000_3与释放ptr_1000_1一致,释放后如图所示
此时pagemap[3].bcount=1,当释放最后一个内存块 ptr_1000_2时,因为是该页最后一块状况有所不一样,条件(--heap->pagemap[pagenum].bcount > 0)不知足。执行以下.
default: log2size = heap->pagemap[pagenum].type;/*10*/ bsize = (1 << log2size);/*1024*/ if ((boffset & (bsize - 1)) != 0) /* Not a block start? */ goto bad_block; ilog = log2size - XNHEAP_MINLOG2; if (likely(--heap->pagemap[pagenum].bcount > 0)) { ...... break; } npages = bsize / XNHEAP_PAGESZ; if (unlikely(npages > 1)) goto free_page_list; freepage = heap->membase + pagenum * XNHEAP_PAGESZ; block = freepage; tailpage = freepage; nextpage = freepage + XNHEAP_PAGESZ; nblocks = XNHEAP_PAGESZ >> log2size; heap->buckets[ilog].fcount -= (nblocks - 1); XENO_BUG_ON(COBALT, heap->buckets[ilog].fcount < 0); if (likely(heap->buckets[ilog].fcount == 0)) { heap->buckets[ilog].freelist = NULL; goto free_pages; } /* * Worst case: multiple pages are traversed by the * bucket list. Scan the list to remove all blocks * belonging to the freed page. We are done whenever * all possible blocks from the freed page have been * traversed, or we hit the end of list, whichever * comes first. */ for (tailptr = &heap->buckets[ilog].freelist, freeptr = *tailptr, xpage = 1; freeptr != NULL && nblocks > 0; freeptr = *((caddr_t *) freeptr)) { if (unlikely(freeptr < freepage || freeptr >= nextpage)) { if (unlikely(xpage)) { *tailptr = freeptr; xpage = 0; } tailptr = (caddr_t *)freeptr; } else { --nblocks; xpage = 1; } } *tailptr = freeptr; goto free_pages; } heap->used -= bsize;
如今知道了该块是页的最后一块,接着看该块否是bucket[7]中的最后一个块,判断方式为看fcount-nblocks - 1是否等于0,以下。
nblocks = XNHEAP_PAGESZ >> log2size; heap->buckets[ilog].fcount -= (nblocks - 1); if (likely(heap->buckets[ilog].fcount == 0)) { /*是*/ heap->buckets[ilog].freelist = NULL; goto free_pages; }
不是bucket的最后一块,可是页2已经所有空闲,接下来重整页面。
for (tailptr = &heap->buckets[ilog].freelist, freeptr = *tailptr, xpage = 1; freeptr != NULL && nblocks > 0; freeptr = *((caddr_t *) freeptr)) { if (unlikely(freeptr < freepage || freeptr >= nextpage)) { if (unlikely(xpage)) { *tailptr = freeptr; xpage = 0; } tailptr = (caddr_t *)freeptr; } else { --nblocks; xpage = 1; } } *tailptr = freeptr; goto free_pages;
根据frelist找出已经空闲的页,而后跳转至标签free_pages进行释放页2,free_pages主要调整空闲页之间的freelist,是链表freelist保持递增。
free_pages: /* Mark the released pages as free. */ for (pagecont = 0; pagecont < npages; pagecont++) heap->pagemap[pagenum + pagecont].type = XNHEAP_PFREE; /* * Return the sub-list to the free page list, keeping * an increasing address order to favor coalescence. */ for (nextpage = heap->freelist, lastpage = NULL; nextpage != NULL && nextpage < (caddr_t) block; lastpage = nextpage, nextpage = *((caddr_t *)nextpage)) ; /* Loop */ *((caddr_t *)tailpage) = nextpage; if (lastpage) *((caddr_t *)lastpage) = (caddr_t)block; else heap->freelist = (caddr_t)block; break;
下面释放ptr_1000_4,因为ptr_1000_4是bucket[7]最后一块直接将bucket[7].freelist指向NULL,而后跳转至标签free_pages进行释放页3就行,释放后以下。
ptrt_一、ptr_50、ptr_5000均为页和bucket的最后一块,释放流程相同,再也不说明。
最后看一下ptr_10000的释放,ptr_10000占用连续的3个页,一样根据ptr_10000计算出块开始页的tpye=2(XNHEAP_PLIST),进入XNHEAP_PLIST分支释放,经过看紧接着的页的tpye计算内存块的页数npages。计算该内存块的大小bsize,接着开始释放页。
void xnheap_free(struct xnheap *heap, void *block) { caddr_t freepage, lastpage, nextpage, tailpage, freeptr, *tailptr; int log2size, npages, nblocks, xpage, ilog; u32 pagenum, pagecont, boffset, bsize; spl_t s; ....... /* Compute the heading page number in the page map. */ pagenum = ((caddr_t)block - heap->membase) / XNHEAP_PAGESZ; boffset = ((caddr_t)block - (heap->membase + pagenum * XNHEAP_PAGESZ)); switch (heap->pagemap[pagenum].type) { case XNHEAP_PFREE: /* Unallocated page? */ case XNHEAP_PCONT: /* Not a range heading page? */ bad_block: xnlock_put_irqrestore(&heap->lock, s); XENO_BUG(COBALT); return; case XNHEAP_PLIST: npages = 1; while (npages < heap->npages && heap->pagemap[pagenum + npages].type == XNHEAP_PCONT) npages++; bsize = npages * XNHEAP_PAGESZ; free_page_list: /* Link all freed pages in a single sub-list. */ for (freepage = (caddr_t) block, tailpage = (caddr_t) block + bsize - XNHEAP_PAGESZ; freepage < tailpage; freepage += XNHEAP_PAGESZ) *((caddr_t *) freepage) = freepage + XNHEAP_PAGESZ; ....... default: ...... } heap->used -= bsize;
freepage指向块的第一页,tailpage指向块的最后一页,将释放的几页链起来,成为一个子列表,如图所示。
如今仅将块内的页连接起来,接下来执行标签free_pages,将这些要释放的页连接到空闲页列表。
先将这些也对应的pagemap.type标志为空闲(XNHEAP_FREE)。
free_pages: /* Mark the released pages as free. */ for (pagecont = 0; pagecont < npages; pagecont++) heap->pagemap[pagenum + pagecont].type = XNHEAP_PFREE;
将子列表放回空闲页列表,并保持它们递增的连接关系。
for (nextpage = heap->freelist, lastpage = NULL; nextpage != NULL && nextpage < (caddr_t) block; lastpage = nextpage, nextpage = *((caddr_t *)nextpage)) ; /* Loop */ *((caddr_t *)tailpage) = nextpage; if (lastpage) *((caddr_t *)lastpage) = (caddr_t)block; else heap->freelist = (caddr_t)block; break;
将子列表插入空闲链表后,完成释放,视图以下(ptrt_一、ptr_50、ptr_5000还未释放)。
xenomai内核经过本身管理一片内存来避免内存分配释放影响实时性。
针对小于2*PAGE_SIZE 的内存请求,xnheap使用bucket创建内存池,使小内存请求迅速获得知足。对于大于2*PAGE_SIZE 的内存请求,直接向空闲页列表分配。
缺点:当内存页列表比较疏松时,可能会出现分配一个大内存(>4K)须要遍历全部空闲页到最后才分配到的状况。此时复杂度为$O(n)$,n表示空闲页块数。xenomai3.1对此进行了优化,使用红黑树按空闲块大小来管理空闲页,红黑树时间复杂度$O(logn)$。