FreeRTOS任務源碼分析以及程序堆棧與任務堆棧的關系


之前的文章學習了ARM函數調用和返回時的操作,但是對於操作系統下的任務堆棧以及任務切換時堆棧的切換還不太了解,因此,首先分析了一下任務的源碼,包括創建任務時,創建堆棧的過程,以及任務調度過程。后來,發現這個分析清楚了,就可以把程序堆棧和任務堆棧也梳理清楚,於是,就繼續梳理一下程序堆棧和任務堆棧的關系。

以STM32F4x7_ETH_LwIP_V1.1.1工程為例,使用的版本是FreeRTOSV7.3.0。

STM32F4x7_ETH_LwIP_V1.1.1\Project\FreeRTOS\udptcp_echo_server_netconn\src\main.c中啟動任務如下

 1 int main(void)
 2 {
 3  /* Configures the priority grouping: 4 bits pre-emption priority */
 4   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
 5 
 6   /* Init task */
 7   xTaskCreate(Main_task, (int8_t *)"Main", configMINIMAL_STACK_SIZE * 2, NULL,MAIN_TASK_PRIO, NULL);
 8   
 9   /* Start scheduler */
10   vTaskStartScheduler();
11 
12   /* We should never get here as control is now taken by the scheduler */
13   for( ;; );
14 }

