0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

xenomai系统中的xnheap管理机制

Linux阅码场 来源:Linux阅码场 作者:Linux阅码场 2022-05-25 10:15 次阅读


作者简介

顺刚(网名:沐多),一线码农,从事工控行业,目前在一家工业自动化公司从事工业实时现场总线开发工作,喜欢钻研Linux内核及xenomai,个人博客 wsg1100,欢迎大家关注!

目录

  • xenomai内存池管理

  • 1.xnheap

  • 2. xnpagemap

  • 3. xnbucket

  • 4. xnheap初始化

  • 5. 内存块分配

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

  • 1.分配1Byte

  • 2.分配50Byte

  • 3.分配1000 Byte

  • 4. 分配5000字节

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

  • 1. 分配10000字节

  • 6. 内存释放

  • 页内块释放

  • 页连续的块释放

  • 7. 总结

本文分析的xenomai系统中的内存池(xnheap)管理机制。

xenomai内存池管理

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

但对于硬实时操作系统,首先要保证实时性,即确定性,不同内存大小的分配释放时间必须是确定的,同时也要快。

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

xenomai作为硬实时内核,不能使用linux这样的内存分配释放接口,为此xenomai采取的措施是:

  • 在内核态,在xenomai内核初始化时,先调用__vmalloc()从linux管理的ZONE_NORMAL中分配 一片内存,然后由xenomai自己来管理这片内存,且xenomai提供的内存分配释放时间确定的,这样就不会因为内存的分配释放影响实时性(该内存管理代码量非常少,有效代码数3百行左右,实现精巧,值得研究利用)。

  • 在用户态,glibc的内存管理不具有时间确定性,RT应用只能在程序初始化时分配并访问(避免运行中产生pagefault),运行中不能使用,否则会严重影响实时性。为此xenomai实时应用库libcobalt为RT应用实现了时间确定的内存动态分配释放heap,供实时任务运行中分配内存,使用方法参见Heap management services,其内存管理分配释放算法与内核里的差不多,不在赘述,下面开始。

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

1.xnheap

xenomai管理的内存池称为xnheap,内存池大小预先配置,如xenomai的系统内存池cobaltheap,负责内核大多内核数据分配,其大小为 sysheap_size_arg*1024 Byte(sysheapsizearg KB),sysheapsize_arg可在内核配置时设置,或者通过内核参数 xenomai.sysheap_size=配置。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)Sizeofsharedheap(Kb)

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

  • (512)Numberof registry slots,xenomai内核运行中内核资源对象存储槽的大小,用于分配系统使用资源的最大大小,如信号(signal)、互斥对象(mutex)、信号量等.

  • (4096)Sizeof system heap(Kb)系统内存池,用于cobalt内核工作过程中动态内存分配,内核中很多任务共享的内存会从该区域分配,例如XDDP通讯时数据缓冲区默认从该区域分配。

  • (512)Sizeofprivateheap(Kb)每个Cobalt任务私有的内存池,在实时任务创建时,从linux分配内存并初始化,位于Cobalt任务调度实体cobalt_process中,当实时任务内核上下文需要分配内存时,就会从该区域中获取,XDDP 通讯中可选从该内存区分配缓冲区。

本节以xenomai的系统内存池cobaltheap为例来了解xenomai内存池管理。cobaltheap在xenomai内核初始化过程中初始化,先调用_vmalloc()从linux管理的ZONENORMAL中分配,然后在调用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要求管理的内存大小必须是PAGESIZE的倍数,且至少有2页,其最大值在xenomai3.0.8版本里为2GB(1<<31),其他版本可能有所改变。以sysheapsizearg默认值256为例,即cobaltheap大小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实时性。初始化后如下图所示

1d28cd04-dbbf-11ec-ba43-dac502259ad0.png

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。

