个人名片:
🎓作者简介:嵌入式领域优质创作者
**🌐个人主页:**妄北y**📞个人QQ:**2061314755
💌个人邮箱: [mailto:2061314755@qq.com]
📱**个人微信:**Vir2025WBY
🖥️个人公众号: 科技妄北
🖋️本文为妄北y原创佳作,独家首发于CSDN🎊🎊🎊
**💡座右铭:**改造世界固然伟大,但改造自我更为可贵。
专栏导航:
妄北y系列专栏导航:
物联网嵌入式开发项目:大学期间的毕业设计,课程设计,大创项目,各种竞赛项目,全面覆盖了需求分析、方案设计、实施与调试、成果展示以及总结反思等关键环节。📚💼💡
QT基础入门学习:对QT的基础图形化页面设计进行了一个简单的学习与认识,利用QT的基础知识进行了翻金币小游戏的制作。🛠️🔧💭
Linux基础编程:初步认识什么是Linux,为什么学Linux,安装环境,进行基础命令的学习,入门级的shell编程。🍻🎉🖥️
深耕Linux应用开发:分享Linux的基本概念、命令行操作、文件系统、用户和权限管理等,网络编程相关知识,TCP/IP 协议、套接字(Socket)编程等,可以实现网络通信功能。常见开源库的二次开发,如libcurl、OpenSSL、json-c、freetype等💐📝💡
Linux驱动开发:Linux驱动开发是Linux系统不可或缺的组成部分,它专注于编写特殊的程序------驱动程序。这些程序承载着硬件设备的详细信息,并扮演着操作系统与硬件间沟通的桥梁角色。驱动开发的核心使命在于确保硬件设备在Linux系统上顺畅运作,同时实现与操作系统的无缝集成,为用户带来流畅稳定的体验。🚀🔧💻
Linux项目开发:Linux基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🌱🚀
非常期待与您一同在这个广阔的互联网天地里,携手探索知识的海洋,互相学习,共同进步。🌐💫🌱 熠熠星光,照亮我们的成长之路
✨✨欢迎订阅本专栏,对专栏内容任何问题都可以随时联系博主,共同书写属于我们的精彩篇章!✨✨
文章介绍:
📚本篇文章将深入剖析RTOS学习的精髓与奥秘,与您一同分享相关知识!🎉🎉🎉
若您觉得文章尚可入目,期待您能慷慨地送上点赞、收藏与分享的三连支持!您的每一份鼓励,都是我创作路上源源不断的动力。让我们携手并进,共同奔跑,期待在顶峰相见的那一天,共庆辉煌!🚀🚀🚀
🙏衷心感谢大家的点赞👍、收藏⭐和评论✍️,您的支持是我前进的动力!
一、内核控制函数
内核控制函数就是FreeRTOS内核所使用的函数,一般情况下应用层程序不使用这些函数,在FreeRTOS官网可以找到这些函数,如图所示:
这些函数含义如下:
二、内核控制函数详解
2.1 函数task YIELD()
FreeRTOS中的一些**API函数也会调用 taskYIELD() 函数,这些API函数会导致任务切换。**这些API函数和任务切换函数 taskYIELD() 统称为系统调用。实际上,taskYIELD() 函数是一个宏,在文件 task.h 中有如下定义:
换句话说,**系统调用是指调用FreeRTOS系统提供的API函数,这些函数包括任务切换函数 taskYIELD()。**这些API函数会引发任务切换,导致当前任务暂停,切换到另一个任务执行。 taskYIELD() 函数实际上是在文件 task.h中定义的一个宏。
在FreeRTOS中,执行系统调用意味着调用系统提供的相关API函数,这些函数会导致任务切换。一个典型的任务切换函数是 taskYIELD()
。实际上, taskYIELD()
是一个宏,在 task.h
文件中有如下定义:
cpp
#define taskYIELD() portYIELD()
portYIELD()
也是一个宏,在 portmacro.h
文件中定义:
cpp
#define portYIELD() \
{ \
/* 通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSV来启动PendSV中断 */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
__DSB(portSY_FULL_READ_WRITE); \
__ISB(portSY_FULL_READ_WRITE); \
}
1. 启动PendSV中断:
portYIELD()
会向中断控制和状态寄存器(ICSR)的bit28写入1,从而挂起PendSV(Pendable Service Call)中断。- 这会触发PendSV中断服务函数的执行,进行任务切换。
2. 内存屏障指令:
__DSB(portSY_FULL_READ_WRITE)
和__ISB(portSY_FULL_READ_WRITE)
是数据同步屏障(DSB)和指令同步屏障(ISB)指令,用于确保内存操作顺序和指令执行顺序。
在中断级别也可以进行任务切换,使用的是 portYIELD_FROM_ISR()
宏。定义如下:
cpp
#define portEND_SWITCHING_ISR(xSwitchRequired) \
if (xSwitchRequired != pdFALSE) { \
portYIELD(); \
}
#define portYIELD_FROM_ISR(x) portEND_SWITCHING_ISR(x)
2.2 函数taskENTER_CRITICAL()与taskEXIT_CRITICAL()
在FreeRTOS中,临界区用于保护共享资源,确保在多个任务或中断访问同一资源时不会发生数据竞争。用于进入和退出临界区,taskENTER_CRITICAL()
和 taskEXIT_CRITICAL()
是用于任务级的临界代码保护,它们必须成对使用。
在FreeRTOS中,宏 taskENTER_CRITICAL()
和 taskEXIT_CRITICAL()
被定义为调用相应的端口特定函数:
cpp
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
而 portENTER_CRITICAL()
和 portEXIT_CRITICAL()
宏在 portmacro.h
文件中被定义为调用端口特定的实现函数:
cpp
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
在 port.c
文件中,vPortEnterCritical()
和 vPortExitCritical()
函数的实现如下:
cpp
// 用于记录临界区嵌套次数的全局变量
static UBaseType_t uxCriticalNesting = 0;
void vPortEnterCritical(void)
{
// 关闭中断
portDISABLE_INTERRUPTS();
// 增加嵌套计数
uxCriticalNesting++;
if (uxCriticalNesting == 1)
{
// 确保没有嵌套在中断上下文中
configASSERT((portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK) == 0);
}
}
void vPortExitCritical(void)
{
// 确保嵌套计数不为零
configASSERT(uxCriticalNesting > 0);
// 减少嵌套计数
uxCriticalNesting--;
if (uxCriticalNesting == 0)
{
// 使能中断
portENABLE_INTERRUPTS();
}
}
1. 进入临界区 (vPortEnterCritical
):
- 关闭中断,以确保临界区代码不会被中断打断。
- 递增嵌套计数
uxCriticalNesting
。 - 如果嵌套计数为1,确保当前不在中断上下文中执行。
- 退出临界区 (
vPortExitCritical
):
- 检查嵌套计数,确保其大于0。
- 递减嵌套计数
uxCriticalNesting
。 - 如果嵌套计数为0,重新使能中断。
2.3 函数taskENTER CRITICAL FROM ISR()与taskEXIT CRITICAL FROM ISR()
在FreeRTOS中,**taskENTER_CRITICAL_FROM_ISR()
和 taskEXIT_CRITICAL_FROM_ISR()
用于中断服务程序(ISR)中的临界区保护,进入和退出临界区,用于中断服务函数中。**与任务级的临界区保护类似,这些宏用于确保在处理共享资源时不会受到中断的干扰。
在FreeRTOS中,taskENTER_CRITICAL_FROM_ISR()
和 taskEXIT_CRITICAL_FROM_ISR(x)
被定义为调用端口特定的函数:
cpp
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
而 portSET_INTERRUPT_MASK_FROM_ISR()
和 portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
在 portmacro.h
文件中被定义为调用端口特定的实现函数:
cpp
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
ulPortRaiseBASEPRI()
这个函数用于提升 BASEPRI
寄存器的值,以屏蔽低优先级中断:
cpp
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI(void)
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
// 将当前的BASEPRI值保存到ulReturn中
asm volatile (
"mrs %0, basepri \n" // (1)
"msr basepri, %1 \n" // (2)
"dsb \n"
"isb \n"
: "=r" (ulReturn)
: "r" (ulNewBASEPRI)
);
return ulReturn; // (3)
}
1. 保留当前 BASEPRI
值 : 从 BASEPRI
寄存器中读取当前值并保存在 ulReturn
中。
2. 设置新的 BASEPRI
值 : 将 configMAX_SYSCALL_INTERRUPT_PRIORITY
写入 BASEPRI
寄存器,以屏蔽低优先级中断。
3. 返回原始 BASEPRI
值: 用于退出临界区时恢复。
中断级临界代码保护方法如下:
cpp
// 定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) // 检查是否为溢出中断
{
// 进入临界区,保存原始的BASEPRI值
uint32_t status_value = taskENTER_CRITICAL_FROM_ISR();
{
// 临界区代码,保护共享资源 total_num
total_num += 1;
printf("total_num 的值为:%d\n", total_num);
}
// 退出临界区,恢复原始的BASEPRI值
taskEXIT_CRITICAL_FROM_ISR(status_value);
// 清除中断标志位
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
2.4 函数taskENABLE INTERRUPTS()与taskENABLE INTERRUPTS()
用于关闭进入中断。
在FreeRTOS中,portDISABLE_INTERRUPTS()
和portENABLE_INTERRUPTS()
宏定义如下:
cpp
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
这些宏在portmacro.h
文件中定义,并调用相应的函数来控制中断。
以下是vPortSetBASEPRI()
和vPortRaiseBASEPRI()
函数的实现:
cpp
static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
{
asm volatile (
"msr basepri, %0\n"
:
: "r" (ulBASEPRI)
);
}
static portFORCE_INLINE void vPortRaiseBASEPRI(void)
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
asm volatile (
"msr basepri, %0\n"
"dsb\n"
"isb\n"
:
: "r" (ulNewBASEPRI)
);
}
1. vPortSetBASEPRI(uint32_t ulBASEPRI)
:
- 这个函数向
BASEPRI
寄存器写入一个值,该值作为参数ulBASEPRI
传递进来。 - 当传递的值为0时,表示开启中断,因为0值不会屏蔽任何中断。
2. vPortRaiseBASEPRI()
:
- 这个函数向
BASEPRI
寄存器写入configMAX_SYSCALL_INTERRUPT_PRIORITY
的值。 - 这个值会屏蔽所有优先级低于
configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断。
2.5 函数vTaskStartScheduler()与vTaskEndScheduler()
vTaskStartScheduler()
和vTaskEndScheduler()
函数分别用于启动和关闭任务调度器。
vTaskStartScheduler()
用于启动FreeRTOS任务调度器。这是一个关键函数,它启动任务调度并将控制权交给任务,通常在主程序的最后调用vTaskStartScheduler()
来启动调度。调用此函数后,任务调度器开始运行,任务按其优先级和调度策略被执行。
以下是vTaskStartScheduler()
函数的简化实现示意:
cpp
void vTaskStartScheduler(void)
{
// 配置定时器中断,用于实现系统节拍
prvSetupTimerInterrupt();
// 启动第一个任务
portRESTORE_CONTEXT();
// 此函数通常不会返回,除非调度器停止
for(;;);
}
vTaskEndScheduler()
用于关闭FreeRTOS任务调度器。这个函数在大多数移植中未实现,因为关闭调度器通常不会被使用。
在需要停止调度器时调用此函数。调用后,调度器停止,系统返回到调用vTaskStartScheduler()
之前的状态。
以下是vTaskEndScheduler()
函数的典型实现:
cpp
void vTaskEndScheduler(void)
{
// 停止系统节拍的生成
portDISABLE_INTERRUPTS();
// 恢复到调度器启动前的状态
// 此部分具体实现取决于硬件和移植层
}
2.6 函数VTaskSuspendAll()与函数xTaskResumeAll()
vTaskSuspendAll()和
xTaskResumeAll()函数用于挂起和恢复任务调度器。
在文件tasks.c
中,vTaskSuspendAll()
函数的定义如下:
cpp
void vTaskSuspendAll(void)
{
++uxSchedulerSuspended;
}
uxSchedulerSuspended
是一个挂起嵌套计数器。- 每次调用
vTaskSuspendAll()
,uxSchedulerSuspended
加一,表示调度器被挂起的次数。 - 支持嵌套挂起,即可以多次调用
vTaskSuspendAll()
。
在文件tasks.c
中,xTaskResumeAll()
函数的定义如下:
cpp
BaseType_t xTaskResumeAll(void)
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
configASSERT(uxSchedulerSuspended);
taskENTER_CRITICAL();
{
--uxSchedulerSuspended;
if (uxSchedulerSuspended == (UBaseType_t)pdFALSE)
{
if (uxCurrentNumberOfTasks > (UBaseType_t)0U)
{
while (listLIST_IS_EMPTY(&xPendingReadyList) == pdFALSE)
{
pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(&xPendingReadyList);
(void)uxListRemove(&(pxTCB->xEventListItem));
(void)uxListRemove(&(pxTCB->xStateListItem));
prvAddTaskToReadyList(pxTCB);
if (pxTCB->uxPriority > pxCurrentTCB->uxPriority)
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
if (xYieldPending != pdFALSE)
{
#if (configUSE_PREEMPTION != 0)
{
xAlreadyYielded = pdTRUE;
#endif
taskYIELD_IF_USING_PREEMPTION();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
return xAlreadyYielded;
}
1. 进入临界区:
taskENTER_CRITICAL();
:进入临界区,保护调度器状态。
2. 减少挂起计数器:
--uxSchedulerSuspended;
:减少挂起计数器,表示解除一次挂起。
- 检查是否完全恢复:
if (uxSchedulerSuspended == (UBaseType_t)pdFALSE)
:如果挂起计数器为0,表示调度器完全恢复。
4.处理挂起的任务:
while (listLIST_IS_EMPTY(&xPendingReadyList) == pdFALSE)
:循环处理xPendingReadyList
中的任务。pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(&xPendingReadyList);
:获取挂起的任务控制块。(void)uxListRemove(&(pxTCB->xEventListItem));
:从事件列表中移除任务。(void)uxListRemove(&(pxTCB->xStateListItem));
:从状态列表中移除任务。prvAddTaskToReadyList(pxTCB);
:将任务添加到就绪列表中。if (pxTCB->uxPriority > pxCurrentTCB->uxPriority)
:如果任务优先级高于当前任务,设置xYieldPending
为pdTRUE
。
5. 任务切换:
if (xYieldPending != pdFALSE)
:如果需要任务切换。xAlreadyYielded = pdTRUE;
:标记在xTaskResumeAll()
中进行了任务切换。taskYIELD_IF_USING_PREEMPTION();
:进行任务切换。
- 退出临界区:
taskEXIT_CRITICAL();
:退出临界区。
vTaskSuspendAll()
:挂起任务调度器,增加挂起计数器。
xTaskResumeAll()
:恢复任务调度器,减少挂起计数器,处理挂起的任务,并进行必要的任务切换。
2.7 VTaskStepTick()
FreeRTOS的低功耗tickless模式通过停止系统时钟节拍中断来减少功耗。**在这种模式下,当系统进入空闲状态时,系统时钟中断会被停止,并且在系统从低功耗模式退出时需要补偿这段暂停时间。用于设置系统节拍值,****用于在tickless模式下补偿系统时钟暂停的时间。**补偿的工作由vTaskStepTick()
函数完成。
在文件tasks.c
中,vTaskStepTick()
函数的定义如下:
cpp
void vTaskStepTick(const TickType_t xTicksToJump)
{
// 确保跳过的tick数不会超过下一个任务需要解除阻塞的时间
configASSERT((xTickCount + xTicksToJump) <= xNextTaskUnblockTime);
// 更新系统时间
xTickCount += xTicksToJump;
// 记录系统时间更新的事件
traceINCREASE_TICK_COUNT(xTicksToJump);
}
📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!
❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊
💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!
🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