openLuat / LuatOS

LuatOS -- Powerful embedded Lua Engine for IoT devices, with many components and low memory requirements (16K RAM, 128K Flash)
https://wiki.luatos.com
MIT License
452 stars 102 forks source link

uart代码触发内存爆满的讨论 #46

Closed wendal closed 4 years ago

wendal commented 4 years ago

先把原始代码贴上

local sys = require "sys"
log.info("main", "uart demo")
local uartid = 1
local recvBuff = {{}, {}}
--初始化
local result = uart.setup(
    uartid,--串口id
    115200,--波特率
    8,--数据位
    1--停止位
)
local function read(uid)
    local s = table.concat(recvBuff[uid])
    recvBuff[uid] = {}
    uart.write(uartid,s)
end
sys.timerLoopStart(function()
    log.info("RAM:", _G.collectgarbage("count"))-- 打印占用的RAM
end, 1000)
local function taskRead()
    uart.on(
        1,
        'receive',
        function(uid, length)
            table.insert(recvBuff[uid], uart.read(uid, length or 1024))
            sys.timerStart(sys.publish, 50, 'UART_RECV_WAIT_', uid)
        end
    )
end
sys.subscribe('UART_RECV_WAIT_', read)
sys.taskInit(taskRead)
sys.run()
wendal commented 4 years ago

硬件: w60x, Lua虚拟机专属内存64k

上述代码在uart数据量小的时候正常.

以300字节每次, 100ms为间隔, 即可触发异常,提示无法not enough memory for buffer allocation

异常所致代码在 lauxlib.c

static void *resizebox (lua_State *L, int idx, size_t newsize) {
  void *ud;
  lua_Alloc allocf = lua_getallocf(L, &ud);
  UBox *box = (UBox *)lua_touserdata(L, idx);
  void *temp = allocf(ud, box->box, box->bsize, newsize);
  if (temp == NULL && newsize > 0) {  /* allocation error? */
    lua_gc(L, LUA_GCCOLLECT, 0);
  }
  if (temp == NULL && newsize > 0) {  /* allocation error? */
    resizebox(L, idx, 0);  /* free buffer */
    luaL_error(L, "not enough memory for buffer allocation");
  }
  box->box = temp;
  box->bsize = newsize;
  return temp;
}

代码的风格是LuaTask 2.x, 即合宙Air202/Air80x/Air72x的Luat的写法.

从语法和代码逻辑上看, 是合法的代码:

有uart数据进入事,压入缓存队列, 发布消息 监听消息, 将缓存队列合并为字符串, 写入uart

wendal commented 4 years ago

luat_uart_rtt的当前实现是这样的:

  1. uart硬件中断, 经rtt流转,到达luat注册的回调方法
  2. uart c回调, 将通知消息插入消息队列, 这里没有读取uart数据.
  3. lua主线程被唤醒(因为消息队列有数据了)
  4. 读取消息,触发uart回调方法
  5. 回调方法读取uart数据

其中一个已知限制(或者叫问题), 如果uart有大量数据输入, 不断触发uart中断, lua线程无法启动, 最终消息队列塞满. 直至uart数据输入减慢, lua线程才有机会复活.

wendal commented 4 years ago

对原始代码的改造:

        function(uid, length)
            --table.insert(recvBuff[uid], uart.read(uid, length or 1024))
            --sys.timerStart(sys.publish, 50, 'UART_RECV_WAIT_', uid)
            uart.write(uid, uart.read(uid, length or 1024))
        end

读取数据并马上回写, 能在原报错条件下正常运行.

wendal commented 4 years ago

还有个地方是

local function read(uid)
    local s = table.concat(recvBuff[uid])
    recvBuff[uid] = {}
    uart.write(uartid,s)
end

写入1kb数据, 会起码使用2kb的内存. 这写法在Air202/Air72x系列是没问题的, 它们内存都在1mb以上, 有足够的内存空间, 但对于w60x来说就是太奢侈的写法.

还有一个疑惑, 如果能支持 uart.write(uartid, recvBuff[uid]) 即uart.write直接传入table, 也许能解决, 待验证.

chain01 commented 4 years ago

测试中还发现一个问题,如果一次发送数据超过1k,间隔过小会出现抛出总线异常。看代码好像是串口回调导致的。一会贴详情。

chain01 commented 4 years ago

发送1k数据,间隔300ms会出现如下警告,系统会陷入假死,过一段时间重启 W/luat.msgbus: msgbus is FULL!!!! W/luat.msgbus: msgbus is FULL!!!! W/UART: Warning: There is no enough buffer for saving data, please increase the RT_SERIAL_RB_BUFSZ option. W/luat.msgbus: msgbus is FULL!!!!

wendal commented 4 years ago

看来得改成缓存区才行

wendal commented 4 years ago

原因有两个:

  1. uart中断的msg, 应该做个标志位, 不能一直往msgbus塞消息
  2. l_uart_read有bug, 因为luat_uart_read返回的是int, 接收用了uint8_t,超过255字节后, 就很可能爆了