一、 xenomai内存池管理

本文讲述的xenomai内核内存管理,供cobalt内核服务RT应用过程中动态分配使用。

在用户态,glibc的内存管理不具有时间确定性,RT应用一般不使用,为此实时应用库libcobalt为RT应用实现了时间确定的内存动态分配释放heap,使用方法参见Heap management services,其分配释放算法与内核里的差不多,不在赘述,下面开始。

通常,操作系统的内存管理,内存分配算法一定要快,否则会影响应用程序的运行效率,另一个是内存利用率。

无论linux还是xenomai,在服务或管理应用程序过程中经常需要内存分配,通常linux内存的分配与释放都是时间不确定的,例如,缺页异常和页面换出会导致大且不可预测的延迟,不适用于受严格时间限制的实时应用程序。

xenomai作为硬实时内核,不能使用linux这样的内存分配释放接口,为此xenomai采取的措施是:在xenomai内核初始化时,先调用__vmalloc()从linux管理的ZONE_NORMAL中分配 一片内存,然后由xenomai自己来管理这片内存,且xenomai提供的内存分配释放时间确定的,这样就不会因为内存的分配释放影响实时性(该内存管理代码量非常少,有效代码数3百行左右,实现精巧,值得研究利用。在需要自己实现一个内存管理的场合很有帮助)。

下面代码基于 xenomai-3.0.8。xenomai 3.1开始有所不同详见文末。

1.xnheap

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)

简单介绍一下配置项中的几个内存池的用途:

  • (512) Number of registry slots,xenomai内核运行中内核资源对象存储槽的大小,用于分配系统使用资源的最大大小,如信号(signal)、互斥对象(mutex)、信号量等.
  • (4096) Size of system heap (Kb) 系统内存池,用于cobalt内核工作过程中动态内存分配,内核中很多任务共享的内存会从该区域分配,例如XDDP通讯时数据缓冲区默认从该区域分配。
  • (512) Size of private heap (Kb) 每个Cobalt任务私有的内存池,在实时任务创建时,从linux分配内存并初始化,位于Cobalt任务调度实体cobalt_process中,当实时任务内核上下文需要分配内存时,就会从该区域中获取,XDDP 通讯中可选从该内存区分配缓冲区。

本节以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。

每个内存池分配一个对象xnheap来管理,xnheap结构如下。

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记录内存结束地址.

2. xnpagemap

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大小分割成许多子块);

3. xnbucket

	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页内的使用信息。

4. xnheap初始化

当分配到一片内存作为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实时性。初始化后如下图所示

5. 内存块分配

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);

5.1 小内存分配流程(<= 2*PAGE_ZISE)

1.分配1Byte

首先来看分配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并更新几个成员变量即可,速度极快。

2.分配50Byte

同样,根据以上步骤请求分配50字节空间时,先对50向上向上对齐得到64,计算bucket索引\(ilog = log_2 64-3=3\),本次分配请求从bucket[3]管理的内存池中分配,由于首次分配,bucket[3]中没有还管理的空间需要先从xnheap中分配空闲页,最终分配得到64字节大小的空间,分配后如下图所示。

3.分配1000 Byte

请求分配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指向新的空闲页。

4. 分配5000字节

由于请求大小是5000字节,前面说过超过页大小后会与页对齐,也就是8K的空间,且该大小满足<=2*PAGE_SIZE,会向bucket[13]分配。

与小于页大小(4KB)的分配不同的是,向页对齐后8K,8K空间占用2个页,所以图中连续的页5、页5分配出去,bucket内没有剩余块,页5对应的xnpagemap[5]的type被设置为XNHEAP_PCONT(1)表示该页与上页是连续的。

5.2 大内存分配(> 2*PAGE_ZISE)

1. 分配10000字节

由于请求大小是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;
}

分配后的内存视图如下。

6. 内存释放

通过以上分析,我们可以将分配到的内存块分为两类:

  • 从bucket中分配,大小小于等于4KB,不仅bucket记录着数量,该块所在页的pagemap[].type也记录着该块的大小。
  • 直接从空闲列表分配,大小大于4KB,pagemap[n].type为XNHEAP_PLIST(2)表示页n是该块的开始页,后续的n+i页,pagemap[n+i].type都为XNHEAP_PCONT(1)。

内存块释放的过程就是根据这些信息来定位要释放的块,并将它重新放回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_1、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_1、ptr_50、ptr_5000还未释放)。

7. 总结

