Lua的设计与实现——前言与概述

本文介绍了Lua语言作为游戏服务器的常用架构,强调其作为辅助语言的高效性和可扩展性。Lua以其简洁、小巧、快速的特性,成为游戏开发中的胶水语言,尤其在热更新能力和与C++结合使用方面表现出色。文章还回顾了Lua的发展历程,以及其在其他领域的应用,如OpenResty和Redis。同时,概述了Lua源码的组织结构,虚拟机工作流程,以及内嵌库的相关文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言与概述

出自书籍:李创.Lua设计与实现[M].北京.人民邮电出版社.2017:186.

前言

  假如采用C++这样的编译型语言来开发游戏,那么典型的开发流程大致时这样的:擂起袖子来写了一大段代码,然后编译解决调试编译的错误,中间可能还要处理类似崩溃、段错误、内存泄露等问题。另外,由于重新编译了代码,又需要重启服务器,而重启过程中势必涉及数据的加载。总而言之,采用纯编译型语言开发的情况下,相当一部分时间并没有用在真正的业务逻辑开发中。

  项目采用的是C++编写的核心引擎模块,暴露核心接口给Lua脚本层,网络数据的收发都在C++层完成,而业务逻辑采用Lua实现。这个架构也是很多游戏服务器采用的经典架构。使用这个架构来开发游戏服务时,不再会把大量的精力放在语言本身的问题上,而可以集中精力来做业务逻辑。另外,借助于 Lua 的热更新能力,整个开发过程中需要重启服务的次数并不多。

  Lua作为一门诞生已经超过20年的语言,在设计上是非常克制的。在Lua5.1.4这个版本来说,已经是Lua发展了十几年之后稳定了很长时间的版本,其解释器加上周边的库函数等不过就是一万多行的代码量,而如果再进行精简,只需要吃透几千行代码就能明白其核心原理,这是一个性价比极高的诱惑。

  Lua在设计上,从一开始就把简洁、高效、可移植性、可嵌入型、可扩展性等作为自己的目标。打一个可能不是太恰当的比方,Lua专注于做一个配角,作为胶水语言来辅助像C、C++这样的主角来更好地完成工作。当其他语言在前面攻城拔寨时,它在后方完成自己辅助的作用。在现在大部分主流编程语言都在走大而全的路线,在号称学会某一门语言就能能成为所谓的“全栈工程师”的年代,Lua始终恪守本分地做好自己的胶水语言的本职工作,不得不说是异类的存在。

  “上善若水,守善利万物而不争”,简单、极致、强大的可扩展性,大概是能想到最适合用来描述Lua语言设计哲学的句子。

概述

  我们首先对Lua语言的历史进行简单回顾,了解其发展经历及设计哲学。接下来,会对Lua的源码组织、函数命名等做一些介绍。Lua是C语言项目的典范,在这方面也做得很规范,整齐,这给我们阅读源码带来了便利。最后,我们简单介绍一下Lua虚拟机整体的工作流程。

