草庐IT

FreeRTOS学习记录----任务切换

雨落城 2023-03-28 原文

首先,先上结构图,请对照代码理解。

 

 

(一)什么是任务切换?

  任务切换就是在就绪列表里面寻找优先级最高的就绪任务,然后执行该任务。

(二)任务什么时候切换?

  1)、当执行系统调用的时候,进行任务切换。

  2)、当发生滴答定时器(systick)中断的时候,进行任务切换。

情况1:执行系统调用时

  所谓的系统调用就是指执行taskYIELD()函数。taskYIELD()是一个宏。
   

#define taskYIELD()    portYIELD()

  接着往下看 portYIELD()函数,它也是一个宏。

#define portYIELD() 
{ 
    portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 

    __dsb( portSY_FULL_READ_WRITE ); 
    __isb( portSY_FULL_READ_WRITE ); 
}

  通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSV,来启动PendSV中断。portNVIC_PENDSVSET_BIT 的值为如下宏定义。

#define portNVIC_PENDSVSET_BIT        ( 1UL << 28UL )

  

情况2:发生滴答定时器(systick)中断的时候

  OK,第一种情况很简单,来看看第二种情况,

void SysTick_Handler(void)
{
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
      xPortSysTickHandler();
    }
}

  定时器中断中,调用了 xPortSysTickHandler()函数。此函数具体如下:

void xPortSysTickHandler( void )
{
    vPortRaiseBASEPRI();  //关闭中断
    {
        if( xTaskIncrementTick() != pdFALSE ) //增加时钟计数器 xTickCount 的值
        {
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;  //置为ICSR寄存器bit28来挂起PendSV异常。
        }
    }
    vPortClearBASEPRIFromISR(); //打开中断
}

 可以看出 xPortSysTickHandler()函数,和portYIELD()函数是通过一样的方法来启动任务切换的。

 

(三)如何进行任务切换?

  PendSV中断中的xPortPendSVHandler()是真正实现任务切换的地方,我们来看看源码:

__asm void xPortPendSVHandler( void )
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;

    PRESERVE8        //栈的8字节对齐

    mrs r0, psp          //读取当前psp进程指针,存入r0
    isb
    /* 获取当前任务控制块 */
    ldr    r3, =pxCurrentTCB  //把pxCurrentTCB的地址给R3,(注意pxCurrrentTCB本身是指针变量),所以r3是地址的地址。
    ldr    r2, [r3]        //把r3地址中的值给r2,r2中就存储当前的任务控制块
    stmdb r0!, {r4-r11, r14}    // 以R0为基地址,依次向下递减,将寄存器r4-r11存储到任务栈。             

    /* 保存最新的栈顶指针到当前任务控制块的第一字段*/
    str r0, [r2]          //把r0的值存入r2的地址,相当于*r2 = r0

    stmdb sp!, {r3}      //将寄存器R3的值临时压栈,寄存器r3中仍然保存着当前任务的任务控制块,
                         //而接下来要调用函数vTaskSwitchContext,防止r3的值被改写,故临时压栈
    
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0         //关中断,进入临界区
    
    dsb
    isb
    bl vTaskSwitchContext  //调用函数vTaskSwitchContext,此函数用来获取下一个要运行的任务,并将pxCurrentTCB更新为要运行的这个任务
    
    mov r0, #0
    msr basepri, r0      //开中断,退出临界区
    
    ldmia sp!, {r3}   //刚刚保存的寄存器R3的值出栈,恢复寄存器R3的值。注意,经过调用函数vTaskSwitchContext,此时
                      //pxCurrentTCB的值已经改变了,所以读取R3所保存的地址处的数据就会发现其值改变了,成
                      //为了下一个要运行的任务的任务控制块。


    ldr r1, [r3]
    ldr r0, [r1]     //获取新的运行任务的栈顶,并存到r0中去

    /* 出栈内核寄存器中的值 */
    ldmia r0!, {r4-r11, r14}  //含义::依次出栈 ,将任务栈的值依次出栈赋值给r4-r11。地址向上递增。  

    msr psp, r0 //更新进程栈指针PSP的值
    isb
    bx r14    //执行此行代码以后硬件自动恢复寄存器R0~R3、R12、LR、PC和xPSR的值,
          确定异常返回以后应该进入处理器模式还是进程模式,使用主栈指针(MSP)还是进程栈指针(PSP)。         
          很明显这里会进入进程模式,并且使用进程栈指针(PSP), 寄存器PC值会被恢复为即将运行的任务的任务函数,新的任务开始运行!
         
}

  ok。这段PendSV中断服务函数还是比较难以理解的,不懂的可以跳过。接下来看看调用的vTaskSwitchContext()来获取下一个要运行的任务是怎么操作的。

