0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

FreeRTOS优化与错误排查方法有哪些

汽车电子技术 来源:物联网IoT开发 作者:杰杰 2023-02-14 09:59 次阅读

写在前面

主要是为刚接触 FreeRTOS 的用户指出那些新手通常容易遇到的问题。这里把最主要的篇幅放在栈溢出以及栈溢出检测上,因为栈相关的问题是初学者遇到最多的问题。

printf-stdarg.c

当调用 C 标准库 的函数时,栈空间使用量可能会急剧上升,特别是 IO 与字符串处理函数,比如 sprintf()、printf()等。在 FreeRTOS 源码包中有一个名为 printf-stdarg.c 的文件。这个文件实现了一个栈效率优化版的小型 sprintf()、printf(),可以用来代替标准 C 库函数版本。在大多数情况下,这样做可以使得调用 sprintf()及相关函数的任务对栈空间的需求量小很多。

可能很多人都不知道freertos中有这样子的一个文件,它放在第三方资料中,路径为“ FreeRTOSv9.0.0\\FreeRTOS-Plus\\Demo\\FreeRTOS_Plus_UDP_and_CLI_LPC1830_GCC ”,我们发布工程的时候就无需依赖 C 标准库 ,这样子就能减少栈的使用,能优化不少空间。

该文件源码(部分):

1static int print( char **out, const char *format, va_list args )
 2{
 3    register int width, pad;
 4    register int pc = 0;
 5    char scr[2];
 6
 7    for (; *format != 0; ++format) {
 8        if (*format == '%') {
 9            ++format;
10            width = pad = 0;
11            if (*format == '\\0') break;
12            if (*format == '%') goto out;
13            if (*format == '-') {
14                ++format;
15                pad = PAD_RIGHT;
16            }
17            while (*format == '0') {
18                ++format;
19                pad |= PAD_ZERO;
20            }
21            for ( ; *format >= '0' && *format <= '9'; ++format) {
22                width *= 10;
23                width += *format - '0';
24            }
25            if( *format == 's' ) {
26                register char *s = (char *)va_arg( args, int );
27                pc += prints (out, s?s:"(null)", width, pad);
28                continue;
29            }
30            if( *format == 'd' || *format == 'i' ) {
31                pc += printi (out, va_arg( args, int ), 10, 1, width, pad, 'a');
32                continue;
33            }
34            if( *format == 'x' ) {
35                pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'a');
36                continue;
37            }
38            if( *format == 'X' ) {
39                pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'A');
40                continue;
41            }
42            if( *format == 'u' ) {
43                pc += printi (out, va_arg( args, int ), 10, 0, width, pad, 'a');
44                continue;
45            }
46            if( *format == 'c' ) {
47                /* char are converted to int then pushed on the stack */
48                scr[0] = (char)va_arg( args, int );
49                scr[1] = '\\0';
50                pc += prints (out, scr, width, pad);
51                continue;
52            }
53        }
54        else {
55        out:
56            printchar (out, *format);
57            ++pc;
58        }
59    }
60    if (out) **out = '\\0';
61    va_end( args );
62    return pc;
63}
64
65int printf(const char *format, ...)
66{
67    va_list args;
68
69    va_start( args, format );
70    return print( 0, format, args );
71}
72
73int sprintf(char *out, const char *format, ...)
74{
75    va_list args;
76
77    va_start( args, format );
78    return print( &out, format, args );
79}
80
81
82int snprintf( char *buf, unsigned int count, const char *format, ... )
83{
84    va_list args;
85
86    ( void ) count;
87
88    va_start( args, format );
89    return print( &buf, format, args );
90}

使用的例子与 C 标准库基本一样:

