¿Qué hace uCOS durante el cambio de tarea y el análisis del código ensamblador de cambio de tarea?

A través de la explicación más simple de la función de conmutación de tareas, el proyecto utiliza el proyecto del Capítulo 5 de "[野火 ®]" Guía práctica de implementación del kernel y desarrollo de aplicaciones de uCOS-III basada en STM32 ". Las direcciones que se mencionan a continuación pueden ser diferentes cuando las crea usted mismo.
Primero, explique algunas variables globales relacionadas con la tarea:

#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

Luego, varias variables globales relacionadas con el sistema operativo:

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;//不关注

¿Qué sucedió cuando se creó la tarea? (Nota: escriba la dirección de la variable global anterior en una hoja de papel, y será más fácil de analizar más adelante)

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

Después de esta función de creación de tareas OSTaskCreate, Task1TCB.StkPtr apunta a la parte superior de la pila inactiva Task1 y Task1TCB.StkSize es el tamaño de la pila de registros. El siguiente análisis detallado:

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;
}

Luego analice la función 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);
}

Los registros que se guardan automáticamente y se guardan manualmente deben tener una imagen.
Vuelve a la función de creación de tareas OSTaskCreate () anterior. Después de crear la tarea a través de esta función, Task1TCB.StkPtr apunta a la parte superior de la pila inactiva Task1, Task1TCB.StkSize es el tamaño de la pila de grabaciones (repítelo)
mientras está en Tarea 1 y Tarea 2. El espacio de la pila se convierte en el siguiente:

//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:

Entonces:
Task1TCB.StkPtr apunta a 0x20000038 después de la función de creación de la tarea; su propia dirección es 0x20000008;
Task2TCB.StkPtr apunta a 0x20000088 después de la función de creación de la tarea; su propia dirección es 0x20000010;

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

Luego, inicie la tarea, como se muestra a continuación:

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;
	}
}

El análisis de la función OSStartHighRdy () es el siguiente:

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 significa cargar una palabra de la memoria a un registro;
STRB almacena el byte bajo de un registro en la memoria (es decir, dirección)
STR Almacena un registro palabra por palabra en la memoria (es decir, dirección)
B Saltar a la función
Servicio de interrupción PendSV La función es la siguiente: (Entre ellos, el comentario en el lado derecho de la declaración es un comentario de incendio forestal, y el comentario debajo de la declaración es mi entendimiento)

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                                       ; 汇编文件结束

Luego ejecute la tarea Task1, de la siguiente manera:

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

Después de eso, el cambio de tarea se realiza ejecutando 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();
}

Después de ingresar la interrupción PendSV, xPSR, PC (dirección de entrada de tarea), R14, R12, R3, R2, R1, R0 se insertan automáticamente en la pila de acuerdo con la dirección indicada por el PSP y, al mismo tiempo, el PSP disminuye. , y espera hasta que todos los registros insertados automáticamente se inserten en la pila. Después de eso, PSP apunta a 0x20000058 y debe guardar manualmente la posición superior de la pila del registro R4-R11. Vea el código de la siguiente manera:

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                                       ; 汇编文件结束

Después de este proceso, el entorno de ejecución actual de la tarea se guarda cuando se cambia la tarea y se carga el entorno de ejecución de la siguiente tarea, de modo que se realiza el cambio de tarea.

Supongo que te gusta

Origin blog.csdn.net/qq_40831436/article/details/114096471
Recomendado
Clasificación