FreeRTOS临界段的保护与阻塞延时

4.1什么是临界段
        临界段用一句话概括就是一段在执行的时候不能被中断的代码段。在 FreeRTOS 里面, 这个临界段最常出现的就是对全局变量的操作,全局变量就好像是一个枪把子,谁都可以对他开枪,但是我开枪的时候,你就不能开枪,否则就不知道是谁命中了靶子。可能有人会说我可以在子弹上面做个标记,我说你能不能不要瞎扯淡。
        那么什么情况下临界段会被打断?一个是系统调度,还有一个就是外部中断。在 FreeRTOS,系统调度,最终也是产生 PendSV 中断,在 PendSV Handler 里面实现任务的切 换,所以还是可以归结为中断。既然这样,FreeRTOS 对临界段的保护最终还是回到对中断的开和关的控制。

4.2Cortex-M内核快速关中断指令

CPSID I ;PRIMASK=1       ;关中断
CPSIE I ;PRIMASK=0       ;开中断
CPSID F ;FAULTMASK=1       ;关异常
CPSIE F ;FAULTMASK=0       ;开异常

        在 FreeRTOS 中,对中断的开和关是通过操作 BASEPRI 寄存器来实现的,即大于等于 BASEPRI 的值的中断会被屏蔽,小于 BASEPRI 的值的中断则不会被屏蔽,不受 FreeRTOS 管理。用户可以设置 BASEPRI 的值来选择性的给一些非常紧急的中断留一条后路.

5.1空闲任务与阻塞延时的实现

        在前面的介绍中,任务体内的延时使用的是软件延时,即还是让 CPU 空等来达到延时的 效果。使用 RTOS 的很大优势就是榨干 CPU 的性能,永远不能让它闲着,任务如果需要延 时也就不能再让 CPU 空等来实现延时的效果。RTOS 中的延时叫阻塞延时,即任务需要延 时的时候,任务会放弃 CPU 的使用权,CPU 可以去干其它的事情,当任务延时时间到,重 新获取 CPU 使用权,任务继续运行,这样就充分地利用了 CPU 的资源,而不是干等着。
        当任务需要延时,进入阻塞状态,那 CPU 又去干什么事情了?如果没有其它任务可以运行,RTOS 都会为 CPU 创建一个空闲任务,这个时候 CPU 就运行空闲任务。在 FreeRTOS 中,空闲任务是系统在【启动调度器】的时候创建的优先级最低的任务,空闲任务主体主要是做一些系统内存的清理工作。但是为了简单起见,我们本章实现的空闲任务 只是对一个全局变量进行计数。鉴于空闲任务的这种特性,在实际应用中,当系统进入空 闲任务的时候,可在空闲任务中让单片机进入休眠或者低功耗等操作。

5.2创建空闲任务

        目前我们在创建任务时使用的栈和 TCB 都使用的是静态的内存,即需要预先定义好内
存,空闲任务也不例外。

#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) 
StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; 

//定义空闲任务的任务控制块
TCB_t IdleTaskTCB;

//创建空闲任务
/*=======================创建空闲任务 start=======================*/ 
TCB_t *pxIdleTaskTCBBuffer = NULL; /* 用于指向空闲任务控制块 */
StackType_t *pxIdleTaskStackBuffer = NULL; /* 用于空闲任务栈起始地址 */
uint32_t ulIdleTaskStackSize;

/* 获 取 空 闲任务的内存: 任务栈和任务 TCB */ (1)
     vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,
                                  &pxIdleTaskStackBuffer,
                                   &ulIdleTaskStackSize );
 /* 创建空闲任务 */ (2)
   xIdleTaskHandle =xTaskCreateStatic((TaskFunction_t)prvIdleTask, /* 任务入口 */
                                        (char *)"IDLE", /* 任务名称,字符串形式 */
                                        (uint32_t)ulIdleTaskStackSize , /* 任务栈大小,单位为字 */ 
                                        (void *) NULL, /* 任务形参 */
                                        (StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */ 
                                          (TCB_t *)pxIdleTaskTCBBuffer ); 

       /* 任务控制块 */ /* 手动指定第一个运行的任务 */
   pxCurrentTCB = &Task1TCB;

     /* 启动调度器 */
    if ( xPortStartScheduler() != pdFALSE ) 
    {
     /* 调度器启动成功,则不会返回,即不会来到这里 */ 36 }
       }

