-
freertos
是一个轻量级的
rtos
p>
,
它目前实现了一个微内核,
并且
port
到
arm7, avr,
pic18, coldfire
等众多处理器上;目前已经在
rtos
的市场上占有不少的份额。它当然不是一个与
vxwo
rks
之类的
rtos
竞争的操作系统
,它的目标在
于低性能小
RAM
p>
的处理器上。整个系统只有
3
个文件,外加
上
port
的和处理器相关的两个文件,实现是很简洁的。
p>
与
ucosi
i
不同,它是
free
的,
ucosii
不是
free
的,虽然它的代码是公开的。
FreeRTOS
提供的
功能包括:任务管理、时间管理、信号量、消息队列、内存管理。
Fr
eeRTOS
内核支持优先
级调度算法,
每个任务可根据重要程度的不同被赋予一定的优先级,
CPU
总是让处于就绪态
的、
优先级最高的
任务先运行。
FreeRT0S
内核同时支持轮换调度算法,系
统允许不同的任
务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先<
/p>
级的任务共享
CPU
< br>的使用时间。这一点是和
ucosii
不同的。
另外一点不同是
f
reertos
既可以配置为可抢占内核也可以配置为不可抢占内核。
< br>当
FreeRTOS
被设置为可剥夺型内核时,
处于就绪态的高优先级任务能剥夺低优先级任务的
CPU
使用权,
这样可保证系统
满
足实时性的要求;当
FreeRTOS
被设置为不可剥夺型内核
时,处于就绪态的高优先级任务
只有等当前运行任务主动释放
C
PU
的使用权后才能获得运行,
这
<
/p>
样可提高
CPU
的运行效率。
这篇文章是以
freertos v5.0
版本的代码为例子分析下它的任务管理方面的实现。时间关系可
能没有太多时间写的
很详细了。
1.
链表管理
freertos
里面的任务管理,
queue,semaphore
管理等都借助于双向链表,它
定义了个通用的
数据结构
struct
xLIST_ITEM
{
portTickType xItemValue;
//
链表节点的数据项,通常用在任务延时,表示
//
一个任务延时的节拍数
volatile
struct xLIST_ITEM * pxNext;
//
通过这两个成员变量将所有节点
volatile struct
xLIST_ITEM *
pxPrevious;//
链接成双向链表
void * pvOwner;
//
指向该
item
的所有者,通常是任务控制块
void * pvContainer;
//
指向此链表结点所在的链表
};
这个数据结构定义了一个通用
的链表节点;下面的数据结构定义了一个双向链表
typedef struct xLIST
{
volatile unsigned portBASE_TYPE uxNumbe
rOfItems;//
表示该链表中节点的数目
volatile
xListItem *
pxIndex;//
用于遍历链表,指向上次访问的节点
volatile xMiniListItem
xListEnd;//
链表尾结点
} xList;
而下面这个数据结构用在
xList
中,只是为了标记一个链表的尾,是一个
marker
struct xMINI_LIST_ITEM
{
portTickType xItemValue;
volatile struct
xLIST_ITEM *pxNext;
volatile struct xLIST_ITEM *pxPrevious;
};
typedef struct xMINI_LIST_ITEM
xMiniListItem;
对于链表的操作也定义了一系列的函数和宏,在
list.c<
/p>
文件中。如初始化个链表,吧一个节
点插入链表等。
初始化链表
:
void vListInitialise( xList
*pxList )
{
/* The list structure
contains a list item which is used to
mark the
end of the list.
To
initialise the list the list end
is
inserted
as
the only list entry. */
pxList->pxIndex = ( xListItem * ) &(
pxList->xListEnd );
/* The list end value is the highest
possible value in the
list to
ensure it
remains at the end of the list. */
pxList->alue =
portMAX_DELAY;
/* The list end next and
previous pointers point to itself
so we
know
when
the list is empty. */
pxList-> = ( xListItem * ) &(
pxList->xListEnd );
pxList->ious = (
xListItem * ) &(
pxList->xListEnd );
pxList->uxNumberOfItems = 0;
}
把一个节点插入到链表尾部:
void vListInsertEnd( xList *pxList,
xListItem *pxNewListItem )
{
volatile
xListItem * pxIndex;
/* Insert a
new list item into pxList, but rather than sort
the list,
makes the new list item the last item
to be removed by a
call to
pvListGetOwnerOfNextEntry.
This means it has to be the
item pointed to by
the pxIndex member. */
pxIndex =
pxList->pxIndex;
pxNewListItem->pxNext =
pxIndex->pxNext;
pxNewListItem->pxPrevious =
pxList->pxIndex;
pxIndex->pxNext->pxPrevious = (
volatile xListItem * )
pxNewListItem;
pxIndex->pxNext = ( volatile xListItem
* ) pxNewListItem;
pxList->pxIndex = ( volatile xListItem
* ) pxNewListItem;
/* Remember
which list the item is in. */
pxNewListItem->pvContainer = ( void * )
pxList;
(
pxList->uxNumberOfItems )++;
}
这些就不多说了。
2.
任务控制块
typedef struct
tskTaskControlBlock
{
volatile
portSTACK_TYPE
*pxTopOfStack;//
指向堆栈顶
xListItem
xGenericListItem;
//
通过它将任务连入就绪链表或者延时链表或
者挂起链表中
xListItem
< br>xEventListItem;//
通过它把任务连入事件等待链表
unsigned portBASE_TYPE
uxPriority;//
优先级
portSTACK_TYPE
*pxStack;
//
指向堆栈起始位置
signed
portCHAR
pcTaskName[configMAX_TASK_NAME_LEN ];
#if (
portCRITICAL_NESTING_IN_TCB== 1 )
Unsigned
portBASE_TYPE uxCriticalNesting;
#endif
#if (configUSE_TRACE_FACILITY == 1 )
Unsigned portBASE_TYPE
ux
TCBNumber;//
用于
trace
,
debug
时候提供方便
#endif
#if ( configUSE_MUTEXES
== 1
)
unsigned
portBASE_TYPE uxBas
ePriority;//
当用
mutex
发生优先级反转时用
#endif
#if (
configUSE_APPLICATION_TASK_TAG == 1 )
pdTASK_HOOK_CODE
pxTaskTag;
#endif
} tskTCB;
其中
uxBasePriority
用于解决优先
级反转,
freertos
采用优先级继承的办法解决这个问题
,
在
继承时,将任务原先的优先级保存在这个成员中,将来再从
这里恢复任务的优先级。
3.
系统全局变量
freertos
将任务根据他们的
状态分成几个链表。所有就绪状态的任务根据任务优先级加到对
应的就绪链表中。系统为
每个优先级定义了一个
xList
。如下:
static xList
pxReadyTasksLists[ configMAX_PRIORITIES ];
/*< Prioritised ready tasks. */
此外,所有延时的任务加入到两个延时链表之一。
static xList
xDelayedTaskList1;
static xList
xDelayedTaskList2;
还定义了两个指向延时链表的指针:
static xList * volatile
pxDelayedTaskList;
static xList *
volatile pxOverflowDelayedTaskList;
freertos
弄出两个延时链表
是因为它的延时任务管理的需要。
freertos
根据任务延
时时间的长
短按序将任务插入这两个链表之一。在插入前先把任务将要延时的
xTicksToDelay
数加上系
统当前
p>
tick
数,这样得到了一个任务延时
du
e time
(到期时间)的绝对数值。但是有可能这
个相加操
作会导致溢出,如果溢出则加入到
pxOverflowDelayedTaskLis
t
指向的那个链表,否
则加入
pxDe
layedTaskList
指向的链表。
freertos
还定义了个
pending
链表:
static xList
xPendingReadyList;
这个链表用在调度
器被
lock
(就是禁止调度了)
的时
期,如果一个任务从非就绪状态变为就
绪状态,
它不直接加到就
绪链表中,
而是加到这个
pending
链表中。
等调度器重新启动
(unlock)
的时候再检查这个链表,把里面的任务加到就绪链表中
static volatile xList
xTasksWaitingTermination;
/*< Tasks that have been deleted
- but the their memory not yet freed.
*/
static volatile unsigned
portBASE_TYPE uxTasksDeleted = ( unsigned
portBASE_TYPE ) 0;
< br>一个任务被删除的时候加入到
xTasksWaitingTerminatio
n
链表中,
uxTasksDeleted
跟中系统中
有多少任务被删除(即加到
xTasksWai
tingTermination
链表的任务数目)
.
static xList
xSuspendedTaskList;
/*<
Tasks that are currently
suspended. */
这个链表记录着所有被
xTaskSuspend
挂起的任务,注意这不是那些等待信号量的
任务。
static
volatile
unsigned portBASE_TYPE uxCurrentNumberOfTasks
;记录了当前系统任务的数目
static
volatile portTickType
xTickCount;
是自启动以来系统运行的
ticks<
/p>
数
static
unsigned
portBASE_TYPE uxTopUsedPriority
;记录当前系统中
被使用的最高优先级,
static
volatile unsigned
portBASE_TYPE uxTopReadyPriority
;记录当前系统
中处于就绪状态的最高优
先级。
static
volatile signed
portBASE_TYPE xSchedulerRunning
;
表示当前调度器是否在运行,也即内核是
否启动了
4.
任务管理
freertos
与
ucosii
不同,它的任务控制块并不是静态分配的,而是在创建任务的时候
动态分
配。另外,
freertos
的
优先级是优先级数越大优先级越高,和
ucosii
正好相反。
任务控制块
中也没有任务状态的成员变量,
这是因为
freertos
中的任务总是根据他们的状态连入对应的
链表,没有必要在任务控制块中维护一个状态。此外
freertos
对任务的数量没有限制,而且
同一个优先级可以有多个任务。
先看任务创建:
< br>/********************************************** *****************
**
参数
:
pvTaskCode---
任务函数名称
**
pcName---
任务名字,可选
**ucStackDepth---
任务堆栈的深度,即大小
**
p
vParamenters---
参数,即传给任务函数的参数
,
所有的任务函数原型是
void
task
(void
*pvParameters)
**
uxPriority
—
任务优先级
** pxCreatedTask
—
可选,通过它返回被创建任务的
tcb
***************************
****************************************/
signed portBASE_TYPE
xTaskCreate( pdTASK_CODE pvTaskCode, const
signed portCHAR * const pcName,
unsigned portSHORT usStackDepth, void
*pvParameters, unsigned portBASE_TYPE
uxPriority, xTaskHandle *pxCreatedTask )
{
signed portBASE_TYPE xReturn;
tskTCB * pxNewTCB;
#if (
configUSE_TRACE_FACILITY == 1 )
static unsigned
portBASE_TYPE uxTaskNumber = 0; /*lint
!e956 Static is deliberate - this is
guarded before use. */
#endif
/*
动态分配
tcb
和任务堆栈
*
/
pxNewTCB =
prvAllocateTCBAndStack( usStackDepth );
/*
如果分配成功的话
*/
if(
pxNewTCB != NULL )
{
portSTACK_TYPE
*pxTopOfStack;
/*
初始化
tcb*/
prvInitialiseTCBVariables(
pxNewTCB, pcName, uxPriority );
/*
计算堆栈的顶
*/
#if portSTACK_GROWTH
< 0
{
pxTopOfStack
=
pxNewTCB->pxStack + ( usStackDepth - 1 );
}
#else
{
pxTopOfStack
=
pxNewTCB->pxStack;
}
#endif
/*
初始化任务堆栈,并将返回地址保存在
< br>tcb
中的
pxTopOfStack
< br>变量
*/
pxNewTCB->pxTopOfStack
=
pxPortInitialiseStack( pxTopOfStack, pvTaskCode,
pvParameters );
/*
关中断
*/
portENTER_CRITICAL();
{
/*
更新系统的任务数
*/
uxCurrentNumberOfTasks++;
if(
uxCurrentNumberOfTasks == ( unsigned
portBASE_TYPE ) 1 )
{
/*
如果这是系统中第一个任务,则把它设为当前任务
*/
pxCurrentTCB
=
pxNewTCB;
/*
如果这是系统中的第一个任务,那也就意味着内核刚准备启
动,实际上这
第一个任务一定是
idle
任务,
这个时候我们要做一些系统初始化,
即初始化那些全局
链表
*/
prvInitialiseTaskLists();
}
else
{
/*
如果内核还没有运行,则把当前任务设成已经创建的任务
中优先级最高的那个
,
将来内核一
旦运
行,调度器会马上选择它运行
*/
if(
xSchedulerRunning ==
pdFALSE )
{
if(
pxCurrentTCB->uxPriority
<= uxPriority )
{
pxCurrentTCB
= pxNewTCB;
}
}
}
/*
我们记录下当前使用的最高优先级,这为了方便任务调度<
/p>
*/
if(
pxNewTCB->uxPriority >
uxTopUsedPriority )
{
uxTopUsedPriority
=
pxNewTCB->uxPriority;
}
#if (
configUSE_TRACE_FACILITY == 1 )
{
/*
Add a counter into the
TCB for tracing only. */
pxNewTCB->uxTCBNumber
=
uxTaskNumber;
uxTaskNumber++;
}
#endif
/*
把新创建的任务加到就绪链表
*/
prvAddTaskToReadyQueue(
pxNewTCB );
xReturn =
pdPASS;
traceTASK_CREATE(
pxNewTCB
);
}
portEXIT_CRITICAL();
}
/*
如果分配内存失败,我们返回错
误
*/
else
{
xReturn =
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
traceTASK_CREATE_FAILED(
pxNewTCB );
}
if( xReturn == pdPASS
)
{
if( ( void * )
pxCreatedTask
!= NULL )
{
/*
将新创建任务的
tcb
返回给调用者
*pxCreatedTask
= (
xTaskHandle ) pxNewTCB;
}
/*
如果调度器已经运行
*/
if(
xSchedulerRunning !=
pdFALSE )
{
/*
如果新创建的任务的优先级高于当前正在运行的任务,则调
度
*/
if(
pxCurrentTCB->uxPriority
< uxPriority )
{
taskYIELD();
}
}
}
return xReturn;
}
其中
prvAllocateTCB
AndStack
分配
tcb
和
stack
内存,这个里面调用了
pvpor
tMalloc
和
pvPortFree
函数来分配和释放内存,
这两个函数对应于
C
标准库里面的
malloc
和
free
。
但是
标准库中的
mallo
和
free
存
在以下缺点:
并不是在所有的嵌入式系统中都可用,
要占用不定
的程序空间,可重人性欠缺以及执行时间具有不可确定性
,
p>
而且多次反复调用可能导致严重
的内存碎片。因此
< br>freertos
在内存管理那块自己实现了这两个函数。
static tskTCB
*prvAllocateTCBAndStack( unsigned portSHORT
usStackDepth )
{
tskTCB *pxNewTCB;
/* Allocate space for
the
TCB.
Where the memory comes from
depends on
the implementation of
the
port malloc function. */
pxNewTCB = ( tskTCB * )
pvPortMalloc( sizeof( tskTCB ) );
if( pxNewTCB != NULL )
{
/* Allocate
space for the stack used by the task
being created.
The base of the
stack memory
stored in the TCB so the task can
be deleted
later
if required. */
pxNewTCB->pxStack
= (
portSTACK_TYPE * ) pvPortMalloc( ( ( size_t
)usStackDepth ) * sizeof(
portSTACK_TYPE ) );
if(
pxNewTCB->pxStack ==
NULL )
{
/* Could
not allocate the
stack.
Delete the
allocated
TCB. */
vPortFree(
pxNewTCB );
pxNewTCB
= NULL;
}
else
{
/* Just
to help debugging.
*/
memset(
pxNewTCB->pxStack,
tskSTACK_FILL_BYTE, usStackDepth * sizeof(
portSTACK_TYPE
) );
}
}
return pxNewTCB;
}
再看任务删除。
< br>freertos
的任务删除分两步完成,
第一步在
p>
vTaskDelete
中完成,
Free
RTOS
先把要删除的任务
从就绪任务链表和事件等待链表中删
除,然后把此任务添加到任务删除链表(即那个
xTasksWaitingTermi
nation
)
,若删除的任务是当前运行任务,系统就执行任
务调度函数
.
第
2
步则是在
idle
任务中完成,
i
dle
任务运行时,检查
xTasksWaitingTerm
ination
链表,如果有任
务在这个表上,释放该任务占用
的内存空间,并把该任务从任务删除链表中删除。
/******************************************
**********************
**
p>
参数:
pxTaskToDelete
是一
个指向被删除任务的句柄,这里其实就是等价于任务控制块
**
如果这个句柄
==NULL
,则表示要删除当前任务
**********************************************
*********************/
void
vTaskDelete( xTaskHandle pxTaskToDelete )
{
tskTCB
*pxTCB;
taskENTER_CRITICAL();
{
/*
如果删除的是当前任务,则删除完成后需要进行调度
*/
if(
-
-
-
-
-
-
-
-
-
上一篇:人教版九年级英语第一单元重点
下一篇:atlys开发板之SPI FLASH实验