前世今生

  Lua语言于1993年诞生于巴西里约热 Pontifical Catholic 学(简称PUC-Rio )的Tecgraf 实验室,作者是Roberto Ierusalimschy、Luiz Henrique de Figueiredo 和Waldemar Celes。Tecgraf实验室创立于 1987年,主要专注于图形图像相关的工具研发,创立之后,该实验室的工作就是向客户提供基本的图形相关的软件工具,比如图形库、图形终端等。

  1977年到1992年,巴西政府实施了“市场保护”政策,这使得计算机软硬件存在巨大的贸易壁垒。在这种大环境下,Tecgraf实验室的很多客户由于政治和经济上的原因,都不能国外公司购买定制化的软件。这些原因都驱使Tecgraf实验室的工作人员从头开始构建面向本国用户的软件工具。

  Petrobras (巴西石油公司)是Tecgraf 最大客户之一,Tecgraf为其开发了两门语言,分别是DEL和SOL,这两门语言是Lua语言的前身。

  语言(Lua)的前身之一是SOL语言,在葡萄牙语中这个单词的意思是“太阳”,它们决定给这门新的语言起名为Lua,葡萄牙语的意思是“月亮”。Lua语言就这样诞生了。

  1996年对Lua来说是很重要的一年,Lua开始在国际上获得了关注,迎来国际用户。在这一年,作者在Softtware: Practice & Experience杂志上发表了一篇关于Lua的论文,引来了不少的关注。同年12 月, Lua 2.5版本发布,Dr. Dobb’s Journal杂志也专门针对Lua做了报告。由于这本杂志在程序员圈子里受众非常多,吸引了软件业中不少从业者注意,这其中包括当时任职于Lucas艺术旗下Grim Fandango游戏项目的主管Bret Mogilefsky。由于Lua的良好特性,他在自己的项目中使用Lua替换掉了项目原来用的脚本语言,后来又在Game Developers Conference (简称为GDC ,是游戏程序员最重要的会议之一)分享了自己使用Lua的成功经验。从此,Lua在游戏圈就开始流行起来了,这其中包括了后来大获成功的WOW等。如今Lua语言已经是游戏领域使用最广泛的脚本语言之一。

  Lua虽然起源于巴西,也是从巴西公司的项目中受需求驱动而开发的,但是从一开始这门语言的设计者就把眼光投向世界。在很长一段时间里, Lua的文档只有英语版本,而不是作者的母语葡萄牙语。前面提到的1996年发表的论文,同样也可以看作Lua作者们国际化视野的一个标志。

  Lua语言从一个开始就将自己定位成一个“嵌入式的脚本语言”,提供了如下的特性。

  • 可移植性:使用clean C编写的解释器,可以在Mac、Unix、Windows等多个平台轻松编译通过。
  • 良好的嵌入性:Lua提供了非常丰富的API,可供宿主程序与Lua脚本之间进行通信和交换数据。
  • 非常小的尺寸:Lua 5.1 版本的压缩包,仅有208KB,解压缩之后也不过是835KB, 一张软盘就可以装下。Lua解释器的源代码只有 17 000多行的C代码,编译之后的二进制库文件仅有143KB,这些都决定了使用Lua的设备并不会因为添加了它导致非常明显的空间 占用。
  • Lua 的效率很高,是速度最快的脚本语言之一:为了提高Lua的性能,作者们将最初使用Lex、Yacc等工具自动生成的代码都变成了自己手写的词法分析器和解析器。

  这意味着,用户使用C、C++等语言进行主要功能的开发,而一些需要扩展、配置等会频繁动态变化的部分使用Lua语言来进行开发。 Lua语言的以上几个特性,都决定了它能很好地完成这些辅助作用。Lua的作者甚至戏称这门语言是一门能穿过针孔的语言( Passing a Language through the Eye of a Needle ),“小而精”大概是对Lua语言最好的描述了。

  作为一门从发展中国家起源的语言,在一开始的选择和定位上,Lua都做了现在看来正确的选择:面向国际,老老实实做好辅助作用 。在一个点上做精做细,而不是走大而全的路线去与类似背景的语言进行竞争,这是Lua后来取得巨大成功的原因。这也能理解为什么过去了这么多年,至今Lua解释器的代码只有非常少的代码量(以本书中分析的5. 1.4版本来看,全部代码只有17 193行。如果只算核心部分,那就更少了)。

  这也是一直很推崇Lua解释器源码,并且决定将这门语言进行分析的原因: Lua解释器的代码是殿堂级的语言代码范本,Lua作者对语言特性、设计目标、受众的取舍值得我们学习。一万多行的源码中,就能学习到一门工业级脚本语言的实现,性价比是极高的。

  除了在游戏领域的广泛使用,Lua在其他领域也获得了运用。

  • OpenResty使用Lua来扩展Nginx服务器的功能,使用者仅需要编写Lua代码就能轻松完成业务逻辑。值得一提的是,这个项目的作者是中国人章亦春。
  • Redis服务提供Lua脚本。
  • Adobe的Lightroom项目使用Lua来编写插件。

  还有很多非游戏领域的成功项目,在此不一一列举了。

  那么,如何在你的项目中使用Lua语言呢?以笔者比较熟悉的游戏服务器领域来说,一般是这样组织和分工的。

  • C\C++语言实现的服务器引擎内核,其中包括最核心的功能,比如网络收发、数据库查询、游戏主逻辑循环等。以下将这一层简称为引擎层
  • 向引擎层注册一个Lua主逻辑脚本,当接收到用户数据时,将数据包放到Lua脚本中进行处理,主逻辑脚本主要是一个大的函数表,可以根据接收到的协议包的类型,调用相关的函数进行处理。以下将这一层简称为脚本层
  • 引擎层向脚本曾提供很多API,能方便地调用引擎层的操作,比如脚本层处理完逻辑之后调用引擎层的接口应答数据等。

  可以看到,在这个架构中,引擎层实现了游戏服务的核心功能,这部分的变动相对而言不那么频繁;而游戏的逻辑、玩法是变动很频繁的,这部分使用脚本来完成。这个组合架构的优势在于如下几点。

  • 编码效率高:由于引擎层相对稳定,而脚本不需要进行编译就能直接运行,省去了很多编译的时间。
  • 开发效率高:大部分脚本,包括Lua在内都支持热更新功能,这意味着在调试开发期间,可以不用停服务器就能调试新的脚本代码,这省去了重启服务的时间,比如加载数据库数据、静态配置文件等的耗时。
  • 对人员素质要求相对低: 一般的游戏服务器团队配置,都是由主程级别的人来把控引擎的质量,其他的成员负责编写脚本玩法逻辑,即使出错,大部分时候并不会导致服务器岩机等严重问题。

