概览
本章主要是为刚接触FreeRTOS 的用户指出那些新手通常容易遇到的问题。这里把最主要的篇幅放在栈溢出以及栈溢出侦测上,因为栈相关的问题是过去几年遇到最多的问题。对其它一些比较常见的问题,本章简要的以FAQ(问答)的形式给出可能的原因和解决方法。
printf-stdarg.c
当调用标准C 库函数时,栈空间使用量可能会急剧上升,特别是IO 与字符串处理函数,比如sprintf()。在FreeRTOS 下载包中有一个名为printf-stdarg.c 的文件。
这个文件实现了一个栈效率优化版的小型sprintf(),可以用来代替标准C 库函数版本。在大多数情况下,这样做可以使得调用sprintf()及相关函数的任务对栈空间的需求量小很多。
printf-stdarg.c 源代码开放,但是为第三方所有。所以此源代码的license 独立于FreeRTOS。具体的license 条款包含在该源文件的起始部分。
见 printf-stdarg.c 代码:
-
/*
-
Copyright 2001, 2002 Georges Menie (www.menie.org)
-
stdarg version contributed by Christian Ettinger
-
-
This program is free software; you can redistribute it and/or modify
-
it under the terms of the GNU Lesser General Public License as published by
-
the Free Software Foundation; either version 2 of the License, or
-
(at your option) any later version.
-
-
This program is distributed in the hope that it will be useful,
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-
GNU Lesser General Public License for more details.
-
-
You should have received a copy of the GNU Lesser General Public License
-
along with this program; if not, write to the Free Software
-
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
*/
-
-
/*
-
putchar is the only external dependency for this file,
-
if you have a working putchar, leave it commented out.
-
If not, uncomment the define below and
-
replace outbyte(c) by your own function call.
-
-
#define putchar(c) outbyte(c)
-
*/
-
-
#include <stdarg.h>
-
-
static void printchar(char **str, int c)
-
{
-
extern int putchar(int c);
-
-
if (str) {
-
**str = c;
-
++(*str);
-
}
-
else (void)putchar(c);
-
}
-
-
#define PAD_RIGHT 1
-
#define PAD_ZERO 2
-
-
static int prints(char **out, const char *string, int width, int pad)
-
{
-
register int pc = 0, padchar = ' ';
-
-
if (width > 0) {
-
register int len = 0;
-
register const char *ptr;
-
for (ptr = string; *ptr; ++ptr) ++len;
-
if (len >= width) width = 0;
-
else width -= len;
-
if (pad & PAD_ZERO) padchar = '0';
-
}
-
if (!(pad & PAD_RIGHT)) {
-
for ( ; width > 0; --width) {
-
printchar (out, padchar);
-
++pc;
-
}
-
}
-
for ( ; *string ; ++string) {
-
printchar (out, *string);
-
++pc;
-
}
-
for ( ; width > 0; --width) {
-
printchar (out, padchar);
-
++pc;
-
}
-
-
return pc;
-
}
-
-
/* the following should be enough for 32 bit int */
-
#define PRINT_BUF_LEN 12
-
-
static int printi(char **out, int i, int b, int sg, int width, int pad, int letbase)
-
{
-
char print_buf[PRINT_BUF_LEN];
-
register char *s;
-
register int t, neg = 0, pc = 0;
-
register unsigned int u = i;
-
-
if (i == 0) {
-
print_buf[0] = '0';
-
print_buf[1] = '\0';
-
return prints (out, print_buf, width, pad);
-
}
-
-
if (sg && b == 10 && i < 0) {
-
neg = 1;
-
u = -i;
-
}
-
-
s = print_buf + PRINT_BUF_LEN-1;
-
*s = '\0';
-
-
while (u) {
-
t = u % b;
-
if( t >= 10 )
-
t += letbase - '0' - 10;
-
*--s = t + '0';
-
u /= b;
-
}
-
-
if (neg) {
-
if( width && (pad & PAD_ZERO) ) {
-
printchar (out, '-');
-
++pc;
-
--width;
-
}
-
else {
-
*--s = '-';
-
}
-
}
-
-
return pc + prints (out, s, width, pad);
-
}
-
-
static int print( char **out, const char *format, va_list args )
-
{
-
register int width, pad;
-
register int pc = 0;
-
char scr[2];
-
-
for (; *format != 0; ++format) {
-
if (*format == '%') {
-
++format;
-
width = pad = 0;
-
if (*format == '\0') break;
-
if (*format == '%') goto out;
-
if (*format == '-') {
-
++format;
-
pad = PAD_RIGHT;
-
}
-
while (*format == '0') {
-
++format;
-
pad |= PAD_ZERO;
-
}
-
for ( ; *format >= '0' && *format <= '9'; ++format) {
-
width *= 10;
-
width += *format - '0';
-
}
-
if( *format == 's' ) {
-
register char *s = (char *)va_arg( args, int );
-
pc += prints (out, s?s:"(null)", width, pad);
-
continue;
-
}
-
if( *format == 'd' ) {
-
pc += printi (out, va_arg( args, int ), 10, 1, width, pad, 'a');
-
continue;
-
}
-
if( *format == 'x' ) {
-
pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'a');
-
continue;
-
}
-
if( *format == 'X' ) {
-
pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'A');
-
continue;
-
}
-
if( *format == 'u' ) {
-
pc += printi (out, va_arg( args, int ), 10, 0, width, pad, 'a');
-
continue;
-
}
-
if( *format == 'c' ) {
-
/* char are converted to int then pushed on the stack */
-
scr[0] = (char)va_arg( args, int );
-
scr[1] = '\0';
-
pc += prints (out, scr, width, pad);
-
continue;
-
}
-
}
-
else {
-
out:
-
printchar (out, *format);
-
++pc;
-
}
-
}
-
if (out) **out = '\0';
-
va_end( args );
-
return pc;
-
}
-
-
int printf(const char *format, ...)
-
{
-
va_list args;
-
-
va_start( args, format );
-
return print( 0, format, args );
-
}
-
-
int sprintf(char *out, const char *format, ...)
-
{
-
va_list args;
-
-
va_start( args, format );
-
return print( &out, format, args );
-
}
-
-
-
int snprintf( char *buf, unsigned int count, const char *format, ... )
-
{
-
va_list args;
-
-
( void ) count;
-
-
va_start( args, format );
-
return print( &buf, format, args );
-
}
-
-
-
#ifdef TEST_PRINTF
-
int main(void)
-
{
-
char *ptr = "Hello world!";
-
char *np = 0;
-
int i = 5;
-
unsigned int bs = sizeof(int)*8;
-
int mi;
-
char buf[80];
-
-
mi = (1 << (bs-1)) + 1;
-
printf("%s\n", ptr);
-
printf("printf test\n");
-
printf("%s is null pointer\n", np);
-
printf("%d = 5\n", i);
-
printf("%d = - max int\n", mi);
-
printf("char %c = 'a'\n", 'a');
-
printf("hex %x = ff\n", 0xff);
-
printf("hex %02x = 00\n", 0);
-
printf("signed %d = unsigned %u = hex %x\n", -3, -3, -3);
-
printf("%d %s(s)%", 0, "message");
-
printf("\n");
-
printf("%d %s(s) with %%\n", 0, "message");
-
sprintf(buf, "justif: \"%-10s\"\n", "left"); printf("%s", buf);
-
sprintf(buf, "justif: \"%10s\"\n", "right"); printf("%s", buf);
-
sprintf(buf, " 3: %04d zero padded\n", 3); printf("%s", buf);
-
sprintf(buf, " 3: %-4d left justif.\n", 3); printf("%s", buf);
-
sprintf(buf, " 3: %4d right justif.\n", 3); printf("%s", buf);
-
sprintf(buf, "-3: %04d zero padded\n", -3); printf("%s", buf);
-
sprintf(buf, "-3: %-4d left justif.\n", -3); printf("%s", buf);
-
sprintf(buf, "-3: %4d right justif.\n", -3); printf("%s", buf);
-
-
return 0;
-
}
-
-
/*
-
* if you compile this file with
-
* gcc -Wall $(YOUR_C_OPTIONS) -DTEST_PRINTF -c printf.c
-
* you will get a normal warning:
-
* printf.c:214: warning: spurious trailing `%' in format
-
* this line is testing an invalid % at the end of the format string.
-
*
-
* this should display (on 32bit int machine) :
-
*
-
* Hello
-
* printf test
-
* (null) is null pointer
-
* 5 = 5
-
* -2147483647 = - max int
-
* char a = 'a'
-
* hex ff = ff
-
* hex 00 = 00
-
* signed -3 = unsigned 4294967293 = hex fffffffd
-
* 0 message(s)
-
* 0 message(s) with %
-
* justif: "left "
-
* justif: " right"
-
* 3: 0003 zero padded
-
* 3: 3 left justif.
-
* 3: 3 right justif.
-
* -3: -003 zero padded
-
* -3: -3 left justif.
-
* -3: -3 right justif.
-
*/
-
-
#endif
栈溢出
FreeRTOS 提供了多种特性来辅助跟踪调试栈相关的问题。
uxTaskGetStackHighWaterMark() API 函数
每个任务都独立维护自己的栈空间,栈空间总量在任务创建时进行设定。uxTaskGetStackHighWaterMark()主要用来查询指定任务的运行历史中,其栈空间还差多少就要溢出。这个值被称为栈空间的”高水线(High Water Mark)”。
函数原型:
unsigned portBASE_TYPE uxTaskGetStackHighWaterMark( xTaskHandle xTask );
xTask
被查询任务的句柄——欲知如何获得任务句柄,详情请参见API 函数xTaskCreate()的参数pxCreatedTask。如果传入NULL 句柄,则任务查询的是自身栈空间的高水线。
返回值
任务栈空间的实际使用量会随着任务执行和中断处理过程上下浮动。
uxTaskGetStackHighWaterMark()返回从任务启动执行开始的运行历史中,栈空间具有的最小剩余量。这个值即是栈空间使用达到最深时的剩下的未使用的栈空间。这个值越是接近0,则这个任务就越是离栈溢出不远了。
|
运行时栈侦测 —— 概述
FreeRTOS 包含两种运行时栈侦测机制,由FreeRTOSConfig.h 中的配置常量configCHECK_FOR_STACK_OVERFLOW 进行控制。这两种方式都会增加上下切换开销。
栈溢出钩子函数(或称回调函数)由内核在侦测到栈溢出时调用。要使用栈溢出钩子函数,需要进行以下配置:
(1)在FreeRTOSConfig.h 中把configCHECK_FOR_STACK_OVERFLOW 设为1 或2。
(2)提供钩子函数的具体实现
void 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 个字节相对来说也是很快的。这种方法应该可以侦测到任何时候发生的栈溢出,虽然理论上还是有可能漏掉一些情况,但这些情况几乎是不可能发生的。
其它常见错误
问题现象1:在一个Demo 应用程序中增加了一个简单的任务,导致应用程序崩溃任务
创建时需要在内存堆中分配空间。许多Demo 应用程序定义的堆空间大小只够用于创建Demo 任务——所以当任务创建完成后,就没有足够的剩余空间来增加其它的任务,队列或信号量。
空闲任务是在vTaskStartScheduler()调用中自动创建的。如果由于内存不足而无法创建空闲任务,vTaskStartScheduler()会直接返回。在调用vTaskStartScheduler()后加上一条空循环[for(;;)]可以使这种错误更加容易调试。
如果要添加更多的任务,可以增加内存堆空间大小,或是删掉一些已存在的Demo任务。
问题现象2:在中断中调用一个API 函数,导致应用程序崩溃
除了具有后缀为”FromISR”函数名的API 函数,千万不要在中断服务例程中调用其它API 函数。
问题现象3:有时候应用程序会在中断服务例程中崩溃
需要做的第一件事是检查中断是否导致了栈溢出。
在不同的移植平台和不同的编译器上,中断的定义和使用方法是不尽相同的——所以,需要做的第二件事是检查在中断服务例程中使用的语法,宏和调用约定是否符合Demo 程序的文档描述,以及是否和Demp 程序中提供的中断服务例程范例相同。
如果应用程序工作在Cotex M3 上,需要确定给中断指派优先级时,使用低优先级号数值表示逻辑上的高优先级中断,因为这种方式不太直观,所以很容易被忘记。一个比较常见的错误就是,在优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断中调用了FreeRTOS API 函数。
问题现象4:在启动第一个任务时,调度器就崩溃了
如果使用的是ARM7,那么请确定调用vTaskStartScheduler()时处理器处于管理模式(Supervisor mode)。最简单的方式就是在main()之前的C 启动态码中将处理器设置为管理模式。ARM7 的Demo 应用程序就是这么做的。如果处理器不在管理模式下,调度器是无法启动的。
问题现象5:临界区无法正确嵌套
除了taskENTER_CRITICA()和taskEXIT_CRITICAL(),千万不要在其它地方修改控制器的中断使能位或优先级标志。这两个宏维护了一个嵌套深度计数,所以只有当所有的嵌套调用都退出后计数值才会为0,也才会使能中断。
问题现象6:在调度器启动前应用程序就崩溃了
如果一个中断会产生上下文切换,则这个中断不能在调度器启动之前使能。这同样适用于那些需要读写队列或信号量的中断。在调度器启动之前,不能进行上下文切换。还有一些API 函数不能在调度器启动之前调用。在调用vTaskStartScheduler()之前,最好是限定只使用创建任务,队列和信号量的API 函数。
问题现象7:在调度器挂起时调用API 函数,导致应用程序崩溃
调用vTaskSuspendAll()使得调度器挂起,而唤醒调度器调用xTaskResumeAll()。千万不要在调度器挂起时调用其它API 函数。
问题现象8:函数原型pxPortInitialiseStack()导致编译失败
每种移植都需要定义一个对应的宏,以把正确的内核头文件加入到工程中。如果编译函数原型pxPortInitialiseStack()时出错,这种现象基本上可以确定是因为没有正确定义相应的宏。
可以基本相应平台的Demo 工程建立新的应用程序。这种方式就不用担心没有包含正确的文件,也不必担心没有正确地配置编译器选项。
阅读(4321) | 评论(0) | 转发(0) |