news 2026/1/15 9:48:29

FreeRTOS源码深入研究:6.软件定时器(software timer)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS源码深入研究:6.软件定时器(software timer)

一、软件定时器的特性

软件定时器是由FreeRTOS内核提供的纯软件功能,通过守护任务(prvTimerTask)统一管理所有定时器,不依赖于硬件定时器外设。软件定时器可以让某个任务只执行一次(一次性:启动后,回调函数只会被调用一次),也可以让任务周期性执行(自动加载定时器:启动后,时间到之后会自动启动它,使得回调函数被周期性得调用)。

当FreeRTOS的配置项configUSE_TIMERS被设置为1时,在启动调度器时,会自动创建守护任务(RTOS Damemon Task)

在传统定时器设计中,每次发生硬件节拍中断时,中断处理函数会立即检查是否有定时器超时。如果发现超时,会直接在中断上下文中调用对应的定时器函数。但这种方式存在明显问题:若定时器任务函数执行时间过长,会阻塞整个中断系统,影响系统的实时响应。

FreeRTOS采用了更高效的机制:当定时器超时,中断函数通过写队列来唤醒守护任务。守护任务中一直不断的在读队列,一旦读取到超时消息的队列,便从队列中读取并处理,然后在守护任务的上下文中执行定时器任务函数。这样,定时器任务函数的执行不再受中断环境限制,增强了系统的稳定性和响应性。

我们自己编写的任务函数要使用定时器时,是通过"定时器命令队列"(timer command queue)与守护任务交互的。守护任务的优先级为:configTIMER_TASK_PRIORITY

二、软件定时器的相关函数源码研究

说明:这里只研究常见的、常用的函数源码

2.1定时器控制块结构体(tmrTimerControl)

/* 该结构体用于管理软件定时器的所有属性 */ typedef struct tmrTimerControl { const char *pcTimerName; /* 定时器名称字符串 */ /* 用于将定时器插入内核管理的链表中(如就绪链表或阻塞链表) */ ListItem_t xTimerListItem; /* 定时器周期(启动定时器和运行回调函数两者的的间隔),以 Tick 为单位 */ TickType_t xTimerPeriodInTicks; /* 自动重载标志,设置为 pdTRUE 时,为周期性定时器。 设置为 pdFALSE 时,为一次性定时器 */ UBaseType_t uxAutoReload; void *pvTimerID; /* 定时器标识 ID */ /* 回调函数指针,指向用户定义的函数。定时器超时时,内核将调用此函数处理超时事件 */ TimerCallbackFunction_t pxCallbackFunction; /* 当启用“跟踪功能”时,分配定时器编号,用于内核调试识别定时器 */ #if( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTimerNumber; #endif /* 仅在同时启用静态和动态分配时存在 */ /* 静态分配标志:设置为 pdTRUE 表示定时器内存由用户静态分配,删除定时器时不尝试释放内存;否则为动态分配,删除时需要释放内存 */ #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) uint8_t ucStaticallyAllocated; #endif } xTIMER; typedef xTIMER Timer_t;

2.2定时器创建函数

与队列一样,创建定时器有:动态分配内存 和 静态分配内存。原型如下:

/* 使用动态分配内存的方法创建定时器 * pcTimerName:定时器名字, 用处不大, 尽在调试时用到 * xTimerPeriodInTicks: 周期, 以Tick为单位 * uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性 * pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器 * pxCallbackFunction: 回调函数 * 返回值: 成功则返回TimerHandle_t, 否则返回NULL */ TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ); /* 使用静态分配内存的方法创建定时器 * pcTimerName:定时器名字, 用处不大, 尽在调试时用到 * xTimerPeriodInTicks: 周期, 以Tick为单位 * uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性 * pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器 * pxCallbackFunction: 回调函数 * pxTimerBuffer: 传入一个StaticTimer_t结构体, 将在上面构造定时器 * 返回值: 成功则返回TimerHandle_t, 否则返回NULL */ TimerHandle_t xTimerCreateStatic(const char * const pcTimerName, TickType_t xTimerPeriodInTicks, UBaseType_t uxAutoReload, void * pvTimerID, TimerCallbackFunction_t pxCallbackFunction, StaticTimer_t *pxTimerBuffer );

