タスク切り替えおよびタスク切り替えアセンブリコードの分析中にuCOSは何をしますか

最も簡単なタスク切り替え機能の説明を通じて、プロジェクトは「[野火®]」「uCOS-IIIカーネル実装およびアプリケーション開発実践ガイド-STM32に基づく」の第5章のプロジェクトを使用します。下記のアドレスは、自分で作成した場合と異なる場合があります。
まず、いくつかのタスク関連のグローバル変数について説明します。

#define  TASK1_STK_SIZE       20
#define  TASK2_STK_SIZE       20

static   CPU_STK   Task1Stk[TASK1_STK_SIZE]; //Task1Stk[0]地址为0x20000028;Task1Stk[TASK1_STK_SIZE]地址为0x20000078
static   CPU_STK   Task2Stk[TASK2_STK_SIZE];//Task2Stk[0]地址为0x20000078;Task2Stk[TASK2_STK_SIZE]地址为0x200000c8

static   OS_TCB    Task1TCB;//Task1TCB的地址为0x20000008
static   OS_TCB    Task2TCB;//Task2TCB的地址为0x20000010

void     Task1( void *p_arg );//任务入口地址为0x00000401
void     Task2( void *p_arg );//任务入口地址为0x00000492

次に、いくつかのオペレーティングシステム関連のグローバル変数:

OS_EXT OS_TCB *OSTCBCurPtr; //OSTCBCurPtr的地址为0x20000018
OS_EXT OS_TCB *OSTCBHighRdyPtr;//OSTCBHighRdyPtr的地址为0x2000001c
OS_EXT OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX];//OSRdyList[0]的地址为0x200000c8;OSRdyList[0]的地址为0x200000d0
OS_EXT OS_STATE OSRunning;//不关注

タスクが作成されたときに何が起こりましたか?(注:上記のグローバル変数のアドレスを紙に書いてください。後で分析しやすくなります)

OSTaskCreate ((OS_TCB*)      &Task1TCB, 
	              (OS_TASK_PTR ) Task1, 
	              (void *)       0,
	              (CPU_STK*)     &Task1Stk[0],
	              (CPU_STK_SIZE) TASK1_STK_SIZE,
	              (OS_ERR *)     &err);

このOSTaskCreateタスク作成関数の後、Task1TCB.StkPtrはTask1アイドルスタックの最上位を指し、Task1TCB.StkSizeはレコードスタックのサイズです。次の詳細な分析:

void OSTaskCreate(OS_TCB *p_tcb,
	              OS_TASK_PTR p_task,
                   void          *p_arg,
                   CPU_STK       *p_stk_base,
                   CPU_STK_SIZE  stk_size,
                   OS_ERR        *p_err)
{
    
    
	CPU_STK *p_sp;
	p_sp = OSTaskStkInit(p_task,p_arg,p_stk_base,stk_size);//返回值就是剩下空闲栈的栈顶
	p_tcb ->StkPtr = p_sp;//让Task1TCB.StkPtr指向剩下空闲栈的栈顶
	p_tcb->StkSize = stk_size;//Task1TCB.StkSize记录栈的大小
	*p_err = OS_ERR_NONE;
}

次に、OSTaskStkInit()関数を分析します。

CPU_STK *OSTaskStkInit (OS_TASK_PTR  p_task,
                        void         *p_arg,
                        CPU_STK      *p_stk_base,
                        CPU_STK_SIZE stk_size)
{
    
    
	CPU_STK  *p_stk;

	p_stk = &p_stk_base[stk_size];
	/* 异常发生时自动保存的寄存器*/
	*--p_stk = (CPU_STK)0x01000000u;/* xPSR的bit24必须置1*/
	*--p_stk = (CPU_STK)p_task;     /* 任务的入口地址*/
	*--p_stk = (CPU_STK)0x14141414u;/* R14 (LR)*/
	*--p_stk = (CPU_STK)0x12121212u;/* R12*/
	*--p_stk = (CPU_STK)0x03030303u;/* R3*/
	*--p_stk = (CPU_STK)0x02020202u;/* R2*/
	*--p_stk = (CPU_STK)0x01010101u;/* R1*/
	*--p_stk = (CPU_STK)p_arg;      /* R0 : 任务形参*/
	/* 异常发生时需手动保存的寄存器*/
	*--p_stk = (CPU_STK)0x11111111u;/* R11*/
	*--p_stk = (CPU_STK)0x10101010u;/* R10*/
	*--p_stk = (CPU_STK)0x09090909u;/* R9*/
	*--p_stk = (CPU_STK)0x08080808u;/* R8*/
	*--p_stk = (CPU_STK)0x07070707u;/* R7*/
	*--p_stk = (CPU_STK)0x06060606u;/* R6*/
	*--p_stk = (CPU_STK)0x05050505u;/* R5*/
	*--p_stk = (CPU_STK)0x04040404u;/* R4*/
	return (p_stk);
}

