大家晚上好,我是杰杰,最近挺忙的,好久没有更新了,今天周末就吐血更新一下吧!

# 前言

`FreeRTOS`是一个是实时内核,任务是程序执行的最小单位,也是调度器处理的基本单位,移植了`FreeRTOS`,则避免不了对任务的管理,在多个任务运行的时候,任务切换显得尤为重要。而任务切换的效率会决定了系统的稳定性与效率。

`FreeRTOS`的任务切换是干嘛的呢,`rtos`的实际是永远运行的是具有最高优先级的运行态任务,而那些之前在就绪态的任务怎么变成运行态使其得以运行呢,这就是我们`FreeRTOS`任务切换要做的事情,它要做的是找到最高优先级的就绪态任务,并且让它获得cpu的使用权,这样,它就能从就绪态变成运行态,这样子,整个系统的实时性就会很好,响应也会很好,而不会让程序阻塞卡死。

要知道怎么实现任务切换,那就要知道任务切换的机制,在不同的`cpu(mcu)`中,触发的方式可能会不一样,现在是以Cortex-M3为例来讲讲任务的切换。为了大家能看懂本文,我就抛转引玉一下,`引用《Cortex-M3权威指南-中文版》的部分语句(如涉及侵权,请联系杰杰删除)`

# SVC 和 PendSV

SVC(系统服务调用,亦简称系统调用)和 `PendSV`(`Pended System Call`,可悬起系统调用),它们多用于在操作系统之上的软件开发中。`SVC` 用于产生系统函数的调用请求。例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个 `SVC` 异常,然后操作系统提供的 `SVC` 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。

另一个相关的异常是` PendSV`(可悬起的系统调用),它和 `SVC` 协同使用。一方面,`SVC`异常是必须立即得到响应的(若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬 fault——译者注),应用程序执行 `SVC` 时都是希望所需的请求立即得到响应。另一方面,PendSV 则不同,它是可以像普通的中断一样被悬起的(不像 `SVC` 那样会上访)。OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。悬起 `PendSV` 的方法是:手工往 `NVIC` 的` PendSV` 悬起寄存器中写 1。悬起后,如果优先级不够高,则将缓期等待执行。

如果一个发生的异常不能被即刻响应,就称它被“悬起”(pending)。不过,少数 fault异常是不允许被悬起的。一个异常被悬起的原因,可能是系统当前正在执行一个更高优先级异常的服务例程,或者因相关掩蔽位的设置导致该异常被除能。对于每个异常源,在被悬起的情况下,都会有一个对应的“悬起状态寄存器”保存其异常请求,直到该异常能够执行为止,这与传统的 ARM 是完全不同的。在以前,是由产生中断的设备保持住请求信号。现在NVIC 的悬起状态寄存器的出现解决了这个问题,即使后来设备已经释放了请求信号,曾经的中断请求也不会错失。

# 系统任务切换的工程分析

在系统中正常执行的任务(假设没有外部中断`IRQ`),用`Systick`直接做上下文切换是完全没有问题的,如图:

![switch](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203844583-458792078.png)

但是问题是几乎很少嵌入式的设备会不用其丰富的中断响应,所以,直接用systick做系统的上下文切换那是不实际的,这存在很大的风险,因为假设`systick`打断了一个中断(`IRQ`),立即做出上下文切换的话,则触犯用法 `fault `异常,除了重启你没有其他办法了,这样子做出来的产品就是垃圾!!用我老板的话说就是写的什么狗屎!!!如图所示:

![IRQ-switch](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203845053-931417081.png)

那这么说这样不行那也不行,怎么办啊?请看看前面接介绍的`PendSV`,是不是有点豁然开朗了?`PendSV` 来完美解决这个问题。`PendSV` 异常会自动延迟上下文切换的请求,直到其它的` ISR` 都完成了处理后才放行。为实现这个机制,需要把 `PendSV` 编程为最低优先级的异常。如果` OS `检测到某 `IRQ `正在活动并且被 SysTick 抢占,它将悬起一个 `PendSV` 异常,以便缓期执行上下文切换。

懂了吗?就是说,只要将`PendSV`的优先级设为最低的,systick即使是打断了IRQ,它也不会马上进行上下文切换,而是等到IRQ执行完,`PendSV` 服务例程才开始执行,并且在里面执行上下文切换。过程如图所示:

