在嵌入式系统中,为了对内存资源进行很好地控制,避免发生不可预测的后果,一般建议使用静态内存分配。ecos是专门为实时嵌入式系统量身定做的操作系统,它的大多数系统调用在被使用时都要求将预先指定的由系统调用所产生的目标对象的内存地址传递给该系统调用(即对其进行静态内存分配)。如第一讲cyg_thread_create系统调用函数就要求传入句柄指针和线程数据结构体指针及堆栈起址,而这三个指针对应的变量都是预先分配的静态变量。如下:
static char stack[4][STACK_SIZE];
static cyg_thread thread_data[4];
static cyg_handle_t thread_handle[4];
cyg_thread_create(10, // Priority - just a number
taska, // entry
1, // entry parameter
"taska", // Name
&stack[1], // Stack <-------------
STACK_SIZE, // Size
&thread_handle[1], // Handle <-------------
&thread_data[1] // Thread data structure <-------------
);
对比ucos for 51中类似的创建任务系统调用,可见,两者的内核数据和堆栈空间都是静态分配的。不过,ecos更灵活,它的内核数据存储空间也由应用程序提供,实际用多少变量就分配多少空间,而ucos是在配置内核时预先定死的,不够灵活。
#define OS_MAX_TASKS 27
OS_STK TaskStartStkyya[MaxStkSize];//注意:我在ASM文件中设置?STACK空间为40H即64。
OSTaskCreate(TaskStartyya, (void *)0, &TaskStartStkyya[0],2);
ecos是一个单进程多线程系统,作为一个嵌入式可配置操作系统,它的内存管理相对简单,不分段也不分页,没有存储保护,直接映射成一个平板内存。ecos采用一种基于内存池的动态内存分配机制,这是由uITRON兼容层实现的一种灵活有效的内存管理方式。
在实际使用时,内存可能不连续,ecos通过定义多个堆段自动分配内存池,不过,不连续内存的管理会增加开销,最好不用这种不连续的堆。开发板有一块内存是CPU内部的,还有一块是外部的,速度、容量、成本不同,可以把常用数据存入内部RAM以加快处理速度。
在一些特殊情况下,某些系统具有现场增加内存的支持能力,这种情形要求对这些内存进行自动分配。为此,在硬件抽象层中(hal_intr.h)有一个宏定义:HAL_MEM_REAL_REGION_TOP(cyg_uint8 *regionend),该宏采用正常情况下的内存末端地址作为参数。它返回一个由HAL实时检测到的实际内存末端地址。通过使用该宏,可以灵活地对多个内存区进行操作。
ecos允许对拥有的所有可用内存的内存池进行自动定义,可以自动分配堆的大小。
ecos提供了两种内存池:一种是变长内存池(variable size memory pool),根据申请的大小进行分配;另一种是定长内存池(fixed size memory pool),以固定大小的块为单位进行分配。变长内存池使用链表来进行管理,定长内存池使用位图来进行管理。C库函数malloc使用变长内存池实现内存分配,用户可以直接使用C库函数malloc和free管理内存。
下面分别介绍两种内存池对应的API函数使用方法:(函数定义位于头文件<cyg/memalloc/kapi.h>内)
抽象出来的内存操作主要包括:创建、删除、分配(阻塞/超时阻塞/非阻塞)、释放、查询等待、查询信息。
***********************
* 固定长度内存分配API *
***********************
1、创建内存池
void cyg_mempool_fix_create(堆起址,堆大小,内存块大小,返回的内存池句柄,固定内存池结构体)
ecos函数的命名都是有特点的,就象前面讲过的一样,cyg表示cygnus公司出品,mempool表示这个函数是与内存池相关的,fix表示是固定长度内存池,create顾名思义是创建固定长度内存池,很好记吧,即使忘了也很容易“蒙”出来。
该函数产生一个可进行固定大小内存分配的定长内存池。堆大小不一定是整个可用内存的大小。定长内存池在速度上要优于变长的。新产生的内存池可以通过句柄对其进行访问。
2、删除内存池
void cyg_mempool_fix_delete(内存池句柄)
该函数删除定长内存池。不要删除正在使用的内存池,否则会引起系统错误。
3、分配内存(带阻塞)
void * cyg_mempool_fix_alloc(句柄)
4、分配内存(带超时阻塞)
void * cyg_mempool_fix_timed_alloc(句柄,超时值)
5、分配内存(非阻塞)
void * cyg_mempool_fix_try_alloc(句柄)
6、释放内存
void cyg_mempool_fix_free(句柄,要释放回内存池的内存的指针)
7、检查是否有线程正等待分配内存
cyg_bool_t cyg_mempool_fix_waiting(句柄)
8、取得内存池信息
void cyg_mempool_fix_get_info(句柄,要返回的信息结构指针)
***********************
* 可变长度内存分配API *
***********************
与固定长度内存分配API类似,只是把fix换成了var,注意参数不一样了。
1、创建内存池
void cyg_mempool_var_create(堆起址,堆大小,返回的内存池句柄,固定内存池结构体)
2、删除内存池
void cyg_mempool_var_delete(内存池句柄)
该函数删除定长内存池。不要删除正在使用的内存池,否则会引起系统错误。
3、分配内存(带阻塞)
void * cyg_mempool_var_alloc(句柄,要分配的内存块大小)
4、分配内存(带超时阻塞)
void * cyg_mempool_var_timed_alloc(句柄,要分配的内存块大小,超时值)
5、分配内存(非阻塞)
void * cyg_mempool_var_try_alloc(句柄,要分配的内存块大小)
6、释放内存
void cyg_mempool_var_free(句柄,要释放回内存池的内存的指针)
7、检查是否有线程正等待分配内存
cyg_bool_t cyg_mempool_var_waiting(句柄)
8、取得内存池信息
void cyg_mempool_var_get_info(句柄,要返回的信息结构指针)
注意不定长分配内存时为了避免小碎片影响系统性能,应限定可变内存申请的最小值为一个比较大的数。
----------------------------------------------------------------------------------------
下面给出lwip在ecos上移植的sys_arch中的函数实现,有兴趣的读者可以对照分析lwip内存管理和ecos内存管理的对应关系。
----------------------------------------------------------------------------------------
//这个文件实现了lwIP使用的针对eCos平台移植的sys_arch函数
#include "lwip/opt.h"
#include "arch/sys_arch.h"
#include "lwip/sys.h"
#include "lwip/def.h"
#define tick_to_msec(tick) ((u16_t)((tick)*10+1))
#define msec_to_tick(msec) ((cyg_tick_count_t)(msec+9)/10)
//我们使用一个通用的可变长内存池来分配信号量、信箱和线程所需内存块
static char memvar[CYGNUM_LWIP_VARMEMPOOL_SIZE];//可变长内存池实体
static cyg_mempool_var var_mempool;//内存池结构体变量,详见kapidata.h.
static cyg_handle_t var_mempool_h;//可变长内存池句柄
#define SYS_THREADS 2 /* polling thread and tcpip_thread */
#define THREAD_COUNT (CYGNUM_LWIP_APP_THREADS + SYS_THREADS)
static char memfix[CYGNUM_LWIP_THREAD_STACK_SIZE * THREAD_COUNT];//所有线程堆栈空间
//threads链表:包含lwIP超时信息的eCos线程信息。
struct lwip_thread {
struct lwip_thread * next;
struct sys_timeouts to;
cyg_handle_t th;
cyg_thread t; //线程数据存储空间
} *threads;
/*
* Timeout for threads which were not created by sys_thread_new
* usually "main"
*/
struct sys_timeouts to;
//建立内存池,初始化thread和to。
void sys_init(void)
{
cyg_mempool_var_create(memvar, sizeof(memvar), &var_mempool_h, &var_mempool);
threads = NULL;
to.next = NULL;
}
//创建一个新邮箱。如果内存不够,返回NULL。
sys_mbox_t sys_mbox_new(void)
{
cyg_mbox * mbox;
cyg_handle_t m;
mbox = (cyg_mbox *)cyg_mempool_var_try_alloc(var_mempool_h, sizeof(cyg_mbox));
if(!mbox)
return SYS_MBOX_NULL;
//m和mbox实际是一样的
cyg_mbox_create(&m, mbox);
return m;
}
//销毁邮箱并释放其占用的内存空间。
void sys_mbox_free(sys_mbox_t mbox)
{
if (mbox != SYS_MBOX_NULL) {
cyg_mbox_delete(mbox);
cyg_mempool_var_free(var_mempool_h,(void*)mbox);
}
}
//cyg_mbox_put不能传送NULL,否则cyg_mbox_get将不能区分到底是真实数据还是错误条件。
//但是,lwIP确实在某些适于使用信号量的时候传递NULL。所以要用&dummy_msg代替NULL。
int dummy_msg = 1;
//发送消息到信箱
void sys_mbox_post(sys_mbox_t mbox, void *data)
{
if (!data) //cyg_mbox_put的消息不能为NULL,用&dummy_msg代替。
data = &dummy_msg;
while (cyg_mbox_put(mbox,data) == false);
}
#if 0 //注释用
void
sys_mbox_fetch(sys_mbox_t mbox, void **msg){
void *d;
d = cyg_mbox_get(mbox);
if (msg)
*msg = d;
}
#endif
//超时等待信箱,超时返回-1,正常返回等待ms数。
u32_t sys_arch_mbox_fetch(sys_mbox_t mbox, void **data, u32_t timeout)
{
void *d;
cyg_tick_count_t end_time = 0, start_time = 0;
if (timeout) {//超时等待信箱
start_time = cyg_current_time();
d = cyg_mbox_timed_get(mbox, start_time + msec_to_tick(timeout));
end_time = cyg_current_time();
//超时
if (d == NULL)
return SYS_ARCH_TIMEOUT;
}
else{//永远等待信箱
d = cyg_mbox_get(mbox);
}
if (data) {
if (d == (void *)&dummy_msg)
*data = NULL;
else
*data = d;
}
//返回延时ms数
return tick_to_msec(end_time - start_time);
}
//分配一个新信号量并初始化,如果没有空间可以提供则返回NULL
sys_sem_t sys_sem_new(u8_t count)
{
sys_sem_t sem; //????sys_sem_t*???
//从内存池分配可变长内存块,如无空间则立即返回NULL,否则返回新指针。
sem = (cyg_sem_t *)cyg_mempool_var_try_alloc(var_mempool_h, sizeof(cyg_sem_t));
if(!sem)
return SYS_SEM_NULL;
cyg_semaphore_init(sem, count);
return sem;
}
#if 0 //注释用
void
sys_sem_wait(sys_sem_t sem)
{
cyg_semaphore_wait(sem);
}
void
sys_timeout(u16_t msecs, sys_timeout_handler h, void *arg)
{}
#endif
//超时等待一个信号量,如果超时返回-1,否则返回等待时间.
u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
{
cyg_bool_t r;
cyg_tick_count_t end_time = 0, start_time = 0;
if (timeout) {//带延时等待信号量
start_time = cyg_current_time();
r = cyg_semaphore_timed_wait(sem, start_time + msec_to_tick(timeout));
end_time = cyg_current_time();
if (r == false) {//超时
return SYS_ARCH_TIMEOUT;
}
}
else{//永远等待信号量
cyg_semaphore_wait(sem);
}
//收到信号量,返回延时ms数
return tick_to_msec(end_time - start_time);
}
//发送信号量
void sys_sem_signal(sys_sem_t sem)
{
cyg_semaphore_post(sem);
}
//销毁信号量并释放其所占用的内存空间
void sys_sem_free(sys_sem_t sem)
{
//销毁信号量
cyg_semaphore_destroy(sem);
//释放内存,内存池,分配时返回指针
cyg_mempool_var_free(var_mempool_h,(void*)sem);
}
//创建新线程(线程函数,参数,优先级)
//thread_create(优先级,线程实体函数,线程参数,线程名字,堆栈基址,堆栈大小,返回的线程句柄,线程数据存储空间)
sys_thread_t sys_thread_new(void (*function) (void *arg), void *arg,int prio)
{
struct lwip_thread * nt;
void * stack;
static int thread_count = 0;
//可变长度内存分配,内存池,大小
nt = (struct lwip_thread *)cyg_mempool_var_alloc(var_mempool_h, sizeof(struct lwip_thread));
nt->next = threads;
nt->to.next = NULL;
threads = nt;
//堆栈起址为memfix+每个堆栈大小*线程数++
stack = (void *)(memfix+CYGNUM_LWIP_THREAD_STACK_SIZE*thread_count++);
cyg_thread_create(prio, (cyg_thread_entry_t *)function, (cyg_addrword_t)arg,
(char *)arg , stack, CYGNUM_LWIP_THREAD_STACK_SIZE, &(nt->th), &(nt->t) );
cyg_thread_resume(nt->th);
return NULL;
}
//返回当前线程的timeouts信息结构指针
struct sys_timeouts *sys_arch_timeouts(void)
{
cyg_handle_t ct;
struct lwip_thread *t;
ct = cyg_thread_self();
for(t = threads; t; t = t->next)
if (t->th == ct)
return &(t->to);
return &to;
}