void vTaskSwitchContext( void )
{
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ) //如果任务调度器挂起,那么不进行任务切换
    {
        xYieldPending = pdTRUE;       
    }
    else
    {
        xYieldPending = pdFALSE;
        traceTASK_SWITCHED_OUT();
     taskCHECK_FOR_STACK_OVERFLOW();
     taskSELECT_HIGHEST_PRIORITY_TASK(); //调用函数 taskSELECT_HIGHEST_PRIORITY_TASK()获取下一个要运行的任务。
     traceTASK_SWITCHED_IN();
  } 
} 

  taskSELECT_HIGHEST_PRIORITY_TASK()本质上是一个宏,在 tasks.c 中有定义。
  FreeRTOS 中查找下一个要运行的任务有两种方法:一个是通用的方法,另外一个就是使用
硬件的方法,这个在我们讲解 FreeRTOSCofnig.h 文件的时候就提到过了,至于选择哪种方法通
过宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 来决定的。当这个宏为 1 的时候就使
用硬件的方法,否则的话就是使用通用的方法。
 

1、通用方法:

#define taskSELECT_HIGHEST_PRIORITY_TASK() 
{ 
    UBaseType_t uxTopPriority = uxTopReadyPriority; 
    while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) 
    {
    configASSERT( uxTopPriority ); 
    --uxTopPriority; 
    } 
  listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); 
  uxTopReadyPriority
= uxTopPriority; }

 

  pxReadyTasksLists[]为就绪任务列表数组,每一个优先级都有一个就绪列表。通用方法就是通过while循环,从最高优先级 uxTopReadyPriority开始,

循环判断就绪列表中,哪个不为空。然后再将 uxTopPriority递减,并且记录有就绪任务的优先级。

  找到了有就绪任务的优先级之后,接下来调用 listGET_OWNER_OF_NEXT_ENTRY()来获得列表中的一个列表项,然后将获得列表项所获取到的任务控制块给pxCurrentTCB,这样就找到了下一个要运行的任务。
      这种方法对于任务的数量没有限制,效率不高。

 

2、硬件方法:

 

#define taskSELECT_HIGHEST_PRIORITY_TASK() 
{ 
    UBaseType_t uxTopPriority; 
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority); //获取处于就绪态的最高优先级;
configASSERT( listCURRENT_LIST_LENGTH(
& ( pxReadyTasksLists[ uxTopPriority ] ) )> 0 );
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB,
&( pxReadyTasksLists[ uxTopPriority ] ) ); //这一步与通用方法一样;获得列表中的列表项,
//然后获取相应的任务控制块给pxCurrrentTCB; }

 

  portGET_HIGHEST_PRIORITY 本质上是个宏,定义如下

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )  uxTopPriority = ( 31UL- ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

   注意使用硬件方法的时候,uxTopReadyPriority 就不代表处于就绪态的最高优先级了,而是每个bit 代表一个优先级,bit0 代表优先级0,当某个优先级有任务的话,就将相应的bit置为1。

  __clz(uxReadyPriorities)就是计算 uxReadyPriorities 的前导零个数,前导零个数就是指从最高位开始(bit31)到第一个为 的 bit,其间 的个数。然后用31减去前导0个数,就得到处于就绪态的最高优先级了。


 

 

 

 

 

有关FreeRTOS学习记录----任务切换的更多相关文章

  1. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  2. ruby-on-rails - Ruby on Rails with Haml - 如何从 erb 切换 - 2

    我正在从erb文件切换到HAML。我将hamlgem添加到我的系统中。我创建了app/views/layouts/application.html.haml文件。我应该只删除application.html.erb文件吗?此外,仍然有/public/index.html文件被呈现为默认页面。我想创建自己的默认index.html.haml页面。我应该把它放在哪里以及如何使系统呈现该文件而不是默认索引文件?谢谢! 最佳答案 是的,您可以删除任何已转换为HAML的View的ERB版本。至于你的另一个问题,删除public/index/h

  3. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

    如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

  4. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  5. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

    我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

  6. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  7. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  8. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  9. ruby-on-rails - 事件记录 : Select max of limit - 2

    我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

  10. ruby-on-rails - Rake 任务仅调用一次时执行两次 - 2

    我写了一个非常简单的rake任务来尝试找到这个问题的根源。namespace:foodotaskbar::environmentdoputs'RUNNING'endend当在控制台中执行rakefoo:bar时,输出为:RUNNINGRUNNING当我执行任何rake任务时会发生这种情况。有没有人遇到过这样的事情?编辑上面的rake任务就是写在那个.rake文件中的所有内容。这是当前正在使用的Rakefile。requireFile.expand_path('../config/application',__FILE__)OurApp::Application.load_tasks这里

随机推荐