xenomai内核通过自己管理一片内存来避免内存分配释放影响实时性。

针对小于2*PAGE_SIZE 的内存请求,xnheap使用bucket建立内存池,使小内存请求迅速得到满足。对于大于2*PAGE_SIZE 的内存请求,直接向空闲页列表分配。

缺点:当内存页列表比较疏松时,可能会出现分配一个大内存(>4K)需要遍历所有空闲页到最后才分配到的情况。此时复杂度为\(O(n)\),n表示空闲页块数。xenomai3.1对此进行了优化,使用红黑树按空闲块大小来管理空闲页,通过大小直接查找空闲页速度极快,红黑树时间复杂度\(O(logn)\),此外从红黑树中分配的内存从原来4K改变为512Byte对齐,这样使内存利用率进一步提高,有机会继续出一篇关于xenomai 3.1内存管理的文章。

版权声明:本文为本文为博主原创文章,转载请注明出处。如有错误,欢迎指正。博客地址:https://www.cnblogs.com/wsg1100/

【原创】xenomai内核解析--实时内存管理--xnheap的更多相关文章

  1. 【原创】xenomai内核解析--实时IPC概述

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1.概述 2.Real-time IPC 2. ...

  2. 【原创】xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 1.概述 上篇文章xenomai内核解析--实时IP ...

  3. 【原创】xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(二)--实时与非实时关联(bind流程)

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 1.概述 上篇文章介绍了实时端socket创建和配置 ...

  4. 【原创】xenomai内核解析--双核系统调用(二)--应用如何区分xenomai/linux系统调用或服务

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正. 1. 引出问题 上一篇文章xenomai内核解析--双核系统调用(一)以X86处理器为例,分析了xenomai内核调用的流程, ...

  5. 【xenomai内核解析】系列文章大纲

    xenomai内核解析 本博客为本人学习linux实时操作系统框架xenomai的一些记录,主要剖析xenomai内核实现,以及与linux相关的知识.方便读者定位具体文章,现列出本博客大纲,后续会陆 ...

  6. xenomai内核解析---内核对象注册表—xnregistry(重要组件)

    1. 概述 上篇文章xenomai内核解析--同步互斥机制(一)--优先级倒置讲到,对于所有内核对象: xnregistry:保存内核对象,提供内核对象存储和快速检索. xnsynch:资源抽象,提供 ...

  7. 【原创】xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(三)--实时与非实时数据交互

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1.概述 1.概述 [原创]实时IPC概述 [ ...

  8. 【原创】xenomai内核解析--同步互斥机制(一)--优先级倒置

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 一.xenomai 资源管理简要 二.优先级倒 ...

  9. xenomai内核解析之双核系统调用(一)

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 xenomai 内核系统调用 一.32位Lin ...

随机推荐

  1. sql如何查询不包含中文

    SELECT * FROM dbo.表名 WHERE 字段名 NOT LIKE '%[吖-座]%'

  2. 简单编程:如何用java来打印出一个5行的三角形

  3. MySQL关于useSSL的问题,会弹出警告

    我在手动配置dbcp时,执行数据库相关的操作时,报错以下: Fri Aug 28 21:10:19 CST 2020 WARN: Establishing SSL connection without ...

  4. PHP之道(PHP The Right Way)

    原文地址:http://laravel-china.github.io/php-the-right-way/

  5. [LeetCode]394. 字符串解码(栈)

    题目 给定一个经过编码的字符串,返回它解码后的字符串. 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次.注意 k 保证为正整数. ...

  6. 关于人人开源renren-fast-vue 中npm install各种报错的解决方案

    首先吐槽一下,因为这个问题我整了好几天,把报错信息复制百度,试遍了各种方法,node.js我是卸载了安装,安装了卸载,甚至renren-fast-vue我也删了再下,然后再删,无限循环.然而没有什么软 ...

  7. 13.深入k8s:Pod 水平自动扩缩HPA及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 Pod 水平自动扩缩 Pod 水平自动扩缩工作原理 Pod 水平自动 ...

  8. Spring Cloud系列(四):Eureka源码解析之客户端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...

  9. 008 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 02 Java 中的关键字

    008 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 02 Java 中的关键字 关键字 关键字就是一些有特殊意义的词 之前学习的程序中涉及到的关键字 Java中 ...

  10. 003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程

    003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程 Java程序长啥样? 首先编写一个Java程序 记事本编写程序 打开记事本 1.wi ...