skynet源码分析(九)snlua服务详解

  snlua 服务绝对是 skynet 中最重要的服务了,是一切 lua 服务的原型,也是 99% 情况下业务中使用的服务。本篇会讲解一下 snlua 服务的实现。

结构

1
2
3
4
5
6
7
8
9
struct snlua {
    lua_State *L; // 状态机
    struct skynet_context *ctx; // 关联的 skynet context
    size_t mem; // 已经使用的内存
    size_t mem_report; // 触发内存警告的阈值
    size_t mem_limit; // 内存的上限设置
    lua_State *activeL; // 目前正在运行的状态机
    ATOM_INT trap; // 打断状态
};

  snlua 的核心数据就是一个 lua 状态机,围绕状态机,skynet 做了一些扩展工作,主要是为其自定义了内存分配函数,以及为其提供了一个接受外部信号打断其运行状态的机制。

创建

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#define MEMORY_WARNING_REPORT (1024 * 1024 * 32)
struct snlua *snlua_create(void) {
    struct snlua *l = skynet_malloc(sizeof(*l));
    memset(l, 0, sizeof(*l));
    l->mem_report = MEMORY_WARNING_REPORT;
    l->mem_limit = 0;
    l->L = lua_newstate(lalloc, l);
    l->activeL = NULL;
    ATOM_INIT(&l->trap, 0);
    return l;
}

  module 的创建主要就是对其私有数据的创建,snlua 中也不例外,过程就是创建一个 snlua 的结构体变量,然后初始化它其中的变量。
  可以看到单个 snlua 服务的内存警告的初始阈值是 32M 大小。这个并不是硬上限,只是一个警告阈值,超过了会触发一次警告,然后这个值会被修改为原来的两倍,直到下一次触发警告。内存分配使用了自定义的 lalloc 来做,还有一点需要注意,在创建状态机的时候,把 snlua 的指针也传进去了,这个在协程部分会用到。

初始化

  初始化的时候用了一个有趣的操作,并没有直接在 snlua_init 里面做初始化,而是给自己发了一条消息,然后在消息的回调函数 launch_cb 里面再通过调用了 init_cb 做了初始化。
  Lua 状态机的环境都是在 init_cb 中进行初始化的。大概进行了以下几点操作

  • 加载标准库
  • 加载 skynet.profile 库
  • 使用 skynet.profile 库中的 resume 和 wrap 替换掉标准库协程中的同名函数
  • 加载 skynet.codecache 库
  • 从环境变量中拿取配置的那些文件加载的地址数据,加载进来
  • 加载 loader.lua
  • 通过 loader 加载指定的服务文件
  • 检查是否设置了 lua 服务的内存上限,如果有则要设置

内存分配

  自定了 Lua 的内存分配函数为 lalloc,主要的操作就是处理 snlua 中跟内存有关的三个变量 mem / mem_limit / mem_report

  • 修改当前占用的内存 mem
  • 检查内存分配上限 mem_limit,如果超了上限不能分配
  • 检查内存使用的警告阈值,如果超过了阈值,要给出警告,并且把警告阈值翻倍

  因为自定义了内存分配函数,所以同时也支持了输出当前使用的内存,可以通过 debug 后台发信号查询指定服务的内存占用。

信号打断

  snlua 在自己的 signal 接口中,支持了打断正在运行的脚本的功能。这个功能主要是用来打断可能陷入死循环的服务的。
  打断的实现需要用到 snlua 中的 activeL 和 trap 这两个变量。trap 是个原子变量,用来标识当前是否正在打断过程中,之所以需要是原子变量是因为 signal 可能并行触发。activeL 变量用来标识当前正在执行的 Lua 状态机。
  要理解 activeL 变量的作用需要了解一些 Lua 的协程实现才可以。Lua 的每个协程对应的结构也是 lua_State,和 Lua 的主虚拟机是相同的,在协程内部的调用中会使用协程自己的 lua_State 执行各种操作。这就有了一个问题,要打断当前运行的脚本,需要给 lua_State 设置钩子函数,所以需要一个变量来记录当前是哪个 lua_State 正在执行,又因为切换 lua_State 的过程是发生在对协程的 resume 调用中的,所以需要自己实现一个 resume 来替换掉标准库中的 coroutine.resume,也就是 luaB_coresume 了。在发生 lua_State 切换的时候,snlua 都会修改 activeL 变量指向新的活动状态机。
  打断脚本的操作本身很简单,通过 Lua 的钩子函数机制调用 signal_hook,signal_hook 中使用 lua_getallocf 拿到创建 lua_State 的时候存进去的 snlua 结构体的指针,将其 trap 变量修改为可打断状态,然后调用 luaL_error 报错来打断当前的执行逻辑。

profile

  snlua 中还实现了 profile 的功能,通过在脚本中调用 profile.start 和 profile.stop 来拿到中间的时间间隔。实现是通过在 start 的时候创建一个 start_time 变量,在 stop 的时候计算时间间隔。中间涉及协程的调用时,也是通过自定义的 resume 函数来实现的,在 resume 协程之前和之后,分别记录时间,将其汇总到总时间中去。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计