FreeRTOS从代码层面进行原理分析(1 任务的建立)

FreeRTOS_分析

FreeRTOS 是一个开源的实时操作系统。可以在很的低内存使用的情况下运行在单片机上,使得单片机可以并发(虽然某一时刻还是只有一个任务运行) 的运行程序。关于一些 FreeRTOS 优缺点的介绍文章很多,这里就不再赘述直接深入代码探究原理。

关于 FreeRTOS 的疑问

在刚接触 FreeRTOS 的时候我是有以下几个问题的。
1. FreeRTOS 是如何建立任务的呢?
2. FreeRTOS 是调度和切换任务的呢?
3. FreeRTOS 是如何保证实时性呢?

这篇就是对第一个问题,FreeRTOS 是如何建立任务从代码上进行分析。

在 FreeRTOS 官方的指南上对创建任务就是调用了 xTaskCreate 函数。下面的图截取与官方的教程,里面对 xTaskCreate 函数的用法进行了展示,并对参数也进行了大体上的介绍。

在这里插入图片描述

xTaskCreate 函数分析

这个函数被包含在 FreeRTOS 代码的 task.c 中。这个函数比较长,下面贴出的函数对里面的内容进行了省略。

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ){TCB_t * pxNewTCB;BaseType_t xReturn;/* Allocate space for the TCB.  Where the memory comes from depends on* the implementation of the port malloc function and whether or not static* allocation is being used. */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );...pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); ...prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;

从代码中很容易看出,xTaskCreate 函数主要的工作就是 3 步。

第1步 给新的任务申请空间

pvPortMalloc 函数,其作用是给新的任务申请一块任务控制块(TCB, Task Control Block)空间。后面继续使用 pvPortMallocStack 函数为新的任务申请一块栈空间。 其具体的实现在 heapX.c 中 X 可以是1~5。相关的细节可以看一下相关的介绍,这里的2个用于申请空间的函数可以简单的看作是 malloc 函数就可以了。

第2步 为新任务初始化空间

prvInitialiseNewTask 函数也很长,故省略里面很多的内容。专注于实现逻辑,先放弃具体细节。

static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask,TCB_t * pxNewTCB,const MemoryRegion_t * const xRegions )
{StackType_t * pxTopOfStack;UBaseType_t x;
...#if ( portSTACK_GROWTH < 0 ){pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type.  Checked by assert(). *//* Check the alignment of the calculated top of stack is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
...}
.../* Store the task name in the TCB. */if( pcName != NULL ){for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than* configMAX_TASK_NAME_LEN characters just in case the memory after the* string is not accessible (extremely unlikely). */if( pcName[ x ] == ( char ) 0x00 ){break;}else{mtCOVERAGE_TEST_MARKER();}}/* Ensure the name string is terminated in the case that the string length* was greater or equal to configMAX_TASK_NAME_LEN. */pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';}
...pxNewTCB->uxPriority = uxPriority;vListInitialiseItem( &( pxNewTCB->xStateListItem ) );vListInitialiseItem( &( pxNewTCB->xEventListItem ) );/* Set the pxNewTCB as a link back from the ListItem_t.  This is so we can get* back to  the containing TCB from a generic item in a list. */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );/* Event lists are always in priority order. */listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );...pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
...if( pxCreatedTask != NULL ){/* Pass the handle out in an anonymous way.  The handle can be used to* change the created task's priority, delete the created task, etc.*/*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}
...
}

初始化了两个存储在 任务 TCB 中的链表 xStateListItem & xEventListItem 。从这链表的名字可以能看出来一个是记录这个任务的状态,另一个是记录了任务的事件列表。
再往下的 pxPortInitialiseStack 函数比较关键,这个函数根据不同的芯片架构来为寄存器分配栈空间。下面的函数以 Cortex-m3 为例,具体文件位置为 FreeRTOS\Source\portable\GCC\ARM_CM3\port.c

StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,TaskFunction_t pxCode,void * pvParameters )
{/* Simulate the stack frame as it would be created by a context switch* interrupt. */pxTopOfStack--;                                                      /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */*pxTopOfStack = portINITIAL_XPSR;                                    /* xPSR */pxTopOfStack--;*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */pxTopOfStack--;*pxTopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS;             /* LR */pxTopOfStack -= 5;                                                   /* R12, R3, R2 and R1. */*pxTopOfStack = ( StackType_t ) pvParameters;                        /* R0 */pxTopOfStack -= 8;                                                   /* R11, R10, R9, R8, R7, R6, R5 and R4. */return pxTopOfStack;
}

在 Cortex-m3 中按照下面的形式在栈上为寄存器规划好存储的位置。
在这里插入图片描述

第3步 将新任务加入准备执行队列

prvAddNewTaskToReadyList 函数就是做这个工作的。在加入到 ready 队列后,FreeRTOS 的调度器就可以在上面根据不同的优先级和 Delay 等信息挑选合适的任务转入到 running 模式。

static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{/* Ensure interrupts don't access the task lists while the lists are being* updated. */taskENTER_CRITICAL();{uxCurrentNumberOfTasks++;if( pxCurrentTCB == NULL ){/* There are no other tasks, or all the other tasks are in* the suspended state - make this the current task. */pxCurrentTCB = pxNewTCB;if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){prvInitialiseTaskLists();}
...}else{/*看一下新加入的任务的优先级是不是大于当前的任务,如果是的话就把当前任务换成这个新的优先级更高的任务*/if( xSchedulerRunning == pdFALSE ){if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}}
...}uxTaskNumber++;prvAddTaskToReadyList( pxNewTCB );}taskEXIT_CRITICAL();
}
prvInitialiseTaskLists

如果是编号为 1 的任务的话,该函数还会额外初始化5个链表。5个链表的作用从注释中也能看出。

PRIVILEGED_DATA static List_t xDelayedTaskList1;                         /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2;                         /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;                         /*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;              /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;      /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */

xDelayedTaskList1 链表是用于记录目前的延迟任务。
xDelayedTaskList2 链表是用于记录延迟已经达到当前滴答定时器的溢出数量的任务。
xPendingReadyList 链表使用于记录任务已经准备就绪,这些任务目前处于挂起状态。当调度器恢复后这些任务就会被移动到就绪状态。
pxDelayedTaskList 链表适用于记录当前正在使用的延迟列表。
pxOverflowDelayedTaskList 用于当前用于保存已超出当前tick计数的任务的延迟任务列表。

下面是 prvInitialiseTaskLists 函数的全部代码。

static void prvInitialiseTaskLists( void )
{UBaseType_t uxPriority;for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ ){vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );}vListInitialise( &xDelayedTaskList1 );vListInitialise( &xDelayedTaskList2 );vListInitialise( &xPendingReadyList );#if ( INCLUDE_vTaskDelete == 1 ){vListInitialise( &xTasksWaitingTermination );}#endif /* INCLUDE_vTaskDelete */#if ( INCLUDE_vTaskSuspend == 1 ){vListInitialise( &xSuspendedTaskList );}#endif /* INCLUDE_vTaskSuspend *//* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList* using list2. */pxDelayedTaskList = &xDelayedTaskList1;pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
prvAddNewTaskToReadyList

prvAddNewTaskToReadyList 函数从字面意思就可以看出来,将新的 task 放进 pxReadyTasksLists 链表中。

#define prvAddTaskToReadyList( pxTCB )                                                                 \traceMOVED_TASK_TO_READY_STATE( pxTCB );                                                           \taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );                                                \listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )

这里面全部使用宏进行实现,以提升速度。因为宏就相当于内联了,在编译的时候就会直接进行替换,省去了调用函数时候的开销。
让当前任务的优先级始终保持为最大。

总结

通过以上的代码分析我们已经知道其实建立一个任务需要三个步骤。

  1. 给新的任务申请空间
  2. 为新任务初始化空间
  3. 将新任务加入准备执行队列