自動保存および手動保存されるレジスタには、イメージが必要です。
前のOSTaskCreate()タスク作成関数に戻ります。この関数を使用してタスクを作成した後、Task1TCB.StkPtrはTask1アイドルスタックの最上位を指します。Task1TCB.StkSizeは、記録スタックのサイズです(繰り返します)
。タスク1とタスク2。スタックスペースは次のようになります。

//Task1Stk空间
0x20000078:&Task1Stk[TASK1_STK_SIZE];堆栈的栈顶,记住这个地址

0x20000074:0x01000000u;/* xPSR的bit24必须置1*/
0x20000070:0x00000401  /* 任务的入口地址*/
0x2000006c:0x14141414u;/* R14 (LR)*/
0x20000068:0x12121212u;/* R12*/
0x20000064:0x03030303u;/* R3*/
0x20000060:0x02020202u;/* R2*/
0x2000005c:0x01010101u;/* R1*/
0x20000058:p_arg;      /* R0 : 任务形参*/需要保存的R4-R11寄存器的栈顶,记住这个值

0x20000054:0x11111111u;/* R11*/
0x20000050:0x10101010u;/* R10*/
0x2000004c:0x09090909u;/* R9*/
0x20000048:0x08080808u;/* R8*/
0x20000044:0x07070707u;/* R7*/
0x20000040:0x06060606u;/* R6*/
0x2000003c:0x05050505u;/* R5*/
0x20000038:0x04040404u;/* R4*/空闲栈的栈顶,记住这个值
0x20000034:
0x20000030:
0x2000002c:
0x20000028:
//Task2Stk空间
0x200000C8:&Task2Stk[TASK2_STK_SIZE];堆栈的栈顶,记住这个地址

0x200000C4:0x01000000u;/* xPSR的bit24必须置1*/
0x200000C0:0x00000492  /* 任务的入口地址*/
0x200000Bc:0x14141414u;/* R14 (LR)*/
0x200000B8:0x12121212u;/* R12*/
0x200000B4:0x03030303u;/* R3*/
0x200000B0:0x02020202u;/* R2*/
0x200000Ac:0x01010101u;/* R1*/
0x200000A8:p_arg;      /* R0 : 任务形参*/需要保存的R4-R11寄存器的栈顶,记住这个值

0x200000A4:0x11111111u;/* R11*/
0x200000A0:0x10101010u;/* R10*/
0x2000009c:0x09090909u;/* R9*/
0x20000098:0x08080808u;/* R8*/
0x20000094:0x07070707u;/* R7*/
0x20000090:0x06060606u;/* R6*/
0x2000008c:0x05050505u;/* R5*/
0x20000088:0x04040404u;/* R4*/空闲栈的栈顶,记住这个值
0x20000084:
0x20000080:
0x2000007c:
0x20000078:

つまり
、Task1TCB.StkPtrはタスク作成関数の後で0x20000038を指し、それ自体のアドレスは0x20000008です
。Task2TCB.StkPtrはタスク作成関数の後で0x20000088を指します。それ自体のアドレスは0x20000010です。

OSRdyList[0].HeadPtr = &Task1TCB;//指向Task1TCB(0x20000008),OSRdyList[0]本身地址为0x200000c8
OSRdyList[1].HeadPtr = &Task2TCB;//指向Task2TCB(0x20000010),OSRdyList[1]本身地址为0x200000D0

次に、以下に示すように、タスクを開始します。