5.3实现阻塞延时

        阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻 塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。在任务阻塞的这段时 间,CPU 可以去执行其它的任务,如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务。

//阻塞延时函数
void vTaskDelay( const TickType_t xTicksToDelay ) 
{
      TCB_t *pxTCB = NULL;

    /* 获取当前任务的 TCB */
        pxTCB = pxCurrentTCB; (1)

     /* 设置延时时间 */
    pxTCB->xTicksToDelay = xTicksToDelay; (2)

    /* 任务切换 */
    taskYIELD(); (3)
}

5.4main函数

*
************************************************************************* * 包含的头文件 *************************************************************************  */
#include "FreeRTOS.h"
#include "task.h"


*************************************************************************
 * 全局变量
*************************************************************************  */
portCHAR flag1;
portCHAR flag2;

extern List_t pxReadyTasksLists[ configMAX_PRIORITIES ];

/*
*************************************************************************  * 任务控制块 & STACK
 *************************************************************************  */
TaskHandle_t Task1_Handle;
#define TASK1_STACK_SIZE 128
StackType_t Task1Stack[TASK1_STACK_SIZE];
TCB_t Task1TCB;

TaskHandle_t Task2_Handle;
#define TASK2_STACK_SIZE 128
StackType_t Task2Stack[TASK2_STACK_SIZE];
TCB_t Task2TCB;

/*
************************************************************************* 38 * 函数声明
************************************************************************* 40 */
void delay (uint32_t count);
void Task1_Entry( void *p_arg );
void Task2_Entry( void *p_arg );

int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */

/* 初始化与任务相关的列表,如就绪列表 */
   prvInitialiseTaskLists();

 /* 创建任务 */
   Task1_Handle = xTaskCreateStatic((TaskFunction_t)Task1_Entry,/* 任务入口 */
                                    (char *)"Task1",/* 任务名称,字符串形式 */ 
                                    (uint32_t)TASK1_STACK_SIZE ,/* 任务栈大小,单位为字 */ 
                                    (void *) NULL,/* 任务形参 */
                                    (StackType_t *)Task1Stack,/* 任务栈起始地址 */
                                    (TCB_t *)&Task1TCB ); /* 任务控制块 */

/* 将任务添加到就绪列表 */
 vListInsertEnd( &( pxReadyTasksLists[1] ),
                  &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );

    Task2_Handle =xTaskCreateStatic((TaskFunction_t)Task2_Entry,
                                    (char *)"Task2",
                                    (uint32_t)TASK2_STACK_SIZE ,
                                    (void *) NULL,
                                    (StackType_t *)Task2Stack,
                                    (TCB_t *)&Task2TCB );
/* 将任务添加到就绪列表 */
   vListInsertEnd( &( pxReadyTasksLists[2] ),
                   &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );

/* 启动调度器,开始多任务调度,启动成功则不返回 */
   vTaskStartScheduler();
    
    for (;;)
    {
         /* 系统启动成功不会到达这里 */
    }
}

/*************************************************************************** 
函数实现
**************************************************************************/
/* 软件延时 */
void delay (uint32_t count)
{
    for (; count!=0; count--); 
}
/* 任务1 */
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
    }
}

/* 任务2 */
void Task2_Entry( void *p_arg )
{
    for ( ;; )
{
#if 0
         flag2 = 1;
        delay( 100 );
        flag2 = 0;
        delay( 100 );

/* 任务切换,这里是手动切换 */
       portYIELD();
#else
         flag2 = 1;
         vTaskDelay( 2 );                             
         flag2 = 0;
         vTaskDelay( 2 );
#endif
     }
}

/* 获取空闲任务的内存 */
StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; 
TCB_t IdleTaskTCB;
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer,
                                StackType_t **ppxIdleTaskStackBuffer,
                                uint32_t *pulIdleTaskStackSize )

{
                        *ppxIdleTaskTCBBuffer=&IdleTaskTCB;
                        *ppxIdleTaskStackBuffer=IdleTaskStack;
                        *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}    

  进入软件调试,全速运行程序,从逻辑分析仪中可以看到两个任务的波形是完全同步,就好像 CPU 在同时干两件事情。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kitter Gourd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值