源码组织

  讲解基于Lua 5.1.4版本进行分析,打开src目录下的Makefile文件,可以看到这样一段代码:

效果图

  在Lua 5.3.6中这部分也是几乎一样的,如下:

效果图

  从中能看到,Lua源码大体分为三个部分:虚拟机核心、内嵌库以及解释器、编译器。

  虚拟机核心的文件列表如表1-1所示。需要补充说明的是,Lua解释器中,内部模块对外提供的接口、数据结构都以“lua模块名简称_”作为前缀,而供外部调用的API则使用"lua_”前缀。

TIPS:简单地说,供外部调用的API就是供C\C++这种角色使用的,而内部模块对外提供的接口、数据结构只供Lua内部使用。

表1-1 虚拟机核心相关文件列表
文件名作用对外接口前缀
lapi.cC语言接口lua_
lcode.c源码生成器luaK_
ldebug.c调试库luaG_
ldo.c函数调用及栈管理luaD_
ldump.o序列化预编译的Lua字节码
lfunc.c提供操作函数原型及闭包的辅助函数luaF_
lgc.cGC(垃圾回收)luaC_
llex.c词法分析luaX_
lmem.c内存管理luaM_
lobject.c对象管理luaO_
lopcodes.c字节码操作luaP_
lparser.c分析器luaY_
lstate.c全局状态机luaE_
lstring.c字符串操作luaS_
ltable.c表操作luaH_
lundump.c加载预编译字节码luaU_
ltm.ctag方法luaT_
lzio.c缓存流接口luaZ_
表1-2 内嵌库相关文件列表
文件名作用
lauxlib.c库编写时需要用到的辅助函数库
lbaselib.c基础库
ldblib.c调试库
liolib.cIO库
lmathlib.c数学库
loslib.cOS库
ltablib.c表操作库
lstrlib.c字符串操作库
loadlib.c动态扩展库加载器
linit.c负责内嵌库的初始化

  Luau 5.3.6新增内嵌库如下:

文件名作用
lbitlib.cbit位操作库
lcorolib.c协程库
lutf8lib.cutf8库
lctype.c封装C标准库中的ctype文件
解析器、字节码编译器相关文件如表1-3所示。
文件名作用
lua.c解释器
luac.c字节码编译器

Lua虚拟机工作流程

  Lua虚拟机工作流程先大概了解一下。

  Lua代码是通过翻译成Lua虚拟机能识别的字节码运行的,以此它主要分为两大部分。

  • 翻译代码以及编译为字节码的部分。这部分代码负责将Lua代码进行词法分析、语法分析等,最终生成字节码。设计这部分的代码文件包括llex.c(用于进行词法分析)和lparser.c(用于进行语法分析),而最终生成的代码则使用了lcode.c文件中的功能。在lopcodes.h、lopcodes.c文件中,则定义了Lua虚拟机相关的字节码指令的格式以及相关的API。
  • Lua虚拟机相关的部分。在第一步中,经过分析阶段之后,生成了对应的字节码,第二步就是将这些字节码装载到虚拟机中执行。Lua虚拟机相关的代码在lvm.c中,虚拟机执行的主函数是luaV_execute,不难想象这个函数是一个大的循环,依次从字节码中取出指令并执行。Lua虚拟机对外看到的数据结构是lua_State,这个结构体将一直贯穿到整个分析以及执行阶段。除了虚拟机的执行之外,Lua的核心部分还包括了进行函数调用和返回处理的相关代码,主要处理函数调用前后环境的准备和还原,这部分代码在ldo.c中,垃圾回收部分的代码在lgc.c中。Lua是一门嵌入式的脚本语言,这意味着它的设计目标之一必须满足能够与宿主系统进行交互,这部分代码在lapi.c中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ufgnix0802

总结不易,谢谢大家的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值