![pendsv-switch](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203845522-40802098.png)

# 任务切换的源码实现

过程差不多了解了,那看看FreeRTOS中怎么实现吧!!

FreeRTOS有两种方法触发任务切换:

1. 一种就是`systick`触发`PendSV`异常,这是最经常使用的。

2. 另一种是主动进行切换任务,执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用`portYIELD_FROM_ISR()`强制任务切换。

## 第一种
先说说第一种吧,就在`systick`中断中调用`xPortSysTickHandler()`;

下面是源码:
```js
void xPortSysTickHandler( void )
{
    vPortRaiseBASEPRI();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* A context switch is required.  Context switching is performed in
            the PendSV interrupt.  Pend the PendSV interrupt. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    vPortClearBASEPRIFromISR();
}
```

它的执行过程是这样子的,屏蔽所有中断,因为SysTick以最低的中断优先级运行,所以当这个中断执行时所有中断必须被屏蔽。vPortRaiseBASEPRI();就是屏蔽所有中断的。而且并不需要保存本次中断的值,因为systick的中断优先级是已知的,执行完直接恢复所有中断即可。

在`xTaskIncrementTick()`中会对`tick`的计数值进行自加,然后检查有没有处于就绪态的最优先级任务,如果有,则返回非零值,然后表示需要进行任务切换,而并非马上进行任务切换,此处要注意,它只是向中断状态寄存器`bit28`位写入`1`,只是将`PendSV`挂起,假如没有比`PendSV`更高优先级的中断,它才会进入`PendSV`中断服务函数进行任务切换。

```js
#define portNVIC_PENDSVSET_BIT        ( 1UL  0 );     \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );       \
    } /* taskSELECT_HIGHEST_PRIORITY_TASK() */
```

其方法是利用硬件提供的计算前导零指令CLZ,具体宏定义为:
```js
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
```

静态变量`uxTopReadyPriority`包含了处于就绪态任务的最高优先级的信息,因为`FreeRTOS`运行的永远是处于最高优先级的运行态,而下个处于最高优先级的就绪态则必定会在下次任务切换的时候运行,`uxTopReadyPriority`使用每一位来表示任务是否处于就绪态,比如变量`uxTopReadyPriority`的`bit0为1`,则表示存在优先级为0的任务处于就绪态,`bit6为1`则表示存在优先级为6的任务处于就绪态。并且,由于`bit0`的优先级高于`bit6`,那么下个任务就是bit0的任务运行了(数组越低优先级越高)。由于32位整形数最多只有`32`位,因此使用这种特殊方法限定最大可用优先级数目为`32`,即优先级`0~31`。得到了下个处于最高优先级就绪态任务了,就调用`listGET_OWNER_OF_NEXT_ENTRY`来获取下一个任务的列表项,然后将该列表项的任务控制块TCB赋值给`pxCurrentTCB`,那么我们就得到下一个要运行的任务了。

至此,任务切换已经完成。

# END

# 关注我
![欢迎关注我公众号](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203846459-518953156.jpg)

更多资料欢迎关注“物联网IoT开发”公众号!

