如何为低内存系统设置 flex 和 bison?
How to setup flex and bison for low memory systems?
我喜欢借助 flex 和 bison 构建一个简单的协议解析器。它解析像 "reset"、"led on"、"set uart 115200,N,8" 这样的命令。
解析器应该 运行 在 stm32f4 MCU(48Mhz,256KBytes RAM)上。
在 MCU 运行s Zephyr OS 组织时序和内存。将执行线程的堆栈大小设置得更高一些(大约 5kBytes)后,我可以在没有内存异常的情况下调用 yyparse。
现在我面临一个新问题。似乎 flex 运行s 内存不足:
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!
有什么办法可以解决这个问题吗?
我的 flexer 设置是:
%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;
}
}
}
在开始讨论节省内存之前的重要说明。您覆盖 yy_fatal_error
,大概是为了避免使用 fprintf
。这很好,但重要的是要注意 yy_fatal_error
被声明为 noreturn
,并且 flex 扫描器不期望它为 return。您的实施 必须 遵守此规则;它应该调用 exit
或 Zephyr OS 中的任何等效项;否则,扫描器将在发生致命错误后尝试继续,从而导致更多致命错误或段错误或其他不良后果。 (此外,添加感叹号有点奇怪。它看起来像是您使用的是非标准版本的 flex。)
实际上我对错误 out of dynamic memory in yyensure_buffer_stack()!
有点吃惊,因为该错误是在仅分配了一个指针的位置产生的,大概在您的系统上总共有四个字节。这表明 malloc
根本不起作用,这将被证明是一个问题,因为如果没有一些动态存储分配,flex 扫描器将无法工作。 (如果 malloc
不可用,可以提供替代功能。)
扫描器分配四个字节来存储指向新创建的缓冲区状态对象的指针后,它会继续分配缓冲区状态本身(大约四十个字节),然后分配缓冲区。缓冲区(通常)为 16378 字节(加上两个额外的字节,总共 16380),这对于只有 256k 内存的机器来说是相当多的。这就是您要关注的部分。
实际上,很少需要这么大的缓冲区,特别是因为扫描器通常可以在需要时重新分配缓冲区。缓冲区需要比您希望解析的最大单个标记大一点;尽管对于交互式程序来说,它的长度足以容纳一行输入可能会有所帮助。默认设置对于需要处理长注释和字符串常量的编译器很有帮助,但对于您的应用程序,使用更小的缓冲区可能就足够了。
有两种减少缓冲区大小的基本方法。最简单的方法是在序言中定义一个宏:
#define YY_BUF_SIZE 200
然后您可以使用正常的 YY_INPUT
机制来读取输入,就像您示例中的代码一样。
虽然这很简单,但并不是最有效的。一次读取一个字符(或一次读取几个字符)会导致大量的重新扫描,而且内核队列机制也会增加一些开销,其中 none 是必需的。假设您的协议消息明确地以换行符终止(也就是说,每条消息都以换行符终止并且每个换行符终止一条消息)那么一个更有效的解决方案是将一行输入累积到您自己的缓冲区中,您可以直接从串口做,然后从缓冲区解析整行。
您需要记住的一件事是弹性缓冲区必须以两个 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 状态,如果在没有读取整个输入的情况下解析失败,这会很有用。
缓冲区本身永远不会 malloc'd 或 free'd,因为它是您的缓冲区。它也永远不会重新填充,因此您可以定义 YY_INPUT
不执行任何操作或抛出错误。
我喜欢借助 flex 和 bison 构建一个简单的协议解析器。它解析像 "reset"、"led on"、"set uart 115200,N,8" 这样的命令。 解析器应该 运行 在 stm32f4 MCU(48Mhz,256KBytes RAM)上。
在 MCU 运行s Zephyr OS 组织时序和内存。将执行线程的堆栈大小设置得更高一些(大约 5kBytes)后,我可以在没有内存异常的情况下调用 yyparse。 现在我面临一个新问题。似乎 flex 运行s 内存不足:
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!
有什么办法可以解决这个问题吗?
我的 flexer 设置是:
%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;
}
}
}
在开始讨论节省内存之前的重要说明。您覆盖 yy_fatal_error
,大概是为了避免使用 fprintf
。这很好,但重要的是要注意 yy_fatal_error
被声明为 noreturn
,并且 flex 扫描器不期望它为 return。您的实施 必须 遵守此规则;它应该调用 exit
或 Zephyr OS 中的任何等效项;否则,扫描器将在发生致命错误后尝试继续,从而导致更多致命错误或段错误或其他不良后果。 (此外,添加感叹号有点奇怪。它看起来像是您使用的是非标准版本的 flex。)
实际上我对错误 out of dynamic memory in yyensure_buffer_stack()!
有点吃惊,因为该错误是在仅分配了一个指针的位置产生的,大概在您的系统上总共有四个字节。这表明 malloc
根本不起作用,这将被证明是一个问题,因为如果没有一些动态存储分配,flex 扫描器将无法工作。 (如果 malloc
不可用,可以提供替代功能。)
扫描器分配四个字节来存储指向新创建的缓冲区状态对象的指针后,它会继续分配缓冲区状态本身(大约四十个字节),然后分配缓冲区。缓冲区(通常)为 16378 字节(加上两个额外的字节,总共 16380),这对于只有 256k 内存的机器来说是相当多的。这就是您要关注的部分。
实际上,很少需要这么大的缓冲区,特别是因为扫描器通常可以在需要时重新分配缓冲区。缓冲区需要比您希望解析的最大单个标记大一点;尽管对于交互式程序来说,它的长度足以容纳一行输入可能会有所帮助。默认设置对于需要处理长注释和字符串常量的编译器很有帮助,但对于您的应用程序,使用更小的缓冲区可能就足够了。
有两种减少缓冲区大小的基本方法。最简单的方法是在序言中定义一个宏:
#define YY_BUF_SIZE 200
然后您可以使用正常的 YY_INPUT
机制来读取输入,就像您示例中的代码一样。
虽然这很简单,但并不是最有效的。一次读取一个字符(或一次读取几个字符)会导致大量的重新扫描,而且内核队列机制也会增加一些开销,其中 none 是必需的。假设您的协议消息明确地以换行符终止(也就是说,每条消息都以换行符终止并且每个换行符终止一条消息)那么一个更有效的解决方案是将一行输入累积到您自己的缓冲区中,您可以直接从串口做,然后从缓冲区解析整行。
您需要记住的一件事是弹性缓冲区必须以两个 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 状态,如果在没有读取整个输入的情况下解析失败,这会很有用。
缓冲区本身永远不会 malloc'd 或 free'd,因为它是您的缓冲区。它也永远不会重新填充,因此您可以定义 YY_INPUT
不执行任何操作或抛出错误。