从头开始编写一个实时嵌入式操作系统的内核(二)
2017-11-06 17:59
387 查看
一、RTOS里面的重要数据结构----链表
很多RTOS包括Linux的内核在内,内核里面都大量使用了链表这一种数据结构。内核的链表一般都是双向循环链表,这是因为双向循环链表的效率是最高的,找头节点、尾节点,直接前驱、直接后继时间复杂度都是O(1),这是使用单链表、单向循环链表或其他形式的链表是不能完成的。我们平时上课所学的链表一般都是指针域和数据域,但是如果有研究过Linux内核里面链表的人应该知道和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域。Linux内核链表在linux源代码下include/Lish.h中有精彩的定义。这个链表具备通用性,使用非常方便。只需要在结构定义一个链表结构就可以使用。用这种定义有以下两种好处:1是可扩展性,2是封装。可扩展性肯定是必须的,内核一直都是在发展中的,所以代码都不能写成死代码,要方便修改和追加。
所以,参考Linux内核链表的定义构建了人如下一套链表结构,作为整个OS里面的基础,方便队列、信号量去使用。但是,在这里我做了一点小小的修改,为了我使用的方便,我还是定义了一个类型为void*的指针vPstrTcb,这个指针其实可以理解为这个结构体的数据域,因为我们在创建一个链表节点的时候可以将这个指针指向这一个节点的地址,甚至指向任意一块内存或数据的地址,方便我们在后面对节点所指向的节点或者数据做操作。因为我们需要使用的时候,只需要进行一次类型强转就可以了。这样子其实更方便了一些功能的实现,如果有学习过freertos或者ucos的话,其实他们的一些模块如TCB,都是有一个指针指向这一个TCB的地址。
好吧,关于链表的操作建议还是自己去看《大话数据结构》吧。以上代码不懂请自己看注释画图理解。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
二、任务就绪表和时间片轮询的实现
1.代码如下:
2.任务就绪表的实现
任务就绪表我们可以使用链表数组来实现,可以这样定义:
OS_DLISTreadyList[TASKPRIONUM];/*TASKPRIONUM=8*/
每个数组元素分别对应一个优先级链表的根节点,readyList如果我们任务要挂在我们的就绪表中,我们只需要在任务的TCB那里添加一个任务就绪表的节点就可以了。我们根据链表的定义,当链表为空的时候链表的根节点的头尾指针指向NULL即可实现初始化,我们可以简单一个for循环遍历一下即可。所以代码53~61的函数OS_TaskReadyTabInit(void)就是实现了初始化。同样的,如果我们想要查找我们任务就绪表里面是否有任务或者就绪表中的最高优先级是哪一个,最简单的方法我们只需要对这个链表进行遍历,查找去根节点的头尾指针是否指向NULL即可,但是这种方法存在一个问题,在查找最高优先级任务的时候,遍历的话,查找的时间是不固定的。而且,如果当优先级不只8个,比如32个的话,假设最高优先级是最后一维,那么如此将做32次的循环,这是非常浪费时间的。为此,我们为每一个根节点添加一个标志位,指示这个根节点是否为空。所以我们可以根据这个标志位快速查找到挂有最高优先级任务的根节点,不需要去查询根节点是否为空。所以暂定优先级的个数小于等于8,我们就恶意用一个byte去描述这个就绪表的,这个byte的每一个bit作为每一个优先级根节点的标志位,0代表空,1代表非空。然后,查询任务的最高优先级的我们如果想要简单可以直接用一个for循环遍历,从头开始查询每一个bit,但是这样做仍然太过于占用时间,如果我没记错,freertos所使用的方法类似,都是遍历。我们可以参考ucos的方法,建立一个优先级的方向查找表。方法如下:
假设我们定义就绪表的最高优先级由标志为1的最低位决定,也就是说从0~7,只要发现第一个为1的位,那么所对应的就是就绪表中的最高优先级了,其他的位我们都i可以不考虑。举个例子:假设这个Flag的数值位0b00000011,也就是3的时候,为1的最低位是0,所以其优先级是0;假设这个Flag的数值位0b00000101,也就是5的时候,为1的最低位是0,所以其优先级是0;假设这个Flag的数值位0b11111110,也就是254的时候,为1的最低位是1,所以其优先级是1。所以我们可以根据这个Flag的值,最为数组的下标,将优先级作为数组的元素,建立一个方向查找表,8位的话对应256种情况,如下:
如果,我们想要知道当前任务就绪表的最高优先级,只要将就绪表的标志输入查找表,即可得出其最高优先级的任务,这是OS_GetHighestTaskPrio(unsignedchartaskPrioFlag)这个函数所做的事情。
综上所述,为了更好地实现模块化地封装,我们的任务就绪表就应该扩展成一个结构体,如下所示:
而我们的任务TCB相应地也要进行扩展,添加一些架构帮助我们实现功能。如下所示:
我们的任务构建函数也将添加一些新的功能,代码如下:比较重要的就是我们在初始化后,将任务的TCB的地址传给了我们这个TCB的结构体的就绪表节点的指针queReadyTab.vPstrTcb,所以我们后面可以直接通过我们的节点里的这个指针访问到我们的TCB,这是一个很重要的“回调”功能。
3.任务切换
任务的切换,是这里的重点,这里实现的是根据任务就绪表进行切换和相同优先级任务的时间轮询。代码如下:
为了实现一个相同优先级任务的时间片轮询,我在这里偷了一个懒,我直接定义了一个数组,保存当前的各个优先级任务它所运行的时间,也就是一个数组:
unsignedintosTaskTimeSlice[TASKPRIONUM];/*各个任务的时间片表*/
每个数组元素代表运行了的时间,所以只要一个if判断即可简单实现时间片轮询。然后实现任务切换我们的流程就是检查就绪表,查找里面的最高优先级任务,然后找到这个最高优先级的任务节点,通过节点的指针找到其TCB,将TCB赋值给OSTCBHighRdyPtr;判断时间片是否用尽,用尽则检查下后面是否还有任务,有的话重新获取TCB,没有则只清零时间片;调用OS_CtxSw()触发pendSV中断,继而实现任务切换。注意,任务就绪表里必须保证每个时候都有任务可以执行,否则会出问题,所以最低优先级任务是idle任务。
顺便讲一个很好玩的做法,也如果我们使用freertos,在中断里循环使用vTaskDelay函数将任务删除后添加进延时表进行延时,当我们最后将ldle任务也删除了之后,下一次查找最高优先级任务的时候就会发现找不到,因为freertos是通过某个变量自加向下查找所以最后就会溢出然后程序就崩了。所以一定要注意在中断里面使用这些函数。
4.最后这个函数怎么调用??
我们配置滴答定时器为每一个TICK(ms)进入一次中断,此时调用这个任务切换函数即可。代码如下:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
三、测试程序的主函数
#include"stm32f10x.h"
#include"bsp_usart1.h"
#include"OS.h"
#include"os_task.h"
TCBtcb_task1,tcb_task2,tcb_task3;
unsignedintTASK_STK1[128],TASK_STK2[128],TASK_STK3[128];
intmutex_flag=0;/*自旋锁*/
/*
voidtaskSwitch(void)
{
if(OSTCBCurPtr==&tcb_task1)
OSTCBHighRdyPtr=&tcb_task2;
else
OSTCBHighRdyPtr=&tcb_task1;
OS_CtxSw();
}
*/
voidtask1(void*pvPara)
{
while(1)
{
while(mutex_flag==0)
{
printf("%s\n",(char*)pvPara);
mutex_flag=1;
}
}
}
voidtask2(void*pvPara)
{
while(1)
{
while(mutex_flag==1)
{
printf("%s\n",(char*)pvPara);
mutex_flag=2;
}
}
}
voidtask3(void*pvPara)
{
while(1)
{
while(mutex_flag==2)
{
printf("%s\n",(char*)pvPara);
mutex_flag=0;
}
}
}
/**
*@brief主函数
*@param无
*@retval无
*/
intmain(void)
{
/*USART1config1152008-N-1*/
USART1_Config();
OS_SysTickTimerInit();
OS_TaskReadyTabInit();
vTaskCreate(&tcb_task1,task1,&TASK_STK1[128-1],1,(void*)"task-1running");
vTaskCreate(&tcb_task2,task2,&TASK_STK2[128-1],1,(void*)"task-2running");
vTaskCreate(&tcb_task3,task3,&TASK_STK3[128-1],1,(void*)"task-3running");
OSTCBHighRdyPtr=&tcb_task3;
OSStartHighRdy();
}
/*********************************************ENDOFFILE**********************/
测试效果如下:
很多RTOS包括Linux的内核在内,内核里面都大量使用了链表这一种数据结构。内核的链表一般都是双向循环链表,这是因为双向循环链表的效率是最高的,找头节点、尾节点,直接前驱、直接后继时间复杂度都是O(1),这是使用单链表、单向循环链表或其他形式的链表是不能完成的。我们平时上课所学的链表一般都是指针域和数据域,但是如果有研究过Linux内核里面链表的人应该知道和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域。Linux内核链表在linux源代码下include/Lish.h中有精彩的定义。这个链表具备通用性,使用非常方便。只需要在结构定义一个链表结构就可以使用。用这种定义有以下两种好处:1是可扩展性,2是封装。可扩展性肯定是必须的,内核一直都是在发展中的,所以代码都不能写成死代码,要方便修改和追加。
所以,参考Linux内核链表的定义构建了人如下一套链表结构,作为整个OS里面的基础,方便队列、信号量去使用。但是,在这里我做了一点小小的修改,为了我使用的方便,我还是定义了一个类型为void*的指针vPstrTcb,这个指针其实可以理解为这个结构体的数据域,因为我们在创建一个链表节点的时候可以将这个指针指向这一个节点的地址,甚至指向任意一块内存或数据的地址,方便我们在后面对节点所指向的节点或者数据做操作。因为我们需要使用的时候,只需要进行一次类型强转就可以了。这样子其实更方便了一些功能的实现,如果有学习过freertos或者ucos的话,其实他们的一些模块如TCB,都是有一个指针指向这一个TCB的地址。
1#ifndefOS_LIST_H 2#defineOS_LIST_H 3 4#include"stm32f10x.h" 5 6#ifndefNULL 7#defineNULL((void*)0) 8#endif 9 10typedefstructoslist 11{ 12structoslist*pstrHead;/*头指针*/ 13structoslist*pstrTail;/*尾指针*/ 14void*vPstrTcb; 15}OSLIST; 16 17/***********************************函数声明*************************************/ 18voidOS_DlistInit(OSLIST*pstrList); 19voidOS_DlistNodeAdd(OSLIST*pstrList,OSLIST*pstrNode); 20OSLIST*OS_DlistNodeDelete(OSLIST*pstrList); 21voidOS_DlistCurNodeInsert(OSLIST*pstrList,OSLIST*pstrNode, 22OSLIST*pstrNewNode); 23OSLIST*OS_DlistCurNodeDelete(OSLIST*pstrList,OSLIST*pstrNode); 24OSLIST*OS_DlistEmpInq(OSLIST*pstrList); 25OSLIST*OS_DlistNextNodeEmpInq(OSLIST*pstrList,OSLIST*pstrNode); 26 27#endif
1#include"os_list.h" 2 3/////////////////////////////////////////////////////////////////////////////////// 4////////////////////////双向循环链表函数声明///////////////////////////////////// 5/////////////////////////////////////////////////////////////////////////////////// 6 7/*********************************************************************************** 8函数功能:初始化链表. 9入口参数:pstrList:链表根节点指针. 10返回值:none. 11***********************************************************************************/ 12voidOS_DlistInit(OSLIST*pstrList) 13{ 14/*空链表的头尾都指向空节点*/ 15pstrList->pstrHead=(OSLIST*)NULL; 16pstrList->pstrTail=(OSLIST*)NULL; 17} 18 19/*********************************************************************************** 20函数功能:向链表添加一个节点,从链表尾部加入. 21入口参数:pstrList:链表根节点指针. 22pstrNode:加入的节点指针. 23返回值:none. 24***********************************************************************************/ 25voidOS_DlistNodeAdd(OSLIST*pstrList,OSLIST*pstrNode) 26{ 27/*链表非空*/ 28if(NULL!=pstrList->pstrTail) 29{ 30/*根节点的head指向原链表的末尾,所以将新节点的tail指向原链表的末尾*/ 31pstrNode->pstrHead=pstrList->pstrHead; 32/*新节点的tail指向根节点的地址*/ 33pstrNode->pstrTail=pstrList; 34/*根节点的head指向原链表的末尾,所以将原节点的tail指向新节点*/ 35pstrList->pstrHead->pstrTail=pstrNode; 36/*将根节点的heap指向链表的末端,即要指向新的节点(此时已经是末端了)*/ 37pstrList->pstrHead=pstrNode; 38} 39else/*链表为空*/ 40{ 41/*新节点的头尾都指向根节点*/ 42pstrNode->pstrHead=pstrList; 43pstrNode->pstrTail=pstrList; 44 45/*根节点的头尾都指向新节点*/ 46pstrList->pstrHead=pstrNode; 47pstrList->pstrTail=pstrNode; 48} 49} 50 51/*********************************************************************************** 52函数功能:从链表删除一个节点,从链表头部删除. 53入口参数:pstrList:链表根节点指针. 54返回值:删除的节点指针,若链表为空则返回NULL. 55***********************************************************************************/ 56OSLIST*OS_DlistNodeDelete(OSLIST*pstrList) 57{ 58OSLIST*pstrTempNode; 59 60/*链表中的第一个节点*/ 61pstrTempNode=pstrList->pstrTail; 62 63/*链表非空*/ 64if(NULL!=pstrTempNode) 65{ 66/*如果有多个节点,即根节点外还有其他节点,则先删除节点*/ 67if(pstrList->pstrHead!=pstrList->pstrTail) 68{ 69/*根节点后的第一个节点的tail就根节点的第二个节点,它的head指向原链表起始地址,即根节点*/ 70pstrTempNode->pstrTail->pstrHead=pstrList; 71/*根节点的tail指向根节点后的第二个节点*/ 72pstrList->pstrTail=pstrTempNode->pstrTail; 73} 74else/*只有一个根节点*/ 75{ 76/*取出节点后链表为空*/ 77pstrList->pstrHead=(OSLIST*)NULL; 78pstrList->pstrTail=(OSLIST*)NULL; 79} 80 81/*返回取出的节点指针*/ 82returnpstrTempNode; 83} 84else/*链表为空返回NULL*/ 85{ 86return(OSLIST*)NULL; 87} 88} 89 90/*********************************************************************************** 91函数功能:向链表指定的节点前插入一个节点. 92入口参数:pstrList:链表根节点指针. 93pstrNode:基准节点指针,将新节点插到该节点前面. 94pstrNewNode:新插入节点的指针. 95返回值:none. 96***********************************************************************************/ 97voidOS_DlistCurNodeInsert(OSLIST*pstrList,OSLIST*pstrNode, 98OSLIST*pstrNewNode) 99{ 100/*基准节点不是根节点*/ 101if(pstrList!=pstrNode) 102{ 103/*新节点的tail指向基准节点*/ 104pstrNewNode->pstrTail=pstrNode; 105/*新节点的head指向基准节点的上一个节点*/ 106pstrNewNode->pstrHead=pstrNode->pstrHead; 107/*基准节点的上一个节点的tail指向新节点*/ 108pstrNode->pstrHead->pstrTail=pstrNewNode; 109/*基准节点的head指向新节点*/ 110pstrNode->pstrHead=pstrNewNode; 111} 112else/*基准节点是根节点*/ 113{ 114OS_DlistNodeAdd(pstrList,pstrNewNode); 115} 116} 117 118/*********************************************************************************** 119函数功能:从链表删除指定的节点,并返回下个节点的指针. 120入口参数:pstrList:链表根节点指针. 121pstrNode:要删除的节点的指针. 122返回值:删除节点的下个节点指针,若没有下个节点则返回NULL. 123***********************************************************************************/ 124OSLIST*OS_DlistCurNodeDelete(OSLIST*pstrList,OSLIST*pstrNode) 125{ 126/*要删除的节点不是根节点*/ 127if(pstrList!=pstrNode) 128{ 129/*链表中有多个节点*/ 130if((pstrNode->pstrHead!=pstrList)||(pstrNode->pstrTail!=pstrList)) 131{ 132/*要删除节点的上个节点的尾指向要删除节点的下个节点*/ 133pstrNode->pstrHead->pstrTail=pstrNode->pstrTail; 134 135/*要删除节点的下个节点的头指向要删除节点的上个节点*/ 136pstrNode->pstrTail->pstrHead=pstrNode->pstrHead; 137 138/*返回删除节点的下个节点指针*/ 139returnpstrNode->pstrTail; 140} 141else/*链表中只有一个节点*/ 142{ 143(void)OS_DlistNodeDelete(pstrList); 144 145/*没有下个节点,返回NULL*/ 146return(OSLIST*)NULL; 147} 148} 149else/*删除根节点直接返回NULL*/ 150{ 151return(OSLIST*)NULL; 152} 153} 154 155/*********************************************************************************** 156函数功能:查询链表是否为空. 157入口参数:pstrList:链表根节点指针. 158返回值:若非空则返回第一个节点的指针,若空则返回NULL. 159***********************************************************************************/ 160OSLIST*OS_DlistEmpInq(OSLIST*pstrList) 161{ 162returnpstrList->pstrTail; 163} 164 165/*********************************************************************************** 166函数功能:查询链表中指定节点的下一个节点是否为空. 167入口参数:pstrList:链表根节点指针. 168pstrNode:基准节点指针,查询该节点的下一个节点. 169返回值:若指定节点的下一个节点非空则返回下一个节点的指针,若空则返回NULL. 170***********************************************************************************/ 171OSLIST*OS_DlistNextNodeEmpInq(OSLIST*pstrList,OSLIST*pstrNode) 172{ 173/*基准节点是最后一个节点则返回NULL*/ 174if(pstrNode->pstrTail==pstrList) 175{ 176return(OSLIST*)NULL; 177} 178else 179{ 180returnpstrNode->pstrTail; 181} 182}
好吧,关于链表的操作建议还是自己去看《大话数据结构》吧。以上代码不懂请自己看注释画图理解。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
二、任务就绪表和时间片轮询的实现
1.代码如下:
1#include"OS.h" 2 3unsignedintCPU_ExceptStack[1024]; 4unsignedint*OS_CPU_ExceptStackBase=&CPU_ExceptStack[1023]; 5 6TCB*OSTCBCurPtr; 7TCB*OSTCBHighRdyPtr; 8 9TASKREADYTABosTaskReadyTab;/*任务就绪表*/ 10 11unsignedintosTaskTimeSlice[5]={0}; 12 13constunsignedcharcaucTaskPrioUnmapTab[256]=/*优先级反向查找表*/ 14{ 150,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 164,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 175,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 184,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 196,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 204,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 215,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 224,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 237,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 244,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 255,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 264,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 276,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 284,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 295,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 304,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0 31}; 32 33 34/*********************************************************************************** 35函数功能:初始化tick定时器. 36入口参数:none. 37返回值:none. 38***********************************************************************************/ 39voidOS_SysTickTimerInit(void) 40{ 41/*设置Tick中断定时周期*/ 42(void)SysTick_Config(SystemCoreClock/(1000/TICK)); 43 44/*设置Tick中断优先级为6*/ 45NVIC_SetPriority(SysTick_IRQn,6); 46} 47 48/*********************************************************************************** 49函数功能:初始化任务就绪表osTaskReadyTab的readylist. 50入口参数:none. 51返回值:none. 52***********************************************************************************/ 53voidOS_TaskReadyTabInit(void) 54{ 55inti=0; 56for(i=0;i<TASKPRIONUM;;i++) 57{ 58OS_DlistInit(&osTaskReadyTab.readyList[i]); 59} 60osTaskReadyTab.prioFlag=0x00; 61} 62 63/*********************************************************************************** 64函数功能:设置任务就绪表的优先级标志prioFlag. 65入口参数:taskPrio:任务优先级. 66返回值:none. 67***********************************************************************************/ 68voidOS_SetReadyTabPrioFlag(unsignedchartaskPrio) 69{ 70osTaskReadyTab.prioFlag|=(unsignedchar)(1<<taskPrio); 71} 72 73/*********************************************************************************** 74函数功能:获取任务就绪表里面的最高优先级. 75入口参数:taskPrioFlag:任务就绪表的优先级标志位. 76返回值:none. 77***********************************************************************************/ 78unsignedcharOS_GetHighestTaskPrio(unsignedchartaskPrioFlag) 79{ 80returncaucTaskPrioUnmapTab[taskPrioFlag]; 81} 82 83/*********************************************************************************** 84函数功能:查找就绪表里的最高优先级任务,并根据时间片轮询,触发pendSV中断实现任务切换. 85入口参数:none. 86返回值:none. 87***********************************************************************************/ 88voidOS_TaskSwitch(void) 89{ 90unsignedcharhighestTaskPrio; 91OSLIST*pstrNode; 92OSLIST*pstrNextNode; 93TCB*pstrTask; 94/*获取就绪表里任务的最高优先级任务的优先级*/ 95highestTaskPrio=OS_GetHighestTaskPrio(osTaskReadyTab.prioFlag); 96/*查询就绪表中最高优先级的节点指的下一个节点是否为空,如果非空说明有任务需要执行,就获取其TCB节点,然后赋值给OSTCBHighRdyPtr*/ 97pstrNode=OS_DlistEmpInq(&osTaskReadyTab.readyList[highestTaskPrio]); 98pstrTask=(TCB*)pstrNode->vPstrTcb; 99OSTCBHighRdyPtr=pstrTask; 100/*该优先级的任务运行时间片自加,进行统计,并判断是否大于阈值,如果大于则查找该任务节点后是否有下一个任务,如果有,则切换,实现想同优先级时间片轮询*/ 101osTaskTimeSlice[highestTaskPrio]++; 102if(osTaskTimeSlice[highestTaskPrio]>=TASKSLICECNT)/*时间片用尽,任务切换*/ 103{ 104osTaskTimeSlice[highestTaskPrio]=0;/*时间片清零*/ 105pstrNextNode=OS_DlistNextNodeEmpInq(&osTaskReadyTab.readyList[highestTaskPrio],pstrNode); 106if(NULL!=pstrNextNode) 107{ 108OS_DlistNodeDelete(&osTaskReadyTab.readyList[highestTaskPrio]); 109OS_DlistNodeAdd(&osTaskReadyTab.readyList[highestTaskPrio],pstrNode); 110pstrTask=(TCB*)pstrNextNode->vPstrTcb; 111OSTCBHighRdyPtr=pstrTask; 112} 113} 114OS_CtxSw();/*任务切换*/ 115}
2.任务就绪表的实现
任务就绪表我们可以使用链表数组来实现,可以这样定义:
OS_DLISTreadyList[TASKPRIONUM];/*TASKPRIONUM=8*/
每个数组元素分别对应一个优先级链表的根节点,readyList如果我们任务要挂在我们的就绪表中,我们只需要在任务的TCB那里添加一个任务就绪表的节点就可以了。我们根据链表的定义,当链表为空的时候链表的根节点的头尾指针指向NULL即可实现初始化,我们可以简单一个for循环遍历一下即可。所以代码53~61的函数OS_TaskReadyTabInit(void)就是实现了初始化。同样的,如果我们想要查找我们任务就绪表里面是否有任务或者就绪表中的最高优先级是哪一个,最简单的方法我们只需要对这个链表进行遍历,查找去根节点的头尾指针是否指向NULL即可,但是这种方法存在一个问题,在查找最高优先级任务的时候,遍历的话,查找的时间是不固定的。而且,如果当优先级不只8个,比如32个的话,假设最高优先级是最后一维,那么如此将做32次的循环,这是非常浪费时间的。为此,我们为每一个根节点添加一个标志位,指示这个根节点是否为空。所以我们可以根据这个标志位快速查找到挂有最高优先级任务的根节点,不需要去查询根节点是否为空。所以暂定优先级的个数小于等于8,我们就恶意用一个byte去描述这个就绪表的,这个byte的每一个bit作为每一个优先级根节点的标志位,0代表空,1代表非空。然后,查询任务的最高优先级的我们如果想要简单可以直接用一个for循环遍历,从头开始查询每一个bit,但是这样做仍然太过于占用时间,如果我没记错,freertos所使用的方法类似,都是遍历。我们可以参考ucos的方法,建立一个优先级的方向查找表。方法如下:
假设我们定义就绪表的最高优先级由标志为1的最低位决定,也就是说从0~7,只要发现第一个为1的位,那么所对应的就是就绪表中的最高优先级了,其他的位我们都i可以不考虑。举个例子:假设这个Flag的数值位0b00000011,也就是3的时候,为1的最低位是0,所以其优先级是0;假设这个Flag的数值位0b00000101,也就是5的时候,为1的最低位是0,所以其优先级是0;假设这个Flag的数值位0b11111110,也就是254的时候,为1的最低位是1,所以其优先级是1。所以我们可以根据这个Flag的值,最为数组的下标,将优先级作为数组的元素,建立一个方向查找表,8位的话对应256种情况,如下:
1constunsignedcharcaucTaskPrioUnmapTab[256]=/*优先级反向查找表*/ 2{ 30,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 44,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 55,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 64,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 76,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 84,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 95,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 104,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 117,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 124,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 135,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 144,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 156,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 164,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 175,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0, 184,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0 19};
如果,我们想要知道当前任务就绪表的最高优先级,只要将就绪表的标志输入查找表,即可得出其最高优先级的任务,这是OS_GetHighestTaskPrio(unsignedchartaskPrioFlag)这个函数所做的事情。
综上所述,为了更好地实现模块化地封装,我们的任务就绪表就应该扩展成一个结构体,如下所示:
1typedefstructtaskReadytab 2{ 3OSLISTreadyList[5];/*各个优先级的根节点*/ 4unsignedcharprioFlag;/*优先级标志位*/ 5}TASKREADYTAB; 6 7TASKREADYTABosTaskReadyTab;
而我们的任务TCB相应地也要进行扩展,添加一些架构帮助我们实现功能。如下所示:
1typedefstructos_tcb 2{ 3unsignedint*pstrStack;/*任务堆栈指针*/ 4unsignedchartaskPrio;/*任务优先级*/ 5OSLISTqueReadyTab;/*就绪队列的链表*/ 6}TCB;
我们的任务构建函数也将添加一些新的功能,代码如下:比较重要的就是我们在初始化后,将任务的TCB的地址传给了我们这个TCB的结构体的就绪表节点的指针queReadyTab.vPstrTcb,所以我们后面可以直接通过我们的节点里的这个指针访问到我们的TCB,这是一个很重要的“回调”功能。
1voidTask_End(void) 2{ 3while(1) 4{ 5; 6} 7} 8 9/* 10参数1:任务的TCB 11参数2:任务执行函数 12参数3:传入的任务栈的栈顶地址 13*/ 14voidvTaskCreate(TCB*tcb,void(*task)(void*),unsignedint*stack,unsignedchartaskPrio,void*pvPara) 15{ 16OSLIST*pstrList; 17OSLIST*pstrNode; 18 19/*tcb的stack初始化*/ 20tcb->pstrStack=vTaskStackInit(task,stack,pvPara); 21tcb->taskPrio=taskPrio; 22tcb->queReadyTab.vPstrTcb=tcb; 23 24pstrList=&osTaskReadyTab.readyList[tcb->taskPrio]; 25pstrNode=&tcb->queReadyTab; 26 27/*将任务就绪表的任务优先级标志位置一*/ 28OS_SetReadyTabPrioFlag(taskPrio); 29/*将节点添加到相应的链表里面*/ 30OS_DlistNodeAdd(pstrList,pstrNode); 31} 32 33/* 34参数1:任务执行函数 35参数2:传入的任务栈的栈顶地址 36*/ 37unsignedint*vTaskStackInit(void(*task)(void*),unsignedint*stack,void*pvPara) 38{ 39unsignedint*pstrStack; 40pstrStack=stack; 41pstrStack=(unsignedint*)((unsignedint)(pstrStack)&0xfffffff8u);/*8字节对齐*/ 42*(--pstrStack)=(unsignedint)0x01000000ul;/*XPSR为空状态*/ 43*(--pstrStack)=(unsignedint)task;/*r15*/ 44*(--pstrStack)=(unsignedint)Task_End;/*r14*/ 45*(--pstrStack)=(unsignedint)0x12121212ul;/*r12*/ 46*(--pstrStack)=(unsignedint)0x03030303ul;/*r3*/ 47*(--pstrStack)=(unsignedint)0x02020202ul;/*r2*/ 48*(--pstrStack)=(unsignedint)0x01010101ul;/*r1*/ 49*(--pstrStack)=(unsignedint)pvPara;/*r0*/ 50 51*(--pstrStack)=(unsignedint)0x11111111ul;/*r11*/ 52*(--pstrStack)=(unsignedint)0x10101010ul;/*r10*/ 53*(--pstrStack)=(unsignedint)0x09090909ul;/*r9*/ 54*(--pstrStack)=(unsignedint)0x08080808ul;/*r8*/ 55*(--pstrStack)=(unsignedint)0x07070707ul;/*r7*/ 56*(--pstrStack)=(unsignedint)0x06060606ul;/*r6*/ 57*(--pstrStack)=(unsignedint)0x05050505ul;/*r5*/ 58*(--pstrStack)=(unsignedint)0x04040404ul;/*r4*/ 59 60returnpstrStack; 61}
3.任务切换
任务的切换,是这里的重点,这里实现的是根据任务就绪表进行切换和相同优先级任务的时间轮询。代码如下:
为了实现一个相同优先级任务的时间片轮询,我在这里偷了一个懒,我直接定义了一个数组,保存当前的各个优先级任务它所运行的时间,也就是一个数组:
unsignedintosTaskTimeSlice[TASKPRIONUM];/*各个任务的时间片表*/
每个数组元素代表运行了的时间,所以只要一个if判断即可简单实现时间片轮询。然后实现任务切换我们的流程就是检查就绪表,查找里面的最高优先级任务,然后找到这个最高优先级的任务节点,通过节点的指针找到其TCB,将TCB赋值给OSTCBHighRdyPtr;判断时间片是否用尽,用尽则检查下后面是否还有任务,有的话重新获取TCB,没有则只清零时间片;调用OS_CtxSw()触发pendSV中断,继而实现任务切换。注意,任务就绪表里必须保证每个时候都有任务可以执行,否则会出问题,所以最低优先级任务是idle任务。
顺便讲一个很好玩的做法,也如果我们使用freertos,在中断里循环使用vTaskDelay函数将任务删除后添加进延时表进行延时,当我们最后将ldle任务也删除了之后,下一次查找最高优先级任务的时候就会发现找不到,因为freertos是通过某个变量自加向下查找所以最后就会溢出然后程序就崩了。所以一定要注意在中断里面使用这些函数。
83/*********************************************************************************** 84函数功能:查找就绪表里的最高优先级任务,并根据时间片轮询,触发pendSV中断实现任务切换. 85入口参数:none. 86返回值:none. 87***********************************************************************************/ 88voidOS_TaskSwitch(void) 89{ 90unsignedcharhighestTaskPrio; 91OSLIST*pstrNode; 92OSLIST*pstrNextNode; 93TCB*pstrTask; 94/*获取就绪表里任务的最高优先级任务的优先级*/ 95highestTaskPrio=OS_GetHighestTaskPrio(osTaskReadyTab.prioFlag); 96/*查询就绪表中最高优先级的节点指的下一个节点是否为空,如果非空说明有任务需要执行,就获取其TCB节点,然后赋值给OSTCBHighRdyPtr*/ 97pstrNode=OS_DlistEmpInq(&osTaskReadyTab.readyList[highestTaskPrio]); 98pstrTask=(TCB*)pstrNode->vPstrTcb; 99OSTCBHighRdyPtr=pstrTask; 100/*该优先级的任务运行时间片自加,进行统计,并判断是否大于阈值,如果大于则查找该任务节点后是否有下一个任务,如果有,则切换,实现想同优先级时间片轮询*/ 101osTaskTimeSlice[highestTaskPrio]++; 102if(osTaskTimeSlice[highestTaskPrio]>=TASKSLICECNT)/*时间片用尽,任务切换*/ 103{ 104osTaskTimeSlice[highestTaskPrio]=0;/*时间片清零*/ 105pstrNextNode=OS_DlistNextNodeEmpInq(&osTaskReadyTab.readyList[highestTaskPrio],pstrNode); 106if(NULL!=pstrNextNode) 107{ 108OS_DlistNodeDelete(&osTaskReadyTab.readyList[highestTaskPrio]); 109OS_DlistNodeAdd(&osTaskReadyTab.readyList[highestTaskPrio],pstrNode); 110pstrTask=(TCB*)pstrNextNode->vPstrTcb; 111OSTCBHighRdyPtr=pstrTask; 112} 113} 114OS_CtxSw();/*任务切换*/ 115}
4.最后这个函数怎么调用??
我们配置滴答定时器为每一个TICK(ms)进入一次中断,此时调用这个任务切换函数即可。代码如下:
1/** 2*@briefThisfunctionhandlesSysTickHandler. 3*@paramNone 4*@retvalNone 5*/ 6voidSysTick_Handler(void) 7{ 8OS_TaskSwitch(); 9}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
三、测试程序的主函数
#include"stm32f10x.h"
#include"bsp_usart1.h"
#include"OS.h"
#include"os_task.h"
TCBtcb_task1,tcb_task2,tcb_task3;
unsignedintTASK_STK1[128],TASK_STK2[128],TASK_STK3[128];
intmutex_flag=0;/*自旋锁*/
/*
voidtaskSwitch(void)
{
if(OSTCBCurPtr==&tcb_task1)
OSTCBHighRdyPtr=&tcb_task2;
else
OSTCBHighRdyPtr=&tcb_task1;
OS_CtxSw();
}
*/
voidtask1(void*pvPara)
{
while(1)
{
while(mutex_flag==0)
{
printf("%s\n",(char*)pvPara);
mutex_flag=1;
}
}
}
voidtask2(void*pvPara)
{
while(1)
{
while(mutex_flag==1)
{
printf("%s\n",(char*)pvPara);
mutex_flag=2;
}
}
}
voidtask3(void*pvPara)
{
while(1)
{
while(mutex_flag==2)
{
printf("%s\n",(char*)pvPara);
mutex_flag=0;
}
}
}
/**
*@brief主函数
*@param无
*@retval无
*/
intmain(void)
{
/*USART1config1152008-N-1*/
USART1_Config();
OS_SysTickTimerInit();
OS_TaskReadyTabInit();
vTaskCreate(&tcb_task1,task1,&TASK_STK1[128-1],1,(void*)"task-1running");
vTaskCreate(&tcb_task2,task2,&TASK_STK2[128-1],1,(void*)"task-2running");
vTaskCreate(&tcb_task3,task3,&TASK_STK3[128-1],1,(void*)"task-3running");
OSTCBHighRdyPtr=&tcb_task3;
OSStartHighRdy();
}
/*********************************************ENDOFFILE**********************/
测试效果如下:
相关文章推荐
- 从头开始编写一个实时嵌入式操作系统的内核(一)
- 【从头开始写操作系统系列】实现一个 GDT(3)
- 如何编写一个最简单的嵌入式操作系统(1)简单任务调度
- 如何编写一个简单的嵌入式操作系统 (2)时间片轮转
- 嵌入式实时操作系统µC/OS-II内核结构简介
- 从头开始编写一个android后台服务器
- 编写一个操作系统内核
- 嵌入式操作系统内核原理和开发(实时系统中的定时器)
- 非常好!!!【从头开始写操作系统系列】实现一个-GDT(1)【转】
- 【翻译】从头开始编写一个Orchard网上商店模块(1) - 介绍
- 如何编写一个简单的嵌入式操作系统 (2)时间片轮转
- 嵌入式操作系统内核原理和开发(实时调度)
- 从头开始编写操作系统
- 从头开始构建一个嵌入式 Linux 发行版
- 嵌入式实时操作系统µC/OS-II内核结构简介
- 从头开始构建一个嵌入式 Linux 发行版
- 从头开始编写一个Orchard网上商店模块(1) - 介绍
- 如何编写一个最简单的嵌入式操作系统(1)简单任务调度
- 从头开始构建一个嵌入式 Linux 发行版
- 【从头开始写操作系统系列】实现一个-GDT(2)