目录
任务管理是操作系统中重中之重,不管什么 OS ,任务的调度管理都是核心,FreeRTOS 也是一样;在深入到 FreeRTOS 任务管理的源码之前,鄙人觉得有必要先去从全局的角度进行把握,从全局到局部,从粗线条,到细节,鄙人觉得这样方可更快的熟悉相关的内部原理;
从全局来看的话,可以先梳理 FreeRTOS 关于任务相关的 APIs,支持的 Feature,以及相关的特性,这样一来,在深入到源码级分析的话,知道使用场景,便知道为何这样设计;
分析基于 FreeRTOS V 10.3.1
首先,FreeRTOS 任务支持如下特性:
1、多任务执行; 2、支持配置任务优先级; 3、支持任务的阻塞,挂起; 4、任务都是自己的栈空间; 5、支持周期性任务; 6、任务抢占; 7、协作式调度;
在单核处理器上,多任务是宏观并行,微观串行的;每个任务可以处于不同的状态:
几乎所有的 OS 下,任务都分为了 Ready、Blocked、Running、Suspend 等状态;这样划分是根据具体的使用场景进行的;
Running:指的是正在运行的任务,在单核系统中,同一时刻只有一个任务处于 Running; Ready:指的是可以被调度运行的任务,也就是处于就绪的任务; Blocked:指的是因为某种原因(等待资源,等待时间)暂时不满足执行条件的任务的状态; Suspend:指的是被挂起的任务,暂时不参与调度的状态;
FreeRTOS 中,创建一个任务使用 **xTaskCreate **接口:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
unsigned short usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * pvCreatedTask);
参数的含义如下:
pvTaskCode:任务执行的函数,此函数必须是一个死循环,不会返回。 pcName:任务的名字,是一个字符串;最大长度由宏 configMAX_TASK_NAME_LEN 指定,该宏位于 FreeRTOSConfig.h 文件中。 usStackDepth:任务堆栈的大小,它的单位不是字节,比如在 32-bits 位宽的情况下,这个值设置为 100,那么就是分配了 400 字节大小的堆栈。 pvParameters:传递给任务执行函数的参数。 uxPriority:任务的优先级。 pvCreatedTask:创建任务成功后的任务句柄,后期可以使用这个句柄来调用任务相关的 API。
返回值的含义如下:
Return:如果创建任务成功,返回 pdPASS 否则返回 pdFAIL
比如:
创建了两个任务 vTask1 和 vTask2,堆栈深度为 1000,优先级都为 1,没有入参;
创建完后,两个任务都默认被添加进入了 Ready 状态,调用 vTaskStartScheduler() 开启调度器;
它们的实现都是无限循环,执行的时候,进行打印;
它们在时间线上的执行如图所示:
在任务初始化的时候,可以指定任务的优先级,同样在任务运行过程中,也可以通过 API 来改变任务的优先级;
FreeRTOS 支持的最大优先级由 configMAX_PRIORITIES 确定,优先级的值越低,代表优先级越低;0 是最低优先级,所有在 FreeRTOS 中,优先级的范围处于 0 ~ configMAX_PRIORITIES 之间;
因为任务存在优先级不一样,调度器总是选择处于 Ready 状态的优先级最高的任务;
比如:
Task 1 优先级是 1,Task 2 优先级是 2;两个 Task 都是之前的实现的情况下,每次调度器选择任务的时候,因为 Task 2 优先级高,每次都会选择 Task 2,那么 Task 1 就会被饿死:
类似上面的情况,Task 1 被饿死,得不到执行,显然不是我们想要的,归根结底,是因为 Task 2 一直满足执行条件!按照设计来说,Task 2 优先级高,它应该是去做一些急需快速完成的任务,其他任何事情不准阻拦他,但是哪有任务一直都急需被完成呢?所以,正常情况下,Task 2 可能是在等待某个事件(比如,某个中断后)或者某个时间点来完成急需完成的任务,其他时间应该处于一种等待的状态,我们称之为阻塞,也就是 Blocked;
在 FreeRTOS 中,有两种方式可以让任务进入阻塞状态:
1、阻塞在时间上:也就是执行任务的时间未到; 2、等待事件:也就是对应的事件还没有发生;
如果类似上面的代码,在死循环里面调用了 vTaskDelay 函数,这就是会导致任务处于阻塞状态,等时间到了指定的延时后,才再次进入 Ready 状态,被调度器调度;
在这种设计下,不同优先级的任务同时运行的时候,就不会有被饿死的情况,同时当两个任务都没有执行的时候,时间让给 IDLE 线程:
被挂起的任务,进入 Suspend 状态,调度器在任务选择的时候,不再调度进入 Suspend 状态的任务,除非再次对此任务调用 Resume,重新进入 Ready 队列,接受调度器的调度;
在调度器初始化的时候,会创建一个 Idle 任务,这样可以确保至少有一个任务在运行;此任务优先级最低,为 0;
空闲任务用来在处理被删除的任务的内存,所有删除任务后,一定要确保空闲任务被运行,这样才能够内存回收;
FreeRTOS 支持空闲任务的钩子函数,当开启 configUSE_IDLE_HOOK 宏是 1 的时候,在空闲任务的时候,函数:
void vApplicationIdleHook( void );
被调用,常用的方式是在此实现低功耗相关的处理逻辑;
FreeRTOS 的任务调度基于周期性的 Tick 心跳,调度器从 Ready 状态列表中选择下一个优先级最高的任务投入运行;被阻塞的任务可以通过 event 来临或者阻塞时间到,重新进入 Ready 状态;
软件上,可以通过配置宏,来改变调度算法的行为,这些宏位于 FreeRTOSConfig.h
configUSE_PREEMPTION:配置为 1 则说明支持抢占式调度,否则称之为协作式调度;
注:协作式调度需要任务主动放弃 CPU,下一个才能够被调度;抢占式调度由系统决定调度;
**configUSE_TIME_SLICING **:配置为 1 的时候,同样优先级的任务会被轮转调度执行;否则优先级相同的任务,不会被轮转执行,只会执行其中一个;
最常用的配置是,上述两个都配置为 1;
configUSE_PREEMPTION = 1 configUSE_TIME_SLICING = 1
比如有 3 个任务:Task 1、Task 2、Task 3 如下所示,他们的优先级也标记出来;
Task 1 处于 blocked 状态,等待 event 满足条件; Task 2 是周期性任务; Task 3 是低优先级任务,也处于 blocked 状态,等待它的 event; t1 时刻,Task 2 执行,Task 1 和 Task 3 的 event 都没满足; t2 时刻,没有任务执行,Idle 线程执行; t3 时刻,Task 3 的 event 满足了,被调度执行; t4 时刻,执行 Idle; t5 时刻,Task 3 的 event 满足了,被调度执行; t6 时刻,Task 2 周期性任务来了,优先级高于 Task 3,即便是 Task 3 未执行完,OS 依然调度 Task 2 先执行; t7 时刻,Task 2 执行完毕,OS 调度 Task 3; t8 时刻,没有需要调度的任务,进入 Idle; t9 时刻,Task 2 周期性任务来了,被调度; t10时刻,最高优先级的 Task 1 的 event 满足,即便是 Task 2 未执行完,OS 依然调度 Task 1 执行; t11 时刻,Task 1 执行完毕,继续调度尚未执行完毕的 Task 2;此刻低优先级的 Task 3 的 event 虽然也满足,但是优先级低,执行被推后; t12 时刻,Task 2 执行完毕,Task 3 得以被调度; t13 时刻,没有任务活动,调度 Idle 线程;
在 **configUSE_PREEMPTION 和 configUSE_TIME_SLICING **都配置为 1 的时候,如果线程和 Idle 线程一样优先级,那么他们会被轮转调度:
上面的调度过程不在多说,Task 1 优先级最高,当满足他的 event 的时候,抢占其他任务执行,随后进入 blocked 状态;
在上面的例子中(有任务和 Idle 线程一样连续执行并且优先级一样),还有一个宏 configIDLE_SHOULD_YIELD,可能会导致调度器行为变化:
**configIDLE_SHOULD_YIELD **= 0,那么行为和上面的一样,也是默认情况; configIDLE_SHOULD_YIELD = 1,那么 Idle 线程会 loop 一次然后主动让出 CPU,如下所示:
如果 :
configUSE_PREEMPTION = 1 configUSE_TIME_SLICING = 0
这表示调度器支持抢占,但是对于同样优先级的任务,不会去轮转,比如:
可以看到 Task 1 优先级高,可以抢占低优先级;
但是 Task 2 和 Idle 优先级一样,但是得不到轮转的调度;
当配置如下:
configUSE_PREEMPTION = 0 configUSE_TIME_SLICING = any value
代表调度器会进行协作式调度,什么意思呢?如下所示:
Task 1 优先级最高,Task 2 其次,Task 3 优先级最低;
t1 时刻,Task 3 处于运行,Task 1 和 Task 2 处于阻塞; t2 时刻,Task 2 满足运行条件进入 Ready 状态,但是由于不支持抢占调度,所以无法执行,Task 3 继续执行; t3 时刻,Task 1 满足运行条件进入 Ready 状态,但是由于不支持抢占调度,所以无法执行,Task 3 继续执行; t4 时刻,Task 3 主动调用 taskYIELD() 函数,主动放弃 CPU,此刻调度器选择优先级最高的 Task 1 进行运行; t5 时刻,Task 1 执行完毕,进入 blocked,调度器调度 Task 2 运行; t6 时刻,Task 2执行完毕,进入 blocked,调度器调度 Task 3运行;
通信工程专业毕业,7年开发经验
精通c/c++
精通golang
熟悉常见的脚本,js,lua,python,php
熟悉电路基础,嵌入式,单片机
服务端开发
嵌入式开发
>gin接口代码CURD生成工具
sql ddl to struct and markdown,将sql表自动化生成代码内对应的结构体和markdown表格格式,节省宝贵的时间。
qt .ui文件转css文件
duilib xml 自动生成绑定控件代码
协议调试器
基于lua虚拟机的的协议调试器软件 支持的协议有:
串口
tcp客户端/服务端
udp 组播/udp节点
tcp websocket 客户端/服务端
软件界面
使用例子: 通过脚本来获得接收到的数据并写入文件和展示在界面上
下载地址和源码
webrtc easy demo
webrtc c++ native 库 demo 实现功能:
基于QT
webrtc摄像头/桌面捕获功能
opengl渲染/多播放窗格管理
janus meeting room
下载地址和源码
wifi,蓝牙 - 无线开关
实现功能:
通过wifi/蓝牙实现远程开关电器或者其他电子设备
电路原理图:
实物图:
深度学习验证工具
虚拟示波器
硬件实物图:
实现原理
基本性能
采集频率: 取决于外部adc模块和ebaz4205矿板的以太网接口速率,最高可以达到100M/8 约为12.5MPS
上位机实现功能: 采集,显示波形,存储wave文件。
参数可运行时配置
上位机:
显示缓冲区大小可调
刷新率可调节
触发显示刷新可调节
又一个modbus调试工具
最近混迹物联网企业,发现目前缺少一个简易可用的modbus调试工具,本软件旨在为开发者提供一个简单modbus测试工具。
主打一个代码简单易修改。
特点:
1. 基于QT5
2. 基于libmodbus
3. 三方库完全跨平台,linux/windows。
开源plutosdr 板卡
1. 完全开源
2. 提高固件定制服务
3. 硬件售价450 手焊产量有线
测试数据
内部DDS回环测试
接收测试
外部发送500MHZ FM波形
matlab测试
2TRX版本
大部分plutosdr应用场景都是讲plutosdr板卡作为射频收发器来使用。
实际上plutosdr板卡本身运行linux 操作系统。是具有一定脱机运算的能力。
对于一些微型频谱检测,简单射频信号收发等应用完全可以将应用层直接实现在板卡上
相较于通过网卡或者USB口传输具有更稳定,带宽更高等优点。
本开源板卡由于了SD卡启动,较原版pluto支持了自定义启动应用的功能。
提供了应用层开发SDK(编译器,buildroot文件系统)。
通过usb连接电脑,经过RNDIS驱动可以近似为通过网卡连接
(支持固件的开发定制)。
二次开发例子
```
all:
arm-linux-gnueabihf-gcc -mfloat-abi=hard --sysroot=/root/v0.32_2trx/buildroot/output/staging -std=gnu99 -g -o pluto_stream ad9361-iiostream.c -lpthread -liio -lm -Wall -Wextra -lrt
clean:
rm pluto_stream
版面分析即分析出图片内的具体文件元素,如文档标题,文档内容,文档页码等,本工具基于cnstd模型