contiki系统分析四:内存分配


contiki有三种分配内存的方式。


memb 内存块分配器,在contiki中使用最频繁

mmem 管理内存分配器,很少去使用

标准C库的malloc的栈内存分配器,在contiki这种受限系统中使有最危险。


memb的内存分配器


有下列的API: 

MEMB(name, structure, num) : 定义memory block.
void memb_init(struct memb *m) : 初始化memory block.
void *memb_alloc(struct memb *m) : 分配memory block.
int memb_free(struct memb *m, void *ptr) : 释放memory block.
int memb_inmemb(struct memb *m, void *ptr) : 判断ptr传入的地址是否是memory block.


MEMB做了哪些事情


#define MEMB(name, structure, num) \
        static char CC_CONCAT(name,_memb_count)[num]; \
        static structure CC_CONCAT(name,_memb_mem)[num]; \
        static struct memb name = {sizeof(structure), num, \
                                          CC_CONCAT(name,_memb_count), \
                                          (void *)CC_CONCAT(name,_memb_mem)}

struct memb {
  unsigned short size;
  unsigned short num;
  char *count;
  void *mem;
};

主要是定义了mem block.

其中CC_CONCAT这个宏把两个参数连接成为一个字符串.

name表示要定义的这个memblock的名称.

num表示memblock的数量,但是在struct memb中把num存储的大小定义为unsigned short型,也就是说,最多存隼128个memblock的对象

strructure参数表示需要扩展内存的结构体.

先定义出记录mem block对象的指针,和内容.然后再把信息记录在memb的结构体中.记录内存的状态跟分配内存是在同一时间完成的.

其中定义了两个数组

count:表示这段内存空间有没有被使用(0表示没使用,1表示已经使用),首地址memb->count

mem:实际的结构体指针存放的数组,首地址memb->mem,其它元素访问memb->mem+i*memb->size

memb->size是存放的sizeof(structure)的值.

num就表示分配数量,实际上mem的总大小为memb->size * memb->num

另外要注意的是,MEMB开辟的内存空间都是用static定义的,count和mem这两个数组由于没赋初值,放在.bss段里,但是memb name赋了初值,放在.data段里.众所周知,传统的C库中的malloc一系列的函数,是在heap中存放开辟的空间的.

由于内存受限系统不能去开辟一段自由使用的空间,而且contiki中的轻量级线程也没有自己的堆和栈.所以放在data或bss段中是比较好的办法.


memb_init

把已经开辟的空间里的所有值都设为0.用memset实现.


memb_alloc

从memb->count[0]开始往后找大小为mem->size的内存块,

如果memb->count[i]标记为1,则这个区块已经使用,继续向后找.

如果标记为0,则把区块分配出去.返回memb->mem +i*memb->size

如果返回NULL,则标记为0.


memb_free

有两个参数,一个是memb结构体的参数,另一个是要free掉的指针.

函数的实现流程是,在memb->mem数组中去掉传入的指针.如果找到.则把memb->count[i]标记为0.然后返回0

没找到这个指针,则返回为-1.


memb_inmemb

去查找传入参数是否是memb分配的地址.也就是看传入参数是否落在memb->mem的区间中.若是返回1,不是返回0.

下面是一个调用API的示例

struct connection {
   int socket;
 };
 MEMB(connections, struct connection, 16);
 
 struct connection *
 open_connection(int socket)
 {
   struct connection *conn;
 
   conn = memb_alloc(&connections);
   if(conn == NULL) {
     return NULL;
   }
   conn->socket = socket;
   return conn;
 }
 
 void
 close_connection(struct connection *conn)
 {
   memb_free(&connections, conn);
 }

mmem管理内存分配器


模型:


mmem(managed memory allocator)                               

实现的文件是core/lib/mmem.c及对应的头文件.

中间涉及一个结构体:

struct mmem {
  struct mmem *next;
  unsigned int size;
  void *ptr;
};

大体的思路,还是用static类型的全局变量.是字符型的数组.数组大小默认4096,也就是4096字节.这个参数可用MMEM_CONF_SIZE进行配置.

然后mmem_init()初始化上图所示的链表.

mmem_alloc()则是分配一段连续的内存.然后在链表中记录下来.

其中用了一个技巧.

  /* Check if we have enough memory left for this allocation. */
  if(avail_memory < size) {
    return 0;
  }

  /* We had enough memory so we add this memory block to the end of
     the list of allocated memory blocks. */
  list_add(mmemlist, m); 

  /* Set up the pointer so that it points to the first available byte
     in the memory block. */
  m->ptr = &memory[MMEM_SIZE - avail_memory];

  /* Remember the size of this memory block. */
  m->size = size;

  /* Decrease the amount of available memory. */
  avail_memory -= size;

avail_memory是一个全局变量.MMEM_SIZE是分配的内存空间的总大小.则MMEM_SIZE-avail_memory就是还没有分配的内存的起始的数组索引.

这也意味着,在这段内存空间里,已分配的内存始终在前面,未分配的内存始终在后面.这样做的话,也有合理性.在内存分配时,不存在内存碎片.

mmem->size是当前分配内存的大小. mmem->ptr是当前分配内存的起始地址.


mmem_free(),有一个参数,即当前分配内存的mmem结构体.实现是,利用memmove,把后面已分配内存的空间的数据覆盖掉刚才要free掉的内存.然后再从链表中删除要free的mmem的记录.

static struct mmem mmem;
 
 static void
 test_mmem(void)
 {
   struct my_struct {
     int a;
   } my_data, *my_data_ptr;
 
   if(mmem_alloc(&mmem, sizeof(my_data)) == 0) {
     printf("memory allocation failed\n");
   } else {
     printf("memory allocation succeeded\n");
     my_data.a = 0xaa;
     memcpy(MMEM_PTR(&mmem), &my_data, sizeof(my_data));
     printf("Value a equals 0x%x\n",
 	((struct my_struct *)MMEM_PTR(&mmem))->a);
     mmem_free(&mmem);
   }
 }


示例代码,没有分析的必要了.

这种操作在中断,多线程及抢占式的代码存在的情况下,极不安全.所以一般只是在contiki的进程中使用.如果获取分配的内存地址,只能用MMEM_PTR()这个宏.

在contiki系统中,可以在platform下面的初始化代码时提供mmem_init.

platform/iris/contiki-iris-main.c有对应的例子.可以看一下.


总结


第三种分配内存的方法即C库里的malloc, calloc等函数.但是在contiki内存受限.不会分配到连续的内存,而且也要开足heap区间.对contiki来说,不是一个好的选择.


memb的话,属于静态的分配内存.mmem属于动态分配内存.

但是现在的contiki系统中通用的做法是静态分配内存.好的方法应该是动态去分配.


原文地址:https://www.cnblogs.com/javawebsoa/p/2981337.html