但是对于最开始提到的第二个问题 FreeRTOS 是调度和切换任务的呢?
请看 FreeRTOS从代码层面进行原理分析(2 任务的调度)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/284675.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode用SSH远程开发c语言

vscode配置远程 这里我使用虚拟机进行展示&#xff0c;首先需要你的虚拟机安装好ssh 没安装好就执行下面的命令安装并开启服务 sudo apt-get install ssh sudo service ssh start ps -e | grep sshvscode安装 remote-ssh扩展 点击左下角的远程连接&#xff0c;我这里已经连接…

mysql 存储引擎 基本介绍

目录 一 存储引擎概念介绍 &#xff08;一&#xff09;存储引擎概念 &#xff08;二&#xff09;MySQL常用的存储引擎 &#xff08;三&#xff09;存储引擎运作方式 二 MyISAM 存储引擎介绍 &#xff08;一&#xff09; MyISAM 存储引擎特点 1&#xff0c;不支持…

8.测试教程-自动化测试selenium-3

文章目录 1.unittest框架解析2.批量执行脚本2.1构建测试套件2.2用例的执行顺序2.3忽略用例执行 3.unittest断言4.HTML报告生成5.异常捕捉与错误截图6.数据驱动 大家好&#xff0c;我是晓星航。今天为大家带来的是 自动化测试selenium第三节 相关的讲解&#xff01;&#x1f600…

四、Elasticsearch 进阶

自定义目录 4.1 核心概念4.1.1 索引&#xff08;Index&#xff09;4.1.2 类型&#xff08;Type&#xff09;4.1.3 文档&#xff08;Document&#xff09;4.1.3 字段&#xff08;Field&#xff09;4.1.5 映射&#xff08;Mapping&#xff09;4.1.6 分片&#xff08;Shards&#…

可视化技术干货:坐标系扫盲—图表呈现的核心,有点难懂,慢慢看

hello&#xff0c;我是贝格前端工场&#xff0c;本篇文章开始分享可视化干货技术&#xff0c;力争用深入浅出的语言讲明白坐标系&#xff0c;并附大量图表案例。欢迎老铁们关注转发&#xff0c;如有项目定制需求可以私信我们。 一、什么是坐标系 在可视化设计中&#xff0c;坐…

【Linux】进程地址空间详解

前言 在我们学习C语言或者C时肯定都听过老师讲过地址的概念而且老师肯定还会讲栈区、堆区等区域的概念&#xff0c;那么这个地址是指的物理内存地址吗&#xff1f;这里这些区域又是如何划分的呢&#xff1f; 我们在使用C语言的malloc或者C的new函数开辟空间时&#xff0c;开辟…

Spring Cloud三:API网关深入探索与实战应用

Spring Cloud一&#xff1a;Spring Cloud 简介 Spring Cloud二&#xff1a;核心组件解析 一、服务发现与动态路由 在微服务架构中&#xff0c;服务的动态注册与发现是一个核心功能。API网关可以与服务注册中心&#xff08;如Eureka、Consul等&#xff09;集成&#xff0c;动态…

C# 将 Word 转文本存储到数据库并进行管理

目录 功能需求 范例运行环境 设计数据表 关键代码 组件库引入 Word文件内容转文本 上传及保存举例 得到文件Byte[]数据方法 查询并下载Word文件 总结 功能需求 将 WORD 文件的二进制信息存储到数据库里&#xff0c;即方便了统一管理文件&#xff0c;又可以实行权限控…

安装IK分词器 + 扩展词典配置 + 停用词典配置

安装IK分词器 1.在线安装ik插件&#xff08;较慢&#xff09; # 进入容器内部 docker exec -it elasticsearch /bin/bash ​ # 在线下载并安装 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elastics…

算法---动态规划

动态规划 1.前言2. 示例 - 第N个泰波那契数2.1 算法原理&#xff08;重点&#xff09;2.2 代码 3. 总结解题思路 1.前言 哪些情况下会用到动态规划&#xff1a; 1.最优化问题&#xff1a;当需要求解最大值或最小值的问题时&#xff0c;可以考虑使用动态规划。例如&#xff0c…