void OSStart(OS_ERR *p_err)
{
    
    
	if( OSRunning == OS_STATE_OS_STOPPED )
	{
    
    
		OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
		//OSTCBHighRdyPtr 将指向&Task1TCB(0x20000008),本身的地址为0x2000001c
		//OSTCBCurPtr 指向(OS_TCB*)0,本身的地址为0x20000018
		/* 启动任务切换,不会返回 */
		OSStartHighRdy();
		/* 不会运行到这里,运行到这里表示发生了致命的错误 */
		*p_err = OS_ERR_FATAL_RETURN;
	}
	else
	{
    
    
		*p_err = OS_STATE_OS_RUNNING;
	}
}

OSStartHighRdy()関数の分析は次のとおりです。

OSStartHighRdy
	LDR		R0, = NVIC_SYSPRI14              ; 设置  PendSV 异常优先级为最低
	;此时寄存器R0的为 NVIC_SYSPRI14( 0xE000ED22
	LDR     R1, = NVIC_PENDSV_PRI
	;此时寄存器R1为 NVIC_PENDSV_PRI( 0x000000FF
	STRB    R1, [R0]
	;R1中的值作为变量,R0中的值作为地址,然后将R1写入NVIC_SYSPRI14(0xE000ED22)地址中
	;即成功设置了 PendSV 异常优先级为最低
	
	MOVS    R0, #0                           ; 设置psp的值为0,开始第一次上下文切换
	;此时R0寄存器为0
	MSR     PSP, R0
	;将寄存器R0中的值移入PSP寄存器当中,此时PSP寄存器中的值为0
	
	LDR     R0, =NVIC_INT_CTRL               ; 触发PendSV异常
	;此时寄存器R0中的值为 NVIC_INT_CTRL(0xE000ED04)
	LDR     R1, =NVIC_PENDSVSET
	;此时寄存器R1中的值为 NVIC_PENDSVSET(0x10000000)
	STR     R1, [R0]
	;将R1中的值作为变量,R0中的值作为地址,然后将R1写入NVIC_INT_CTRL(0xE000ED04)地址中
	;即触发PendSV异常
	
	CPSIE   I                                 ; 开中断
	;打开中断,将运行到PendSV中断服务函数
OSStartHang
	B       OSStartHang                       ; 程序应永远不会运行到这里	

LDRは、メモリからレジスタにワードをロードすることを意味します
。STRBは、レジスタの下位バイトをメモリ(つまりアドレス)に
格納
します
。STRレジスタをワードごとにメモリ(つまりアドレス)に格納します。B関数PendSV割り込みサービスにジャンプします。機能は次のとおりです:(その中で、ステートメントの右側のコメントは野火のコメントであり、ステートメントの下のコメントは私の理解です)

PendSV_Handler
; 任务的保存,即把CPU寄存器的值存储到任务的堆栈中	
	CPSID   I                                 ; 关中断,NMI和HardFault除外,防止上下文切换被中断	
	MRS     R0, PSP                           ; 将psp的值加载到R0
	CBZ     R0, OS_CPU_PendSVHandler_nosave   ; 判断R0,如果值为0则跳转到OS_CPU_PendSVHandler_nosave
	;第一次进入,此时PSP的值为0,所以直接跳转到OS_CPU_PendSVHandler_nosave函数执行
	                                          ; 进行第一次任务切换的时候,R0肯定为0
	
	/*; 在进入PendSV异常的时候,当前CPU的xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0会自动存储到当前任务堆栈,同时递减PSP的值
	STMDB   R0!, {R4-R11}                     ; 手动存储CPU寄存器R4-R11的值到当前任务的堆栈
	
	LDR     R1, = OSTCBCurPtr                 ; 加载 OSTCBCurPtr 指针的地址到R1,这里LDR属于伪指令
	LDR     R1, [R1]                          ; 加载 OSTCBCurPtr 指针到R1,这里LDR属于ARM指令
	STR     R0, [R1]                          ; 存储R0的值到	OSTCBCurPtr->OSTCBStkPtr,这个时候R0存的是任务空闲栈的栈顶*/

; 任务的切换,即把下一个要运行的任务的堆栈内容加载到CPU寄存器中
OS_CPU_PendSVHandler_nosave  
	                                    ; OSTCBCurPtr = OSTCBHighRdyPtr;
	LDR     R0, = OSTCBCurPtr           
	;此时寄存器R0为&OSTCBCurPtr(0x20000018),我把它理解为R0指向OSTCBCurPtr;2级指针?
	LDR     R1, = OSTCBHighRdyPtr       
	;此时寄存器R1为&OSTCBHighRdyPtr(0x2000001C),我把它理解为R1指向OSTCBHighRdyPtr
	LDR     R2, [R1]                         
	;取R1寄存器所指向的地址中的值,赋值给R2;
	;此时,R1指向&OSTCBHighRdyPtr指向&Task1TCB(0x20000008),所以R2为&Task1TCB(0x20000008);即R2指向&Task1TCB
	STR     R2, [R0]                 
	;将R2中的值,存储到R0所指向的地址中,即将&Task1TCB(0x20000008)写入R0所指向的地址&OSTCBCurPtr(0x20000018)
	;即之后R0指向OSTCBCurPtr作为指针指向Task1TCB,也就是OSTCBCurPtr=&Task1TCB
	;此时R2中的地址依旧为&Task1TCB(0x20000008)     
	
	LDR     R0, [R2]
	;取出R2中地址中存放的值,加载到R0
	;此时R2中的地址依旧为&Task1TCB(0x20000008),即取出&Task1TCB(0x20000008)中的值加载到R0
	;Task1TCB(0x20000008)地址中的值为Task1TCB.StkPtr(0x20000038)(不加&表示指向的地址),也就是Task1Stk堆栈的空闲栈的栈顶
	;即R0指向了Task1Stk堆栈的空闲栈的栈顶Task1TCB.StkPtr(0x20000038)                          
	LDMIA   R0!, {
    
    R4-R11}                     
	;将R0地址中的内容出栈,4字节自增依次加载到R4-R11寄存器中
	;此时R0指向地址0x20000058,之后的内容就是退出中断会自动加载到寄存器中
	;但是出栈指针不是R0,而是PSP,所以后面还要把R0存储的值赋值给堆栈指针PSP,让PSP指向那个地址(0x20000058)
	
	MSR     PSP, R0                           ; 更新PSP的值,这个时候PSP指向下一个要执行的任务的堆栈的栈底(这个栈底已经加上刚刚手动加载到CPU寄存器R4-R11的偏移)
	ORR     LR, LR, #0x04                     ; 确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1
	CPSIE   I                                 ; 开中断
	BX      LR                                ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
	                                          ; 同时PSP的值也将更新,即指向任务堆栈的栈顶。在STM32中,堆栈是由高地址向低地址生长的。
	;进行BX这一步之后,会按照跳转加载到xPSR,PC(任务入口地址),R14......寄存器中的PC指针的函数去运行,也就是Task1任务
	;此时,堆栈指针PSP指向0x20000078(即Task1Stk堆栈的栈顶),等待下次触发PendSV中断
	;自动将xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0寄存器中的值压入 Task1Stk堆栈数组中   
	
	NOP                                       ; 为了汇编指令对齐,不然会有警告
	
	
	END                                       ; 汇编文件结束

次に、次のようにTask1タスクを実行します。

void Task1( void *p_arg )
{
    
    
	for( ;; )
	{
    
    
		flag1 = 1;
		delay( 100 );		
		flag1 = 0;
		delay( 100 );
		
		/* 任务切换,这里是手动切换 */		
		OSSched();
	}
}

その後、OSSched()を実行してタスク切り替えを行います。

void OSSched(void)
{
    
    
	if(OSTCBCurPtr == OSRdyList[0].HeadPtr)
	{
    
    
		OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
	}
	else
	{
    
    
		OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
	}
	//到了这一步之后OSTCBCurPtr =&Task1TCB(0x20000008);
	//OSTCBHighRdyPtr  = &Task2TCB(0x20000010);
	//PSP指针指向0x20000078(即Task1Stk堆栈的栈顶)
	OS_TASK_SW();
}

PendSV割り込みに入った後、xPSR、PC(タスクエントリアドレス)、R14、R12、R3、R2、R1、R0は、PSPが指すアドレスに従って自動的にスタックにプッシュされ、同時にPSPがデクリメントされます。 、および自動的にプッシュされたすべてのレジスタがすべてスタックにプッシュされるまで待機します。その後、PSPは0x20000058を指し、R4-R11レジスタのスタックトップ位置を手動で保存する必要があります。次のコードを参照してください。

PendSV_Handler
; 任务的保存,即把CPU寄存器的值存储到任务的堆栈中	
	CPSID   I                                 ; 关中断,NMI和HardFault除外,防止上下文切换被中断	
	MRS     R0, PSP                           ; 将psp的值加载到R0
	;PSP指向0x20000058,经过这一步之后,R0指向0x20000058
	CBZ     R0, OS_CPU_PendSVHandler_nosave   ; 判断R0,如果值为0则跳转到OS_CPU_PendSVHandler_nosave
	;顺序执行,不跳转到OS_CPU_PendSVHandler_nosave   
	                                          
	
	; 在进入PendSV异常的时候,当前CPU的xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0会自动存储到当前任务堆栈,同时递减PSP的值
	STMDB   R0!, {
    
    R4-R11}                     ; 手动存储CPU寄存器R4-R11的值到当前任务的堆栈
	;{
    
    R4-R11}寄存器的值手动压入0x20000058指向的地址中,即保存到Task1的堆栈中
	;同时R0寄存器中的值递减,全部入栈之后R0中的值为0x20000038,即指向空闲栈的栈顶
	
	LDR     R1, = OSTCBCurPtr                 
	;R1寄存器指向&OSTCBCurPtr(0x20000018),而OSTCBCurPtr =&Task1TCB(0x20000008)
	LDR     R1, [R1]                          
	;将R1指向的地址中的值拿出来,再赋值给R1,即将&OSTCBCurPtr(0x20000018)中的值Task1TCB(0x20000008)
	;拿出来,再赋值给R1
	STR     R0, [R1]                          
	;将R0寄存器中的值存储到到R1所指向的地址Task1TCB(0x20000008)
	;即让Task1TCB.StkPtr指向0x20000038,即指向空闲栈的栈顶

; 任务的切换,即把下一个要运行的任务的堆栈内容加载到CPU寄存器中
OS_CPU_PendSVHandler_nosave  
	; OSTCBCurPtr = OSTCBHighRdyPtr;
	LDR     R0, = OSTCBCurPtr                 
	;R0指向&OSTCBCurPtr(0x20000018)
	LDR     R1, = OSTCBHighRdyPtr             
	;R1指向&OSTCBHighRdyPtr(0x2000001C)
	LDR     R2, [R1]                          
	;将R1所指向的地址中的内容赋值到R2,即将OSTCBHighRdyPtr  = &Task2TCB(0x20000010);&Task2TCB(0x20000010)赋值给寄存器R2
	STR     R2, [R0]                          
	;将R2中的值加载到R0所指向的地址中,即让OSTCBCurPtr = &Task2TCB(0x20000010)
	
	LDR     R0, [R2]                          
	;将R2所指向的地址中的内容赋值给R2,即R2指向&Task2TCB(0x20000010)指向空闲栈的栈顶,
	;参考上面保存上文时空闲栈的栈顶保存在任务控制块的地址中
	;也就是说将&Task2TCB(0x20000010)地址中的内容0x20000088地址赋值给R0
	LDMIA   R0!, {
    
    R4-R11}                    
	;将R0所指向的地址0x20000088中的内容出栈到R4-R11寄存器中,同时,R0所指向的地址将同步递增
	;全部出栈之后,R0所指向的地址为0x200000A8,即自动出栈的栈底
	
	MSR     PSP, R0                           
	;将R0所指向的地址赋值给PSP,当退出中断时,将从此地址中自动出栈,PSP的值同步递增,等待下一次任务切换时
	;将自动压入堆栈的寄存器中的值自动压入PSP地址中
	ORR     LR, LR, #0x04                     ; 确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1
	CPSIE   I                                 ; 开中断
	BX      LR                                ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
	                                          ; 同时PSP的值也将更新,即指向任务堆栈的栈顶。在STM32中,堆栈是由高地址向低地址生长的。
	
	NOP                                       ; 为了汇编指令对齐,不然会有警告
	
	
	END                                       ; 汇编文件结束

この処理の後、タスクの切り替え時にタスクの現在の実行環境が保存され、次のタスクの実行環境が読み込まれるため、タスクの切り替えが実現されます。

おすすめ

転載: blog.csdn.net/qq_40831436/article/details/114096471