(四)单片机系统动态内存分配,任务调度思想
扫描二维码
随时随地手机看文章
一、内存分配:
1.1 申请一块内存大小定义:
#define MEM_0_SIZE (8) //8字节
#define MEM_1_SIZE (16) //16字节
#define MEM_2_SIZE (32)
#define MEM_3_SIZE (64)
#define MEM_4_SIZE (128)
#define MEM_5_SIZE (256)
1.2 设定SIZE大小内存可申请到的内存块最大个数定义:
#define MEM_0_COUNT (16) //最大16个内存块
#define MEM_1_COUNT (16)
#define MEM_2_COUNT (32)
#define MEM_3_COUNT (32)
#define MEM_4_COUNT (32)
#define MEM_5_COUNT (16)
1.3 内存数组定义
static u8 g_8bytesmem[MEM_0_COUNT*MEM_0_SIZE];
static u8 g_16bytesmem[MEM_1_COUNT*MEM_1_SIZE];
static u8 g_32bytesmem[MEM_2_COUNT*MEM_2_SIZE];
static u8 g_64bytesmem[MEM_3_COUNT*MEM_3_SIZE];
static u8 g_128bytesmem[MEM_4_COUNT*MEM_4_SIZE];
static u8 g_256bytesmem[MEM_5_COUNT*MEM_5_SIZE];
1.4 内存管理结构体定义
typedef struct _mem_t
{
//控制标记
u32 flag;//每种内存最多32块
/*每块内存的大小*/
u16 size;
/*内存块个数*/
u16 count;
/*内存开始指针*/
u8 *buf;
}mem_t;
1.5 内存分配设计思想:
我们设置动态内存分配的初衷在于:有些单片机系统内存资源比较少,便显得特别珍贵,因此我们要实现内存的反复利用,好像就像一个池子一样,我们要循环利用池子里的水资源。比如说洗澡时,如果是喷头式的,这样如果不去回收水便会浪费;而如果是在澡池子里洗澡,每次利用完水后,水资源便会重新回到池子,可循环的利用起来。我们设置动态内存分配也是这个原理,使用之前先去申请,使用结束后便释放,下次便可继续申请该内存,循环利用内存池里的资源。
我们先定义6个数组,各个数组大小为XXX_SIZE * XXX_COUNT,XXX_SIZE是每个内存块大小,XXX_COUNT是内存块的个数。将各数组的首地址赋给g_mem_mngt[i].buf(i:0-5)m_mngt[i].buf便分别指向每个数组的首地址。我们申请某一长度len的内存时,通过计算选定匹配的内存块大小,然后从对应内存池首地址去查找空闲的内存块,找到即停止查找,将该内存块起始地址取出便为我们申请到的内存块,申请到后将该地址标记,表示已被占用,下次不能再申请到。
释放内存,首先根据内存节点所在的起始地址与各个内存池起始地址和结束地址,判断内存节点所有所在的内存池,然后从该内存池首地址开始查找,定位该内存落在的内存块控制区域,找到后则停止查找,并将该内存块标记位清零,表示该内存块已空闲,下次可申请使用。
1.6 各个内存块初始化,申请的起始地址、内存块个数、字节大小、标志位定义
void mem_init(void)
{
g_mem_mngt[0].buf = g_8bytesmem;
g_mem_mngt[0].count = MEM_0_COUNT;
g_mem_mngt[0].size = MEM_0_SIZE;
g_mem_mngt[0].flag = 0;
g_mem_mngt[1].buf = g_16bytesmem;
g_mem_mngt[1].count = MEM_1_COUNT;
g_mem_mngt[1].size = MEM_1_SIZE;
g_mem_mngt[1].flag = 0;
g_mem_mngt[2].buf = g_32bytesmem;
g_mem_mngt[2].count = MEM_2_COUNT;
g_mem_mngt[2].size = MEM_2_SIZE;
g_mem_mngt[2].flag = 0;
g_mem_mngt[3].buf = g_64bytesmem;
g_mem_mngt[3].count = MEM_3_COUNT;
g_mem_mngt[3].size = MEM_3_SIZE;
g_mem_mngt[3].flag = 0;
g_mem_mngt[4].buf = g_128bytesmem;
g_mem_mngt[4].count = MEM_4_COUNT;
g_mem_mngt[4].size = MEM_4_SIZE;
g_mem_mngt[4].flag = 0;
g_mem_mngt[5].buf = g_256bytesmem;
g_mem_mngt[5].count = MEM_5_COUNT;
g_mem_mngt[5].size = MEM_5_SIZE;
g_mem_mngt[5].flag = 0;
#ifdef MEM_DEBUG
memset(g_count, 0, sizeof(g_count));
#endif
#if CODE_REDUN
mem_fail = 0;
#endif
}
1.7 内存块申请
查找可申请内存起始地址,返回值为内存块起始地址。该类型函数有void * mem_alloc(u8 size)和void *mem_isr_alloc(u8 size)两种函数定义,文章中只附加在非中断模式下代码。在非中断模式下,申请内存块之前要先关闭中断,申请结束后再打开中断通知将申请到的内存地址标志位置1,表示已申请,这样做比较安全。在中断模式下,不必做此操作,其他写法都一致。
void * mem_alloc(u8 size)
{
u8 i, j;
mem_t * memptr = NULL;
u8 * ptr = NULL;
/*先找到内存适合的控制块所在控制头*/
for(i = 0; i < MEM_TYPE_COUNT; i++)
{
if(size <= g_mem_mngt[i].size)
{
memptr = &g_mem_mngt[i];
//找到空闲的控制块
ptr = memptr->buf;
for(j = 0; j < memptr->count; j++, ptr += memptr->size)
{
__disable_irq();
if(!(memptr->flag & (1< { //标记占用 memptr->flag |= (1< __enable_irq(); return ptr; } __enable_irq(); } #ifdef MEM_DEBUG //内存不足,记录一下 MEM_STATIC_INC(i); #endif } } #if CODE_REDUN mem_fail++; #endif return NULL; } 1.8 内存的释放 释放内存,即将表示该内存的占有标志位清零,释放后下次便可申请该内存。释放内存函数分为void mem_free(void * ptr)和void mem_isr_free(void * ptr)两种,一种是在非中断模式下,一种是在中断模式下。在非中断模式下释放之前应先关闭总中断,防止被打断,释放结束后再打开总中断。在中断模式下则不必处理该操作。 void mem_free(void * ptr) { u8 i; mem_t * memp = NULL; u8 * optr = ptr; u8 j; u8 * p; for(i = 0; i < MEM_TYPE_COUNT; i++) { memp = &g_mem_mngt[i]; //定位该内存指针落在哪个控制区域 if(optr >= memp->buf && optr < memp->buf + memp->size*memp->count) { for(p = memp->buf, j=0; j < memp->count; p += memp->size, j++) { if((optr >= p) && (optr < p + memp->size)) { __disable_irq(); memp->flag &= ~(1< #if PRINTF_ON stmprintf("free size:%d,j:%drn",memp->size, j); #endif __enable_irq(); return; } } } } } 二、任务调度 /*链表的定义,list_head g_idlelist表示空闲可用任务节点链表,list_head g_runlist表示即将使用的任务节点链表。*/ static struct list_head g_runlist; static struct list_head g_idlelist; /*任务节点*/ typedef struct node { struct list_head next; //双向链表定义 handle callback; //任务操作函数指针 u8 *para; //任务操作函数参数 u8 flag; //,标志字段,当前用来表示任务优先级 }task_node_t; //任务节点 /*任务优先级*/ #define PRIO_HIGH (0x1) //优先级最高 #define PRIO_NORMAL (0x2) //次优先级 #define PRIO_LOW (0x4) //最低优先级 2.1 任务调度,该算法思想为: 分别建立g_idlelist和g_runlist两个双向链表,在任务初始化时,为各个任务控制块节点申请内存,将各个任务节点挂载到g_idlelist链表上,表示目前空闲可用的任务节点,当有我们要申请任务时,要从链表g_idlelist上取下任务节点,同时将节点挂载到g_runlist链表上,表示即将使用的任务节点,挂载时是有优先级的,当g_runlist为空链表时,我们直接挂载上去,当g_runlist不为空链表时,便要考虑优先级的问题,任务优先级高的任务节点挂载在最前面。然后按照优先级顺序执行对应的任务,等任务执行结束后将任务节点又挂载到g_idlelist链表最后面。等待下次的调用。 2.2 任务节点初始化,为任务节点申请内存,并将任务节点挂载到g_idlelist链表上,表示未使用的任务节点。 void task_queue_init(void) { u8 i; task_node_t * task; list_init_head(&g_runlist); list_init_head(&g_idlelist); for(i =