[C语言]——内存函数

目录 一.memcpy使用和模拟实现&#xff08;内存拷贝&#xff09; 二.memmove 使用和模拟实现 三.memset 函数的使用&#xff08;内存设置&#xff09; 四.memcmp 函数的使用 C语言中规定&#xff1a; memcpy拷贝的就是不重叠的内存memmove拷贝的就是重叠的内存但是在VS202…

SecureCRT:高效安全的远程连接工具

SecureCRT是一款功能强大的终端仿真工具&#xff0c;主要用于连接和运行包括Windows、UNIX和VMS在内的远程系统。它支持多种协议&#xff0c;如SSH1、SSH2、Telnet、SFTP、Rlogin、Serial、SCP等&#xff0c;确保用户与目标设备之间的通信安全&#xff0c;并防止网络攻击和窥探…

Android Preference简单介绍

Android Preference简单介绍 文章目录 Android Preference简单介绍一、前言二、Preference 简单介绍二、PreferenceScreen和SwitchPreference 简单示例2、相关demo代码示例&#xff08;1&#xff09;SettingsActivity.Java&#xff08;2&#xff09;layout\settings_activity.x…

局域网内的手机、平板、电脑的文件共享

在日常工作生活中&#xff0c;经常需要将文件在手机、平板、电脑间传输&#xff0c;以下介绍三种较为便捷的方法&#xff1a; 1.LocalSend 该软件是免费开源的&#xff0c;可以在局域网内的任意手机、平板、电脑间传递文件&#xff0c;并且任意一方都可以作为“发送方”和“接…

taro框架之taro-ui中AtSwipeAction的使用

题记&#xff1a;所需效果&#xff1a;滑动删除 工作进程 官网文档代码 <AtSwipeAction options{[{text: 取消,style: {backgroundColor: #6190E8}},{text: 确认,style: {backgroundColor: #FF4949}} ]}><View classNamenormal>AtSwipeAction 一般使用场景</…

DataEase大屏iframe嵌入自建网站(React)

1、修改dataease 所在的服务器nginx配置 server {listen 80;server_name dataease.ibaiqiu.cn;return 307 https://$host$request_uri; } server {listen 443 ssl;server_name dataease.ibaiqiu.cn;client_max_body_size 30M;ssl_certificate /usr/local/nginx/co…

计算机三级——网络技术(综合题第二题)

路由器工作模式 用户模式 当通过Console或Telnet方式登录到路由器时&#xff0c;只要输入的密码正确&#xff0c;路由器就直接进入了用户模式。在该模式下&#xff0c;系统提示符为一个尖括号(>)。如果用户以前为路由器输入过名称&#xff0c;则该名称将会显示在尖指号的前…

HarmonyOS应用开发实战 - Api9 拍照、拍视频、选择图片、选择视频、选择文件工具类

鸿蒙开发过程中&#xff0c;经常会进行系统调用&#xff0c;拍照、拍视频、选择图库图片、选择图库视频、选择文件。今天就给大家分享一个工具类。 1.话不多说&#xff0c;先展示样式 2.设计思路 根据官方提供的指南开发工具类&#xff0c;基础的拍照、拍视频、图库选照片、选…

分布式组件 Nacos

1.在之前的文章写过的就不用重复写。 写一些没有写过的新东西 2.细节 2.1命名空间 &#xff1a; 配置隔离 默认&#xff1a; public &#xff08;默认命名空间&#xff09;:默认新增所有的配置都在public空间下 2.1.1 开发 、测试 、生产&#xff1a;有不同的配置文件 比如…

计算联合体union的大小

一&#xff1a;联合类型的定义 联合也是一种特殊的自定义类型&#xff0c;这种类型定义的变量也包含一系列的成员&#xff0c;特征是这些成员公用同一块空间&#xff08;所以联合也叫共用体&#xff09; 比如&#xff1a;共用了 i 这个较大的空间 二&#xff1a; 联合的特点 …