freertos 创建互斥量_FreeRTOS源码探析之——任务调度相关

本文探讨了FreeRTOS的任务调度原理,包括任务状态、抢占式调度和时间片调度,以及任务间的通信机制,重点解析了互斥量的使用。通过分析FreeRTOS源码,阐述了任务控制块TCB_t、vTaskDelay函数的工作流程,以及如何停止和恢复任务调度。

11ed7dbcafabd094a746ce2983d42b5e.png

FreeRTOS可以运行多任务,在于其内核的任务调度功能,本篇介绍任务调度的基本思路与部分源码分析。

1 裸机编程与RTOS 的区别

1.1 裸机程序基本框架

/*主函数*/
int main()
{
    
    
    init();//一些初始化
    /*死循环*/
    while(1)
    {
    
    
        do_something_1();//执行一些逻辑
        do_something_2();
    }//循环执行
}

/*中断服务函数*/
IRQ_Handler()
{
    
    
    set_flag();//简短的标记操作
}

单片机裸机编程的思路比较简单,就是一个死循环,程序依次执行while(1)中的各条语句,循环往复即可,需要处理某些紧急事件时,通过中断服务函数来打断while(1)的执行。

裸机编程虽然简单,但只能在一个循环中执行各种裸机,第一项功能执行完后才能执行第二项功能,就好比有多个人在轮流干活,CPU的利用率不高,不能处理并行逻辑。

1.2 RTOS程序基本框架

/*主函数*/
int main()
{
    
    
    init();//一些初始化
    xTaskCreate();
    vTaskStartScheduler(); //启动调度器
}

/*子任务1(死循环)*/
void task1()
{
    
    
    while(1)
    {
    
    
        do_something_1();//执行一些逻辑(如采集传感器信息)
        vTaskDelay();
    }
}

/*子任务2(死循环)*/
void task2()
{
    
    
    while(1)
    {
    
    
        do_something_2();//执行一些逻辑(如执行电机运动)
        vTaskDelay();
    }
}

/*中断服务函数*/
IRQ_Handler()
{
    
    
    set_event();//触发事件、信号量等
}

单片机引入RTOS,可以将各个功能模块分别设计为单独的任务,每个任务都是一个死循环,就好比有多个人在同时干活,这样CPU的利用率就提高了,并且可以处理一些并行逻辑。

单片机只有一个CPU(核),那怎么让多个人同时干活呢?其实每个子任务虽然都是死循环,但并不是每个子任务一直都在执行,每个子任务在执行期间,可能需要延时,也可能需要等另一个任务的数据到来,所有,在某个任务在等待的时候,CPU就可以停止此任务,然后切换到其它任务执行,这样看起来就是多个人在同时干活了

2 RTOS任务间通信

在裸机编程中,当设计了一个稍微复杂的功能是,会设计处许多子函数来实现一个整体功能,这之中通知会用到一些全局变量全局数组等来实现各个子函数之间的联系。

在RTOS中,当然也可以使用全局变量,但RTOS更推荐我们使用系统自带的任务间通信机制。原因有二:

  1. 阻塞等待机制比轮询等待更高效

全局变量当用作某种事件标志时,获取该标志的任务需要轮询检测标志是否变化,这样会产生大量无效的判断,而使用任务间通信中的阻塞等待机制,CPU可以转而处理其它事情,当标志变化时,解除阻塞,又可以及时执行后续处理。

  1. 全局变量会产生不可重入函数造成逻辑混乱

RTOS运行时,CPU是在各个任务间跳来跳去的,若使用全局变量不恰当,会导致原本设计的逻辑产生混乱。比如某个低优先级任务正在访问某个公共函数,并对该函数中的全局变量进行了修改,还未退出该函数时,更高优先级的任务抢占了CPU的使用权,并也对该函数中的全局变量进行了修改,此时,如果低优先级的任务若认为自己对变量修改成功,并因此而执行自己后续的逻辑,则会导致逻辑错误。

FreeRTOS任务间通信方式

  • 信号量(Semaphore):用于任务间的同步,一个任务以阻塞方式等待另一个任务等待另一个任务释放信号量。
  • 互斥量(Mutex):用于任务间共享资源的互斥访问,使用前获取锁,使用后释放锁。
  • 事件标志组(EventGroup):也是用于任务间的同步,相比信号量,事件标志组可以等待多个事件发生。
  • 消息队列(Queue):类比全局数据,它可以一次发送多个数据(一般将数据定义成结构体发送),每次数据的大小固定不变。
  • 流缓冲区(StreamBuffer):在队列的基础上,优化的一种更适合的数据结构,可以一次写入任意数量的字节,并且可以一次读取任意数量的字节。
  • 消息缓冲区(MessageBuffer):在流式缓冲区的基础上实现的,其进一步针对“消息”进行设计改进,每一条消息的写入增加了一个字节用来表示该条消息的长度,读取时需要一次性读出至少一条消息,否则会返回 0。
  • 任务通知(Notify):不同于上面的任务间通信方式(使用某种通信对象,通信对象是独立于任务的实体,有单独的存储空间,可以实现数据传递和较复杂的同步、互斥功能),通知是发向一个指定的任务的,直接改变该任务TCB的某些变量

3 RTOS任务调度

3.1 任务状态

e2fcc54d12cd9bbe18f0cce85faa9d6f.png
  • 1 创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度。
  • 2 就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态。
  • 3 运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务。
  • 4 运行态→阻塞态(Blocked):正在运行的任务发生阻塞(挂起、延时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
  • 5 阻塞态→就绪态阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
  • 6、7、8 就绪态、阻塞态、运行态→挂起态(Suspended):任务可以通过调用vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU的使用权,也不会参与调度,除非它从挂起态中解除。
  • 9 挂起态→就绪态:把 一 个 挂 起 状态 的 任 务 恢复的 唯 一 途 径 就 是 调 用 vTaskResume() 或vTaskResumeFromISR() API 函数,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
以上是任务运行的各种状态,看起来有点复杂,可以这样理解:
为简单起见,先不考虑挂起态,在任一时刻,CPU只能处理某一个任务,则该任务就处于 运行态,对于其它任 务,当是自己想要延时或等待时,则处于 阻塞态,当自己想要执行但因为优先级低而不能执行时,则处于 就绪态
然后,以上状态如何被改变呢?
1.运行态的自己想进入阻塞态,则就绪态的任务即可运行。
2.阻塞态的解除阻塞进入就绪,若该任务的优先级更高,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值