从0开始学FreeRTOS-(任务调度)-4的更多相关文章

  1. <-0基础学python.第一课->

    初衷:我电脑里面的歌曲很久没换了,我想听一下新的歌曲,把他们下载下来听,比如某个榜单的,但是一首一首的点击下载另存为真的很恶心 所以我想有没有办法通过程序的方式来实现,结果还真的有,而且网上已经有有人 ...

  2. 如何从 0 开始学 ruby on rails (漫步版)

    如何从 0 开始学 ruby on rails (漫步版) ruby 是一门编程语言,ruby on rails 是 ruby 的一个 web 框架,简称 rails. 有很多人对  rails 感兴 ...

  3. 如何从 0 开始学 Ruby on Rails

    如何从 0 开始学 Ruby on Rails (漫步版)Ruby 是一门编程语言,Ruby on Rails 是 Ruby 的一个 web 框架,简称 Rails. 有很多人对 Rails 感兴趣, ...

  4. 项目ITP(五) spring4.0 整合 Quartz 实现任务调度

    前言 系列文章:[传送门] 项目需求: 二维码推送到一体机上,给学生签到扫描用.然后需要的是 上课前20分钟 ,幸好在帮带我的学长做 p2p 的时候,接触过.自然 quartz 是首选.所以我就配置了 ...

  5. 从0系统学Android-2.5更多隐式Intent用法

    本系列文章,参考<第一行代码>,作为个人笔记 更多内容:更多精品文章分类 从0系统学Android-2.5更多隐式Intent用法 上一节中我们学习了通过隐式 Intent 来启动 Act ...

  6. 从0系统学Android--4.1探究碎片

    从0系统学Android--4.1探究碎片 本系列文章目录:更多精品文章分类 本系列持续更新中.... 初级阶段内容参考<第一行代码> 第四章:手机平板要兼顾--探究碎片 平板电脑和手机最 ...

  7. 从0系统学Android--3.7 聊天界面编写

    从0系统学Android--3.7 聊天界面编写 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.7 编写界面的最佳实践 前面学习了那么多 UI 开发的知识,下面来进行实践,做一个美观 ...

  8. 从0系统学Android--3.6 RecyclerView

    从0系统学Android--更强大的滚动控件---RecyclerView 本系列文章目录:更多精品文章分类 本系列持续更新中.... 参考<第一行代码> 首先说明一点昨天发了一篇关于 L ...

  9. 从0系统学Android--3.5 最常用和最难用的控件---ListView

    从0系统学Android-- 3.5 最常用和最难用的控件---ListView 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.5 最常用和最难用的控件---ListView Lis ...

  10. 从0系统学Android--3.2四种基本布局

    从0系统学Android--3.2四种基本布局 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.3 系统控件不够用?创建自定义控件 上一节我们学习了 Android 中的一些常用的控件 ...

随机推荐

  1. Springboot2.x 自动创建表并且执行初始化数据

    1.使用springboot jdbc初始化数据库 项目结构 schema.sql drop table if exists user; create table user (id bigint(20 ...

  2. 2、pytest中文文档--使用和调用

    目录 使用和调用 通过python -m pytest调用pytest *pytest执行结束时返回的状态码 pytest命令执行结束,可能会返回以下六种状态码: *获取帮助信息 最多允许失败的测试用 ...

  3. 【Offer】[38] 【字符串的排列】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入一个字符串,打印出该字符串中字符的所有排列.例如,输入字符串abc,则打印出由字符a.b.c所能排列出来的所有字符串abc.acb. ...

  4. IDC是什么?

    IDC:互联网数据中心,是电信部门利用已有的互联网通信线路.贷款资源,建立标准化的电信专业级机房环境,为企业.政府提供服务器托管.服务器租用以及相关增值等方面服务.IDC是全球协作的特定设备网络,用来 ...

  5. Java日志框架SLF4J和log4j以及logback的联系和区别

    1.SLF4J(Simple logging Facade for Java) 意思为简单日志门面,它是把不同的日志系统的实现进行了具体的抽象化,只提供了统一的日志使用接口,使用时只需要按照其提供的接 ...

  6. IDEA中创建maven web项目

    本文将带你一路从IDEA中maven的配置到创建maven web项目,掌握IDEA中maven的使用. 一.IDEA中配置maven 开发中一般我们使用自己下载的maven,不使用IDEA工具自带的 ...

  7. 泛型接口、JAVA API、包装类

    泛型接口就是拥有一个或多个类型参数的接口 语法: public interface 接口名<类型形参>{ 方法名(类型形参 类型形参实例); } 示例: public interface ...

  8. apache ignite系列(六): 服务网格

    简介 ​ 服务网格本质上还是远程方法调用(RPC),而在ignite中注册的服务本质体现还是以cache的形式存在,集群中的节点可以相互调用部署在其它节点上的服务,而且ignite集群会负责部署服务的 ...

  9. sqoop导oracle数据到hive中并动态分区

    静态分区: 在hive中创建表可以使用hql脚本: test.hql USE TEST; CREATE TABLE page_view(viewTime INT, userid BIGINT, pag ...

  10. charles 访问控制设置

    本文参考:charles 访问控制设置 charles 访问控制设置 access control settings 访问账户设置: 这里可以配置连接到charles时的一些配置: 这个访问控制确定谁 ...