当前位置: 首页 > article >正文

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 官方文档

相关文章:

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

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

python每日十题(10)

在Python语言中,源文件的扩展名(后缀名)一般使用.py。 保留字,也称关键字,是指被编程语言内部定义并保留使用的标识符。Python 3.x有35个关键字,分别为:and,as,assert&am…...

基于大模型预测的初治菌阳肺结核诊疗方案研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的 二、初治菌阳肺结核概述 2.1 疾病定义与病理机制 2.2 流行病学特征 2.3 传统诊疗方法与局限性 三、大模型在初治菌阳肺结核预测中的应用原理 3.1 大模型技术简介 3.2 数据收集与预处理 3.3 模型构建与训练 3.4 模型…...

嵌入式系统应用-音乐播放器-按键版本

音乐播放器-按键版本 1 背景介绍1.1 导入音乐播放器1.2 导入按键扫描按键包 2 功能设计2.1 需求分析2.2 程序架构设计2.3 相关知识储备 3 代码编写3.1 led代码实现3.2 按键扫描3.3 音乐播放线程 4 低功耗设计4.1 睡眠模式4.2 停止模式4.3 待机模式 1 背景介绍 这个音乐播放器分…...

LabVIEW液压振动锤控制系统

在现代工程机械领域,液压振动锤的高效与精准控制日益显得重要。本文通过LabVIEW软件,展开液压振动锤启停共振控制技术的研究与应用,探讨如何通过改进控制系统来优化液压振动锤的工作性能,确保其在复杂工况下的稳定性与效率。 ​ …...

简单介绍My—Batis

1.什么是My—Batis? My—Batis是一个持久层框架,提供了sql映射功能,能方便的将数据库表和java对象进行映射,通过My—Batis可以将项目中的数据存储在数据库中,以便我们进行调用。值得注意的是My—Batis和spring不是一回…...

ALTER TABLE SHRINK SPACE及MOVE的区别与适用场景

以下是 ‌Oracle 数据库‌中三个收缩表空间命令的对比: 1. ALTER TABLE table_name SHRINK SPACE;‌ ‌作用‌:直接重组表数据并移动高水位线(HWM),释放未使用的空间到表空间‌。 影响‌: 会锁表&#…...

车载通信方案为何选择CAN/CANFD?

摘要 随着汽车电子技术的飞速发展,车载通信系统在车辆的智能化、网联化进程中扮演着至关重要的角色。控制器局域网络(CAN)及其扩展版本CANFD凭借其卓越的可靠性、高效的数据传输能力和强大的抗干扰特性,成为现代汽车通信架构的核心…...

docker远程debug

1. 修改 Java 启动命令 在 Docker 容器中启动 Java 程序时,需要添加 JVM 调试参数,jdk8以上版本 java -agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 -jar your-app.jar jdk8及以下版本: java -Xdebug -Xrunjdwp:tra…...

rosbag|ROS中.bag数据包转换为matlab中.mat数据类型

代码见代码 msg_dict中设置自定义消息类型 test_config中设置需要记录的具体的值 test_config中topic_name以及message_type照搬plotjuggler打开时的参数 最后生成.mat文件在matlab中进行使用...

Java编程思想:为何有时要将子类对象赋值给父类引用

为何有时要将子类对象赋值给父类引用,用父类来进行实例化? 这就要说多态的优势: 代码的扩展性和降低耦合度,而不是完全避免修改代码。 TuXing t new Changfangxing(); Changfangxing k (Changfangxing)t;原因1: 代码可拓展性 …...

pytest-xdist 进行高效并行自动化测试

pytest-xdist 的核心功能是通过多进程分发测试任务,每个进程独立运行测试,确保测试隔离。2025 年 3 月 25 日,pytest-xdist 在 GitHub 上已有超过 1,200,000 次下载,表明其在测试社区中的广泛接受。 在自动化测试中,随…...

位置编码再思考

最近在做多模态,发现基于 transformer 的多模态,position embedding 是一个非常重要的内容,而且还没有统一方案,先暂做记录,几篇还不错的博客: Transformer学习笔记一:Positional Encoding&…...

Deepseek API+Python 测试用例一键生成与导出 V1.0.3

** 功能详解** 随着软件测试复杂度的不断提升,测试工程师需要更高效的方法来设计高覆盖率的测试用例。Deepseek API+Python 测试用例生成工具在 V1.0.3 版本中,新增了多个功能点,优化了提示词模板,并增强了对文档和接口测试用例的支持,极大提升了测试用例设计的智能化和易…...

[c语言日寄MAX]深度解析:大小端字节序

【作者主页】siy2333 【专栏介绍】⌈c语言日寄MAX⌋:这是一个专注于C语言刷题的专栏,精选题目,搭配详细题解、拓展算法。从基础语法到复杂算法,题目涉及的知识点全面覆盖,助力你系统提升。无论你是初学者,还…...

