Skynet 中 snlua 服务启动整体流程分析

前言
在 Skynet 中,Lua 扮演了极其重要的角色。Skynet 大多数业务逻辑都跑在一个个 Lua 服务里,而能够将 Lua 环境嵌入到 Skynet 框架下,并与 Skynet 消息调度机制完美结合,正是 snlua 服务所承担的核心功能。

本文将着重分析 snlua 服务的核心实现,包括其初始化过程、协程扩展(Profile)、内存管理,以及如何与 Skynet 主循环交互等细节,帮助你在阅读 Skynet 代码或自定义服务时更好地理解这一部分的原理与运作。


1. 整体概览

snlua 服务是 Skynet 提供的一个 C 语言服务(又称为 service ),它的主要职责是:

  1. 创建并管理 Lua 虚拟机:调用 lua_newstate 建立独立的 Lua 环境。
  2. 接收并处理来自 Skynet 的消息:通过设置回调函数 skynet_callback,让 Lua 脚本处理游戏或业务逻辑的各种消息。
  3. 扩展 Lua 协程的性能统计(Profile):覆盖 coroutine.resumecoroutine.wrap,并使用钩子(hook)来计量协程执行的耗时。
  4. 内存使用监控与限制:自定义分配器 lalloc,实现对 Lua 内存占用的统计与警告甚至限制。
  5. 执行 Lua 启动脚本:如 lualoader.lua,并从外部接收启动参数,将其传递给 Lua 环境初始化服务逻辑。

通过 snlua 服务,Skynet 可以在 C 层面精准控制和监控 Lua 层的一些关键点(如内存、协程)。这使得 Skynet 保持了高性能的特性,且拥有动态脚本语言的灵活性。


2. 核心数据结构

snlua.c 中,最核心的结构体是:

struct snlua {lua_State * L;                // 该服务所属的 Lua VM 主线程struct skynet_context * ctx;  // Skynet 服务上下文size_t mem;                   // 当前 Lua VM 内存总占用size_t mem_report;            // 触发内存预警的阈值size_t mem_limit;             // 内存上限,如果 mem 超过此值,分配器将返回 NULLlua_State * activeL;          // 当前处于活跃状态的协程ATOM_INT trap;                // 用于处理 signal_hook 的标志
};
  • L:当前服务对应的 Lua 主线程(Lua VM)。
  • ctx:Skynet 中表示一个服务的上下文,用于在 C 层与 Skynet 通信。
  • mem:当前分配的 Lua 内存总大小。
  • mem_report:当内存超出该值时,会触发一次警告日志,并将此值翻倍。
  • mem_limit:Lua 内存分配的上限,若超出则直接返回 NULL 并引发错误。
  • activeL:当前执行的协程,配合信号机制进行调试或中断操作。
  • trap:一个原子变量,处理调试信号(signal_hook)的标志位。

3. 初始化过程

3.1 创建并初始化 snlua

struct snlua * snlua_create(void) {struct snlua * l = skynet_malloc(sizeof(*l));memset(l,0,sizeof(*l));l->mem_report = MEMORY_WARNING_REPORT; // 默认32M触发报警l->mem_limit = 0;                      // 默认为0,即无限制l->L = lua_newstate(lalloc, l);        // 创建独立的 Lua VM,并使用自定义分配器l->activeL = NULL;ATOM_INIT(&l->trap , 0);return l;
}

这里做了几件事:

  1. 动态分配 snlua 结构体,并初始化所有字段。
  2. l->mem_report 默认 32M,用于记录内存使用量到达 32M 时进行一次报警,并将阈值翻倍。
  3. 调用 lua_newstate 创建新的 Lua VM,分配器采用自定义的 lalloc 用于内存统计。
  4. 初始化 trap 原子变量为 0,表示当前没有任何调试请求。

3.2 服务启动

当 Skynet 创建一个 snlua 服务时,会指定一个启动函数 launch_cb 作为回调:

int snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {// 将 args 的内容复制一份,随后发送给自己的服务句柄// 由 launch_cb 来进行后续处理skynet_callback(ctx, l , launch_cb);...skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);return 0;
}
  • skynet_callback(ctx, l, launch_cb):当收到第一个消息时,launch_cb 会被调用。
  • snlua_init 将传入的 args 数据打包后再以消息方式发送给自己,从而触发 launch_cb

launch_cb 中,执行真正的初始化函数 init_cb

static int launch_cb(struct skynet_context * context, void *ud, int type, int session,uint32_t source , const void * msg, size_t sz) {struct snlua *l = ud;skynet_callback(context, NULL, NULL);int err = init_cb(l, context, msg, sz);if (err) {skynet_command(context, "EXIT", NULL);}return 0;
}