这里只分析动态创建的源码

TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ) { Timer_t *pxNewTimer; //指向新定时器控制块的指针 /* 动态分配定时器结构体内存,大小为Timer_t结构体的大小 * 如果内存分配失败,pvPortMalloc返回NULL */ pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) ); /* 检查内存分配是否成功 */ if( pxNewTimer != NULL ) { /* 调用初始化函数 */ prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer ); #if( configSUPPORT_STATIC_ALLOCATION == 1 ) { /* 标记为动态分配,以便删除定时器时正确释放内存 */ pxNewTimer->ucStaticallyAllocated = pdFALSE; } #endif } return pxNewTimer; }
static void prvInitialiseNewTimer( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction, Timer_t *pxNewTimer ) { /* 检查定时器指针是否有效,防止对空指针进行操作 */ if( pxNewTimer != NULL ) { /* 确保定时器服务任务所需的内部基础设施已创建和初始化 */ prvCheckForValidListAndQueue(); /* 使用函数参数初始化定时器结构体成员 */ pxNewTimer->pcTimerName = pcTimerName; //定时器名称 pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;//定时器周期 pxNewTimer->uxAutoReload = uxAutoReload; //自动重载标志 pxNewTimer->pvTimerID = pvTimerID; //定时器ID标识 pxNewTimer->pxCallbackFunction = pxCallbackFunction; //超时回调函数指针 /* 初始化定时器结构体中的链表节点,设置为一个独立的、未连接的状态, 为后续插入定时器管理链表做准备 */ vListInitialiseItem( &( pxNewTimer->xTimerListItem ) ); traceTIMER_CREATE( pxNewTimer ); }

FreeRTOS用两个有序链表管理活跃的定时器。

static List_t xActiveTimerList1 = {0};
static List_t xActiveTimerList2 = {0};

链表按超时时间升序排列(最近超时的在前面)

定时器何时加入链表?当调用xTimerStart()、xTimerReset()等启动函数时(下面会提到)

2.3定时器删除函数

#define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) ) #define xTimerStop( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) ) #define xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_CHANGE_PERIOD, ( xNewPeriod ), NULL, ( xTicksToWait ) ) #define xTimerDelete( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_DELETE, 0U, NULL, ( xTicksToWait ) ) #define xTimerReset( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) ) #define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U ) #define xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP_FROM_ISR, 0, ( pxHigherPriorityTaskWoken ), 0U ) #define xTimerResetFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )

通过“timers.h”可知,以上函数都是通过调用统一的xTimerGenericCommand函数实现所有定时器操作的,这样可以简化架构设计,提高代码复用性。接下来详细研究这个函数。

定时器通用命令函数,用于向定时器守护任务发送各种操作命令

参数:

TimerHandle_t xTimer:目标定时器句柄,指向要操作的定时器控制块

const BaseType_t xCommandID:命令标识符,指定要执行的操作类型

const TickType_t xOptionalValue:参数值,不同命令有不同的含义

BaseType_t * const pxHigherPriorityTaskWoken:用于ISR版本中传递任务唤醒状态

const TickType_t xTicksToWait:任务上下文发送命令时的最大阻塞时间

BaseType_t xTimerGenericCommand( TimerHandle_t xTimer, const BaseType_t xCommandID, const TickType_t xOptionalValue, BaseType_t * const pxHigherPriorityTaskWoken, const TickType_t xTicksToWait ) { BaseType_t xReturn = pdFAIL; //函数返回值 DaemonTaskMessage_t xMessage; //定时器命令消息结构体,用于封装发送到队列的数据 configASSERT( xTimer ); /* 检查定时器命令队列是否已创建 */ if( xTimerQueue != NULL ) { /* 填充命令消息结构体的字段 */ /* 设置消息ID,即要执行的命令类型 */ xMessage.xMessageID = xCommandID; /* 设置可选参数值,不同类型命令使用此字段传递额外信息 */ xMessage.u.xTimerParameters.xMessageValue = xOptionalValue; /* 设置目标定时器指针,使守护任务知道操作哪个定时器 */ xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer; /* 判断命令类型:是否是中断服务程序的请求 命令ID小于tmrFIRST_FROM_ISR_COMMAND表示是任务上下文调用 */ if( xCommandID < tmrFIRST_FROM_ISR_COMMAND ) { /* 如果调度器正在运行,按正常方式发送 */ if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ) { /* 将命令消息发送到定时器队列尾部,使用指定的阻塞时间 */ xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait ); } else { /* 调度器未运行(可能在初始化阶段),以无延迟方式发送 */ xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY ); } } /* 中断服务程序(ISR)上下文发送命令分支 */ else { xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken ); } traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn ); } else { mtCOVERAGE_TEST_MARKER(); } return xReturn; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/24 15:27:47

Blade构建系统终极指南:新手快速上手指南

Blade构建系统终极指南&#xff1a;新手快速上手指南 【免费下载链接】blade-build Blade is a powerful build system from Tencent, supports many mainstream programming languages, such as C/C, java, scala, python, protobuf... 项目地址: https://gitcode.com/gh_mi…

作者头像 李华
网站建设 2026/1/8 14:40:27

16、探索 Linux:网络应用与文件管理指南

探索 Linux:网络应用与文件管理指南 在当今数字化时代,Linux 系统凭借其强大的功能和高度的可定制性,受到了越来越多用户的青睐。本文将深入介绍 Linux 系统中的网络应用和文件管理操作,帮助你更好地利用 Linux 系统的优势,提升工作和学习效率。 网络应用:即时通讯、文…

作者头像 李华
网站建设 2026/1/10 21:34:04

18、深入了解 Linux 文件系统:导航与分区指南

深入了解 Linux 文件系统:导航与分区指南 1. Linux 常见子目录及其内容 在 Linux 系统中,有许多重要的子目录,每个子目录都有其特定的用途。以下是一些常见的子目录及其内容: | 子目录 | 内容描述 | | — | — | | /usr/games | 系统上安装的游戏,除了那些可选择放置…

作者头像 李华
网站建设 2026/1/6 17:21:28

19、Linux系统使用指南:文件系统、磁盘管理与软件操作

Linux系统使用指南:文件系统、磁盘管理与软件操作 1. Linux文件系统与分区 在Linux中,文件系统的分区管理有着独特的方式。紧跟三位字母标识后的数字代表你所指的分区。例如,用户为Linux创建了三个分区,第一个IDE驱动器是一个单独的分区,分配给根分区;第二个IDE驱动器被…

作者头像 李华
网站建设 2026/1/12 6:40:54

24、Linux系统桌面与文本操作全攻略

Linux系统桌面与文本操作全攻略 1. 桌面快捷方式与主题设置 1.1 创建桌面快捷方式 在Linux系统中,创建桌面快捷方式可以方便我们快速启动程序。以下是具体步骤: 1. 选择要创建快捷方式的程序,但不要打开它。例如,若要为The GIMP创建快捷方式,将鼠标移至该程序上,然后…

作者头像 李华
网站建设 2026/1/11 17:51:53

31、Linux DVD-ROM 使用指南与相关知识详解

Linux DVD-ROM 使用指南与相关知识详解 1. DVD-ROM 内容概述 DVD-ROM 包含了安装和运行 Fedora Core 5 所需的一切,这相当于从 Fedora 项目网站下载的四张光盘内容,其中包括: - Fedora Core 5 :红帽赞助的免费版 Linux 最新且最棒版本的完整软件副本。若对源代码感兴趣…

作者头像 李华