任務控制塊數據結構
任務控制塊數據結構在task.c
聲明
typedef struct tskTaskControlBlock
{
volatile StackType_t * pxTopOfStack; //棧頂指針
ListItem_t xStateListItem; //任務節點
StackType_t * pxStack; //任務棧起始地址
char pcTaskName[configMAX_TASK_NAME_LEN];//任務名稱
}tskTCB;
任務創建函數
下面是用靜態方式創建任務的函數
static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,/*任務函數*/
const char * const pcName,/*任務名稱*/
const uint32_t ulStackDepth,/*任務棧大小,單位字*/
void * const pvParameters, /*任務形參*/
TaskHandle_t* const pxCreatedTask,/*任務句柄*/
TCB_t * pxNewTCB) /*任務控制塊指針*/
{
StackType_t * pxTopOfStack;
UBaseType_t x;
//棧頂地址
pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t) 1);
//項下做8字節對齊
pxTopOfStack = (StackType_t*) ( (uint32_t)pxTopOfStack & (~(uint32_t)0x0007) );
//存儲名字
for(x=(UBaseType_t)0;x<(UBaseType_t)configMAX_TASK_NAME_LEN;x++)
{
pxNewTCB->pcTaskName[x] = pcName[x];
if(pcName[x]=='\0')
break;
}
pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN-1] = '\0';
//初始化TCB中的xStateListItem列表項
vListInitialiseItem(& (pxNewTCB->xStateListItem));
//設置xStateListItem列表項的擁有者即爲傳入的TCB
listSET_LIST_ITEM_OWNER( &(pxNewTCB->xStateListItem), pxNewTCB );
//初始化任務棧,並返回更新的棧頂指針
pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,pxTaskCode,pvParameters);
//返回任務句柄
if((void*)pxCreatedTask !=NULL )
{
*pxCreatedTask = (TaskHandle_t) pxNewTCB;
}
}
下面看pxPortInitialiseStack
是怎麼初始化任務棧的
因爲是向下生長的滿棧,所以是--
操作
#define portINITIAL_XPSR (0x01000000)
#define portSTART_ADDRESS_MASK ((StackType_t)0xfffffffeUL)
StackType_t * pxPortInitialiseStack(StackType_t* pxTopOfStack, TaskFunction_t pxCode, void*pvParameters)
{
//-------------設置內核會自動加載的寄存器,且順序不能變
pxTopOfStack--;
*pxTopOfStack = portINITIAL_XPSR;//xPSR bit24
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;//R15 PC
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError;//R14 LR
pxTopOfStack -= 5; //R12 R3 R2 R1
*pxTopOfStack = ( StackType_t ) pvParameters;//R0
//--------------下面8個需要手動保存
pxTopOfStack-=8;
//返回更新後的棧頂指針
return pxTopOfStack;
}
如下圖
定義就緒表
在task.c
中,就緒表就是列表類型的數組,元素個數是configMAX_PRIORITIES
,就緒表把同一優先級的任務插入到同一優先級的鏈表中,數組下標表示優先級,如下圖
#define configMAX_PRIORITIES 5
List_t pxReadyTasksLists[configMAX_PRIORITIES];
就緒表初始化
就緒表初始化就是把數組每個元素(即列表)初始化(即調用vListInitialise),結果如下
void prvInitialiseTaskLists(void)
{
UBaseType_t uxPriority;
for(uxPriority = (UBaseType_t)0 ;
uxPriority < (UBaseType_t) configMAX_PRIORITIES;
uxPriority ++)
{
vListInitialise(&(pxReadyTasksLists[uxPriority]));
}
}
啓動調度器
調用關係,這裏手動指定第一個運行的任務
vTaskStartScheduler->xPortStartScheduler->prvStartFirstTask
void vTaskStartScheduler(void)
{
//手動指定一個和要運行的任務
pxCurrentTCB = &Task1TCB;
//啓動調度器
if(xPortStartScheduler()!=pdFALSE)
{
//啓動成功不會跑到這裏
}
}
//stm32 用4bit表示優先級,所以最低優先級則是15
#define configKERNEL_INTERRUPT_PRIORITY 15
#define portNVIC_SYSPRI2_REG (*((volatile uint32_t*) 0xe000ed20))
#define portNVIC_PENDSV_PRI (((uint32_t)configKERNEL_INTERRUPT_PRIORITY)<<16UL)
#define portNVIC_SYSTICK_PRI (((uint32_t)configKERNEL_INTERRUPT_PRIORITY)<<24UL)
BaseType_t xPortStartScheduler(void)
{
//把pendsv systick中斷優先級設置爲最低
portNVIC_SYSPRI2_REG |=portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |=portNVIC_SYSTICK_PRI;
//啓動第一個任務,該函數不會返回
prvStartFirstTask();
//不會運行到這裏
return 0;
}
在CM3中,0xE000ED08存放的是SCB_VTOR寄存器地址,SCB_VTOR是向量表的起始地址,即MSP的地址
__asm void prvStartFirstTask(void)
{
PRESERVE8
ldr r0, =0xE000ED08 //r0=0xE000ED08,相當於r0=&SCB_VTOR
ldr r0, [r0] //r0=*r0,即r0=*((uint32_t*)0xE000ED08),即r0=0x0
ldr r0, [r0] //r0=*r0,即r0=*((uint32_t*)0x0),即r0=msp指針值
msr msp, r0 //msp=r0,msp指針獲得msp地址
cpsie i //開中斷
cpsie f //開中斷
dsb
isb
svc 0 //觸發SVC系統調用,此後會進入SVCHandler
nop //下面這2個nop不會執行
nop
}
__asm void vPortSVCHandler(void)
{
extern pxCurrentTCB;
PRESERVE8
//r3=&pxCurrentTCB,注意pxCurrentTCB本身就是一個指針,是指向一個任務的TCB指針
ldr r3, =pxCurrentTCB
//r1=*r3,即r1=pxCurrentTCB
ldr r1, [r3]
//r0=*r1,此時r1=pxCurrentTCB,
//又結構體第一個成員(棧頂指針)的地址和結構體地址數值上是相同的,
//所以r0=*(&(pxCurrentTCB.pxTopOfStack)),即r0=pxCurrentTCB.pxTopOfStack,
//即r0此時指向當前任務空閒棧頂位置
ldr r0, [r1]
//以r0爲開始把r4-r11依次保存到當前任務的psp棧
ldmia r0!,{r4-r11}
//更新當前任務psp棧指針
msr psp,r0
isb
mov r0, #0
msr basepri,r0 //開中斷
//此時r14是0xFFFF_FFF9,表示返回後進入Thread mode,使用MSP,返回Thmub狀態,
//這裏其實是把bit2置1,要求返回後使用PSP
orr r14,#0x0d
//返回,這裏會自動加載初始化任務棧填寫的xPSR、R15、R14、R12、R3-R0值到內核對應寄存器,
//所以返回後就直接到pxCurrentTCB指向的任務
bx r14
}
任務切換
這裏手動觸發任務切換,其實就是向中斷控制及狀態寄存器ICSR(地址0xE000_ED04)PENDSVSET位(bit28)寫1懸起pendsv中斷,在pendsv_handler中尋找下一個要運行的任務並做任務切換
#define taskYIELD() portYIELD()
#define portNVIC_INT_CTRL_REG (*((volatile uint32_t*)0xe000ed04))
#define portNVIC_PENDSVSET_BIT (1<<28UL)
#define portSY_FULL_READ_WRITE (15)
#define portYIELD() \
{ \
//觸發一次pendsv中斷
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
__dsb(portSY_FULL_READ_WRITE); \
__isb(portSY_FULL_READ_WRITE); \
}
__asm void xPortPendSVHandler(void)
{
//進入中斷前會自動保存xPSR、R15、R14、R12、R3-R0到當前任務P棧中,此時任務棧頂指針指向要保存R11的位置
extern pxCurrentTCB;
extern vTaskSwitchContext;//這個函數是用來尋找下一個要運行的任務
PRESERVE8
mrs r0,psp //當前任務棧psp指針存入r0,
isb
ldr r3,=pxCurrentTCB //r3=&pxCurrentTCB
ldr r2,[r3] //r2=*r3, 即r2=pxCurrentTCB
//以r0開始遞減的手動保存r4-r11
stmdb r0!,{r4-r11}
//r0=*r2,此時r2=pxCurrentTCB,
//又結構體第一個成員(棧頂指針)的地址和結構體地址數值上是相同的,
//所以r0=*(&(pxCurrentTCB.pxTopOfStack)),即r0=pxCurrentTCB.pxTopOfStack,
//即r0此時指向當前任務空閒棧頂位置
str r0,[r2]
//到這裏上文就保存完成
//將r3、r14壓入棧保存起來(此時sp使用的是msp)
//r14要保存是因爲等下要調用函數,避免r14被覆蓋無法從pendsv中斷正常返回
//r3要保存是因爲r3保存的是當前正在運行任務控制塊的二級指針&pxCurrentTCB,後面要通過r3來操作pxCurrentTCB來切到下文
stmdb sp!,{r3,r14}
//關中斷,閾值是configMAX_SYSCALL_INTERRUPT_PRIORITY
mov r0,#configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri,r0
dsb
isb
//調用vTaskSwitchContext,功能是找到優先級最高的任務,然後讓pxCurrentTCB = &優先級最高任務TCB,我們這裏手動指定
bl vTaskSwitchContext
//開中斷
mov r0,#0
msr basepri,r0
//從msp中恢復r3、r14
ldmia sp!,{r3,r14}
//r1=*r3,此時r3=&pxCurrentTCB
//即r1=pxCurrentTCB
ldr r1,[r3]
//r0=*r1則r0=pxCurrentTCB.pxTopOfStack 理由前面講過
ldr r0,[r1]
//這裏把pxCurrentTCB保存的需要手動加載的值加載到內核的r4-r11
ldmia r0!,{r4-r11}
//更新加載了r4-r11的任務棧到psp
msr psp,r0
isb
//返回,這裏會以psp自動加載保存了xPSR、R15、R14、R12、R3-R0值到內核的xPSR、R15、R14、R12、R3-R0寄存器,所以返回的是pxCurrentTCB任務
bx r14
nop
}
這裏爲了簡單手動指定TCB
extern TCB_t Task1TCB;
extern TCB_t Task2TCB;
void vTaskSwitchContext(void)
{
if(pxCurrentTCB == &Task1TCB)
{
pxCurrentTCB = &Task2TCB;
}
else
{
pxCurrentTCB = &Task1TCB;
}
}
至此任務切換原理講完
main.c
extern List_t pxReadyTasksLists[configMAX_PRIORITIES];
portCHAR flag1;
portCHAR flag2;
TaskHandle_t Task1_Handle;
StackType_t Task1Stack[128];
TCB_t Task1TCB;
TaskHandle_t Task2_Handle;
StackType_t Task2Stack[128];
TCB_t Task2TCB;
void delay(uint32_t x)
{
for(;x!=0;x--);
}
void Task1_Fntry(void *arg)
{
while(1)
{
flag1=1;
delay(100);
flag1=0;
delay(100);
taskYIELD();//手動觸發切換任務
}
}
void Task2_Fntry(void *arg)
{
while(1)
{
flag2=1;
delay(100);
flag2=0;
delay(100);
taskYIELD();//手動觸發切換任務
}
}
int main(void)
{
//初始化就緒列表
prvInitialiseTaskLists();
//創建任務1
Task1_Handle = xTaskCreateStatic(Task1_Fntry,"task1",128,NULL,Task1Stack,&Task1TCB);
//將任務添加到就緒列表
vListInsertEnd(&pxReadyTasksLists[1],&((&Task1TCB)->xStateListItem));
//創建任務2
Task2_Handle = xTaskCreateStatic(Task2_Fntry,"task2",128,NULL,Task2Stack,&Task2TCB);
//將任務添加到就緒列表
vListInsertEnd(&pxReadyTasksLists[2],&((&Task2TCB)->xStateListItem));
//啓動調度器
vTaskStartScheduler();
for(;;);
}
仿真工程
https://download.csdn.net/download/weixin_41572450/12266952