snlua 服务绝对是 skynet 中最重要的服务了,是一切 lua 服务的原型,也是 99% 情况下业务中使用的服务。本篇会讲解一下 snlua 服务的实现。
结构
|
|
snlua 的核心数据就是一个 lua 状态机,围绕状态机,skynet 做了一些扩展工作,主要是为其自定义了内存分配函数,以及为其提供了一个接受外部信号打断其运行状态的机制。
创建
|
|
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 协程之前和之后,分别记录时间,将其汇总到总时间中去。