我喜欢借助flex和bison构建一个简单的协议解析器。它解析“ reset”,“ led on”,“ set uart 115200,N,8”之类的命令。 解析器应在stm32f4 MCU(48Mhz,256KBytes RAM)上运行。
在MCU上运行Zephyr操作系统,该操作系统可组织时序和内存。在将执行线程的堆栈大小设置得更高(大约5kBytes)之后,我可以在没有内存异常的情况下调用yyparse。 现在,我面临一个新问题。似乎flex内存不足:
out of dynamic memory in yyensure_buffer_stack()!
out of dynamic memory in yy_create_buffer()!
out of dynamic memory in yy_create_buffer()!
flex scanner jammed!
fatal flex scanner internal error--end of buffer missed!
有什么想法可以解决这个问题吗?
我的屈肌设置是:
%option warn noyywrap nodefault
%option 8Bit batch never-interactive
%{
#include <stdlib.h>
#include <stdio.h>
// stack size
#define YYMAXDEPTH 50
#define YY_NO_UNISTD_H
#include "notification_par.h"
#define MY_YY_INPUT(buf,result,max_size) \
{ \
size_t n; \
my_yyInput(buf, &n, max_size); \
result = n; \
} \
#define YY_INPUT MY_YY_INPUT
#define YY_DECL int yylex()
#define YY_FATAL_ERROR(msg) my_fatal_error( msg )
%}
和野牛之一:
%{
#define YY_NO_UNISTD_H
#include <stdio.h>
#include <stdlib.h>
extern int yylex();
extern int yyparse();
extern FILE* yyin;
void yyerror(const char* s);
#include <misc/printk.h>
%}
%union {
char val[64];
}
我从uart3队列中读取字符的简单函数:
void my_yyInput( char *buffer, size_t * numBytesRead, size_t bLn ) {
size_t i;
*numBytesRead = 0;
for (i = 0; i < bLn; i++) {
if ( 0 == k_msgq_get(&uart3.rxQ, buffer, MSEC(100))) {
buffer++;
(*numBytesRead)++;
} else {
// make sure that we read at least one char
if (*numBytesRead) return;
}
}
}
答案 0 :(得分:1)
在谈到节省内存之前,有一个重要说明。您覆盖yy_fatal_error
,大概是避免使用fprintf
。很好,但是必须注意,yy_fatal_error
被声明为noreturn
,而flex扫描程序不希望它返回。您的实现必须遵守此规则;它应该调用exit
或Zephyr OS中的等效名称;否则,扫描仪将在出现致命错误后尝试继续操作,从而导致更多致命错误,段错误或其他不良后果。 (此外,添加感叹号有点奇怪。看起来好像您使用的是非标准版本的flex。)
我实际上对错误out of dynamic memory in yyensure_buffer_stack()!
感到惊讶,因为该错误是在仅分配一个指针的点上产生的,该指针可能在您的系统上总共有四个字节。这表明malloc
根本不起作用,这将成为一个问题,因为如果没有某些动态存储分配,弹性扫描器将无法工作。 (如果malloc
不可用,则可以提供替代功能。)
扫描程序分配了四个字节以存储指向新创建的缓冲区状态对象的指针后,它将继续分配缓冲区状态本身(大约40个字节),然后分配缓冲区。缓冲区通常是16378个字节(加上两个额外的字节,总计16380个字节),对于只有256k内存的机器来说,这是一个很大的数目。这就是您要重点关注的部分。
在实践中,很少需要这么大的缓冲区,尤其是因为扫描程序通常可以根据需要重新分配缓冲区。缓冲区需要比您期望解析的最大单个令牌大一些;尽管对于交互式程序,它足够长以容纳一行输入可能会有所帮助。默认设置对需要处理长注释和字符串常量的编译器很有帮助,但是对于您的应用程序来说,使用较小的缓冲区可能就足够了。
有两种减少缓冲区大小的基本方法。最简单的方法就是在序言中定义一个宏:
#define YY_BUF_SIZE 200
然后,您可以使用常规的YY_INPUT
机制来读取输入,如示例代码中一样。
虽然很简单,但这并不是最有效的。一次读取一个字符(或一次读取几个字符)会导致大量的重新扫描,而且内核队列机制还会增加一些开销,而这些开销都不是必需的。假设您的协议消息明确地以换行符终止(也就是说,每条消息均以换行符终止,而每条换行符均终止一条消息),那么一种更有效的解决方案是将输入行累积到您自己的缓冲区中,您可以直接从串行端口执行操作,然后从缓冲区解析整行。
您需要记住的一件事是,弹性缓冲区必须以两个 NUL字节终止。因此,您的输入缓冲区需要比您期望能够处理的最长协议消息长两个字节。
结果可能看起来像这样:
/* Assume there is an ISR which accumulates characters into line until a newline
* is received, updating line_read for each character. It needs to stop reading
* when it reaches MAX_PROTOCOL_SIZE.
*/
#define MAX_PROTOCOL_SIZE 254
char line[MAX_PROTOCOL_SIZE + 2];
int line_read = 0;
/* When the new line character is read, this function is called. Here I assume
* that the ISR did not bother to NUL-terminate the input.
*/
void process_line(char* buffer, int buflen) {
buffer[buflen] = buffer[buflen + 1] = 0; /* Terminate with two NULs */
YY_BUFFER_STATE flex_buf = yy_scan_buffer(buffer, buflen + 2);
yyparse();
yy_delete_buffer(flex_buf);
yylex_destroy(); /* See note */
}
对yylex_destroy
的调用将释放()动态分配(如前所述,这是很小的:指向缓冲区状态的单个指针和缓冲区状态本身,大约40个字节。您可以重用它们)分配;调用yylex_destroy的好处是它会重新初始化flex状态,这在解析失败而未读取整个输入的情况下很有用。
缓冲区本身永远不会被分配或释放,因为它是您的缓冲区。它也永远不会重新填充,因此您可以定义YY_INPUT
不执行任何操作或引发错误。