实现rtos操作系统 【二】基本任务切换实现
一、临界区保护
临界区保护是一个重要的概念,用于保护共享资源免受并发访问导致的数据损坏和不一致。临界区是指一段代码,这段代码访问共享资源(如全局变量、硬件设备等),并且在任何时候只能由一个线程执行,以确保操作的原子性和数据的一致性。
有些情况下一些特殊操作(比如 XIP 下 Flash 擦写、低功耗模式切换)不能被随意打断,或者一些共享数据区不能被无序访问(A 任务正在读,B 任务却要写),这时候就要用到临界区保护策略了。
RTOS 内实现的方式很简单,通过禁用中断、使用互斥锁或信号量等方法即可。在这里我们将实现一下禁用中断的临界区保护。
我们只需要通过置位和复位 PRIMASK 寄存器即可实现临界段代码保护,代码如下:
uint32_t tTaskEnterCritical (void)
{
__disable_irq();
}
void tTaskExitCritical (void) {
__set_PRIMASK(0);
}
可是这样并不能嵌套保护,比如我们连续保护两次,这样在中间那层就开启了保护。
我们必须避免这个问题,解决办法如下:
uint32_t tTaskEnterCritical (void)
{
uint32_t primask = __get_PRIMASK();
__disable_irq(); // CPSID I
return primask;
}
void tTaskExitCritical (uint32_t status) {
__set_PRIMASK(status);
}
至此,用我们保护时先获得当前的状态,退出保护时恢复原先的状态就可以了。
二、调度锁保护
调度锁的功能是让 RTOS 停止调度,我们只需要一个变量即可实现。
// 调度锁计数器
uint8_t schedLockCount;
值得注意的是,我们在任务调度锁中为了安全,都开启了临界区保护。
以下是开启和关闭调度锁的函数:
//禁止任务调度函数
void tTaskSchedDisable (void)
{
uint32_t status = tTaskEnterCritical();
if (schedLockCount < 255)
{
schedLockCount++;
}
tTaskExitCritical(status);
}
///禁止任务调度函数
void tTaskSchedEnable (void)
{
uint32_t status = tTaskEnterCritical();
if (schedLockCount > 0)
{
if (--schedLockCount == 0)
{
tTaskSched();
}
}
tTaskExitCritical(status);
}
最后在 tTaskSched 函数中进行调度锁判断。
schedLockCount > 0 即不调度
void tTaskSched (void)
{
// 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
uint32_t status = tTaskEnterCritical();
// 如何调度器已经被上锁,则不进行调度,直接退bm
if (schedLockCount > 0)
{
tTaskExitCritical(status);
return;
}
.....
}
三、任务优先级
3.1 什么是任务优先级
在实时操作系统(Real Time Operating System, RTOS)中,任务优先级是一个重要的概念,它决定了任务被调度器调度的顺序。在多任务系统中,高优先级的任务可以打断低优先级任务的执行,获得CPU的使用权。
比如我们考虑 freeRTOS 的两个任务:
xTaskCreate(LED1_task, "LED1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
xTaskCreate(LED2_task, "LED2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
由于他们优先级都是2,所以他们同时运行会按照时间片来回调度。
xTaskCreate(LED1_task, "LED1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
xTaskCreate(LED2_task, "LED2", configMINIMAL_STACK_SIZE, NULL, 3, NULL);
如果 LED1 优先级是2,LED2 优先级是 3。那么 LED1 如果没有 rtos 延迟的话,freeRTOS 是不会调度到 LED2 中的。
void LED1_task(void * pvParameters)
{
for( ;; ){
gpio_bit_set(GPIOB, GPIO_PIN_4);
//vTaskDelay(1000);
}
}
void LED2_task(void * pvParameters)
{
for( ;; ){
gpio_bit_reset(GPIOB, GPIO_PIN_4);
vTaskDelay(500);
}
}
比如这种情况,此时任务即不会调度到 LED2_task 中。只有当我们取消掉 LDE1_TASK 的 vTaskDelay(1000)才会继续调度。
3.2 图数据结构
// 位图类型
typedef struct
{
uint32_t bitmap;
}tBitmap;
#include "tLib.h"
/**********************************************************************************************************
** Function name : tBitmapInit
** Descriptions : 初始化bitmap将所有的位全清0
** parameters : 无
** Returned value : 无
***********************************************************************************************************/
void tBitmapInit (tBitmap * bitmap)
{
bitmap->bitmap = 0;
}
/**********************************************************************************************************
** Function name : tBitmapPosCount
** Descriptions : 返回最大支持的位置数量
** parameters : 无
** Returned value : 最大支持的位置数量
***********************************************************************************************************/
uint32_t tBitmapPosCount (void)
{
return 32;
}
/**********************************************************************************************************
** Function name : tBitmapSet
** Descriptions : 设置bitmap中的某个位
** parameters : pos 需要设置的位
** Returned value : 无
***********************************************************************************************************/
void tBitmapSet (tBitmap * bitmap, uint32_t pos)
{
bitmap->bitmap |= 1 << pos;
}
/**********************************************************************************************************
** Function name : tBitmapClear
** Descriptions : 清除bitmap中的某个位
** parameters : pos 需要清除的位
** Returned value : 无
***********************************************************************************************************/
void tBitmapClear (tBitmap * bitmap, uint32_t pos)
{
bitmap->bitmap &= ~(1 << pos);
}
/**********************************************************************************************************
** Function name : tBitmapGetFirstSet
** Descriptions : 从位图中第0位开始查找,找到第1个被设置的位置序号
** parameters : 无
** Returned value : 第1个被设置的位序号
***********************************************************************************************************/
uint32_t tBitmapGetFirstSet (tBitmap * bitmap)
{
static const uint8_t quickFindTable[] =
{
/* 00 */ 0xff, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 10 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 20 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 30 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 40 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 50 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 60 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 70 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 80 */ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 90 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* A0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* B0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* C0 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* D0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* E0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* F0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};
if (bitmap->bitmap & 0xff)
{
return quickFindTable[bitmap->bitmap & 0xff];
}
else if (bitmap->bitmap & 0xff00)
{
return quickFindTable[(bitmap->bitmap >> 8) & 0xff] + 8;
}
else if (bitmap->bitmap & 0xff0000)
{
return quickFindTable[(bitmap->bitmap >> 16) & 0xff] + 16;
}
else if (bitmap->bitmap & 0xFF000000)
{
return quickFindTable[(bitmap->bitmap >> 24) & 0xFF] + 24;
}
else
{
return tBitmapPosCount();
}
}
其中位图是数据结构本质是一个 uint32_t 的变量。任务优先级中使用任务图数据结构的目的是返回最小的位值。
比如对于二进制 0011 1010 来说,我们就应该返回 1 ,因为最小被置位的 bit 在从右往左第二个。
uint32_t tBitmapGetFirstSet (tBitmap * bitmap)
{
static const uint8_t quickFindTable[] =
{
/* 00 */ 0xff, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 10 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 20 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 30 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 40 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 50 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 60 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 70 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 80 */ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 90 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* A0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* B0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* C0 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* D0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* E0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* F0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};
if (bitmap->bitmap & 0xff)
{
return quickFindTable[bitmap->bitmap & 0xff];
}
else if (bitmap->bitmap & 0xff00)
{
return quickFindTable[(bitmap->bitmap >> 8) & 0xff] + 8;
}
else if (bitmap->bitmap & 0xff0000)
{
return quickFindTable[(bitmap->bitmap >> 16) & 0xff] + 16;
}
else if (bitmap->bitmap & 0xFF000000)
{
return quickFindTable[(bitmap->bitmap >> 24) & 0xFF] + 24;
}
else
{
return tBitmapPosCount();
}
}
对于这个函数来说,他可以快速的返回最小的置位 bit,比如我们输入十六进制 0x60,首先这个数字与 0XFF 相与为真,也就是说在前 8 比特有置位,首先他会进入这个函数:
if (bitmap->bitmap & 0xff)
{
return quickFindTable[bitmap->bitmap & 0xff];
}
然后我们进入查表法,0x60 查表得知为 5。
static const uint8_t quickFindTable[] =
{
/* 00 */ 0xff, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 10 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 20 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 30 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 40 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 50 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 60 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 70 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 80 */ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* 90 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* A0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* B0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* C0 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* D0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* E0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
/* F0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};
所以返回 5。我们验证 0x60 二进制为 0110 0000,正好最小的置位就在 5 位。
3.3 通过图实现优先级
首先定义两个数据结构:
// 任务优先级的标记位置结构
tBitmap taskPrioBitmap;
// 所有任务的指针数组:简单起见,只使用两个任务
tTask * taskTable[TINYOS_PRO_COUNT];
之后我们在 tTaskInit 初始化任务时,将图置位。
void tTaskInit (tTask * task, void (*entry)(void *), void *param, uint32_t prio, uint32_t * stack)
{
//....之前的代码没有变,忽略
taskTable[prio] = task; // 填入任务优先级表
tBitmapSet(&taskPrioBitmap, prio); // 标记优先级位置中的相应位
}
tTaskHighestReady函数会返回当前优先级最低的任务。
tTask * tTaskHighestReady (void)
{
uint32_t highestPrio = tBitmapGetFirstSet(&taskPrioBitmap);
return taskTable[highestPrio];
}
在调度函数 tTaskSched 中,我们始终将 nextTask 指向当前优先级最低的函数。
void tTaskSched (void)
{
tTask * tempTask;
// 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
uint32_t status = tTaskEnterCritical();
// 如何调度器已经被上锁,则不进行调度,直接退bm
if (schedLockCount > 0)
{
tTaskExitCritical(status);
return;
}
// 找到优先级最高的任务,如果其优先级比当前任务的还高,那么就切换到这个任务
tempTask = tTaskHighestReady();
if (tempTask != currentTask)
{
nextTask = tempTask;
tTaskSwitch();
}
// 退出临界区
tTaskExitCritical(status);
}
四、任务延迟队列
#include "tLib.h"
/**********************************************************************************************************
** Function name : tNodeInit
** Descriptions : 初始化结点类型
** parameters : 无
** Returned value : 无
***********************************************************************************************************/
void tNodeInit (tNode * node)
{
node->nextNode = node;
node->preNode = node;
}
// 以下是简化代码编写添加的宏
#define firstNode headNode.nextNode
#define lastNode headNode.preNode
/**********************************************************************************************************
** Function name : tListInit
** Descriptions : 链表初始化
** parameters : 无
** Returned value : 无
***********************************************************************************************************/
void tListInit (tList * list)
{
list->firstNode = &(list->headNode);
list->lastNode = &(list->headNode);
list->nodeCount = 0;
}
/**********************************************************************************************************
** Function name : tListCount
** Descriptions : 返回链表中结点的数量
** parameters : 无
** Returned value : 结点数量
***********************************************************************************************************/
uint32_t tListCount (tList * list)
{
return list->nodeCount;
}
/**********************************************************************************************************
** Function name : tListFirst
** Descriptions : 返回链表的首个结点
** parameters : list 查询的链表
** Returned value : 首个结点,如果链表为空,则返回0
***********************************************************************************************************/
tNode * tListFirst (tList * list)
{
tNode * node = (tNode *)0;
if (list->nodeCount != 0)
{
node = list->firstNode;
}
return node;
}
/**********************************************************************************************************
** Function name : tListLast
** Descriptions : 返回链表的最后一个结点
** parameters : list 查询的链表
** Returned value : 最后的结点,如果链表为空,则返回0
***********************************************************************************************************/
tNode * tListLast (tList * list)
{
tNode * node = (tNode *)0;
if (list->nodeCount != 0)
{
node = list->lastNode;
}
return node;
}
/**********************************************************************************************************
** Function name : tListPre
** Descriptions : 返回链表中指定结点的前一结点
** parameters : list 查询的链表
** parameters : node 参考结点
** Returned value : 前一结点结点,如果结点没有前结点(链表为空),则返回0
***********************************************************************************************************/
tNode * tListPre (tList * list, tNode * node)
{
if (node->preNode == node)
{
return (tNode *)0;
}
else
{
return node->preNode;
}
}
/**********************************************************************************************************
** Function name : tListnextNode
** Descriptions : 返回链表中指定结点的后一结点
** parameters : list 查询的链表
** parameters : node 参考结点
** Returned value : 后一结点结点,如果结点没有前结点(链表为空),则返回0
***********************************************************************************************************/
tNode * tListNext (tList * list, tNode * node)
{
if (node->nextNode == node)
{
return (tNode *)0;
}
else
{
return node->nextNode;
}
}
/**********************************************************************************************************
** Function name : tListRemoveAll
** Descriptions : 移除链表中的所有结点
** parameters : list 待清空的链表
** Returned value : 无
***********************************************************************************************************/
void tListRemoveAll (tList * list)
{
uint32_t count;
tNode * nextNode;
// 遍历所有的结点
nextNode = list->firstNode;
for (count = list->nodeCount; count != 0; count-- )
{
// 先纪录下当前结点,和下一个结点
// 必须纪录下一结点位置,因为在后面的代码中当前结点的next会被重置
tNode * currentNode = nextNode;
nextNode = nextNode->nextNode;
// 重置结点自己的信息
currentNode->nextNode = currentNode;
currentNode->preNode = currentNode;
}
list->firstNode = &(list->headNode);
list->lastNode = &(list->headNode);
list->nodeCount = 0;
}
/**********************************************************************************************************
** Function name : tListAddFirst
** Descriptions : 将指定结点添加到链表的头部
** parameters : list 待插入链表
** parameters : node 待插入的结点
** Returned value : 无
***********************************************************************************************************/
void tListAddFirst (tList * list, tNode * node)
{
node->preNode = list->firstNode->preNode;
node->nextNode = list->firstNode;
list->firstNode->preNode = node;
list->firstNode = node;
list->nodeCount++;
}
/**********************************************************************************************************
** Function name : tListAddLast
** Descriptions : 将指定结点添加到链表的末尾
** parameters : list 待插入链表
** parameters : node 待插入的结点
** Returned value : 无
***********************************************************************************************************/
void tListAddLast (tList * list, tNode * node)
{
node->nextNode = &(list->headNode);
node->preNode = list->lastNode;
list->lastNode->nextNode = node;
list->lastNode = node;
list->nodeCount++;
}
/**********************************************************************************************************
** Function name : tListRemoveFirst
** Descriptions : 移除链表中的第1个结点
** parameters : list 待移除链表
** Returned value : 如果链表为空,返回0,否则的话,返回第1个结点
***********************************************************************************************************/
tNode * tListRemoveFirst (tList * list)
{
tNode * node = (tNode *)0;
if( list->nodeCount != 0 )
{
node = list->firstNode;
node->nextNode->preNode = &(list->headNode);
list->firstNode = node->nextNode;
list->nodeCount--;
}
return node;
}
/**********************************************************************************************************
** Function name : tListInsertAfter
** Descriptions : 将指定的结点插入到某个结点后面
** parameters : list 待插入的链表
** parameters : nodeAfter 参考结点
** parameters : nodeToInsert 待插入的结构
** Returned value : 无
***********************************************************************************************************/
void tListInsertAfter (tList * list, tNode * nodeAfter, tNode * nodeToInsert)
{
nodeToInsert->preNode = nodeAfter;
nodeToInsert->nextNode = nodeAfter->nextNode;
nodeAfter->nextNode->preNode = nodeToInsert;
nodeAfter->nextNode = nodeToInsert;
list->nodeCount++;
}
/**********************************************************************************************************
** Function name : tListRemove
** Descriptions : 将指定结点从链表中移除
** parameters : list 待移除的链表
** parameters : node 待移除的结点
** Returned value : 无
***********************************************************************************************************/
void tListRemove (tList * list, tNode * node)
{
node->preNode->nextNode = node->nextNode;
node->nextNode->preNode = node->preNode;
list->nodeCount--;
}
4.1 任务链表化
typedef struct _tTask {
// 任务所用堆栈的当前堆栈指针
tTaskStack * stack;
// 任务延时计数器
uint32_t delayTicks;
// 延时结点:通过delayNode就可以将tTask放置到延时队列中
tNode delayNode;
// 任务的优先级
uint32_t prio;
// 任务当前状态
uint32_t state;
}tTask;
我们为任务结构体添加了一个 tNode delayNode 的属性。
void tTaskInit (tTask * task, void (*entry)(void *), void *param, uint32_t prio, uint32_t * stack)
{
//...忽略之前的代码
task->stack = stack; // 保存最终的值
task->delayTicks = 0;
task->prio = prio; // 设置任务的优先级
task->state = TINYOS_TASK_STATE_RDY; // 设置任务为就绪状态
tNodeInit(&(task->delayNode)); // 初始化延时结点
taskTable[prio] = task; // 填入任务优先级表
tBitmapSet(&taskPrioBitmap, prio); // 标记优先级位置中的相应位
}
在初始化任务时,即将 tNodeInit(&(task->delayNode)); 进行初始化。
4.2 延迟函数实现
void tTaskDelay (uint32_t delay) {
// 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
uint32_t status = tTaskEnterCritical();
// 设置延时值,插入延时队列
tTimeTaskWait(currentTask, delay);
// 将任务从就绪表中移除
tTaskSchedUnRdy(currentTask);
// 然后进行任务切换,切换至另一个任务,或者空闲任务
// delayTikcs会在时钟中断中自动减1.当减至0时,会切换回来继续运行。
tTaskSched();
// 退出临界区
tTaskExitCritical(status);
}
void tTimeTaskWait (tTask * task, uint32_t ticks)
{
task->delayTicks = ticks;
tListAddLast(&tTaskDelayedList, &(task->delayNode));
task->state |= TINYOS_TASK_STATE_DELAYED;
}
void tTimeTaskWakeUp (tTask * task)
{
tListRemove(&tTaskDelayedList, &(task->delayNode));
task->state &= ~TINYOS_TASK_STATE_DELAYED;
}
4.3 时钟节拍函数
任务链表化最终应用就是再 tTaskSystemTickHandler 节拍函数中,通过循环任务链表将任务判断移除和恢复就绪态。
使用链表替代了之前的数组。
void tTaskSystemTickHandler ()
{
tNode * node;
// 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
uint32_t status = tTaskEnterCritical();
// 检查所有任务的delayTicks数,如果不0的话,减1。
for (node = tTaskDelayedList.headNode.nextNode; node != &(tTaskDelayedList.headNode); node = node->nextNode)
{
tTask * task = tNodeParent(node, tTask, delayNode);
if (--task->delayTicks == 0)
{
// 将任务从延时队列中移除
tTimeTaskWakeUp(task);
// 将任务恢复到就绪状态
tTaskSchedRdy(task);
}
}
// 退出临界区
tTaskExitCritical(status);
// 这个过程中可能有任务延时完毕(delayTicks = 0),进行一次调度。
tTaskSched();
}
五、同优先级时间片运行
5.1 初始化任务
此时我们使用 taskTable[prio] 保存任务链表,如果优先级是 1 我们则保存到 taskTable[1] 链表中,插入到最后即可。
void tTaskInit (tTask * task, void (*entry)(void *), void *param, uint32_t prio, uint32_t * stack)
{
//...
tNodeInit(&(task->delayNode)); // 初始化延时结点
tNodeInit(&(task->linkNode)); // 初始化链接结点
tListAddLast(&taskTable[prio], &(task->linkNode)); // 插入对应的优先级队列中
tBitmapSet(&taskPrioBitmap, prio); // 标记优先级位置中的相应位
}
5.3 滴答定时器的时间片函数
tTaskSystemTickHandler() 直接被滴答定时器回调,其工作是将所有任务delay延迟都先减 -1。
其次是将当前任务优先级的链表后移一位,做到优先级相同切换时间片效果。
在最后进入任务调度函数接口。
void tTaskSystemTickHandler (void)
{
tNode * node;
// 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
uint32_t status = tTaskEnterCritical();
// 检查所有任务的delayTicks数,如果不0的话,减1。
for (node = tTaskDelayedList.headNode.nextNode; node != &(tTaskDelayedList.headNode); node = node->nextNode)
{
tTask * task = tNodeParent(node, tTask, delayNode);
if (--task->delayTicks == 0)
{
// 将任务从延时队列中移除
tTimeTaskWakeUp(task);
// 将任务恢复到就绪状态
tTaskSchedRdy(task);
}
}
// 检查下当前任务的时间片是否已经到了
if (--currentTask->slice == 0)
{
// 如果当前任务中还有其它任务的话,那么切换到下一个任务
// 方法是将当前任务从队列的头部移除,插入到尾部
// 这样后面执行tTaskSched()时就会从头部取出新的任务取出新的任务作为当前任务运行
if (tListCount(&taskTable[currentTask->prio]) > 0)
{
tListRemoveFirst(&taskTable[currentTask->prio]);
tListAddLast(&taskTable[currentTask->prio], &(currentTask->linkNode));
// 重置计数器
currentTask->slice = TINYOS_SLICE_MAX;
}
}
// 退出临界区
tTaskExitCritical(status);
// 这个过程中可能有任务延时完毕(delayTicks = 0),进行一次调度。
tTaskSched();
}
5.2 任务调度接口
使用 tTaskHighestReady(); 找到优先级最高的任务。
void tTaskSched (void)
{
//...
tTask * tempTask;
//只要判断不是当前任务,就立即切换过去
tempTask = tTaskHighestReady();
if (tempTask != currentTask)
{
nextTask = tempTask;
tTaskSwitch();
}
//...
}
tTaskHighestReady() 中先取优先级最低的,然后返回头链表得到任务。
tTask * tTaskHighestReady (void)
{
uint32_t highestPrio = tBitmapGetFirstSet(&taskPrioBitmap);
tNode * node = tListFirst(&taskTable[highestPrio]);
return (tTask *)tNodeParent(node, tTask, linkNode);
}
5.3 RTOS 延迟函数
延迟函数的工作是将延迟值加入到任务中,并且入队延迟链表。
此外还有将延迟函数从就绪链表移除。
void tTaskDelay (uint32_t delay) {
// 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
uint32_t status = tTaskEnterCritical();
// 设置延时值,插入延时队列
tTimeTaskWait(currentTask, delay);
// 将任务从就绪表中移除
tTaskSchedUnRdy(currentTask);
// 然后进行任务切换,切换至另一个任务,或者空闲任务
// delayTikcs会在时钟中断中自动减1.当减至0时,会切换回来继续运行。
tTaskSched();
// 退出临界区
tTaskExitCritical(status);
}
//将任务加入延时队列中
void tTimeTaskWait (tTask * task, uint32_t ticks)
{
task->delayTicks = ticks;
tListAddLast(&tTaskDelayedList, &(task->delayNode));
task->state |= TINYOS_TASK_STATE_DELAYED;
}
//将延时的任务从延时队列中唤醒
void tTimeTaskWakeUp (tTask * task)
{
tListRemove(&tTaskDelayedList, &(task->delayNode));
task->state &= ~TINYOS_TASK_STATE_DELAYED;
}
//将任务从就绪列表中移除
void tTaskSchedUnRdy (tTask * task)
{
tListRemove(&taskTable[task->prio], &(task->linkNode));
// 队列中可能存在多个任务。只有当没有任务时,才清除位图标记
if (tListCount(&taskTable[task->prio]) == 0)
{
tBitmapClear(&taskPrioBitmap, task->prio);
}
}