Android ADB工具使用教程(从安装到使用)

目录 ADB工具介绍 什么是ADB? 组成 主要功能 ADB工具安装与连接设备 WIFI连接,提示计算机积极拒绝10061 WIFI成功连接后,拔掉数据线显示offline 提示adb版本不一致​编辑 ADB工具使用 ★日志操作命令 adb logcat:抓取日志 日志格式…...

开个坑记录一下树莓派4B部署yolo的一些问题

问题一:操作系统与内核信息 这个问题困扰了我一天半,下载的时候显示的信息是aar64的系统,但是这并无意味着一个问题,那就是你的操作系统是64位的。需要采用如下的指令查看: getconf LONG_BIT 我在树莓派得出来的操作…...

基于SSM框架的线上甜品销售系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就很关键。因此网上销售信息的…...

基于 FPGA的HLS技术与应用

1、hls简介 HLS ( high level synthesis )即高层次综合,主要是利用高级编程语言实现算法。 2、循环优化 约束语法: #pragma HLS unroll #pragma HLS PIPELINE II1 绝大多数循环都以串行的方式执行,这种执行方…...

Redis原理:Monitor 实现

在调用 Redis 的 MONITOR 命令后,可以在对应的客户端上实时查看服务器的执行情况。今天,我们将从源码的角度来深入探讨 MONITOR 机制是如何处理这些请求以及如何将数据反馈给用户的。 MONITOR 命令的实现 Redis 中所有命令的具体实现细节都可以在其源代…...

计算机工具基础(七)——Git

Git 本系列博客为《Missing in CS Class(2020)》课程笔记 Git是一种分布式版本控制系统,被其跟踪的文件可被查询精细到行的修改记录、回退版本、建立分支等 模型 一般流程:工作区 → \to →暂存区 → \to →仓库(本地 → \to →远端) 工作区&#xff1…...

鸿蒙开发:父组件如何调用子组件中的方法?

前言 本文基于Api13 很多的场景下,父组件需要触发子组件中的某个方法,来实现一些特定的逻辑,但是ArkUI是声明式UI,不能直接调用子组件中的方法,那么怎么去实现这个功能呢? 举一个很常见的案例,通…...

23种设计模式-创建型模式-工厂方法

文章目录 简介场景问题1. 直接依赖具体实现2. 违反开闭原则3. 条件分支泛滥4. 代码重复风险 解决根本问题完整类图完整代码说明核心优势代码优化静态配置表动态策略 总结 简介 工厂方法是一种创建型设计模式,它提供了在父类中创建对象的接口,但允许子类…...

142. 环形链表 II——考察数学,难!

142. 环形链表 IIhttps://leetcode.cn/problems/linked-list-cycle-ii/ 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,…...

C++常见问题与思考

TLS(线程本地存储)原理 线程本地存储(Thread Local Storage,TLS)是一种机制,它允许每个线程拥有自己独立的变量实例,这些变量的生命周期与线程相同。也就是说,不同线程对同一个 TLS…...

从零开始:使用Luatools工具高效烧录Air780EPM核心板项目的完整指南

本文将深入讲解如何使用Luatools工具烧录一个具体的项目到Air780EPM开发板中。如何使用官方推荐的Luatools工具(一款跨平台、命令行驱动的烧录利器),通过“环境配置→硬件连接→参数设置→一键烧录”四大步骤,帮助用户实现Air780E…...

关于c++的几个简单算法

一. 动态规划(Dynamic Programming) 难点:状态转移方程的构建和初始化条件的设计 典型问题:01背包问题 分析: 状态定义 dp[i][j] 表示前i个物品放入容量为j的背包的最大价值。状态转移需要判断是否选择当前物品。 #i…...

WPF MergedDictionaries详解

在 WPF 中,ResourceDictionary.MergedDictionaries 是一个非常重要的特性,用于将多个资源字典(ResourceDictionary)合并到一个主资源字典中。这种机制使得资源的管理和复用变得更加灵活和高效。 1. MergedDictionaries 的作用 Me…...

一套云HIS系统源码,系统融合HIS与EMR,基于云端部署,采用B/S架构与SaaS模式

云HIS系统完全基于云端部署,采用B/S架构,并通过软件即服务(SaaS)的形式面向二级及以下医院可快速交付、便捷运维、云化的医院核心业务平台产品。融合医院HIS和EMR两大主营系统,构建涵盖患者、费用、医嘱、电子病历等核…...

DisplayPort(DP)详解

一、DisplayPort的定义与核心特性 DisplayPort(DP) 是由 视频电子标准协会(VESA) 制定的 高性能数字音视频接口,专为高分辨率显示器和多屏应用设计。其核心特性包括: 高带宽:DisplayPort 2.0支…...