1int main(void)
 2{
 3    char *ptr = "Hello world!";
 4    char *np = 0;
 5    int i = 5;
 6    unsigned int bs = sizeof(int)*8;
 7    int mi;
 8    char buf[80];
 9
10    mi = (1 << (bs-1)) + 1;
11    printf("%s\\n", ptr);
12    printf("printf test\\n");
13    printf("%s is null pointer\\n", np);
14    printf("%d = 5\\n", i);
15    printf("%d = - max int\\n", mi);
16    printf("char %c = 'a'\\n", 'a');
17    printf("hex %x = ff\\n", 0xff);
18    printf("hex %02x = 00\\n", 0);
19    printf("signed %d = unsigned %u = hex %x\\n", -3, -3, -3);
20    printf("%d %s(s)%", 0, "message");
21    printf("\\n");
22    printf("%d %s(s) with %%\\n", 0, "message");
23    sprintf(buf, "justif: \\"%-10s\"\\n", "left"); printf("%s", buf);
24    sprintf(buf, "justif: \"%10s\"\\n", "right"); printf("%s", buf);
25    sprintf(buf, " 3: %04d zero padded\\n", 3); printf("%s", buf);
26    sprintf(buf, " 3: %-4d left justif.\\n", 3); printf("%s", buf);
27    sprintf(buf, " 3: %4d right justif.\\n", 3); printf("%s", buf);
28    sprintf(buf, "-3: %04d zero padded\\n", -3); printf("%s", buf);
29    sprintf(buf, "-3: %-4d left justif.\\n", -3); printf("%s", buf);
30    sprintf(buf, "-3: %4d right justif.\\n", -3); printf("%s", buf);
31
32    return 0;
33}

栈计算

每个任务都独立维护自己的栈空间, 任务栈空间总量在任务创建时进行设定。uxTaskGetStackHighWaterMark()主要用来查询指定任务的运行历史中, 其栈空间还差多少就要溢出。这个值被称为栈空间的 High Water Mark

函数原型:

1UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )

想要使用它,需要将对应的宏定义打开:INCLUDE_uxTaskGetStackHighWaterMark

函数描述:

参数 说明
xTask 被查询任务的句柄如果传入 NULL 句柄,则任务查询的是自身栈空间的高水线
返回值 任务栈空间的实际使用量会随着任务执行和中断处理过程上下浮动。uxTaskGetStackHighWaterMark()返回从任务启动执行开始的运行历史中,栈空间具有的最小剩余量。这个值即是栈空间使用达到最深时的剩下的未使用的栈空间。这个值越是接近 0,则这个任务就越是离栈溢出不远。

如果不知道怎么计算任务栈大小,就使用这个函数进行统计一下,然后将任务运行时最大的栈空间作为任务栈空间的80%大小即可。即假设统计得到的任务栈大小为常量 A ,那么在创建线程的时候需要 X 大小的空间,那么 X * 80% = A ,算到的 X 作为任务栈大小就差不多了。

运行时栈检测

FreeRTOS 包含两种运行时栈检测机制,由 FreeRTOSConfig.h 中的配置常量configCHECK_FOR_STACK_OVERFLOW 进行控制。这两种方式都会增加上下切换开销。

栈溢出钩子函数(或称回调函数)由内核在检测到栈溢出时调用。要使用栈溢出钩子函数,需要进行以下配置:

  • 在 FreeRTOSConfig.h 中把 configCHECK_FOR_STACK_OVERFLOW 设为 1 或者 2
  • 提供钩子函数的具体实现,采用下面所示的函数名和函数原型。
1void vApplicationStackOverflowHook( xTaskHandle *pxTask, signed portCHAR *pcTaskName );

补充说明:

  • 栈溢出钩子函数只是为了使跟踪调试栈空间错误更容易,而无法在栈溢出时对其进行恢复。函数的入口参数传入了任务句柄和任务名,但任务名很可能在溢出时已经遭到破坏。
  • 栈溢出钩子函数还可以在中断的上下文中进行调用
  • 某些微控制器在检测到内存访问错误时会产生错误异常,很可能在内核调用栈溢出钩子函数之前就触发了错误异常中断。

方法1

configCHECK_FOR_STACK_OVERFLOW 设置为 1 时选用方法 1

任务被交换出去的时候,该任务的整个上下文被保存到它自己的栈空间中。这时任务栈的使用应当达到了一个峰值。当 configCHECK_FOR_STACK_OVERFLOW 设为1 时,内核会在任务上下文保存后检查栈指针是否还指向有效栈空间。一旦检测到栈指针的指向已经超出任务栈的有效范围,栈溢出钩子函数就会被调用。

方法 1 具有较快的执行速度,但栈溢出有可能发生在两次上下文保存之间,这种情况不会被检测到,因为这种检测方式仅在任务切换中检测。

方法2

configCHECK_FOR_STACK_OVERFLOW 设为 2 就可以选用方法 2 。方法 2在方法 1 的基础上进行了一些补充。