此时将回调置空,保证后续消息交给 Lua 层处理。若初始化失败,则发送退出命令。


3.3 init_cb:Lua 环境设置与脚本加载

static int init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {// 1. 绑定 luaL_openlibs, 初始化标准库和 profile 扩展// 2. 设置 Lua 环境变量 (LUA_PATH, LUA_CPATH, LUA_SERVICE 等)// 3. 加载并执行 loader.lua// 4. 处理内存限制...return 0;
}

其中几个关键点:

  1. 注册标准库与 profile
    调用 luaL_openlibs(L) 加载基础标准库,同时调用 luaL_requiref(L, "skynet.profile", init_profile, 0) 引入了自定义的 skynet.profile 模块,以实现对 Lua 协程的耗时统计。

  2. 覆盖 coroutine.resumecoroutine.wrap
    Skynet 会将 resumewrap 替换为自己封装的函数,从而统计协程的运行时长。

  3. 设置全局变量
    LUA_PATH, LUA_CPATH, LUA_SERVICE, LUA_PRELOAD,方便在 Lua 中通过相应的全局变量查找脚本或动态库。

  4. 加载 loader.lua
    默认路径是 ./lualib/loader.lua,这是 Skynet 启动 Lua 服务的核心脚本,其会加载真正的服务主脚本并传入 args 作为启动参数。

  5. 内存限制
    如果在 Lua 中设置了 memlimit,则会读取并保存到 l->mem_limit,用于在分配器 lalloc 中进行判定。

整个过程完成后,Skynet 中的 Lua 环境就处于可运行状态,后续所有消息将交由 Lua 层脚本来处理。


4. 内存分配器 lalloc