/*includecobaltkernelheap.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类,对于大于XNHEAPPAGESZ的向上与XNHEAPPAGESZ对齐;对于小于8Byte的,向上与8Byte对齐;对于大于8Byte,向上与16Byte对齐;这样是为了与bucket一一对应。

例如分配5000Byte,最终分配到的空间大小为8192 Byte(以PAGE_SIZE为4KB计算),要分配1Byte空间,将会得到8Byte的空间,分配50Byte空间得到64Byte空间。

我们请求分配1Byte的内存,对齐后size为8 Byte, buckets[XNHEAP_NBUCKETS]只管理请求大小小于2*PAGESZIE的分配池。当请求的大小大于页大小的2倍时,从空闲页列表直接分配页面会更节省空间。8Byte小于2*PAGESZIE,下面看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求log2size,log28=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分配空间。

先通过getfreerange()分配,分配后计算bucket的剩余块数buckets[ilog].fcount,XNHEAP_PAGESZ >> log2size就是新页面被分成了多少块,且马上就要被分配出去耍一块,所以再减一。

下面看如何分配bucket管理的空间,getfreerange()中,先分配空闲页,然后再对空闲页进行分块。先看从整块内存找空闲页部分

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

1d681e6e-dbbf-11ec-ba43-dac502259ad0.png

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

1ddaf114-dbbf-11ec-ba43-dac502259ad0.png

条件freecont >= bsize表示分配到了满足大小的连续空闲页,否则就是连续内存空间不够,看lastpage指向的下一个空闲空间是否连续,直到分配到符合条件的内存页,否则无法满足此次分配条件,返回 NULL。

我们这里分配到了页0,20行更新heap->freelist指向下一个空闲页 。

1e04f964-dbbf-11ec-ba43-dac502259ad0.png

跳转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;  }

returnheadpage;

splitpage操作将一个4K大小的页分成一个个大小为8Byte的块,并将这些块连起来,并更xpagemap[pagenum]的type为块大小3(2的幂log_2blocksize),表示该页PLIST。bcount=1是即将分配出去的第一个块。

1e282042-dbbf-11ec-ba43-dac502259ad0.png

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

1eb455b2-dbbf-11ec-ba43-dac502259ad0.png

通过以上分析,我们分配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字节大小的空间,分配后如下图所示。

1ef893d0-dbbf-11ec-ba43-dac502259ad0.png

3.分配1000 Byte

请求分配1000字节空间时,先对1000向上对齐得到1024,计算bucket索引ilog = log_2 1024-3=7,本次分配请求从bucket[7]管理的内存池中分配,由于首次分配,bucket[7]中没有还管理的空间需要先从xnheap中分配一个空闲页分成4块交给bucket管理,最终本次分配得到1024字节大小的空间,分配后如下图所示。

1f4b1876-dbbf-11ec-ba43-dac502259ad0.png

以上分配后,buckets[7]中还剩余3个空闲块,如果bucket内的所有块分配完了,再次请求分配大小为1000字节的空间时会怎样?会再去分配一页空闲页进行切割。为了表示这个过程,继续执行以下语句,当ptr10004分配后如下图所示。

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

1f882ab8-dbbf-11ec-ba43-dac502259ad0.png

当分配ptr10003后bucket中不再由空闲块,bucket[7].freelist重新指向NULL,分配ptr10004时就会触发再次从总内存分配空闲页来分成1K大小的块,分配ptr10004后bucket[7].freelist指向新的空闲页。

4. 分配5000字节

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

1fc10cd4-dbbf-11ec-ba43-dac502259ad0.png

与小于页大小(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;}

先判断总大小,然后调用getfreerange()直接从空闲页列表直接分配,参数log2size=0,该情况下getfree_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;}

分配后的内存视图如下。

1feb7d66-dbbf-11ec-ba43-dac502259ad0.png

6. 内存释放

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

  • 从bucket中分配,大小小于等于4KB,不仅bucket记录着数量,该块所在页的pagemap[].type也记录着该块的大小。

  • 直接从空闲列表分配,大小大于4KB,pagemap[n].type为XNHEAPPLIST(2)表示页n是该块的开始页,后续的n+i页,pagemap[n+i].type都为XNHEAPPCONT(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);

页内块释放

首先释放ptr10001,ptr10001实际指向的内存块可用空间为1024字节,首先计算ptr10001所在的内存页页号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值,完成ptr10001的释放。更新整个xnheap内存使用量。释放ptr10001后的内存视图如下。

2027aa3e-dbbf-11ec-ba43-dac502259ad0.png

接着依次释放ptr1000、ptr10003与释放ptr1000_1一致,释放后如图所示

2068d16c-dbbf-11ec-ba43-dac502259ad0.png

此时pagemap[3].bcount=1,当释放最后一个内存块 ptr10002时,由于是该页最后一块情况有所不同,条件(--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;gotofree_pages;

根据frelist找出已经空闲的页,然后跳转至标签freepages进行释放页2,freepages主要调整空闲页之间的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;

209ee3f6-dbbf-11ec-ba43-dac502259ad0.png

下面释放ptr10004,由于ptr10004是bucket[7]最后一块直接将bucket[7].freelist指向NULL,然后跳转至标签free_pages进行释放页3就行,释放后如下。

20e5eb2a-dbbf-11ec-ba43-dac502259ad0.png

ptrt1、ptr50、ptr_5000均为页和bucket的最后一块,释放流程相同,不再说明。

页连续的块释放

最后看一下ptr10000的释放,ptr10000占用连续的3个页,同样根据ptr10000计算出块开始页的tpye=2(XNHEAPPLIST),进入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指向块的最后一页,将释放的几页链起来,成为一个子列表,如图所示。

2113de68-dbbf-11ec-ba43-dac502259ad0.png

现在仅将块内的页链接起来,接下来执行标签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;

将子列表插入空闲链表后,完成释放,视图如下(ptrt1、ptr50、ptr_5000还未释放)。

21299924-dbbf-11ec-ba43-dac502259ad0.png

7. 总结

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

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

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

2185a28c-dbbf-11ec-ba43-dac502259ad0.png

原文标题:xenomai内存池管理

文章出处:【微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

审核编辑:汤梓红


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10942

    浏览量

    206545
  • 内存
    +关注

    关注

    8

    文章

    2737

    浏览量

    72614
  • Xenomai
    +关注

    关注

    0

    文章

    10

    浏览量

    7933

原文标题:xenomai内存池管理

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    控制器如何设计MMU--虚拟内存管理机制

    控制器如何设计MMU--虚拟内存管理机制
    发表于 12-15 09:53

    安卓应用商店和APP市场管理机制

    “工信部正在搭建移动应用软件认证和管理服务,以加强对智能终端的安全管理。”工信部相关负责人昨日表示,目前为了解决智能终端的安全问题,内部确实已经明确了需要一套对安卓应用商店和APP市场管理机制,包括上线前的审核和上线后的监测和抽
    发表于 07-15 07:41

    怎么给RTOS动态分区内存管理机制进行优化?

    怎么给RTOS动态分区内存管理机制进行优化?
    发表于 04-28 06:17

    嵌入式系统所用到的内存管理机制主要有哪几种

    嵌入式系统所用到的内存管理机制主要有以下两种: 1、虚拟内存管理机制: 有一些嵌入式处理器提供了MMU,在MMU具备内存地址映射和寻址功能,它使操作系统的内存
    发表于 12-17 06:34

    阐述FreeRTOS系统机制及在应用的优缺点

    :FreeRTOS是一个源码公开的免费的嵌入式实时操作系统,通过研究其内核可以更好地理解嵌入式操作系统的实现原理.本文主要阐述FreeRTOS系统的任务调度
    发表于 12-20 06:34

    命令终端的常用操作有哪些?软件包管理机制是什么

    软件包redhat Linux提出的软件包管理机制—Rpm软件包2.为什么用软件包管理?之前的阶段,linux没有考虑到系统软件包间复杂的依赖关...
    发表于 12-21 06:38

    阐述FreeRTOS系统机制的实现原理

    2--嵌入式操作系统FreeRTOS的原理与实现摘自::FreeRTOS是一个源码公开的免费的嵌入式实时操作系统,通过研究其内核可以更好地理解嵌入式操作系统的实现原理.本文主要阐述FreeRTOS
    发表于 12-22 07:15

    基于OSEK/DX操作系统的任务管理机制设计

    基于OSEK/VDX操作系统的任务管理机制设计:在汽车电子仿真控制平台开发领域,通常需要遵循OSEKIVDX规范集,而该规范集的核心之一便是OSEK/VDX操作系统规范。要设计一个符合该规
    发表于 04-21 22:02 19次下载

    VxWorks内存管理机制的分析与研究

    实时性、可靠性是嵌入式开发对内存管理的基本要求,本文探讨了操作系统内存管理的主要问题,对嵌入式操作系统Vxworks 的内存管理机制进行分析
    发表于 01-07 12:35 23次下载

    linux内存管理机制浅析

    本内容介绍了arm linux内存管理机制,详细说明了linux内核内存管理,linux虚拟内存管理,arm linux内存管理等方面的知识
    发表于 12-19 14:09 73次下载
    linux内存<b class='flag-5'>管理机制</b>浅析

    一种基于信息流策略的组密钥管理机制

    文中将多级安全的信息流策略引入到安全组通信系统中,设计了一种基于信息流策略的组密钥管理机制。该机制应用密钥多树图的管理方法保证了密钥管理效率
    发表于 01-08 14:54 0次下载

    TMS320F28x 事件管理机制参考

    DSP之TMS320F28x事件管理机制参考,很好的DSP自学资料,快来学习吧。
    发表于 04-15 14:48 15次下载

    最全SPARK内存管理机制

    最全SPARK内存管理机制
    发表于 09-08 14:17 5次下载
    最全SPARK内存<b class='flag-5'>管理机制</b>

    嵌入式系统内存管理机制详解

    操作系统的内存管理功能用于向操作系统提供一致的地址映射功能和内存页面的申请、释放操作。在嵌入式实时系统中,内存管理根据不同的
    发表于 11-18 09:41 4310次阅读

    浅析物理内存与虚拟内存的关系及其管理机制

    本文主要介绍内存管理机制:物理内存与虚拟内存的关系,Linux内存管理机制,Python内存管理机制,Nginx内存管理机制,环形缓冲区机制
    的头像 发表于 04-12 09:55 4574次阅读
    浅析物理内存与虚拟内存的关系及其<b class='flag-5'>管理机制</b>