当创建任务时,任务栈空间中就预置了一个标记。方法 2 会检查任务栈的最后 20个字节的数据,查看预置在这里的标记数据是否被覆盖。如果最后 20 个字节的标记数据与预设值不同,则栈溢出钩子函数就会被调用。

方法 2 没有方法 1 的执行速度快,但测试仅仅 20 个字节相对来说也是很快的。这种方法应该可以检测到任何时候发生的栈溢出,虽然理论上还是有可能漏掉一些情况,但这些情况几乎是不可能发生的。

其它常见错误

在一个 Demo 应用程序中增加了一个简单的任务,导致应用程序崩溃

可能的情况:

  1. 任务创建时需要在内存堆中分配空间。许多 Demo 应用程序定义的堆空间大小只够用于创建 Demo 任务——所以当任务创建完成后,就没有足够的剩余空间来增加其它的 任务,队列或信号

  2. 空闲任务是在 vTaskStartScheduler()调用中自动创建的。如果由于内存不足而无法创建空闲任务,vTaskStartScheduler()会直接返回。所以一般在调用 vTaskStartScheduler()后加上一条空循环for(;;) / while(1)可以使这种错误更加容易调试。

    如果要添加更多的任务,可以增加内存堆空间大小(修改配置文件),或是删掉一些已存在的 Demo任务。

在中断中调用一个 API 函数,导致应用程序崩溃

需要做的第一件事是检查中断是否导致了栈溢出。

然后检查API接口是否正确 ,除了具有后缀为FromISR函数名的 API 函数,千万不要在中断服务程序中调用其它 API 函数。

除此之外,还需要注意中断的优先级:

FreeRTOSConfig.h文件中可以配置系统可管理的最高中断优先级数值,宏定义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY是用于配置basepri寄存器的,当basepri设置为某个值的时候,会让系统不响应比该优先级低的中断,而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为5的时候,中断优先级数值在0、1、2、3、4的这些中断是不受FreeRTOS管理的,不可被屏蔽, 同时也不能调用FreeRTOS中的API函数接口 ,而中断优先级在5到15的这些中断是受到系统管理,可以被屏蔽的,也可以调用FreeRTOS中的API函数接口。

临界区无法正确嵌套

除了 taskENTER_CRITICA()和 taskEXIT_CRITICAL(),千万不要在其它地方修改控制器的中断使能位或优先级标志。这两个宏维护了一个嵌套深度计数,所以只有当所有的嵌套调用都退出后计数值才会为 0,也才会使能中断。

在调度器启动前应用程序就崩溃了

这个问题我也会遇到,如果一个中断会产生上下文切换,则这个中断不能在调度器启动之前使能。这同样适用于那些需要读写队列或信号量的中断。在调度器启动之前,不能进行上下文切换。

还有一些 API 函数不能在调度器启动之前调用。在调用 vTaskStartScheduler()之前,最好是限定只使用创建任务,队列和信号量的 API 函数。

比如有一些初始化需要中断的,或者在初始化完成的时候回产生一个中断,这些驱动的初始化最好放在一个任务中进行,我是这样子处理的,在main函数中创建一个任务,在任务中进行bsp初始化,然后再创建消息队列、信号量、互斥量、事件以及任务等操作。

在调度器挂起时调用 API 函数,导致应用程序崩溃

调用 vTaskSuspendAll()使得调度器挂起,而唤醒调度器调用 xTaskResumeAll()。千万不要在调度器挂起时调用其它 API 函数。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 函数
    +关注

    关注

    3

    文章

    3904

    浏览量

    61311
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61352
  • 栈空间
    +关注

    关注

    0

    文章

    5

    浏览量

    5410