static void * lalloc(void * ud, void *ptr, size_t osize, size_t nsize) {struct snlua *l = ud;size_t mem = l->mem;l->mem += nsize;if (ptr)l->mem -= osize;if (l->mem_limit != 0 && l->mem > l->mem_limit) {// 如果超出限制,分配失败if (ptr == NULL || nsize > osize) {l->mem = mem;return NULL;}}// 报警逻辑if (l->mem > l->mem_report) {l->mem_report *= 2;skynet_error(l->ctx, "Memory warning %.2f M", (float)l->mem / (1024 * 1024));}return skynet_lalloc(ptr, osize, nsize);
}
  • 统计:记录当前服务所使用的 Lua VM 内存总量 l->mem
  • 限制:当 mem_limit != 0l->mem > l->mem_limit,代表超出上限,直接返回 NULL,从而导致 Lua 层内存分配失败并产生错误。
  • 报警:若 l->mem 超过 l->mem_report,则打印一条警告日志并将 mem_report 翻倍。

这样就保证了每个 snlua 服务可以独立监控内存使用量,防止无限制地占用系统资源。


5. Lua 协程 Profile 机制

Skynet 对 Lua 协程做了扩展,使得我们可以统计每个协程的运行时间。主要思路:

  1. 替换 coroutine.resumecoroutine.wrap
    init_profile 函数中,用自定义的函数 luaB_coresumeluaB_cowrap 覆盖原版,以进行耗时计算。

  2. 开始与结束
    start_timetotal_time 表示协程本次启动时间和累计耗时。每次协程 resume 时记下开始时间,协程返回或 yield 时累加耗时。

  3. 结果
    profile.stop() 时,可以拿到协程执行的总耗时,帮助开发者做性能分析。


6. 信号钩子(signal_hook

static void signal_hook(lua_State *L, lua_Debug *ar) {...if (ATOM_LOAD(&l->trap)) {ATOM_STORE(&l->trap , 0);luaL_error(L, "signal 0");}
}
  • snlua_signal(l, 0) 设置 trap 时,Skynet 会在下一次指令执行前(通过 lua_sethook)抛出一个错误,强制中断 Lua 执行。这在调试或终止协程时非常有用。
  • 如果 signal = 1,则仅打印当前内存使用量。

7. snlua 服务启动流程小结

  1. 创建 snlua 结构,设置自定义分配器与内存统计逻辑。
  2. 回调函数 launch_cb 中调用 init_cb 完成 Lua VM 初始化:
    • 加载标准库和 Skynet Profile 扩展。
    • 设置 LUA_PATHLUA_CPATHLUA_SERVICE 等全局环境。
    • 加载并运行 loader.lua,启动 Lua 端业务逻辑。
  3. 后续消息:当 Skynet 将消息派发给这个 snlua 服务时,就会交由 Lua 层的服务脚本来处理,通常通过 skynet.dispatchskynet.start 设定的回调函数进行处理。

8. 实际使用与自定义

8.1 在 config 中配置

在 Skynet 的 config 文件或启动命令行中,你可以指定服务类型为 snlua 来启动一个 Lua 服务。例如:

# config
thread = 8
bootstrap = "snlua bootstrap"
lua_path = "./lualib/?.lua;./lualib/?/init.lua;"
lua_cpath = "./luaclib/?.so;"

8.2 启动一个 Lua 服务

从命令行或在启动脚本中,可以通过 skynet.newservice("xxx")skynet.uniqueservice("xxx") 来创建一个新的 Lua 服务,底层就是在调度器里分配一个 snlua 服务并调用 snlua_init,随后再用 launch_cb 加载 xxx.lua 脚本。

8.3 查看内存警告

当某个 Lua 服务的内存使用量超过 MEMORY_WARNING_REPORT(默认为 32M)时,会打印一条:

[:0000000x] Memory warning 64.00 M

表示该服务已经使用了 64M(后续阈值会翻倍到 128M),便于开发者及时定位内存问题。


9. 结语

snlua 服务作为 Skynet 框架与 Lua 语言结合的关键模块,不仅为 Lua 逻辑层提供了隔离、独立的执行环境,还在 C 层面通过内存管理、协程扩展、信号捕获等手段,为服务器开发者提供了更高的可控性和可调试性。


参考链接

  • Skynet 官方仓库
  • Skynet Wiki
  • Lua 5.4 官方文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/42665.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

旋转变换原理

旋转变换原理 旋转是仿射变换的一种,通过变换矩阵实现图像绕指定中心旋转,保持直线和平行性不变。其数学表示为: 其中: ( c x , c y ) (c_x, c_y) (cx​,cy​) 是旋转中心。 θ \theta θ 是旋转角度(逆时针为正&…

【计算机网络】DHCP工作原理

DHCP(动态主机配置协议) Dynamic Host Configuration Protocol 基于UDP协议传输 DHCP分配IP地址的过程 (1)DHCP DISCOVER客户机请求 IP 地址: 当一个 DHCP 客户机启动时,客户机还没有 IP 地址,所以客户机要通过 DHC…

应用于汽车车灯电路中的电感产品选型及质量管控标准

随着汽车的智能化与电动化发展,汽车车灯系统逐渐从单一照明功能向集成化、智能化和高能效方向演进。汽车车灯的性能关系着行车安全和驾驶体验,而车规级电感器作为车灯驱动电源电路中的核心元件,其性能直接决定了汽车车灯的效率、可靠性及环境…

MinGW下编译ffmpeg源码时生成compile_commands.json

在前面的博文MinGW下编译nginx源码中,有介绍到使用compiledb工具在MinGW环境中生成compile_commands.json,以为compiledb是捕获的make时的输出,而nginx生成时控制台是有输出编译时的命令行信息的,笔者之前编译过ffmpeg的源码&…

JDBC FetchSize不生效,批量变全量致OOM问题分析

背景 一个简单的基于 JDBC 采集数据库表的功能,当采集 Postgre SQL 某表,其数据量达到 500万左右的时候,程序一启动就将 JVM 堆内存「6G」干满了。 问题是程序中使用了游标的只前进配置,且设置了 fetchSize 属性: q…

OpenCV 图形API(或称G-API)

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 引言 OpenCV 图形API(或称G-API)是一个新的OpenCV模块,旨在使常规图像处理更快且更便携。通过引入一种新的基于图的执行…

注意!ChatGPT 全新 AI 图像功能延迟对免费用户开放

2025 年 3 月 25 日,OpenAI 正式宣布在 ChatGPT 中推出基于 GPT-4o 模型的全新原生图像生成功能。 这一功能允许用户通过对话生成和编辑图像,支持从写实风格到插图风格的多种形式。OpenAI 首席执行官萨姆・奥特曼(Sam Altman)在社…

方阵幂次(矩阵快速幂)

#include <bits/stdc.h> using namespace std;int n; vector<vector<int>> fuc(vector<vector<int>> a,vector<vector<int>> b){ // 矩阵乘法函数vector<vector<int>> f(n,vector<int>(n));for(int i0;i<n;i){…

MySQL-- 多表查询的分类,SQL92与SQL99,7种JOIN的实现,SQL99语法的新特性

目录 一&#xff0c;多表查询的分类 角度1&#xff1a;等值连接 vs 非等值连接 角度2&#xff1a;自连接 vs 非自连接 角度3&#xff1a;内连接 vs 外连接 二&#xff0c;SQL92语法实现内连接&#xff1a;见上&#xff0c;略SQL92语法实现外连接&#xff1a;使用 -…

IEEE PDF Xpress校验出现 :字体无法嵌入问题以及pdf版本问题

文章目录 问题描述一、字体嵌入问题首先查看一下&#xff0c;哪些字体没有被嵌入查看window的font文件夹里的字体下载字体的网站修复字体嵌入问题 二、pdf版本不对 问题描述 在处理IEEE的camera ready的时候&#xff0c;提交到IEEE express的文件没有办法通过validate&#xf…

产生式系统的完整示例——医疗诊断系统(附python代码)

产生式系统在现实社会中依然有很重要的应用价值&#xff0c;为了更好的理解它的构建、推理以及完整的实现过程&#xff0c;本文一医疗诊断系统为例&#xff0c;进行详细说明。以下是一个包含10条规则的医疗诊断产生式系统示例&#xff0c;包含完整的规则定义、推理流程图、Pyth…

【CSS3】02-选择器 + CSS特性 + 背景属性 + 显示模式

本文主要介绍&#xff1a;选择器、CSS属性、背景属性和显示模式的操作写法。 目录 1. 复合选择器 1.1 后代选择器 1.2 子代选择器 1.3 并集选择器 1.4 交集选择器 2. 伪类选择器 2.1 超链接&#xff08;拓展&#xff09; 3. CSS特性 3.1 继承性 3.2 层叠性 3.3 优先…

清华大学第10讲:迈向未来的AI教学实验396页PPT 探索未来教育的无限可能|附PPT下载方法

导 读INTRODUCTION 今天跟大家分享的是清华大学新闻与传播学院、人工智能学院双聘教授沈阳教授团队出品的《迈向未来的AI教学实验》课程作业集&#xff0c;随着人工智能技术的飞速发展&#xff0c;教育领域也迎来了前所未有的变革。该报告为沈阳教授与学生们在“迈向未来的AI教…

亮数据爬取API爬取亚马逊电商平台实战教程

前言 在当今数据驱动的商业环境中&#xff0c;企业需要快速、精准地获取互联网上的公开数据以支持市场分析、竞品调研和用户行为研究。然而&#xff0c;传统的手动网页爬取方式面临着诸多挑战&#xff1a;IP封锁、验证码干扰、网站结构频繁变更&#xff0c;以及高昂的运维成本…

【新手初学】读取服务器敏感文件数据

一、引言 想要让利用注入点让SQL语句能执行读取相关文件的操作&#xff0c;需要一个非常重要的条件&#xff0c;即在MySql数据库的配置文件my.ini中一定要有 secure file priv" " 二、读取文件配置 ①在虚拟机中查看my.ini文件中有没有secure file priv" &qu…

央视少儿APP V2.6.2

安装好软件就能直接看&#xff0c;界面干净&#xff0c;播放流畅得很。里面的内容都是经过严格筛选的&#xff0c;动画、纪录片、教育课程这些资源应有尽有 这款软件的画质高清到让人惊艳&#xff0c;就算不登录也丝毫不影响观看体验&#xff0c;播放的时候几乎不用缓冲&#…

mysqlworkbench导入.sql文件

1、MySQL Workbench 新建数据库 或者 在左侧导航栏的 ​Schemas 区域右键选择 ​Create Schema...输入数据库名称&#xff08;例如 mydatabase&#xff09;&#xff0c;点击 ​Apply确认创建&#xff0c;点击 ​Finish 2、选择目标数据库 在左侧导航栏的 ​Schemas 列表中&a…

比较4点结构和4次函数

在行列可自由变换的平面上3点结构只有6个 设与之对应的函数分别是 3a1 x*x*y y*y*x 3a2 xy*y*y 3a3 x*x*y y*y*y 3a4 x*x*x y*y*x 3a5 x*x*xy*y*y 3a6 x*x*xy 用同样的办法计算4点结构的16个函数 4(4a1-1)2*3a32*3a1 4(4a2-1)3a43a33a53a1 4(4a3-1)3a23a3…

线性回归 + 基础优化算法

线性回归 线性回归是机器学习最基础的模型&#xff0c;也是理解后续所有深度学习的基础。 线性模型可以看做是单层神经网络。 上述有个0.5是在求导的时候可以很方便的将2消去。 实际上&#xff0c;这里的数据样本受限很大&#xff0c;比如地球上房子就那么多&#xff0c;肯…

邪性!Anaconda安装避坑细节Windows11

#工作记录 最近不断重置系统和重装Anaconda&#xff0c;配置的要累死&#xff0c;经几十次意料之外的配置状况打击之后&#xff0c;最后发现是要在在Anaconda安装时&#xff0c;一定要选“仅为我安装”这个选项&#xff0c;而不要选“为所有用户安装”这个选项。 选“仅为我安…