在main中一般都會啟動一個主任務或者叫啟動任務,然后,開始任務調度,在主任務中,完成其它任務的創建。(為什么要這種模式呢?直接在main中創建所有任務,然后,開始任務調度不可以嗎?

任務控制塊TCB,首個成員是任務堆棧頂部地址,第17行表示任務堆棧起始(堆棧像一個桶,桶底是高地址,桶上面是低地址,桶底部為“任務堆棧起始”pxStack,桶里的最后一個數據位置為“任務堆棧頂部地址”pxTopOfStack)。

xGenericListItem是用於將任務串成列表的列表成員,后續該任務加入就緒任務列表還是其他任務列表,都是將該列表成員插入進任務列表。

xEventListItem用於記錄該任務是否在等待事件,比如是否向隊列發送數據但隊列已滿、是否從隊列讀取數據但隊列是空的,且設置了等待時間或無限等待。例如,若是向隊列發送數據但隊列已滿,則該任務的xEventListItem會插入該隊列的xTasksWaitingToSend列表中;同時將xGenericListItem從就緒任務列表刪除,插入到掛起任務隊列(若等待時間是無限)或延時任務隊列(若等待時間是有限)(該過程由vTaskPlaceOnEventList完成)。若是隊列非滿了,則會將任務的xEventListItem從xTasksWaitingToSend中移除;同時,將任務的xGenericListItem從掛起任務隊列或延時任務隊列中移除,並添加到就緒隊列中(該過程由xTaskRemoveFromEventList完成)。

 1 /*
 2  * Task control block.  A task control block (TCB) is allocated for each task,
 3  * and stores task state information, including a pointer to the task's context
 4  * (the task's run time environment, including register values)
 5  */
 6 typedef struct tskTaskControlBlock
 7 {
 8     volatile portSTACK_TYPE    *pxTopOfStack;        /*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
 9 
10     #if ( portUSING_MPU_WRAPPERS == 1 )
11         xMPU_SETTINGS xMPUSettings;                /*< The MPU settings are defined as part of the port layer.  THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
12     #endif
13 
14     xListItem                xGenericListItem;        /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
15     xListItem                xEventListItem;        /*< Used to reference a task from an event list. */
16     unsigned portBASE_TYPE    uxPriority;            /*< The priority of the task.  0 is the lowest priority. */
17     portSTACK_TYPE            *pxStack;            /*< Points to the start of the stack. */
18     signed char                pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created.  Facilitates debugging only. */
19 
20     #if ( portSTACK_GROWTH > 0 )
21         portSTACK_TYPE *pxEndOfStack;            /*< Points to the end of the stack on architectures where the stack grows up from low memory. */
22     #endif
23 
24     #if ( portCRITICAL_NESTING_IN_TCB == 1 )
25         unsigned portBASE_TYPE uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
26     #endif
27 
28     #if ( configUSE_TRACE_FACILITY == 1 )
29         unsigned portBASE_TYPE    uxTCBNumber;    /*< Stores a number that increments each time a TCB is created.  It allows debuggers to determine when a task has been deleted and then recreated. */
30         unsigned portBASE_TYPE  uxTaskNumber;    /*< Stores a number specifically for use by third party trace code. */
31     #endif
32 
33     #if ( configUSE_MUTEXES == 1 )
34         unsigned portBASE_TYPE uxBasePriority;    /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
35     #endif
36 
37     #if ( configUSE_APPLICATION_TASK_TAG == 1 )
38         pdTASK_HOOK_CODE pxTaskTag;
39     #endif
40 
41     #if ( configGENERATE_RUN_TIME_STATS == 1 )
42         unsigned long ulRunTimeCounter;            /*< Stores the amount of time the task has spent in the Running state. */
43     #endif
44 
45 } tskTCB;

 

任務創建

下面看任務創建函數,xTaskCreate實際調用的是xTaskGenericCreate

E:\project\rtos\STM32F4x7_ETH_LwIP_V1.1.1\Utilities\Third_Party\FreeRTOSV7.3.0\include\task.h

1 #define xTaskCreate( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask ) xTaskGenericCreate( ( pvTaskCode ), ( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask ), ( NULL ), ( NULL ) )

E:\project\rtos\STM32F4x7_ETH_LwIP_V1.1.1\Utilities\Third_Party\FreeRTOSV7.3.0\tasks.c,486

  1 signed portBASE_TYPE xTaskGenericCreate( pdTASK_CODE pxTaskCode, const signed char * const pcName, unsigned short usStackDepth, void *pvParameters, unsigned portBASE_TYPE uxPriority, xTaskHandle *pxCreatedTask, portSTACK_TYPE *puxStackBuffer, const xMemoryRegion * const xRegions )
  2 {
  3 signed portBASE_TYPE xReturn;
  4 tskTCB * pxNewTCB;
  5 
  6     configASSERT( pxTaskCode );
  7     configASSERT( ( ( uxPriority & ( ~portPRIVILEGE_BIT ) ) < configMAX_PRIORITIES ) );
  8 
  9     /* Allocate the memory required by the TCB and stack for the new task,
 10     checking that the allocation was successful. */
 11     pxNewTCB = prvAllocateTCBAndStack( usStackDepth, puxStackBuffer );
 12 
 13     if( pxNewTCB != NULL )
 14     {
 15         portSTACK_TYPE *pxTopOfStack;
 16 
 17         #if( portUSING_MPU_WRAPPERS == 1 )
 18             /* Should the task be created in privileged mode? */
 19             portBASE_TYPE xRunPrivileged;
 20             if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
 21             {
 22                 xRunPrivileged = pdTRUE;
 23             }
 24             else
 25             {
 26                 xRunPrivileged = pdFALSE;
 27             }
 28             uxPriority &= ~portPRIVILEGE_BIT;
 29         #endif /* portUSING_MPU_WRAPPERS == 1 */
 30 
 31         /* Calculate the top of stack address.  This depends on whether the
 32         stack grows from high memory to low (as per the 80x86) or visa versa.
 33         portSTACK_GROWTH is used to make the result positive or negative as
 34         required by the port. */
 35         #if( portSTACK_GROWTH < 0 )
 36         {
 37             pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - ( unsigned short ) 1 );
 38             pxTopOfStack = ( portSTACK_TYPE * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK  ) );
 39 
 40             /* Check the alignment of the calculated top of stack is correct. */
 41             configASSERT( ( ( ( unsigned long ) pxTopOfStack & ( unsigned long ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
 42         }
 43         #else
 44         {
 45             pxTopOfStack = pxNewTCB->pxStack;
 46 
 47             /* Check the alignment of the stack buffer is correct. */
 48             configASSERT( ( ( ( unsigned long ) pxNewTCB->pxStack & ( unsigned long ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
 49 
 50             /* If we want to use stack checking on architectures that use
 51             a positive stack growth direction then we also need to store the
 52             other extreme of the stack space. */
 53             pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( usStackDepth - 1 );
 54         }
 55         #endif
 56 
 57         /* Setup the newly allocated TCB with the initial state of the task. */
 58         prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority, xRegions, usStackDepth );
 59 
 60         /* Initialize the TCB stack to look as if the task was already running,
 61         but had been interrupted by the scheduler.  The return address is set
 62         to the start of the task function. Once the stack has been initialised
 63         the    top of stack variable is updated. */
 64         #if( portUSING_MPU_WRAPPERS == 1 )
 65         {
 66             pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
 67         }
 68         #else
 69         {
 70             pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
 71         }
 72         #endif
 73 
 74         /* Check the alignment of the initialised stack. */
 75         portALIGNMENT_ASSERT_pxCurrentTCB( ( ( ( unsigned long ) pxNewTCB->pxTopOfStack & ( unsigned long ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
 76 
 77         if( ( void * ) pxCreatedTask != NULL )
 78         {
 79             /* Pass the TCB out - in an anonymous way.  The calling function/
 80             task can use this as a handle to delete the task later if
 81             required.*/
 82             *pxCreatedTask = ( xTaskHandle ) pxNewTCB;
 83         }
 84 
 85         /* We are going to manipulate the task queues to add this task to a
 86         ready list, so must make sure no interrupts occur. */
 87         taskENTER_CRITICAL();
 88         {
 89             uxCurrentNumberOfTasks++;
 90             if( pxCurrentTCB == NULL )
 91             {
 92                 /* There are no other tasks, or all the other tasks are in
 93                 the suspended state - make this the current task. */
 94                 pxCurrentTCB =  pxNewTCB;
 95 
 96                 if( uxCurrentNumberOfTasks == ( unsigned portBASE_TYPE ) 1 )
 97                 {
 98                     /* This is the first task to be created so do the preliminary
 99                     initialisation required.  We will not recover if this call
100                     fails, but we will report the failure. */
101                     prvInitialiseTaskLists();
102                 }
103             }
104             else
105             {
106                 /* If the scheduler is not already running, make this task the
107                 current task if it is the highest priority task to be created
108                 so far. */
109                 if( xSchedulerRunning == pdFALSE )
110                 {
111                     if( pxCurrentTCB->uxPriority <= uxPriority )
112                     {
113                         pxCurrentTCB = pxNewTCB;
114                     }
115                 }
116             }
117 
118             /* Remember the top priority to make context switching faster.  Use
119             the priority in pxNewTCB as this has been capped to a valid value. */
120             if( pxNewTCB->uxPriority > uxTopUsedPriority )
121             {
122                 uxTopUsedPriority = pxNewTCB->uxPriority;
123             }
124 
125             #if ( configUSE_TRACE_FACILITY == 1 )
126             {
127                 /* Add a counter into the TCB for tracing only. */
128                 pxNewTCB->uxTCBNumber = uxTaskNumber;
129             }
130             #endif
131             uxTaskNumber++;
132 
133             prvAddTaskToReadyQueue( pxNewTCB );
134 
135             xReturn = pdPASS;
136             portSETUP_TCB( pxNewTCB );
137             traceTASK_CREATE( pxNewTCB );
138         }
139         taskEXIT_CRITICAL();
140     }
141     else
142     {
143         xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
144         traceTASK_CREATE_FAILED();
145     }
146 
147     if( xReturn == pdPASS )
148     {
149         if( xSchedulerRunning != pdFALSE )
150         {
151             /* If the created task is of a higher priority than the current task
152             then it should run now. */
153             if( pxCurrentTCB->uxPriority < uxPriority )
154             {
155                 portYIELD_WITHIN_API();
156             }
157         }
158     }
159 
160     return xReturn;
161 }
View Code

分配TCB和stack空間

1     /* Allocate the memory required by the TCB and stack for the new task,
2     checking that the allocation was successful. */
3     pxNewTCB = prvAllocateTCBAndStack( usStackDepth, puxStackBuffer );

第9行先分配TCB的空間,11行,TCB分配成功,再分配堆棧空間。第16行,分配輸入參數*4個字節的堆棧,賦值給“任務堆棧起始”pxStack。如果分配成功,那么27行,會初始化堆棧為填充圖案,這里為0xa5。

 1 /*-----------------------------------------------------------*/
 2 
 3 static tskTCB *prvAllocateTCBAndStack( unsigned short usStackDepth, portSTACK_TYPE *puxStackBuffer )
 4 {
 5 tskTCB *pxNewTCB;
 6 
 7     /* Allocate space for the TCB.  Where the memory comes from depends on
 8     the implementation of the port malloc function. */
 9     pxNewTCB = ( tskTCB * ) pvPortMalloc( sizeof( tskTCB ) );
10 
11     if( pxNewTCB != NULL )
12     {
13         /* Allocate space for the stack used by the task being created.
14         The base of the stack memory stored in the TCB so the task can
15         be deleted later if required. */
16         pxNewTCB->pxStack = ( portSTACK_TYPE * ) pvPortMallocAligned( ( ( ( size_t )usStackDepth ) * sizeof( portSTACK_TYPE ) ), puxStackBuffer );
17 
18         if( pxNewTCB->pxStack == NULL )
19         {
20             /* Could not allocate the stack.  Delete the allocated TCB. */
21             vPortFree( pxNewTCB );
22             pxNewTCB = NULL;
23         }
24         else
25         {
26             /* Just to help debugging. */
27             memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) usStackDepth * sizeof( portSTACK_TYPE ) );
28         }
29     }
30 
31     return pxNewTCB;
32 }

task.c, 204,定義的堆棧填充圖案為a5,僅用於檢測任務的高地址水印。

1 /*
2  * The value used to fill the stack of a task when the task is created.  This
3  * is used purely for checking the high water mark for tasks.
4  */
5 #define tskSTACK_FILL_BYTE    ( 0xa5U )

計算任務堆棧頂部指針

第5行,會根據堆棧生成方向來分別計算,對於arm堆棧是向下生長的,分配的pxStack是低地址,因此,第7行,棧頂就是pxStack+深度-1(-1是因為是full stack,堆棧指針指向最后一個數據)。第8行會進行一下對齊,因為ARM是4字節對齊,因此,該句不會改變地址。

 1         /* Calculate the top of stack address.  This depends on whether the
 2         stack grows from high memory to low (as per the 80x86) or visa versa.
 3         portSTACK_GROWTH is used to make the result positive or negative as
 4         required by the port. */
 5         #if( portSTACK_GROWTH < 0 )
 6         {
 7             pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - ( unsigned short ) 1 );
 8             pxTopOfStack = ( portSTACK_TYPE * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK  ) );
 9 
10             /* Check the alignment of the calculated top of stack is correct. */
11             configASSERT( ( ( ( unsigned long ) pxTopOfStack & ( unsigned long ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
12         }

初始化TCB變量

prvInitialiseTCBVariables主要給TCB的變量賦值。重點關注以下幾個地方,第3、4行,初始化兩個鏈表的成員,第8、12行設置兩個鏈表的擁有者為TCB(擁有者Owner一般為包含該鏈表成員的結構體對象),第11行設置xEventListItem的鏈表成員數值為優先級補數,事件鏈表永遠按優先級排序。

 1 static void prvInitialiseTCBVariables( tskTCB *pxTCB, const signed char * const pcName, unsigned portBASE_TYPE uxPriority, const xMemoryRegion * const xRegions, unsigned short usStackDepth )
 2 {
 3     vListInitialiseItem( &( pxTCB->xGenericListItem ) );
 4     vListInitialiseItem( &( pxTCB->xEventListItem ) );
 5 
 6     /* Set the pxTCB as a link back from the xListItem.  This is so we can get
 7     back to    the containing TCB from a generic item in a list. */
 8     listSET_LIST_ITEM_OWNER( &( pxTCB->xGenericListItem ), pxTCB );
 9 
10     /* Event lists are always in priority order. */
11     listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), configMAX_PRIORITIES - ( portTickType ) uxPriority );
12     listSET_LIST_ITEM_OWNER( &( pxTCB->xEventListItem ), pxTCB );
13 }

初始化堆棧

注釋中說,初始化TCB的堆棧,看起來像任務正在運行,但是被調度器打斷。返回地址設置為task函數的起始。堆棧初始化后,堆棧頂部指針變量也會更新。

 1         /* Initialize the TCB stack to look as if the task was already running,  2  but had been interrupted by the scheduler. The return address is set  3  to the start of the task function. Once the stack has been initialised  4  the top of stack variable is updated. */
 5         #if( portUSING_MPU_WRAPPERS == 1 )
 6         {
 7             pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
 8         }
 9         #else
10         {
11             pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
12         }
13         #endif

STM32F4x7_ETH_LwIP_V1.1.1\Utilities\Third_Party\FreeRTOSV7.3.0\portable\RVDS\ARM_CM4F\port.c

第3行注釋,模擬棧幀就像是它由一個上下文切換中斷創建的。

(后面在prvStartFirstTask——》vPortSVCHandler中,會恢復現場。需要注意的是,這里做了兩個現場,一是處理器的stacking,二是OS的入棧。第一個任務在首次恢復時在vPortSVCHandler中,異常進入時處理器會unstacking一部分,OS在vPortSVCHandler再恢復一部分。第一個任務后面的恢復是OS的調度器恢復的,都在xPortPendSVHandler異常處理中,且這里面會先做保存,后作恢復。對於不是第一個任務的其它任務,則都是在xPortPendSVHandler異常處理中恢復。

總結一下:

        首次保存    首次恢復      后續恢復

第一個任務    任務創建   vPortSVCHandler   xPortPendSVHandler

非第一個任務   任務創建   xPortPendSVHandler  xPortPendSVHandler

 1 portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *pxTopOfStack, pdTASK_CODE pxCode, void *pvParameters )
 2 {
 3     /* Simulate the stack frame as it would be created by a context switch  4  interrupt. */
 5 
 6     /* Offset added to account for the way the MCU uses the stack on entry/exit
 7     of interrupts, and to ensure alignment. */
 8     pxTopOfStack--;
 9 
10     *pxTopOfStack = portINITIAL_XPSR;    /* xPSR */
11     pxTopOfStack--;
12     *pxTopOfStack = ( portSTACK_TYPE ) pxCode;    /* PC */
13     pxTopOfStack--;
14     *pxTopOfStack = 0;    /* LR */
15 
16     /* Save code space by skipping register initialisation. */
17     pxTopOfStack -= 5;    /* R12, R3, R2 and R1. */
18     *pxTopOfStack = ( portSTACK_TYPE ) pvParameters;    /* R0 */
19 
20     /* A save method is being used that requires each task to maintain its 21  own exec return value. */
22     pxTopOfStack--;
23     *pxTopOfStack = portINITIAL_EXEC_RETURN;
24 
25     pxTopOfStack -= 8;    /* R11, R10, R9, R8, R7, R6, R5 and R4. */
26 
27     return pxTopOfStack;
28 }

這里的設置是依據Cortex-M4的堆棧使用規則設置的,如下圖(STM32F3與F4系列Cortex M4內核編程手冊,p42),寄存器號小的在低地址,所以下圖,從上往下地址遞減,由於是Descending堆棧,因此,下圖中從上往下壓棧。(PSR,R15=PC,R14=LR,R12,。。。)

第10行,設置PSR為portINITIAL_XPSR=0x01000000,設置bit24為1,下圖中b24(STM32F3與F4系列Cortex M4內核編程手冊,p19)是預留的啊,這是怎么回事?

第12行,設置PC為任務函數入口。

第14行,設置LR為0.

第18行,設置R0為輸入參數。

第20-23行,設置exec return=0xfffffffd,即返回時使用線程模式Thread和程序堆棧PSP,第25行,給剩余R11~R4寄存器預留位置。這兒的設置在哪兒有說明呢

將任務添加到任務隊列

需要操作任務隊列,並將此任務添加到就緒列表,要保證不發生中斷。因為就緒列表許多地方使用?那可以用信號量之類的啊?

6行,當前任務為空,10行,設置創建的任務為當前任務;

12行,當前任務數量為1,表示這是創建的第一個任務,因此,初始化任務相關的列表,見后面分析。

20行,當前任務不為空,則27行判斷(25行是為什么?),若創建任務優先級大於當前任務,則設置創建的任務為當前任務。

36行,更新最大優先級全局變量。

43行,將創建任務添加到就緒列表中,見后面分析。

 1         /* We are going to manipulate the task queues to add this task to a  2  ready list, so must make sure no interrupts occur. */
 3         taskENTER_CRITICAL();
 4         {
 5             uxCurrentNumberOfTasks++;
 6             if( pxCurrentTCB == NULL )
 7             {
 8                 /* There are no other tasks, or all the other tasks are in
 9                 the suspended state - make this the current task. */
10                 pxCurrentTCB =  pxNewTCB;
11 
12                 if( uxCurrentNumberOfTasks == ( unsigned portBASE_TYPE ) 1 )
13                 {
14                     /* This is the first task to be created so do the preliminary
15                     initialisation required.  We will not recover if this call
16                     fails, but we will report the failure. */
17                     prvInitialiseTaskLists();
18                 }
19             }
20             else
21             {
22                 /* If the scheduler is not already running, make this task the
23                 current task if it is the highest priority task to be created
24                 so far. */
25                 if( xSchedulerRunning == pdFALSE )
26                 {
27                     if( pxCurrentTCB->uxPriority <= uxPriority )
28                     {
29                         pxCurrentTCB = pxNewTCB;
30                     }
31                 }
32             }
33 
34             /* Remember the top priority to make context switching faster.  Use
35             the priority in pxNewTCB as this has been capped to a valid value. */
36             if( pxNewTCB->uxPriority > uxTopUsedPriority )
37             {
38                 uxTopUsedPriority = pxNewTCB->uxPriority;
39             }
40 
41             uxTaskNumber++;
42 
43             prvAddTaskToReadyQueue( pxNewTCB );
44 
45             xReturn = pdPASS;
46         }
47         taskEXIT_CRITICAL();

  初始化任務相關的列表

任務相關的列表

(1)pxReadyTasksLists[ configMAX_PRIORITIES ],針對每個優先級都分配一個就緒任務列表,同時,記錄最高優先級,這樣在切換任務時,直接找最高優先級對應列表,查找會非常快。

(2)延時任務列表,有2個,其中1個用於溢出計數的,two lists are used - one for delays that have overflowed the current tick count。

(3)延時任務列表指針2個。

(4)掛起就緒任務列表,當調度器掛起時,就緒的任務列表,會在調度器繼續時,添加到就緒列表。

1 /* Lists for ready and blocked tasks. --------------------*/
2 PRIVILEGED_DATA static xList pxReadyTasksLists[ configMAX_PRIORITIES ];    /*< Prioritised ready tasks. */
3 PRIVILEGED_DATA static xList xDelayedTaskList1;                            /*< Delayed tasks. */
4 PRIVILEGED_DATA static xList xDelayedTaskList2;                            /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
5 PRIVILEGED_DATA static xList * volatile pxDelayedTaskList ;                /*< Points to the delayed task list currently being used. */
6 PRIVILEGED_DATA static xList * volatile pxOverflowDelayedTaskList;        /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
7 PRIVILEGED_DATA static xList xPendingReadyList;                            /*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready queue when the scheduler is resumed. */

5行,初始化所有優先級的就緒任務列表;10、11、12初始化2個延時任務列表1個掛起任務列表;16行,等待刪除任務列表(自己刪除自己的任務會在這上面,會由空閑任務執行實際刪除);22行,掛起任務列表;28、29延時任務列表指針賦值。

 1 static void prvInitialiseTaskLists( void )
 2 {
 3 unsigned portBASE_TYPE uxPriority;
 4 
 5     for( uxPriority = ( unsigned portBASE_TYPE ) 0U; uxPriority < configMAX_PRIORITIES; uxPriority++ )
 6     {
 7         vListInitialise( ( xList * ) &( pxReadyTasksLists[ uxPriority ] ) );
 8     }
 9 
10     vListInitialise( ( xList * ) &xDelayedTaskList1 );
11     vListInitialise( ( xList * ) &xDelayedTaskList2 );
12     vListInitialise( ( xList * ) &xPendingReadyList );
13 
14     #if ( INCLUDE_vTaskDelete == 1 )
15     {
16         vListInitialise( ( xList * ) &xTasksWaitingTermination );
17     }
18     #endif
19 
20     #if ( INCLUDE_vTaskSuspend == 1 )
21     {
22         vListInitialise( ( xList * ) &xSuspendedTaskList );
23     }
24     #endif
25 
26     /* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList
27     using list2. */
28     pxDelayedTaskList = &xDelayedTaskList1;
29     pxOverflowDelayedTaskList = &xDelayedTaskList2;
30 }

添加任務到就緒列表

這是一個宏,根據創建任務的優先級選擇相應就緒任務隊列,將TCB的xGenericListItem作為列表成員,添加進就緒任務列表。

 1 /*
 2  * Place the task represented by pxTCB into the appropriate ready queue for
 3  * the task.  It is inserted at the end of the list.  One quirk of this is
 4  * that if the task being inserted is at the same priority as the currently
 5  * executing task, then it will only be rescheduled after the currently
 6  * executing task has been rescheduled.
 7  */
 8 #define prvAddTaskToReadyQueue( pxTCB )                                                                                \
 9     traceMOVED_TASK_TO_READY_STATE( pxTCB )                                                                            \
10     taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );                                                                \
11     vListInsertEnd( ( xList * ) &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xGenericListItem ) )

 

任務調度

 vTaskStartScheduler

 1 void vTaskStartScheduler( void )
 2 {
 3 portBASE_TYPE xReturn;
 4 
 5     /* Add the idle task at the lowest priority. */
 6     #if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
 7     {
 8         /* Create the idle task, storing its handle in xIdleTaskHandle so it can
 9         be returned by the xTaskGetIdleTaskHandle() function. */
10         xReturn = xTaskCreate( prvIdleTask, ( signed char * ) "IDLE", tskIDLE_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle );
11     }
12     #else
13     {
14         /* Create the idle task without storing its handle. */
15         xReturn = xTaskCreate( prvIdleTask, ( signed char * ) "IDLE", tskIDLE_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), NULL );
16     }
17     #endif
18 
19     #if ( configUSE_TIMERS == 1 )
20     {
21         if( xReturn == pdPASS )
22         {
23             xReturn = xTimerCreateTimerTask();
24         }
25     }
26     #endif
27 
28     if( xReturn == pdPASS )
29     {
30         /* Interrupts are turned off here, to ensure a tick does not occur
31         before or during the call to xPortStartScheduler().  The stacks of
32         the created tasks contain a status word with interrupts switched on
33         so interrupts will automatically get re-enabled when the first task
34         starts to run.
35 
36         STEPPING THROUGH HERE USING A DEBUGGER CAN CAUSE BIG PROBLEMS IF THE
37         DEBUGGER ALLOWS INTERRUPTS TO BE PROCESSED. */
38         portDISABLE_INTERRUPTS();
39 
40         xSchedulerRunning = pdTRUE;
41         xTickCount = ( portTickType ) 0U;
42 
43         /* If configGENERATE_RUN_TIME_STATS is defined then the following
44         macro must be defined to configure the timer/counter used to generate
45         the run time counter time base. */
46         portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
47 
48         /* Setting up the timer tick is hardware specific and thus in the
49         portable interface. */
50         if( xPortStartScheduler() != pdFALSE )
51         {
52             /* Should not reach here as if the scheduler is running the
53             function will not return. */
54         }
55         else
56         {
57             /* Should only reach here if a task calls xTaskEndScheduler(). */
58         }
59     }
60 
61     /* This line will only be reached if the kernel could not be started. */
62     configASSERT( xReturn );
63 }
View Code

上面函數主要就是調用xPortStartScheduler。

xPortStartScheduler

開啟調度器,主要就是設置PendSV和SysTick定時器,使能VFP,啟動第一個任務。

 1 portBASE_TYPE xPortStartScheduler( void )
 2 {
 3     /* Make PendSV, CallSV and SysTick the same priroity as the kernel. */
 4     portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
 5     portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
 6 
 7     /* Start the timer that generates the tick ISR.  Interrupts are disabled
 8     here already. */
 9     vPortSetupTimerInterrupt();
10 
11     /* Initialise the critical nesting count ready for the first task. */
12     uxCriticalNesting = 0;
13 
14     /* Ensure the VFP is enabled - it should be anyway. */
15     prvEnableVFP();
16 
17     /* Lazy save always. */
18     *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
19 
20     /* Start the first task. */
21     prvStartFirstTask();
22 
23     /* Should not get here! */
24     return 0;
25 }

 

vPortSetupTimerInterrupt

 設置SysTick定時器。

4行,168M/1000-1,每1000分之一秒循環一次,即1ms一個時間片。

5行,設置SysTick定時器時鍾來源為Processor Clock(系統主頻168MHz)、使能中斷、使能定時器。在SysTick定時器中斷中,會進行任務切換調度。

1 void vPortSetupTimerInterrupt( void )
2     {
3         /* Configure SysTick to interrupt at the requested rate. */
4         portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;;
5         portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
6     }

 

prvEnableVFP

 使能FPU協處理器

The REQUIRE8 and PRESERVE8 directives specify that the current file requires or preserves eight-byte alignment of the stack.

LDR (pc-relative) in Thumb-2 You can use the .W width specifier to force LDR to generate a 32-bit instruction in Thumb-2 code. LDR.W always generates a 32-bit instruction, even if the target could be reached using a 16-bit LDR.

 1 __asm void prvEnableVFP( void )
 2 {
 3  PRESERVE8  4 
 5     /* The FPU enable bits are in the CPACR. */
 6     ldr.w r0, =0xE000ED88
 7     ldr    r1, [r0]
 8 
 9     /* Enable CP10 and CP11 coprocessors, then save back. */
10     orr    r1, r1, #( 0xf << 20 )
11     str r1, [r0]
12     bx    r14
13     nop
14 }

 

prvStartFirstTask

 SystemInit設置的0xE000ED08 VTOR=0x0800 0000,即Flash的首地址。

6、7、8行,從VTOR(Vector Table Offset Reg向量表偏移寄存器)獲取堆棧地址,存儲在Flash的首地址處,0x0800 0004地址處是Reset處理向量。

10行,將獲取的堆棧地址賦值給msp。12行,使能中斷;14行,調用SVC,System Service異常。

這個函數其實是在任務調度環境設置好之后,初始化任務調度環境,比如,把msp復位,使能中斷,觸發系統調用異常(在那里才是啟動第一個任務)。

 1 __asm void prvStartFirstTask( void )
 2 {
 3     PRESERVE8
 4 
 5     /* Use the NVIC offset register to locate the stack. */
 6     ldr r0, =0xE000ED08
 7     ldr r0, [r0]
 8     ldr r0, [r0]
 9     /* Set the msp back to the start of the stack. */
10     msr msp, r0
11     /* Globally enable interrupts. */
12     cpsie i
13     /* Call SVC to start the first task. */
14     svc 0
15     nop
16 }

 

vPortSVCHandler

6行,將當前TCB指針地址加載到r3。

7行,將r3處的數值加載給r1,r1為堆棧頂部變量的地址。

8行,將r1處的數值加載給r0,r0為當前任務堆棧地址。

10行,出棧r4-r11,r14。

11行,將r0賦值給psp。

12、13行,使能中斷。(中斷禁止是在vTaskStartScheduler中)

14行,退出SVC異常處理。

這里面才是真正的啟動第一個任務,比如獲取當前任務TCB棧頂指針,將之前保存(在創建任務時,初始化堆棧時會將堆棧設置的像被調度打斷了)的寄存器現場恢復出來。主要包括恢復r4-r11和LR(CM3不會恢復LR,因為硬件會自己入棧和出棧),恢復堆棧,打開中斷。其它的8個寄存器在異常處理退出時,處理器硬件會自動unstacking,這8個寄存器就包括PC指針(創建任務時壓入的PC是任務入口),這8個寄存器最初是在創建任務時,初始化堆棧時,壓入堆棧的,模擬好像被調度器打斷了。從而,SVC異常退出后,會開始執行第一個任務。

 1 __asm void vPortSVCHandler( void )
 2 {
 3     PRESERVE8
 4 
 5     /* Get the location of the current TCB. */
 6     ldr    r3, =pxCurrentTCB
 7     ldr r1, [r3]
 8     ldr r0, [r1]
 9     /* Pop the core registers. */
10     ldmia r0!, {r4-r11, r14}
11     msr psp, r0
12     mov r0, #0
13     msr    basepri, r0
14     bx r14
15 }

  

任務切換

時間片到了會觸發PendSV異常,主動的yeild也會觸發PendSV異常,這里面會將當前任務現場壓棧,查找下一個任務,恢復下一個任務現場。

時間片由SysTick實現,vPortSetupTimerInterrupt已經設置1ms一個時間片,1ms到了會觸發SysTick中斷,在這里實際就是觸發PendSV中斷,第6行(其中搶占設置為1)。

xPortSysTickHandler

 1 void xPortSysTickHandler( void )
 2 {
 3     #if configUSE_PREEMPTION == 1
 4     {
 5         /* If using preemption, also force a context switch. */
 6         portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
 7     }
 8     #endif
 9 
10     ( void ) portSET_INTERRUPT_MASK_FROM_ISR();
11     {
12         vTaskIncrementTick();
13     }
14     portCLEAR_INTERRUPT_MASK_FROM_ISR( 0 );
15 }

xPortPendSVHandler

 1 __asm void xPortPendSVHandler( void )
 2 {
 3     extern uxCriticalNesting;
 4     extern pxCurrentTCB;
 5     extern vTaskSwitchContext;
 6 
 7     PRESERVE8
 8 
 9     mrs r0, psp
10 
11     /* Get the location of the current TCB. */
12     ldr    r3, =pxCurrentTCB
13     ldr    r2, [r3]
14 
15     /* Is the task using the FPU context?  If so, push high vfp registers. */
16     tst r14, #0x10
17     it eq
18     vstmdbeq r0!, {s16-s31}
19 
20     /* Save the core registers. */
21     stmdb r0!, {r4-r11, r14}
22 
23     /* Save the new top of stack into the first member of the TCB. */
24     str r0, [r2]
25 
26     stmdb sp!, {r3, r14}
27     mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
28     msr basepri, r0
29     bl vTaskSwitchContext
30     mov r0, #0
31     msr basepri, r0
32     ldmia sp!, {r3, r14}
33 
34     /* The first item in pxCurrentTCB is the task top of stack. */
35     ldr r1, [r3]
36     ldr r0, [r1]
37 
38     /* Pop the core registers. */
39     ldmia r0!, {r4-r11, r14}
40 
41     /* Is the task using the FPU context?  If so, pop the high vfp registers
42     too. */
43     tst r14, #0x10
44     it eq
45     vldmiaeq r0!, {s16-s31}
46 
47     msr psp, r0
48     bx r14
49     nop
50 }

第9行,將psp保存在r0,后面會使用r0操作程序堆棧。

第12行,ldr r3, =pxCurrentTCB,將pxCurrentTCB指針的地址加載到r3中,pxCurrentTCB指針的地址為0x2000 0074,通過MAP文件可以確認;pxCurrentTCB指針的數值value為0x20003538,即指針指向的地址為0x20003538某個任務的TCB。

這里需要注意的是ldr r3, =variable僅僅是將variable變量的地址加載到r3,要想獲取variable的數值,還需要ldr r3, [r3],即使variable是個int變量。

 

16~18行,判斷是否使用fpu,若使用保存fpu現場信息。

21行,stmdb r0!, {r4-r11, r14},decrease before,將r4~r11和r14(LR,存EXC_RETURN)壓入堆棧(LR處理器會存一遍,這里為什么還要存一遍?

24行,str r0, [r2],將更新后的r0=之前任務的PSP,更新到之前任務的TCB堆棧頂部變量中。

26行,stmdb sp!, {r3, r14},將r3和r14保存在處理模式的主堆棧msp中,r3中放着當前任務指針變量地址(vTaskSwitchContext中會查找下一個調度任務,會更新當前任務指針變量的數值,其實在調用完vTaskSwitchContext后也可以重新將pxCurrentTCB加載到r3中),r14=LR放着返回模式字。

27、28行,在匯編模式下關中斷

30、31行,在匯編模式下開中斷

29行,執行vTaskSwitchContext,其實這個函數名不太恰當,在該函數里僅僅是找到下一個待調度的任務,叫findnexttask更合適。而切換上下文主要在xPortPendSVHandler中完成。

32行,ldmia sp!, {r3, r14},恢復r3和r14,此時r3中的pxCurrentTCB已經更新為下一個待調度任務了(pxCurrentTCB的數值value已經更新了)

35、36行,獲取更新的當前任務TCB的堆棧頂部地址。

39行,恢復新任務的現場,將堆棧中的信息恢復進r4-r11, r14。

43-45,判斷是否使用fpu,若是,恢復浮點寄存器。

47行,msr psp, r0,將r0保存在psp中。

48行,bx r14,跳出異常處理,進入新任務。

vTaskSwitchContext

名字雖然叫任務上下文切換,但實際做的就是找到下一個最高優先級的任務,作為下一個調度任務。

 1 void vTaskSwitchContext( void )
 2 {
 3     if( uxSchedulerSuspended != ( unsigned portBASE_TYPE ) pdFALSE )
 4     {
 5         /* The scheduler is currently suspended - do not allow a context
 6         switch. */
 7         xMissedYield = pdTRUE;
 8     }
 9     else
10     {
11         traceTASK_SWITCHED_OUT();
12 
13         taskFIRST_CHECK_FOR_STACK_OVERFLOW();
14         taskSECOND_CHECK_FOR_STACK_OVERFLOW();
15 
16  taskSELECT_HIGHEST_PRIORITY_TASK();
17 
18         traceTASK_SWITCHED_IN();
19     }
20 }

taskSELECT_HIGHEST_PRIORITY_TASK

這是一個宏定義,4行從最高優先級開始查看就緒隊列是否有節點,12行,獲取就緒隊列的下一個任務。

 1 #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                                            \
 2     {                                                                                                                    \
 3         /* Find the highest priority queue that contains ready tasks. */                                                \
 4         while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )                                        \
 5         {                                                                                                                \
 6             configASSERT( uxTopReadyPriority );                                                                            \
 7             --uxTopReadyPriority;                                                                                        \
 8         }                                                                                                                \
 9                                                                                                                         \
10         /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of                                        \
11         the    same priority get an equal share of the processor time. */                                                    \
12         listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );                        \
13     } /* taskSELECT_HIGHEST_PRIORITY_TASK */

 

任務和異常處理之間的堆棧處理

 (STM32F3與F4系列Cortex M4內核編程手冊,p41)

When the processor takes an exception, unless the exception is a tail-chained or a late-arriving exception, the processor pushes information onto the current stack. This operation is referred as stacking and the structure of eight data words is referred as stack frame.

下圖就是stacking過程壓棧的8個寄存器,PSR,R15=PC,R14=LR,R12。下圖可以反過來看,但下圖正好是下面是低地址,上面是高地址。

這個壓棧過程是處理器,也就是硬件完成的,This is achieved through the hardware stacking of registers

The stack frame includes the return address. This is the address of the next instruction in the interrupted program. This value is restored to the PC at exception return so that the interrupted program resumes.棧幀包括返回地址,即PC。

In parallel to the stacking operation, the processor performs a vector fetch that reads the exception handler start address from the vector table. When stacking is complete, the processor starts executing the exception handler. At the same time, the processor writes an EXC_RETURN value to the LR. This indicates which stack pointer corresponds to the stack frame and what operation mode the was processor was in before the entry occurred.

上面的各種操作的先后順序,手冊寫的非常模糊,又是In parallel,又是At the same time的。推測順序如下:

正在線程模式

  |(1)

發生異常

  |(2)

查找向量表,執行異常處理函數

在第(2)步,處理器進行了以下動作:

1、stacking,將8個寄存器壓入堆棧

2、將EXC_RETURN寫入LR

3、查找向量表,執行異常處理函數。

上面第2步是EXC_RETURN寫入LR嗎?從FreeRTOS,CM4,pxPortInitialiseStack來看,是將EXC_RETURN壓棧,即前面有LR,后面有EXC_RETURN,並沒有將EXC_RETURN寫入LR。

還有R11~R4是什么時候壓棧的呢,這個是由OS完成的,一是在創建任務初始化堆棧時,二是在任務切換時xPortPendSVHandler。

FreeRTOS對CM4F壓棧的內容如下,下圖中綠色背景是處理器硬件壓棧的,其它是FreeRTOS壓棧的,這是CM4F壓棧的內容,對於CM3處理器不會壓最后的LR(EXC_RETURN):

 重新梳理一遍任務堆棧相關的操作

main——使用線程模式,MSP(復位后處理器默認使用線程模式和MSP)會將r0-r4,lr壓入堆棧,占用6*4=24=0x18字節,因此,堆棧變為0x200100f8-0x18=0x200100e0

xTaskCreate——創建任務時,線程模式,MSP,會分配任務堆棧內存,並填充堆棧內容為填充圖案a5,堆棧起始賦值給pxStack;堆棧頂部賦值給pxTopOfStack

  ——prvAllocateTCBAndStack

    ——pxStack=pvPortMallocAligned

    ——填充堆棧內容為填充圖案a5

  ——pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - ( unsigned short ) 1 );

  ——pxNewTCB->pxTopOfStack = pxPortInitialiseStack

    這里初始化堆棧的樣子,就像是被調度器打斷一樣,會將處理器stacking的8個寄存器,以及FreeRTOS約定的r4-r11和r14都壓入堆棧,同時更新棧頂指針,形成上面制作的圖形狀態。

 

vTaskStartScheduler——線程模式,MSP,將r0-r4,lr壓入堆棧,占用6*4=24=0x18字節,因此,堆棧變為0x200100e0-0x18=0x200100c8

  ——xPortStartScheduler——線程模式,MSP,將r4,lr壓入堆棧,占用2*4=8=0x8字節,因此,堆棧變為0x200100c8-0x8=0x200100c0

    ——prvStartFirstTask,線程模式,MSP,葉子函數,沒有用堆棧,但將堆棧復位=0x200100f8。這里主要是在任務調度環境設置好之后,初始化任務調度環境,比如,把msp復位,使能中斷,觸發系統調用異常(在那里才是啟動第一個任務)

 

vPortSVCHandler,處理模式,MSP,異常進入,處理器硬件stacking共8個reg,4*8=32=0x20,堆棧變為0x200100f8-0x20=0x200100d8

  ——Exception entry,異常進入時,處理器已經進行了stacking,這里用的是msp

  ——主體,獲取當前任務棧頂,恢復FreeRTOS約定的r4-r11和r14

      設置PSP為更新的任務棧頂

      打開中斷

      設置LR=0xFFFF FFFD,返回線程模式,PSP

  ——Exception return,異常退出時,處理器進行unstacking,將8個寄存器出棧(這些是在創建任務初始化堆棧時設置的,其中PC=任務入口)正式啟動第一個任務

————————————————————在這之前用的全是MSP,在這之后才開始用PSP,在異常處理時也用MSP。

Main_task

  進入第一個任務執行。

 

之后,會進入xPortPendSVHandler,在這里面用的還是MSP,要想操作任務堆棧,需要將PSP讀進來。但xPortPendSVHandler不會再用MSP了,因為xPortPendSVHandler不會再調用別的函數,這是葉子函數,以后,MSP也都不會再更新了。

 

關於程序堆棧和任務堆棧

程序堆棧可以看成是MSP,任務堆棧可以看成是PSP。

在創建任務之前,是沒有任務堆棧的,只有程序堆棧(暫且叫這個名字,也就是沒有OS時的,普通意義上的堆棧),這個堆棧例子工程中定義的是0x200100f8。

2行,在調用時用了BLX,會將PC+4放在LR,同時,在SystemInit會將r4和LR壓棧,因為,在SystemInit中也調用了其它函數,也就是SystemInit是非葉子函數,這里使用了堆棧。

3行,調用已經完成,堆棧恢復為0x200100f8,調用__main時,沒有用BLX而是用BX,所以,__main是葉子函數,且應該沒有用堆棧。調用完__main后,會跳到用戶的main函數中(__main無法單步跟蹤)。

1                  LDR     R0, =SystemInit
2                  BLX     R0
3                  LDR     R0, =__main
4                  BX      R0
5                  ENDP

main函數開始,使用線程模式,MSP(復位后處理器默認使用線程模式和MSP)會將r0-r4,lr壓入堆棧,占用6*4=24=0x18字節,因此,堆棧變為0x200100f8-0x18=0x200100e0(處理器只在進入和離開異常時才進行自動入棧出棧,普通函數調用都在線程模式,不會進行stacking和unstacking)。

 

關於FreeRTOS的ARM-CM4F移植

GCC下的:FreeRTOSV7.3.0\portable\GCC\ARM_CM4F\port.c

Keil下的:FreeRTOSV7.3.0\portable\RVDS\ARM_CM4F\port.c(Keil和RVDS一樣)

其中關於vPortSVCHandler的差異,實際的匯編都是一樣的,差別在於編譯器如何識別c文件里的匯編代碼。

GCC下:

 1 void vPortSVCHandler( void )
 2 {
 3     __asm volatile (
 4                     "    ldr    r3, pxCurrentTCBConst2        \n" /* Restore the context. */
 5                     "    ldr r1, [r3]                    \n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
 6                     "    ldr r0, [r1]                    \n" /* The first item in pxCurrentTCB is the task top of stack. */
 7                     "    ldmia r0!, {r4-r11, r14}        \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
 8                     "    msr psp, r0                        \n" /* Restore the task stack pointer. */
 9                     "    mov r0, #0                         \n"
10                     "    msr    basepri, r0                    \n"
11                     "    bx r14                            \n"
12                     "                                    \n"
13                     "    .align 2                        \n"
14                     "pxCurrentTCBConst2: .word pxCurrentTCB                \n"
15                 );
16 }

RVDS下:

 1 __asm void vPortSVCHandler( void )
 2 {
 3     PRESERVE8
 4 
 5     /* Get the location of the current TCB. */
 6     ldr    r3, =pxCurrentTCB
 7     ldr r1, [r3]
 8     ldr r0, [r1]
 9     /* Pop the core registers. */
10     ldmia r0!, {r4-r11, r14}
11     msr psp, r0
12     mov r0, #0
13     msr    basepri, r0
14     bx r14
15 }

  

結束


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM