網站首頁 編程語言 正文
前言
阻塞態:如果一個任務當前正在等待某個外部事件,則稱它處于阻塞態。
rtos中的延時叫阻塞延時,即任務需要延時的時候,會放棄CPU的使用權,進入阻塞狀態。在任務阻塞的這段時間,CPU可以去執行其它的任務(如果其它的任務也在延時狀態,那么 CPU 就將運行空閑任務),當任務延時時間到,重新獲取 CPU 使用權,任務繼續運行。
空閑任務:處理器空閑的時候,運行的任務。當系統中沒有其他就緒任務時,空閑任務開始運行,空閑任務的優先級是最低的。
空閑任務
定義空閑任務:
#define portSTACK_TYPE uint32_t typedef portSTACK_TYPE StackType_t; /*定義空閑任務的棧*/ #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; /*定義空閑任務的任務控制塊*/ TCB_t IdleTaskTCB;
創建空閑任務:在vTaskStartScheduler調度器啟動函數中創建。
/*任務控制塊的結構體 */ typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /* 棧頂 */ ListItem_t xStateListItem; /* 任務節點 */ StackType_t *pxStack; /* 任務棧起始地址 */ char pcTaskName[ configMAX_TASK_NAME_LEN ];/* 任務名稱,字符串形式 */ TickType_t xTicksToDelay; /* 用于延時 */ } tskTCB; typedef tskTCB TCB_t; /*獲取獲取空閑任務的內存:任務控制塊、任務棧起始地址、任務棧大小*/ void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize ) { *ppxIdleTaskTCBBuffer=&IdleTaskTCB;//空閑任務的任務控制塊 *ppxIdleTaskStackBuffer=IdleTaskStack; //空閑任務的任務棧 *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;//棧的大小 } void vTaskStartScheduler( void ) { /*創建空閑任務start*/ TCB_t *pxIdleTaskTCBBuffer = NULL; /* 用于指向空閑任務控制塊 */ StackType_t *pxIdleTaskStackBuffer = NULL; /* 用于空閑任務棧起始地址 */ uint32_t ulIdleTaskStackSize; /* 獲取:任務控制塊、任務棧起始地址、任務棧大小 */ vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize ); /*創建空閑任務*/ xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任務入口 */ (char *)"IDLE", /* 任務名稱,字符串形式 */ (uint32_t)ulIdleTaskStackSize , /* 任務棧大小,單位為字 */ (void *) NULL, /* 任務形參 */ (StackType_t *)pxIdleTaskStackBuffer, /* 任務棧起始地址 */ (TCB_t *)pxIdleTaskTCBBuffer ); /* 任務控制塊 */ /* 將任務添加到就緒列表 */ vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) ); /*創建空閑任務end*/ /* 手動指定第一個運行的任務 */ pxCurrentTCB = &Task1TCB; /* 初始化系統時基計數器 */ xTickCount = ( TickType_t ) 0U; /* 啟動調度器 */ if( xPortStartScheduler() != pdFALSE ) { /* 調度器啟動成功,則不會返回,即不會來到這里 */ } } //下面是空閑任務的任務入口,看到,里面什么都沒做 //這個我用debug發現一直卡到這個for不動了。 //通過單步運行,發生了中斷,程序也無法進入中斷。 static portTASK_FUNCTION( prvIdleTask, pvParameters ) { /* 防止編譯器的警告 */ ( void ) pvParameters; for(;;) { /* 空閑任務暫時什么都不做 */ } }
阻塞延時
任務函數如下:延時函數由軟件延時替代為阻塞延時。
void Task1_Entry( void *p_arg ) { for( ;; ) { #if 0 flag1 = 1; delay( 100 );/*軟件延時*/ flag1 = 0; delay( 100 ); /* 線程切換,這里是手動切換 */ portYIELD(); #else flag1 = 1; vTaskDelay( 2 );/*阻塞延時*/ flag1 = 0; vTaskDelay( 2 ); #endif } }
任務函數里面調用了vTaskDelay阻塞延時函數,如下。
/*阻塞延時函數的定義 */ void vTaskDelay( const TickType_t xTicksToDelay ) { TCB_t *pxTCB = NULL; /* 獲取當前任務的任務控制塊 */ pxTCB = pxCurrentTCB; /* 設置延時時間:xTicksToDelay個SysTick延時周期 */ pxTCB->xTicksToDelay = xTicksToDelay; /* 任務切換 */ taskYIELD(); }
然后vTaskDelay里面調用了taskYIELD函數,如下。目的是產生PendSV中斷,進入PendSV中斷服務函數。
/* Interrupt control and state register (SCB_ICSR):0xe000ed04 * Bit 28 PENDSVSET: PendSV set-pending bit */ #define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) ) #define portNVIC_PENDSVSET_BIT ( 1UL << 28UL ) #define portSY_FULL_READ_WRITE ( 15 ) /* Scheduler utilities. */ #define portYIELD() \ { \ /* 設置 PendSV 的中斷掛起位,產生上下文切換 */ \ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ \ /* Barriers are normally not required but do ensure the code is completely \ within the specified behaviour for the architecture. */ \ __dsb( portSY_FULL_READ_WRITE ); \ __isb( portSY_FULL_READ_WRITE ); \ }
PendSV中斷服務函數如下,里面調用了vTaskSwitchContext上下文切換函數,目的是尋找最高優先級的就緒任務,然后更新pxCurrentTCB。
__asm void xPortPendSVHandler( void ) { // extern uxCriticalNesting; extern pxCurrentTCB; extern vTaskSwitchContext; PRESERVE8 /* 當進入PendSVC Handler時,上一個任務運行的環境即: xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0(任務的形參) 這些CPU寄存器的值會自動保存到任務的棧中,剩下的r4~r11需要手動保存 */ /* 獲取任務棧指針到r0 */ mrs r0, psp isb ldr r3, =pxCurrentTCB /* 加載pxCurrentTCB的地址到r3 */ ldr r2, [r3] /* 加載pxCurrentTCB到r2 */ stmdb r0!, {r4-r11} /* 將CPU寄存器r4~r11的值存儲到r0指向的地址 */ str r0, [r2] /* 將任務棧的新的棧頂指針存儲到當前任務TCB的第一個成員,即棧頂指針 */ stmdb sp!, {r3, r14} mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY /* 進入臨界段 */ msr basepri, r0 dsb isb bl vTaskSwitchContext /* 調用函數vTaskSwitchContext,尋找新的任務運行,通過使變量pxCurrentTCB指向新的任務來實現任務切換 */ mov r0, #0 /* 退出臨界段 */ msr basepri, r0 ldmia sp!, {r3, r14} /* 恢復r3和r14 */ ldr r1, [r3] ldr r0, [r1] /* 當前激活的任務TCB第一項保存了任務堆棧的棧頂,現在棧頂值存入R0*/ ldmia r0!, {r4-r11} /* 出棧 */ msr psp, r0 isb bx r14 nop }
vTaskSwitchContext上下文切換函數如下。
任務需要延時的時候,會放棄CPU的使用權,進入阻塞狀態。在任務阻塞的這段時間,CPU可以去執行其它的任務(如果其它的任務也在延時狀態,那么 CPU 就將運行空閑任務),當任務延時時間到,重新獲取 CPU 使用權,任務繼續運行。
void vTaskSwitchContext( void ) { if( pxCurrentTCB == &IdleTaskTCB )//如果當前線程是空閑線程 { if(Task1TCB.xTicksToDelay == 0)//如果線程1延時時間結束 { pxCurrentTCB =&Task1TCB;//切換到線程1 } else if(Task2TCB.xTicksToDelay == 0)//如果線程2延時時間結束(線程1在延時中) { pxCurrentTCB =&Task2TCB;//切換到線程2 } else { return; /* 線程延時均沒有到期則返回,繼續執行空閑線程 */ } } else//當前任務不是空閑任務 { if(pxCurrentTCB == &Task1TCB)//如果當前線程是線程1 { if(Task2TCB.xTicksToDelay == 0)//如果線程2不在延時中 { pxCurrentTCB =&Task2TCB;//切換到線程2 } else if(pxCurrentTCB->xTicksToDelay != 0)//如果線程1進入延時狀態(線程2也在延時中) { pxCurrentTCB = &IdleTaskTCB;//切換到空閑線程 } else { return; /* 返回,不進行切換 */ } } else if(pxCurrentTCB == &Task2TCB)//如果當前線程是線程2 { if(Task1TCB.xTicksToDelay == 0)//如果線程1不在延時中 { pxCurrentTCB =&Task1TCB;//切換到線程1 } else if(pxCurrentTCB->xTicksToDelay != 0)//如果線程2進入延時狀態(線程1也在延時中) { pxCurrentTCB = &IdleTaskTCB;//切換到空閑線程 } else { return; /* 返回,不進行切換*/ } } } }
由上面代碼可知,vTaskSwitchContext上下文切換函數通過看xTicksToDelay是否為零,來判斷任務已經就緒or繼續延時。
xTicksToDelay以什么周期遞減,在哪遞減。這個周期由SysTick中斷提供。
SysTick
SysTick是系統定時器,重裝載數值寄存器的值遞減到0的時候,系統定時器就產生一次中斷,以此循環往復。
下面是SysTick的初始化。
//main函數里面 /* 啟動調度器,開始多任務調度,啟動成功則不返回 */ vTaskStartScheduler(); //task.c里面調用了xPortStartScheduler函數 void vTaskStartScheduler( void ) { //.....省略部分代碼 /* 啟動調度器 */ if( xPortStartScheduler() != pdFALSE ) { /* 調度器啟動成功,則不會返回,即不會來到這里 */ } } //port.c里面 //xPortStartScheduler調度器啟動函數,里面調用了vPortSetupTimerInterrupt函數初始化SysTick BaseType_t xPortStartScheduler( void ) { /* 配置PendSV 和 SysTick 的中斷優先級為最低 */ portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; /* 初始化SysTick */ vPortSetupTimerInterrupt(); /* 啟動第一個任務,不再返回 */ prvStartFirstTask(); /* 不應該運行到這里 */ return 0; } //system_ARMCM4.c文件 #define XTAL (50000000UL) /* Oscillator frequency */ #define SYSTEM_CLOCK (XTAL / 2U) //FreeRTOSConfig.h文件 //系統時鐘大小 #define configCPU_CLOCK_HZ ( ( unsigned long ) 25000000 ) //SysTick每秒中斷多少次,配置成100,10ms中斷一次 #define configTICK_RATE_HZ ( ( TickType_t ) 100 ) //下面初始化SysTick /* SysTick 控制寄存器 */ #define portNVIC_SYSTICK_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000e010 ) ) /*SysTick 重裝載寄存器*/ #define portNVIC_SYSTICK_LOAD_REG ( * ( ( volatile uint32_t * ) 0xe000e014 ) ) /*SysTick時鐘源的選擇*/ #ifndef configSYSTICK_CLOCK_HZ #define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ//configSYSTICK_CLOCK_HZ=configCPU_CLOCK_HZ /* 確保SysTick的時鐘與內核時鐘一致 */ #define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )//無符號長整形32位二進制,左移兩位 #else #define portNVIC_SYSTICK_CLK_BIT ( 0 ) #endif #define portNVIC_SYSTICK_INT_BIT ( 1UL << 1UL ) #define portNVIC_SYSTICK_ENABLE_BIT ( 1UL << 0UL ) //初始化SysTick的函數如下 void vPortSetupTimerInterrupt( void ) { /* 設置重裝載寄存器的值 */ portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL; /* 設置系統定時器的時鐘等于內核時鐘 使能SysTick 定時器中斷 使能SysTick 定時器 */ portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT ); }
初始化好SysTick,下面看看SysTick的中斷服務函數。
現在就明白了,xTicksToDelay是以SysTick的中斷周期遞減的。
// port.c文件,SysTick中斷服務函數 //里面調用了xTaskIncrementTick函數更新系統時基 void xPortSysTickHandler( void ) { /* 關中斷 進入臨界段*/ vPortRaiseBASEPRI(); /* 更新系統時基 */ xTaskIncrementTick(); /* 開中斷 退出臨界段*/ vPortClearBASEPRIFromISR(); } //task.c文件, static volatile TickType_t xTickCount = ( TickType_t ) 0U; void xTaskIncrementTick( void ) { TCB_t *pxTCB = NULL; BaseType_t i = 0; /* 更新系統時基計數器xTickCount,xTickCount是一個在port.c中定義的全局變量 */ const TickType_t xConstTickCount = xTickCount + 1; xTickCount = xConstTickCount;//把xTickCount加1 /* 掃描就緒列表中所有線程的xTicksToDelay,如果不為0,則減1 */ for(i=0; ixTicksToDelay > 0) { pxTCB->xTicksToDelay --; } } /* 任務切換 */ portYIELD(); }
實驗現象
這個里面就可以看到,高電平時間是20ms,剛好是阻塞延時的20ms。而且兩個任務波形相同,好像是CPU在同時做兩件事。這就是阻塞延時的好處。
為什么呢,
一開始,所有任務都沒有進入延時。
當一個任務放棄CPU后(進入延時),這一瞬間,CPU立即轉向運行另一個任務(另一個任務也立即進入延時)。這是因為uvTaskDelay阻塞延時函數里面調用了taskYIELD()任務切換函數。所以產生PendSV中斷,進入PendSV中斷服務函數xPortPendSVHandler。
在那個PendSV中斷服務函數里面,調用vTaskSwitchContext上下文切換函數,由于現在兩個任務都在延時過程中,就開始切到空閑任務。
等到重裝載數值寄存器的值遞減到0的時候,系統定時器就產生一次中斷,進入系統定時器的中斷函數中,改變xTicksToDelay,然后再次調用任務切換函數portYIELD()。目的是產生PendSV中斷,進入PendSV中斷服務函數。
然后再次調用vTaskSwitchContext上下文切換函數,判斷現在兩個任務是否還在延時,如果任務1不在延時,那么立即切到任務1,任務1里面又調用uvTaskDelay阻塞延時函數,再次套娃重復上面的活動。
所以波形上幾乎同步。
之前用軟件延時在任務函數里面寫delay(100),這就屬于cpu一直跑這個delay,跑完了才進行任務切換,如下圖所示,一個任務高低電平全搞完,才切到下一個任務。
原文鏈接:https://www.cnblogs.com/jiangyiming/p/16102499.html
相關推薦
- 2022-07-13 redis搭建哨兵集群的實現步驟_Redis
- 2022-05-27 .Net動態生成controller遇到的坑_實用技巧
- 2022-11-02 Kotlin協程的啟動方式介紹_Android
- 2023-12-02 nginx環境配置首頁可以訪問,路由頁面都404報錯
- 2022-08-21 Caffe卷積神經網絡solver及其配置詳解_python
- 2022-06-15 ASP.NET?MVC使用區域(Area)功能_基礎應用
- 2022-03-23 C語言實現貪吃蛇小黑窗_C 語言
- 2022-12-14 C++?Boost?shared_ptr共享指針詳細講解_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支