每次 温故uc/os-ii操作系统的源码都有收获,经典的东西就是经典;uc/os-ii曾经把我搞得好累,但是经过一段时间摸索之后发现只要有正确的学习方法,学好uc/os-ii并不难,同时基于uc/os-ii操作系统的应用程序开发也不难。关于嵌入式系统内存管理的方法应该不少,原子的内存管理教程就有两个例程,一个是“特大号数组”型,一个是动态分配型,两者都使用了malloc()函数。而uc/os-ii操作系统的内存管理实现原理很精妙没有使用malloc()函数,主要是考虑该函数申请内存的时间不确定性。可以说,通过该例程就能理解uc/os-ii操作系统的内存管理实现原理。笔者认为通过实例学习,理解uc/os-ii操作系统的原理是个不错的选择。本实验基于PC机在DOS环境下模拟。
/*设计一个有3个任务的应用程序,这3个任务分别是Mytask Youtask Hertask。在应用程序中创建一个动态内存分区,该分区有8个内存块,每个内存块的长度是6字节。应用程序的任务Youtask Hertask都在任务运行后请求一个内存块.随后就释放它;任务MYTASK也在任务运行后请求一个内存块,但是要在任务MYTASK运行6次后,才释放它所申请的内存块。为了了解内存分区变化的情况,编写代码来观察分区头指针和已被使用内存块的个数*/
#include "INCLUDES.h"
#define TASK_STK_SIZE 512 /* 任务堆栈长度*/
OS_STK StartTaskStk[TASK_STK_SIZE]; /*定义任务堆栈区 */
OS_STK MyTaskStk[TASK_STK_SIZE];
OS_STK YouTaskStk[TASK_STK_SIZE];
OS_STK HerTaskStk[TASK_STK_SIZE];
char *s;
char *s1= "Mytask ";
char *s2= "Youtask ";
char *s3= "Hertask ";
INT8U err;
INT8U y=0; //字符显示位置
INT8U Times=0;
OS_MEM *IntBuffer; /*定义内存控制块指针,创建一个内存分区时,返回值就是它 */
INT8U IntPart[8][6]; /*划分一个具有8个内存块,每个内存块长度是6的内存分区 */
INT8U *IntBlkPtr; /*定义内存块指针,确定内存分区中首个内存块的指针 */
OS_MEM_DATA MemInfo; /*存放内存分区的状态信息 */
void StartTask(void *data); /* 声明起始任务 */
void MyTask(void *data); /* 声明任务 */
void YouTask(void *data); /* 声明任务 */
void HerTask(void *data); /* 声明任务 */
/*
********************************************************************
* MAIN主函数
**********************************************************************
*/
void main (void)
{
OSInit(); /* 初始化uC/OS-II */
PC_DOSSaveReturn(); /* 保存DOS环境 */
PC_VectSet(uCOS, OSCtxSw); /* 安装uC/OS-II的中断 */
IntBuffer=OSMemCreate(IntPart,8,6,&err); /*创建动态内存区 */
OSTaskCreate(StartTask, (void *)0, &StartTaskStk[TASK_STK_SIZE - 1], 0);
OSStart(); /* 启动多任务管理 */
}
/*
***********************************************************
* STARTUP TASK:主要功能就是创建3个任务
***********************************************************
*/
void StartTask(void *pdata)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register,实际应用中该部分可省略 */
OS_CPU_SR cpu_sr;
#endif
INT16S key; /*用于退出的建*/
pdata = pdata; /* Prevent compiler warning */
OS_ENTER_CRITICAL();
PC_VectSet(0x08, OSTickISR); /* 安装时钟中断向量 , */
PC_SetTickRate(OS_TICKS_PER_SEC); /* 设置时钟频率 */
OS_EXIT_CRITICAL();
OSStatInit(); /* 初始化统计任务 */
OSTaskCreate(MyTask, (void *)0, &MyTaskStk[TASK_STK_SIZE - 1], 3);
OSTaskCreate(YouTask, (void *)0, &YouTaskStk[TASK_STK_SIZE - 1], 4);
OSTaskCreate(HerTask, (void *)0, &HerTaskStk[TASK_STK_SIZE - 1], 5);
for (;;) {
//如果恩下ESC键,则退出UC/OS-II
if (PC_GetKey(&key) == TRUE) { /* See if key has been pressed */
if (key == 0x1B) { /* Yes, see if it's the ESCAPE key */
PC_DOSReturn(); /* Return to DOS */
}
}
OSTimeDlyHMSM(0, 0, 3, 0); /* Wait 3s,在这里创建完StartTask任务为什么不挂起自己? */
}
}
/*--------------------MyTask任务-------------------------*/
void MyTask(void *pdata)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
pdata=pdata;
for( ; ; )
{
PC_DispStr(10,++y,s1,DISP_BGND_BLACK+DISP_FGND_WHITE); /*"++y"执行一次就换一次行 */
IntBlkPtr=OSMemGet( /*请求内存分区的内存块的指针 */
IntBuffer, /*这个参数就指明了要获取的内存块属于哪个内存分区 */
&err);
OSMemQuery( /*查询内存控制块信息 */
IntBuffer, /*带查询内存控制块指针 */
&MemInfo);
sprintf(s,"%0x",MemInfo.OSFreeList); /*显示头指针 */
PC_DispStr(30,y,s,DISP_BGND_BLACK+DISP_FGND_WHITE);
sprintf(s,"%d",MemInfo.OSNUsed); /*显示已用的内存块数目 */
PC_DispStr(40,y,s,DISP_BGND_BLACK+DISP_FGND_WHITE);
if(Times>4) /*0-1-2-3-4-5;Times==6时该条件语句为真 */
{
OSMemPut(IntBuffer,IntBlkPtr); /* 进入第6次就释放已经使用过的内存块 */
}
Times++;
OSTimeDlyHMSM(0,0,1,0); //等待2s
}
}
/*-------------------------------Youtask-------------------------------------*/
void YouTask(void *pdata)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
pdata=pdata;
for( ; ; )
{
PC_DispStr(10,++y,s2,DISP_BGND_BLACK+DISP_FGND_WHITE);
IntBlkPtr=OSMemGet( //请求内存块
IntBuffer, //内存分区的指针
&err); //错误信息
OSMemQuery( //查询内存控制块信息
IntBuffer, //带查询内存控制块指针
&MemInfo);
sprintf(s,"%0x",MemInfo.OSFreeList); //显示头指针
PC_DispStr(30,y,s,DISP_BGND_BLACK+DISP_FGND_WHITE);
sprintf(s,"%d",MemInfo.OSNUsed); //显示已用的内存块数目
PC_DispStr(40,y,s,DISP_BGND_BLACK+DISP_FGND_WHITE);
OSMemPut( IntBuffer, IntBlkPtr );
OSTimeDlyHMSM(0,0,2,0); //等待2s
}
}
/*-------------------------------Hertask-------------------------------------*/
void HerTask(void *pdata)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
pdata=pdata;
for( ; ; )
{
PC_DispStr(10,++y,s3,DISP_BGND_BLACK+DISP_FGND_WHITE);
IntBlkPtr=OSMemGet( //请求内存块
IntBuffer, //内存分区的指针
&err); //错误信息
OSMemQuery( //查询内存控制块信息
IntBuffer, //带查询内存控制块指针
&MemInfo);
sprintf(s,"%0x",MemInfo.OSFreeList); //显示头指针
PC_DispStr(30,y,s,DISP_BGND_BLACK+DISP_FGND_WHITE);
sprintf(s,"%d",MemInfo.OSNUsed); //显示已用的内存块数目
PC_DispStr(40,y,s,DISP_BGND_BLACK+DISP_FGND_WHITE);
OSMemPut(
IntBuffer,
IntBlkPtr
);
OSTimeDlyHMSM(0,0,1,0); //等待2s
}
}
/*
通过本例的实验现象能深刻理解内存管理函数的本质;现象分析如下:
y(行) 显示内容: 显示头指针OSFreeList: 显示已用的内存块数目OSNUsed:
1, MyTask 504 1
2, YouTask 50A 2
3, HerTask 50A 2
4, MyTask 50A 2
5, HerTask 510 3
6, MyTask 510 3
7, YouTask 516 4
8, HerTask 516 4
9, MyTask 516 4
10, HerTask 51C 5
11, MyTask 51C 5
12, YouTask 522 6
13, HerTask 522 6
14, MyTask 522 6
15, MyTask 522 6
通过上表可以看出:1,“显示头指针OSFreeList”的数据很有规律,即都是以6为单位递增,这是因为在创建内存控制块时就定义了每个内存块的长度是6个字节,所以就是以6为单位递增,(0x504+0x6)==0x50A;
(0x50A+0x6)==0x510,(0x510+0x6)==0x516,等等; 2,第2行中,因为YouTask任务请求一个内存块之后就立即释放了,所以在第3个任务(HerTask任务)中,HerTask任务申请到的还是50A指针指向的内存块,同样在本任务中及时释放了内存块,实际上此时系统只占用504内存块,所以到第4个任务MyTask 任务执行时,还是显示已用2个内存块,不过这时MyTask 任务已经占用了两个内存块,因为此时MyTask 任务还没有释放内存块;3,当if(Times>4) 时 (0-1-2-3-4-5;Times==6时该条件语句为真)MyTask 任务得到一个内存块就立即释放了该内存块,所以就一直会显示已用6个内存块,指针值停留在0x522处,不会再递增了;4,由此现象得知:a),本例中申请到的8个内存块是连续的地址空间;b),系统在初始化时,就初始化了一个空的任务控制块链表,每个任务控制块的有效数据为空,但是结点指针都有所指;当系统申请一个内存分区时,空闲任务控制块就会减1,释放了就加1;c),整个空闲链表是不是一个大的连续内存空间,还不知道,我觉得应该看OSInit()源码。
*/