代码之家  ›  专栏  ›  技术社区  ›  Stefan Jaritz

如何为低内存系统设置flex和bison?

  •  0
  • Stefan Jaritz  · 技术社区  · 6 年前

    我喜欢在flex和bison的帮助下构建一个简单的协议解析器。它解析命令,如“重置”,“led开启”,“设置uart 115200,N,8”。 解析器应该在stm32f4 MCU(48Mhz,256KBytes RAM)上运行。

    在MCU上运行Zephyr操作系统,它可以组织时间和内存。在将执行线程的堆栈大小设置得更高一点(大约5字节)之后,我可以调用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;
            }
        }
    }
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   rici    6 年前

    在我谈到节省内存之前,有一点很重要。你凌驾于 yy_fatal_error ,大概是为了避免使用 fprintf . 很好,但要注意 yy_致命错误 被宣布 noreturn ,并且flex扫描仪不希望它返回。你的实现 必须 遵守这条规则;它应该 exit 或Zephyr操作系统中的任何等效项;否则,扫描仪将在发生致命错误后尝试继续,从而导致更致命的错误或segfults或其他不希望出现的后果。(还有,加上感叹号有点难。它使您看起来像在使用非标准版本的flex。)

    我真的有点惊讶于这个错误 out of dynamic memory in yyensure_buffer_stack()! ,因为该错误是在只分配一个指针的点上产生的,这可能是系统上总共4个字节。这说明 malloc 根本不起作用,这将证明是一个问题,因为flex扫描器在没有动态存储分配的情况下无法工作。(如果 马洛克 不可用,可以提供替代功能。)

    在扫描器分配4个字节来存储指向新创建的缓冲区状态对象的指针之后,它继续分配缓冲区状态本身(大约40个字节),然后分配缓冲区。缓冲区(通常)是16378字节(加上两个额外字节,总共16380字节),对于一台只有256k内存的机器来说,这是相当大的一部分。这就是你要关注的部分。

    实际上,很少需要这么大的缓冲区,特别是因为扫描仪通常可以在需要时重新分配缓冲区。缓冲区需要比您希望解析的最大单个令牌大一点;尽管对于交互式程序,它可能有助于保持足够长的时间来容纳一行输入。默认设置对于需要处理长注释和字符串常量的编译器很有帮助,但对于您的应用程序,使用更小的缓冲区可能就足够了。

    减少缓冲区大小有两种基本方法。简单的方法是在prolog中定义一个宏:

    #define YY_BUF_SIZE 200
    

    那你就可以用普通的 YY_INPUT 读取输入的机制,如示例中的代码。

    虽然这很简单,但不是最有效的。一次读取一个字符(或者一次读取几个字符)会导致大量的重新扫描,而且内核队列机制会增加一些开销,这些都不是必需的。假设您的协议消息明确地以换行符终止(即,每条消息都以换行符终止,每条换行符终止一条消息),那么更有效的解决方案是将一行输入累积到您自己的缓冲区中,您可以直接从串行端口执行此操作,然后解析缓冲区中的整行。

    您需要记住的一点是,flex缓冲区必须以 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 will free()动态分配(如前所述,它很小:指向缓冲区状态的单个指针和缓冲区状态本身,大约40字节)。您可以重用这些分配;调用yylex_destroy的好处是,它重新初始化flex状态,如果解析失败而不读取整个输入,那么这将非常有用。

    缓冲区本身既不是malloc'd也不是free'd,因为它是您的缓冲区。它也不会再装满,所以你可以定义 YY_输入 无所事事或犯错误。