收藏 人收藏

    评论

    相关推荐

    使用汇编知识排查疑难问题的方法

    那么,本篇文章,我将再介绍一个使用汇编知识排查疑难问题的方法,希望对大家有所帮助。
    发表于 07-27 10:31 519次阅读

    转:第9章 IAR中的FreeRTOS插件使用方法

    第9章 IAR中的FreeRTOS插件使用方法 本章节介绍IAR中所带的FreeRTOS插件的使用方法,这个插件的功能比较弱,初学的话,
    发表于 08-23 09:51

    关于使用stm32l452运行freertos+emwin+fatfs出现的硬件错误问题

    近期在使用STM32L452运行freertos+emwin+fatfs的时候发现了一个奇怪的硬件错误现象如下:运行系统开始正常,但是读书后写入SD卡并在LCD上显示波形一段时间系统卡死,调试发现
    发表于 08-27 09:32

    利用符号模拟技术优化错误诊断方法

    优化基于区域模型错误诊断过程的方法。该方法首先使用基于区域模型错误诊断方法中电路划分
    发表于 07-05 08:05

    关键词优化哪些实用的方法

    我们在做关键词优化排名的时候,经验的seo人员都会有自己的一套关于关键词应该怎么去优化排名的方法,但是对于一些刚接触seo的新手来说就会比较迷茫,不知道应该怎么去做好关键词的排名,大
    发表于 08-11 01:19

    电子书:STM32-FREERTOS快速学习知识解密

    ——应用场景3STM32单片机中,FreeRTOS RAM使用情况及优化方法4FreeRTOS中列表和列表项插入函数分析5FreeRTOS+
    发表于 05-09 14:30

    STM32 FreeRTOS RAM 使用情况及优化方法实用资料分享~

    的一般方法,并给出在 FreeRTOS优化 RAM 使用的方法,也由衷的期望读者在使用其他 RTOS 时,可以通过相似的思路来解决问题。Free
    发表于 01-26 14:10

    什么方法可以查看FreeRTOS任务的运行状态呢

    什么方法可以查看FreeRTOS任务的运行状态呢?怎样去查看FreeRTOS任务的运行状态呢?
    发表于 11-02 07:59

    CH579无规律进入hardfault错误如何排查

    CH579 程序运行时,偶尔进入 hardfault 错误 ,没有什么规律,如何排查?谢谢
    发表于 07-26 07:24

    GPIO无法触发中断常规排查方法哪些?

    1、电源域是否打开 2、IOMUX是否设置对 3、是否配置了中断方式,外部电平是否满足条件 4、是否为输入状态 备注:这次分享的是,我们做展锐平台GPIO排查方法,不同平台、不同版本、不同项目都会
    发表于 11-24 16:11

    建立一个方法和套路来对 Load 高问题排查

    讲解 Linux Load 高如何排查的话题属于老生常谈了,但多数文章只是聚焦了几个点,缺少整体排查思路的介绍。所谓 “授人以鱼不如授人以渔"。本文试图建立一个方法和套路,来帮助读者对 Load 高问题
    的头像 发表于 12-28 14:18 5077次阅读
    建立一个<b class='flag-5'>方法</b>和套路来对 Load 高问题<b class='flag-5'>排查</b>

    单片机硬错误排查方法

    HardFault 错误调试定位方法1、首先更改 startup.s 的启动文件,把里面的 HardFault_Handler 代码段换成下面的代码:HardFault_Handler
    发表于 12-16 16:54 0次下载
    单片机硬<b class='flag-5'>错误</b><b class='flag-5'>排查</b><b class='flag-5'>方法</b>

    FreeRTOS+STM32F103串口通信错误解决方法

    在调试FreeRTOS系统时,在串口中断中用队列存储数据,然后再定时器中断中用队列接收数据,并通过串口打印出来。在调试代码中编译器报错。在网上搜索之后,找到了一个解决方法。在usart.c文件中添加
    发表于 12-24 18:45 10次下载
    <b class='flag-5'>FreeRTOS</b>+STM32F103串口通信<b class='flag-5'>错误解决方法</b>

    科普系列:CAN总线错误帧及排查方法简介

    作者|蒹葭小编|吃不饱CAN帧有多种格式,错误帧作为CAN帧中独特的一种,了解其作用,类型与产生原因,对于进行测试以及开发有很大的帮助,本文将对错误帧的相关基础知识以及后续的分析排查进行介绍。01
    的头像 发表于 02-23 15:11 1971次阅读
    科普系列:CAN总线<b class='flag-5'>错误</b>帧及<b class='flag-5'>排查</b><b class='flag-5'>方法</b>简介

    如何用示波器排查CAN的各种错误帧呢?

    如何用示波器排查CAN的各种错误帧呢? 导言: 控制器局域网络(Controller Area Network,CAN)是一种常用的现场总线通信协议,广泛应用于汽车电子系统、工业自动化等领域。然而
    的头像 发表于 12-07 11:09 649次阅读