print(2^2) –4
print(3^4) –27 — 等于pow(3, 4)
cmd交互
通过
lua -i proj
打开的文件, 可以用
dofiles “xx.lua”
(文件在IDE编辑)重新载入, 热更?
lua -e “string to code”
-l loadlib
-i cmd
cmd/解释器参数args:
[-3] = “lua”
[-2] = “-e”
[-1] = “sin-math.sin”
[0] = “script”
脚本引用 [1], [2] = “a”, “b”
环境变量在LUA_INIT
第一行开头# == 解释器加载时忽略,
方便在POSIX系统将lua脚本当解释器用
例: #!/usr/local/bin/lua
== 不调lua解释器, 直接跑脚本
_A 下划线大写, lua特殊用途
_a 下划线小写, 哑变量(Dummy variable)
number
2^53
int64 lua small(精简模式) int32
float(双精度) float单精度
1 == 1.0, 但math.type不同
-3 == -3.0, 同类型比较少了转化, 效率会更高
算术运算含float, 会先把int转float再运算
除法只产生float, 优先取第2操作数
幂运算操作数时float
%取模只产生正数
x – x%0.01 == 保留2位小数
angle%(2*math.pi) == 角度->(0, 2math.pi)
~:
a+0.0 = a.0
a.0|0 = a
a.2|0 = 异常
溢出
max_int + 2 == -(maxint-1)
max_int + 2.0 == max_int -> float == 科学记数法取近似
string(链接阅读 it-TString)
lua字符串是 不可变值, 修改==新建, 被内存管理
可通过数值指定字符, ’97’ == ‘a’
一定程度转换, “10” + 1 == 11
s=”” 可以管理大串
len == #s
tostring(num)
s3 == s1..s2 –拼接
s = [[
多行字符串
]]
[===[..长复杂字符串..]===] — 两头等号相等
s = [===[xxx[==[]==]yyyy]===]
print(s) — xxx[==[]==]yyyy
string.char(97, 98) –ab
string.char(97, 98, 99) –abc
string.byte(s, i, j) –byte of pos i-j in s
s:byte(i, j)
查一下string.format%指示符
pattern-matching(基于模式匹配), 粗略了
find/match, gsub/gmatch
~:
#”…” = 占用的字节数
ddd = 3个十进制
xhh = 2个16进制
u{…} = 声明utf-8字符
table
对象, 不能声明, 关联数组, 无固定大小({}编译预留就别想了)
匿名, 可被多引用、自动回收
io.read == io模块是个table, 用read索引到函数,
a[“x”] == a.x, s=”x”, a[s] == a[“x”]
len == #a
table.maxn(a) == idx_max
[i] = nil, 删除, 同全局变量(全局table)
表达式索引:
tbl = {“one”, “two”, [5]=”five”, y=1, [“x”]=2}
tbl = {[“+”]=”add”, [“/”]=div, …}
分隔
tbl = {x=1,y=2 ; “one”, “two”}
metatable/metamethod(查一下有效的元方法集合)
约等于c++ operator重载
metatable = {
metakey=metamethod, metakey=metamethod, metakey=metamethod …
} — getmetatable/setmetatable or tbl.__add = function(a, b) … end
为值增加非预定义行为, 例如 table+table, function > function, string()
in lua, 同table共享(table匿名, 可多引用)
in c, 同类userdata共享(同class共享)
__metatable(setmetatable/getmetatable)
__index/__newindex
__call
__tostring
__le/__eq/__lt/
__add/__sub/__mul/__div/__pow/__mod/__unm/__concat
__close
environment(_G, 链接阅读 it-lua_State,CallInfo和global_State)
local v = _G[vname]; _G[vname] = v
注重读写限制
setfenv(v, {}) — 命令行状态只影响同行
给v换个环境=={}, 这里没_G, 于是也没print
print(v) — attempt to call global ‘print’ (a nil value)
setfenv(v, {_G=_G})
回来了
userdata(ptr)
指向一块内存的指针, 该内存由 lua 来创建, 被gc管理
赋值, check是否==
lightuserdata(void*/number, 可运算)
只是个指针, 没元表, 不受垃圾管理
主用于check是否==, lua查找c对象
理解2:lua变量包装c数据/函数,
例:io库打开的文件
require
local tblfunc = require “modname”
package.loaded/path
loadfile –lua
loadlib –clib, luaopen_modname/luaopen_ver-modname
导入时ver-被忽略
子模块 == 环境table嵌套
lua mod/submod
clib luaopen_mod_submod
module
module(“modname”) 相当于
local modname = …
local M = {}
_G[modname] = M
package.loaded[modname] = M
setfenv(1, M)
self
function tbl.func(self, v) self.x = self.x – v end
tbl.func(tbl, v)
function tbl:func(v) self.x = self.x – v end
tbl:func(v)
table/userdata/function 以引用作比较, 引用对象相同, 则==
其它类型是值比较
local
local a –是个语句, 可声明, 隐性=nil
local a = a –全局变量赋值local变量, 加速对全局变量访问
unpack/table.unpack
数组解压为参数
tbl = {“a”, “b”, “c”, “d”}
print(tbl) — 0xfxxxxxx
print(unpack(tbl)) — a b c d
print(unpack(tbl, 2)) — b c d
print(unpack(tbl, 1, 3)) — a b c
…
变长参数, 可作为表达式传递
{…} 参数数组
local a, b = …
function rtall(…) print(…); return … end
select
计算传递进来的参数个数, select(#, …)
print(select(“#”,1,2,”e”,3)) — 4
a = {1, 2, 3, 4}; print(select(“#”,a)) — 1, a被当做1个参数
输出第n个索引后的所有参数
… == 2, 3, 4, 5
print(select(3, …)) — 4, 5
closure
函数的本体, 函数就是一种特殊的closure(function … end, do … end)
function f() local i=1; return function() i=i+1 return i end end
c = f() — this is a closure function, 里面自带状态缓存
print(c) — function:0xffxxxx
print(c()) — 1
print(c()) — 2 f的词法域缓存i状态 (sandbox)
用途:
迭代器(高效), /keys/values
有状态迭代器, iter = pair — use next
无状态迭代器, iter = ipair — return tbl[i]
损耗:
每次调用c, 都会重新生成内部function
upvalue状态保持
local function
无法直接递归 local function f() return f() end — 错误, 内f未定义, 识别为全局f
local f; f = function() return f() end — 正确, 先声明, 调用时先识别为局部变量
tail call(尾调用)
function f(x) return g(x) end — 只有retrun最后调用才算
前函数释放, 不耗栈
if
if then … end
if then … elseif then … end
while/repeat
先判 : while true do … end
先做 : repeat … until true
for
for var=left, right, step do … end
for var=left, right do … end — step == 1
for i=1, math.huge do … end — for式无限循环
left, right, step 一次执行, 不随循环改变
for i, v in ipairs(a) do … end — i为索引, 不连贯会断循环
for k, v in pairs(a) do … end — 键值
— for elem in values(l) do … end
— for key in keys(l) do … end
loadfile/loagstring
f = loadfile(“foo.lua”) — 只编译加载, 不定义, 不生成内容
f() — 定义’foo’
week
week reference, 会被垃圾回收器忽视的引用
if all ref week, 回收
week table
tbl = {__mode = “k”} –“k” key is week ref
tbl = {__mode = “v”} –“v” value is week ref
tbl = {__mode = “kv”} –“kv” key and value is week ref
例1
k = {}; tbl[k] = 1 — k-1, obj key, week 保持了其唯一性
k = {}; tbl[k] = 2 — k-2
例2
tbl[1] = t1
t1 = nil — 此时t1并不会被回收, 因为它正被tbl引用
setmetatable(tbl, {__mode = “v”}) — 声明tbl的value都是弱引用(我是垃圾)
t1 = nil – 此时, tbl[1] == nil, 弱引用被回收
collectgarbage() –k-1没了
如果k是bool/num, 不回收
math
math.random
math.randomseed(os.time())
os
local x = os.clock(); … ; print(“性能耗时”, os.clock() – x) — 当前cpu时间秒数
os.execute(“mkdir file”)
os.getenv(“PATH”) == $PATH
debug
debug.getinfo(func)
debug.traceback()
debug.getlocal
debug.getupvalue/setupvalue
debug.sethook(func, “act”) –把func设为act的钩子函数
oo
obj.func(obj, x) === obj:func(x)
c api(链接阅读 it-c函数在lua_State栈中的调用)
虚拟机 — lua.h, lualib.h, lauxlib.h
lua_State *L = luaL_newstate(); — 新建空lua环境, lua库状态保存在*L
luaL_openlibs(L); — 打开标准库, lua_close(L);
lua-c数据交互栈(虚拟栈, 只能存lua类型值), LIFO — lua_pcall
每个协程独自有一个栈
lua只会改变栈顶, c可更随意 — lua_pushnumber/lua_pushstring/…
— lua_isnumber/lua_isstring/…
— lua_type, LUA_TNUMBER/LUA_TSTRING/…
dump栈
for (i = 1; i 错误处理
lua_pcall/luacpcall, lua_error, lua_call无保护运行
sample: lua_xxx(L, 栈位置, tblkey);
— 以f(t[i])结果替换t[i]
luaL_checktype(L, 1, LUA_TTABLE); — L的参数1是个table
luaL_checktype(L, 2, LUA_TFUNCTION); — L的参数2是个function
f lua_pushvalue(L, 2); — 压入函数f
lua_rawgeti(L, 1, i); — 压入t[i]
lua_call(L, 1, 1); — 压入r=f(t[i])
lua_rawseti(L, 1, i); — t[i]=r
注册表(lua table)
一般供第三方库注册到lua用(例如加了个xml库)
LUA_REGISTRYINDEX — lua_getfield(L, LUA_REGISTRYINDEX, “Key”) — 建议string key
值引用-key
int r = luaL_ref(L, LUA_REGISTRYINDEX); lua_unref(L, LUA_REGISTRYINDEX, r);
lua_rawgeti(L, LUA_REGISTRYINDEX, r) — 从注册表获取
管理全局数据, c模块共享, 可被不同库访问
lua注册的所有c函数都有自己的环境table
upvalue, c closure共享变量
内存管理(链接阅读 it-GC)
lua_Alloc, 分配NULL视为0字节内存块
不会为重用而缓存内存块, 分配函数都能释放上一个分的内存块
垃圾回收
标记并清扫, mark-cleaning(整理)-sweep(清扫)-finalization(收尾)
collectgarbage/lua_gc
collect/LUA_GCCOLLECT — 搞一轮完整收集周期
step/LUA_GCSTEP — 分步收集
动态链接
local path = “/user/…/xxx.so”
local f = package.loadlib(path, “mod_name”)
debug库
debug.debug — lua提示符, 检错
debug.traceback
异常处理:
assert
pcall/xpcall — 保护运行
and/or
短路求值, 只在需要时才评估参数2
x = x or v 等价于 if not x then x=v end
符号
// 地板除 floor division
数字空或0: a = (a and a > 0) and a or 1
if b ~= false : (a and b) or c 等价于 a ? b : c
例如: max = (x > y) and x or y
多重赋值:
先右边求值, 然后才赋值: x, y = y, x
多返回函数必须放最后, 否则只有1个返回
x, y = rt2(), 20 — rt2() return2个值, 实际只有1个
— 如rt2()无return, 也占个nil
其它打断多返回情况(只要不是平铺调用都会打断)
print(rt2()) — 1 2
print((rt2())) — 1
print(rt2(), “x”) — 1 “x”
print(rt2()..”y”) — 1y
a, b = 1, 2
a, b = funab()
a, b = 1, 2, 3 –(3会被忽略)
类似 function f(a, b); f(1, 2, 3) –(3会被忽略)
a, b, c = 0 –结果只有a会被赋值, b, c为nil
类似 function f(a, b, c); f(1, 2) –(c为nil)
域(程序block)
do
…
end
相当于c++
{
…
}
查看版本
print(_VERSION)
value
8种类型以union的形式定义在TValue中
typedef union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;
typedef struct lua_TValue {
Value value_; //value具体的数值
int tt_ //value的类型
} TValue;
nil, boolean, number和lua_CFunction直接存储在TValue中,占用至少12个字节
string, lua function, userdata, thread和table这些可以被垃圾回收管理的类型
TValue只是索引,具体数据存储在堆上,被gc指针索引
Lua 4.0之前,Table是严格的按照Hash的方式实现的,
后续版本为了提升性能和节省空间,
Table内部重构为数组部分和Hash部分两块
Lua内部增加一个string table,这是一个Hash表,
所有的短字符串(40字节以内)都存储在这里,
每次创建一个新的短字符串,都会先到这个表里面查找是否已经存在
空间消耗
{{x=1,y=2}, {x=1,y=2}, …} –95
{{1,2}, {1,2}, …} –65
{{1,1,…}, {2,2,…}} –24
函数调用开销大约是c的30倍
全局变量效率低,尽量local
尽量减少对象产生以减少gc:
注意collectgarbage场合
复用对象
local a;
for xxx do
a = xxx
end
lua 虚拟机构造:
GC head(16)
global_State* -> GCObject*allgc -> gcobj -> gcobj -> …
Stack_last -> TValue
Stack_top -> TValue
Stackbase -> TValue
…
CallInfo -> CL_N -> … CL_1 -> CL_0
global_State把所有可以垃圾回收的对象都串在一个链表里面管理
数据栈是C数组,会动态的增长和回收,不够的时候就realloc, 把栈空间扩大一倍。
一个数据栈和一个调用双向链表(CallInfo)实现了类似C语言中的堆栈的功能
typedef struct CallInfo {
StkId func;
StkId top
struct CallInfo *previous, *next;
…
} CallInfo;
Lua函数调用会触发数据栈栈顶的增长和CallInfo增加新节点, 函数return的时候执行相反的操作
热更相关:
lua 的函数是 first class(执行时创建, 可传递) 对象
lua 完全不区分什么是代码,什么是数据,
所以没有 C 语言中所谓符号表。
所以并没有统一的地方可以查找 lua vm 里已有的函数。甚至函数也没有名字,
你不能用名字索引出新版本的函数,替换掉老版本的。
在 lua 中,所有函数都是闭包。如果你只想替换闭包中函数原型的实现,
那么还需要做 upvalue 的重新关联
upvalue:
upvalue 其实是 lua 中的一个隐式类型,大致相当于 C++ 的引用类:
local a = {}
function foo()
return a
end
a 是 foo 的一个 upvalue ,在 lua 中 a 的类型是 table ,
但 upvalue 的实际类型却不是。因为你可以在别处修改 a ,foo 返回值也会跟着改变。
操作 upvalue: debug.upvalueid / debug.upvaluejoin
prototype(原型):
lua 内部的一种数据类型,可以简单理解成一个函数去掉 upvalue 后的部分:
function foo(a)
return function()
return a
end
end
每次对 foo(a) 的调用都会生成一个不同的匿名函数对象,
它们引用的 a 都是不同的(每个分别指向不同的 a )。
但是这些不同的匿名函数对象拥有相同的 prototype 对象。
——————————- 构建lua解释器(it) ——————————-
it-lua运作
创建Lua虚拟机状态实例(基于寄存器的虚拟机)
(lua_State结构和global_State结构)
*虚拟机
(将源码编译成VM所能执行的具体字节码
(取指令,其中指令来源于内存
(译码,决定指令类型(执行何种操作), 另外译码的过程要包括从内存中取操作数
(执行。指令译码后,被虚拟机执行(其实最终都会借助于物理机资源)
(存储计算结果
图:
lex/parser
code -> bype-code(luac)
+ -> opcodes -> run -> return
op + param
(小知识, 基于栈的vm: 运算时都是直接与操作数栈(operand stack)进行交互,
不能直接操作内存中数据,
即使是数据传递, 可移植, 无视具体的物理架构(特别是寄存器), 例如:
x86:ADD EAX, EBX; 基于栈vm:ADD(默认操作数存放在操作数栈上)
指令短,慢(无论什么操作都要通过操作数栈这一结构)
(小知识, 基于寄存器的vm: 包含多个虚拟寄存器, 其操作数都是别名,
引擎对操作数解释出具体位置, 再存取运算:
ADD R3, R2, R1
这些寄存器存放在运行时栈中,本质上就是一个数组
其实“寄存器”的概念只是当前栈帧中一块连续的内存区域
这些数据在运算的时候, 直接送入物理CPU进行计算
(无需再传送到operand stack上然后再进行运算)
(小知识, 栈帧
也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构
C语言中, 每个栈帧对应着一个未运行完的函数, 栈帧中保存了该函数的返回地址和局部变量
逻辑上讲, 栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等
栈是从高地址向低地址延伸的, 每个函数的每次调用, 都有它自己独立的一个栈帧
加载标准库
加载脚本,通过词法分析器lexer和语法分析器parser将脚本编译成
Lua虚拟机能够识别的opcodes(成虚拟机的指令),并存储在虚拟机状态实例中
编译器为每个函数创建一个原型(prototype)
这个原型包含函数执行的一组指令和函数所用到的数据表
将opcodes dump成二进制文件,以bytecode的形式保存(luac)
在将来某个时刻,运行Lua虚拟机,并加载dump后的文件,直接通过dump数据,
将指令结构还原回来,保存在虚拟机状态实例中
运行虚拟机,对虚拟机状态实例中的opcodes进行执行
Lua解释器的运行示意图, 详细见 it-脚本运行
App — LuaPublicLibrary — LuaAPI — LuaParser — LuaLexer — VmByteCodeGener
Create Lua Interpreter
Reg std lib
Load file
ParseDoc Get token
Gen byte code
Lua Api
1 创建解释器
2 为解释器注册标准库
it-目录结构:
+ 3rd/ #引用的第三方库均放置在这里
+ bin/ #编译生成的二进制文件放置在这里
+ clib/ #外部要在c层使用Lua的C
API,那么只能调用clib里提供的接口,而不能调用其他内部接口
+ common/ #vm和compiler共同使用的结构、接口均放置在这里
+ compiler/ #编译器相关的部分放置在这里
+ test/ #测试用例全部放置在这里
+ vm/ #虚拟机相关的部分放置在这里
main.c
makefile
目录结构对应程序:
App
↓
clib 外部在使用c接口的时候,
↓ 只能通过clib里的辅助库来进行,以隐藏不必要暴露的细节
compiler/vm
↑
3rd/common 作为compiler/vm的基础模块
文件结构:
~ clib/ #外部要在c层使用Lua的C API,
那么只能调用clib里提供的接口,而不能调用其他内部接口
luaaux.h #供外部使用的辅助库
luaaux.c
~ common/ #vm和compiler共同使用的结构、接口均放置在这里
lua.h #提供lua基本类型的定义,
错误码定义,全项目都可能用到的宏均会放置在这里
lua_Integer、lua_Number、lu_byte、lua_CFunction、TValue等
lua_CFunction基本上就是Lua栈中,能被调用的light c function
lua_Value是一个union类型,以上5种类型在32bit环境下,
共用4个字节的内存,在64bit环境下共用8个字节的内存
luamem.h #lua内存分配器
luamem.c
luaobject.h #lua基本类型
luaobject.c
luastate.h #虚拟机状态结构,以及对其相关操作的接口均放置于此
luastate.c
it-lua_State,CallInfo和global_State
虚拟机指令要在栈上运行,那么这个栈就是保存在lua_State
CallInfo结构
标记函数在栈中的位置,
标记调用函数时,它的栈顶位于lua_State栈中的哪个位置
保存要返回多少个返回值的标记
global_State则是包含了lua_State和一个内存分配器等,管理gc
lobject.h
typedef TValue* StkId;
lstate.h
struct CallInfo {
StkId func; // 被调用函数在栈中的位置
StkId top; // 被调用函数的栈顶位置
int nresult; // 有多少个返回值
int callstatus; // 调用状态
struct CallInfo* next; // 下一个调用
struct CallInfo* previous; // 上一个调用
};
lstate.h
typedef struct lua_State {
CommonHeader; // gc header, all gcobject should have the commonheader
StkId stack; // 栈
StkId stack_last; // 从这里开始,栈不能被使用
StkId top; // 栈顶,调用函数时动态改变
int stack_size; // 栈的整体大小
struct lua_longjmp* errorjmp; // 保护模式中,要用到的结构,当异常抛出时,跳出逻辑
int status; // lua_State的状态
struct lua_State* next; // 下一个lua_State,通常创建协程时会产生
struct lua_State* previous;
struct CallInfo base_ci; // 和lua_State生命周期一致的函数调用信息
struct CallInfo* ci; // 当前运作的CallInfo
struct global_State* l_G; // global_State指针
ptrdiff_t errorfunc; // 错误函数位于栈的哪个位置
int ncalls; // 进行多少次函数调用
struct GCObject* gclist; // gray或者grayagain链表中,数据对象的next指针
} lua_State;
lstate.h
typedef struct global_State {
struct lua_State* mainthread; // 我们的lua_State其实是lua thread,某种程度上来说,它也是协程
lua_Alloc frealloc; // 一个可以自定义的内存分配函数
void* ud; // 当我们自定义内存分配器时,
可能要用到这个结构
lua_CFunction panic; // 当调用LUA_THROW接口时,
如果当前不处于保护模式,那么会直接调用panic函数
panic函数通常是输出一些关键日志
struct stringtable strt; // 全局字符串列表
//gc fields
lu_byte gcstate; // 标记gc当前处于哪个阶段
(GCSpause、GCSpropagate、GCSatomic、
GCSinsideatomic、GCSsweepgc和GCSsweepend)
lu_byte currentwhite; // 当前gc是哪种白色,102与012中的一种
在gc的atomic阶段结束时,会从一种切换为另一种
struct GCObject* allgc; // gc root set, 单向链表, 从头入
struct GCObject** sweepgc; // 记录当前sweep的进度
struct GCObject* gray; // 首次白->灰, 入此列, 准备被propagate的对象
struct GCObject* grayagain; // 黑->灰, 入此列, 避免table反复黑灰(重复扫描)
lu_mem totalbytes; // 记录开辟内存字节大小的变量之一,
真实的大小是totalbytes+GCdebt
l_mem GCdebt; // GCdebt will be negative
控制gc触发的时机。当GCdebt>0时,才能触发gc流程
lu_mem GCmemtrav; // per gc step traverse memory bytes
限制每次进行gc操作时,所遍历的对象字节大小之和(byte)
lu_mem GCestimate; // after finish a gc cycle,it records total memory bytes (totalbytes + GCdebt)
int GCstepmul;
} global_State;
it-c函数在lua_State栈中的调用:
{
struct lua_State* L = luaL_newstate(); // 创建虚拟机状态实例
…
luaL_pushcfunction(L, &add_op); // 将要被调用的函数add_op入栈
luaL_pushinteger(L, 1); // 参数入栈 right, left
luaL_pushinteger(L, 1);
luaL_pcall(L, 2, 1); // 调用add_op函数,并将结果push到栈中
int result = luaL_tointeger(L, -1); // 完成函数调用后,
取栈顶就是add_op放入的结果
luaL_pop(L); // 结果出栈,保证栈的正确性
…
luaL_close(L); // 销毁虚拟机状态实例
}
static int add_op(struct lua_State* L) {
int left = luaL_tointeger(L, -2); // 取参数 left, right
int right = luaL_tointeger(L, -1);
luaL_pushinteger(L, left + right); // 结果入栈, 入栈顶
return 1;
}
下图
lua_State
|… | | |
|3 lua_Integer:结果 -1 | | |
|2 lua_Integer -2 | | |
|1 lua_Integer -3 | luaL_pcall-> |lua_Integer:结果 |
| lua_Function:add_op CallInfo| | |
**
lvm.c
luaV_execute
运行opcode的函数, lua_Pcall核心
it-GC
(标记清除(mark-and-sweep)算法)
所有新创建的对象,都要被放入一个单向链表中(allgc链表),新创建的对象实例,直接放置于列表头部
所有被创建的实例,均分为可达和不可达状态,可达意味着它正在被使用,或者有潜在被使用的可能
可达:global变量
栈中的变量
寄存器中的变量
被这些变量引用的变量
算法(不支持拆分/异步):
GC则是以全局变量,栈和寄存器作为起点开始标记的
GC开始,进入标记阶段,从栈起始点开始,标记所有被关联到的对象
标记结束后,进入清除阶段,
所有未被标记的实例将被释放,
而被标记的对象则清除标记
本次gc结束
Incremental Mark and Sweep算法
小知识,三色标记:
白色:尚未访问过。
黑色:本对象已访问过,而且本对象 引用到 的其他对象 也全部访问过了。
灰色:本对象已访问过,但是本对象 引用到 的其他对象 尚未全部访问完。
全部访问后,会转换为黑色
gc能够分n次执行, 每次只执行其中的一小部分, 将gc的性能开销均摊掉
标记
白色, 新创建对象
灰色, 标记阶段白->灰, 入灰色对象的单向链表–gray链表(相当于记录了当前gc扫描的进度)
黑色,
gc对gray链表中的对象扫描,灰->黑,
↑引用的对象->灰, 入gray列表
gray链表为空, 着本轮gc标记阶段结束, 仍然为白色的不可达, 需要被清理
string, 类似的直接黑
轮换gc, white0, white1, …
代码阶段:
restart, push入gray链表, ->灰
propagate,
gray链表pop->黑(累计这个object的字节大小, 超量直接gc),
遍历它所有引用的对, push入gray链表
atomic, 该阶段不可中断,
sweep, 清除白色
gc管理的数据类型主要有string、table、function、proto和lua_State(luaobject.h)
#define CommonHeader struct GCObject* next; lu_byte tt_; lu_byte marked
next:在allgc链表中,指定下一个gc对象的指针
tt_:记录gc对象的类型,不同的gc对象在propagate阶段有不同的处理逻辑
marked:用来标记gc对象颜色用的
struct GCObject {
CommonHeader;
};
Value类型需要它也能表示gc类型
typedef union lua_Value {
struct GCObject* gc;
void* p;
int b;
lua_Integer i;
lua_Number n;
lua_CFunction f;
} Value;
需要被GC管理的数据类型, Value类型和具体的类型(如lua_State)相互转换
union GCUnion {
struct GCObject gc;
lua_State th;
};
1 Lua通过借助grey链表
2 依次利用reallymarkobject对对象进行了颜色的标记
3 之后通过遍历alloc链表
4 依次利用sweeplist清除需要回收的对象。
void luaC_step(struct lua_State*L)
所有的gc流程都在singlestep函数里进行处理
当lua解释器能被处理的内存字节总大小
>= debt+GCSTEPSIZE时,只会处理debt+GCSTEPSIZE
// luagc.c X
static lu_mem singlestep(struct lua_State* L) {
struct global_State* g = G(L);
switch(g->gcstate) {
case GCSpause: {
g->GCmemtrav = 0;
restart_collection(L); // markroot阶段,将lua_State标记为灰色,并push到gray列表中
g->gcstate = GCSpropagate;
return g->GCmemtrav;
} break;
case GCSpropagate:{
g->GCmemtrav = 0;
propagatemark(L); // 从gray列表中,pop出一个对象,并标记为黑色,同时扫描其关联对象,设置为灰色并放入gray列表。
if (g->gray == NULL) {
g->gcstate = GCSatomic;
}
return g->GCmemtrav;
} break;
case GCSatomic:{
g->GCmemtrav = 0;
if (g->gray) {
propagateall(L); // 一次性将gray列表中所有的对象标记和扫描
}
atomic(L); // 一次性将grayagain链表中所有的对象标记和扫描
entersweep(L);
g->GCestimate = gettotalbytes(g);
return g->GCmemtrav;
} break;
case GCSsweepallgc: {
g->GCmemtrav = 0;
sweepstep(L);
return g->GCmemtrav;
} break;
case GCSsweepend: {
g->GCmemtrav = 0;
g->gcstate = GCSpause;
return 0;
} break;
default:break;
}
return g->GCmemtrav;
}
it-TString
string header / string body
Header包含了String的类型(长字符串或短字符串)、hash值、长度等信息
Body, 相当于char*, 长于40字节的是长字符串
字符串内部化的本质是将字符串缓存起来,所有拥有相同内容的字符串共享一个副本
计算String Body的hash值后,放入一个hash map中(strt(意为string table))
要用到相同的字符串时,则直接返回该字符串的指针(size是2的n次幂的数组)
结构:
typedef struct TString {
CommonHeader; // GC头
unsigned int hash; // string hash value
unsigned short extra; // 长(0未hash,1已hash), 短(0gc管,1不gc)
unsigned short shrlen; // 短,短字符串(String Body)的长度
union {
struct TString* hnext; // 短, 冲突的往后排
size_t lnglen; // 长, 长字符串(String Body)的长度
} u;
char data[0]; // 标记String Body起始位置, 配合shrlen或lnglen
找到String Body的结束位置
} TString;
strt(全局字符串表, O(1))
strt存在于global_State, 和main thread同级
strt是一个TString*类型的一维数组,长度2的n次幂
每个元素为1个hash表的slot(指向一个字符串单向链表),
操作:
已知c层字符串char* str, 要为str创建一个lua TString对象, 首先计算str的hash值
在完成hash值计算以后, 查找String Body和str相同的TString对象,
slot_idx = hash & (strt->size – 1)
因strt的size是2的n次幂,所以hash & strt->size – 1必定是0 ~ strt->size – 1之间的值
(如4-1=3,二进制表示0112,任何正整数和它进行&运算区间都是在0~3之间,
这也是为什么strt的size必须是2的n次幂的原因)
找到对应的slot以后, 取出对应slot引用的TString链表,
遍历链表, 如果找到String Body和str匹配的lua TString对象, 则返回其指针,
否则直接插入该链表的头部
扩容(TString链表将会很长,最终查找效率会退化成O(n)):
when >=strt->size, 原来的两倍, size / 2, 缩原来的一半
所有的字符串都会重新做mod操作(hash & (new_size – 1))
分配到新的Slot中,组建新的TString链表
结构:
struct stringtable {
struct TString** hash;
unsigned int nuse;
unsigned int size;
};
使用:
struct TString* createstrobj(struct lua_State* L, const char* str, …, l, hash) {
…
struct GCObject* o = luaC_newobj(…);
struct TString* ts = gco2ts(o);
memcpy(getstr(ts), str, l * sizeof(char));
…
}
// short only
struct TString* internalstr(struct lua_State* L, const char* str, unsigned int l) {
…
unsigned int h = luaS_hash(L, str, l, g->seed); // g is global_State
slot = lmod(h, &g->strt->size);
struct TString** list = &g->strt->hash[slot];
// 从 list 里找啊找(l == shrlen and memcmp)
// 找到就 return TString* in list
// 找不到就看看, 要不&g->strt扩容
// createstrobj, return TString* by create
}
例:
local str = “hello world” — str is TString*
char*(hello world) -> internalstr -> createstrobj -> return TString* -> pop to str
local str2 = “hello world” — str is TString*, 同str
char*(hello world) -> internalstr -> find in list -> return TString* -> pop to str2
it-Table
struct Table {
CommonHeader;
TValue* array; // 存放key为整数类型,值为TValue类型的变量, 数组
unsigned int arraysize;
Node* node; // key为任意lua类型的变量, hash表(size为2^n)
unsigned int lsizenode; // real hash size is 2 ^ lsizenode
Node* lastfree; // node插入位
struct GCObject* gclist;
};
node的key会根据类型值先转hash int, 然后根据hash int算出index
index = hash_value & ((2 ^ lsizenode) – 1)
A B
(lsizenode==低位1的位数
(B段固定, &限制了A段会被截取, 不超过size
typedef struct Node {
TKey key;
TValue value;
} Node;
// lua Table
typedef union TKey {
struct {
Value value_;
int tt_;
int next;
} nk;
TValue tvk;
} TKey;
typedef struct lua_TValue {
Value value_;
int tt_;
} TValue;
typedef union lua_Value {
struct GCObject* gc;
void* p;
int b;
lua_Integer i;
lua_Number n;
lua_CFunction f;
} Value;
resize:
当arraysize比原来大时,扩展原来的array到新的尺寸,并将hash表中,
key值 并将hash表大小调整为ceillog2(total_element – array_used_num),
同时对每个node进行rehash重新定位位置
当arraysize比原来小时,缩小原来的array到新的尺寸,并将array中,
key值超过arraysize的元素转移到hash表中,
此时hash表大小调整为ceillog2(total_element – array_used_num),
同时对每个node进行rehash计算,重新定位位置
hash中的int key, 会根据顺序安排到array, 此时可能触发,
array扩展, hash缩小
图:
hash_index = hash_int -> mainposition
table[hash_index] = [node1, node2, …] // 同hash下不同kv的节点
find by key:(大致流程)
i = find arrayindex(当数组查)
if i == 0
i = hash_index
for node in table[hash_index]
if node->key == key then
i == hash_index + node->sizearray
return i
it-脚本运行
— part05_test.lua : print(“hello world”)
void test_main() {
struct lua_State* L = luaL_newstate(); // 创建了lua_State实例, 保存了lua虚拟机里所有要用到的变量
luaL_openlibs(L); // 为该lua_State加载了标准库, 标准库加载逻辑
int ok = luaL_loadfile(L, “../part05_test.lua”); // lua代码编译, 生成虚拟机指令保存在lua_State数据实例中
if (ok == LUA_OK) {
luaL_pcall(L, 0, 0); // 行已经编译好的虚拟机指令, 编译器逻辑
}
luaL_close(L); // 释放lua_State相关联的数据实例, 销毁该lua_State实例
}
结合看:Lua解释器的运行示意图
lua源码,在经过lua编译器编译以后,会生成所谓的bytecode
而这个bytecode会在lua虚拟机里运行
bytecode:
经过编译器生成的一种供解释器运行的编码
能够被虚拟机的解释器识别并运行的指令
其本质就是一种中间表示(Intermediate Representation)
起源于指令集(Instruction Sets)的编码方式
一个表示指令操作的opcode
和一些可被选择的操作数
编码到一个定长的字节序中
为了减少被编译的程序,对机器的依赖,从而实现跨平台运行
luac:
官方有提供的lua编译器, 将lua源码编译后, 将bytecode保存为一个二进制文件
节约编译时间,但不能够提升运行时的效率
(lua在直接加载脚本并运行的模式中, 也是先将lua脚本编译成bytecode, 再交给虚拟机去运行)
对二进制文件反编译的功能(还原lua脚本在内存中的结构)
— 编译样例 test.lua
print(“hello world”)
— 注释符 ;
1 lua.dump
file_name = ../test.lua.out
+proto+maxstacksize [2] ; maxstacksize, proto所对应的函数的栈的大小
| +k+1 [print] ; 存放脚本里的常量list, 同值不重复
| | +2 [hello world]
| +locvars ; 函数的local变量表(名称和位置)
| +lineinfo+1 [1] ; 列表下标表示的是code列表里的那个指令, [1]=指令所对应的源码的line number
| | +2 [1]
| | +3 [1]
| | +4 [1]
| +p ; 函数内部定义的函数的结构列表(proto列表)
| +numparams [0] ; 函数的参数个数
| +upvalues+1+idx [0] ; upvalue的信息,
| | +instack [1] ; 位置, lua栈上/function的upvalue实例列表里
| | +name [_ENV]
| +source [@../test.lua]
| +is_vararg [1] ; 是[1]否[0]是可变参函数
| +linedefine [0]
| +code+1 [iABC:OP_GETTABUP A:0 B :0 C:256 ; R(A) := UpValue[B][RK(C)]]
| | +2 [iABx:OP_LOADK A:1 Bx :1 ; R(A) := Kst(Bx)]
Bx表示一个无符号整数值
从常量表中, 获取index为Bx的常量,
并赋值到R(A)中
| | +3 [iABC:OP_CALL A:0 B :2 C:1 ; R(A), … ,R(A+C-2) := R(A)(R(A+1), … ,R(A+B-1))]
R(A)表示要被调用的函数,
B表示该函数的参数个数,
B>0, 函数R(A)有B-1个参数(R(A+1)~R(A+B-1))
B==0, R(A+1)到栈顶都是该函数的参数
C代表该函数R(A)调用后的返回值个数
C>=1, 该函数有C-1个返回值(R(A)~R(A+C-2))
C==0, 函数调用完以后, 从R(A)到栈顶都是其返回值
| | +4 [iABC:OP_RETURN A:0 B :1 ; return R(A), … ,R(A+B-2) (see note)]
R(A)是第一个返回值存放的位置
B>=1, 返回值是B-1个(R(A)~R(A+B-2))
B==0, 返回值不定, 从R(A)到栈顶
| +type_name [Proto] ; 类型名
| +lastlinedefine [0]
+upvals+1+name [_ENV]
+type_name [LClosure]
补充:
proto: LClosure结构的function类型, 内部也可包含proto
code : bytecode列表, 包含虚拟机可以运行的指令列表
指令集:
——————————————————————————————
— name mode description 编码方式
——————————————————————————————
OP_MOVE A B R(A) := R(B) | iABC | B:9 | C:9 | A:8 |Opcode:6|
OP_LOADK A Bx R(A) := Kst(Bx) | iABx | Bx:18(unsigned)| A:8 |Opcode:6|
OP_CLOSURE A Bx R(A) := closure(KPROTO[Bx]) | iAsBx | sBx:18(signed) | A:8 |Opcode:6|
…
编码方式说明:
Opcode表示操作指令
A表示函数栈中,哪个位置将作为RA寄存器(一般是操作的目标)
sBx符号, Bx右移得到
description说明:
R( A ):寄存器A,一般是操作目标,由指令中的A域的值,指定该寄存器在栈中的位置
R( B ):寄存器B,一般是操作数,由指令(iABC模式)中的B域的值,指定该寄存器在栈中的位置
R( C ):寄存器C,一般是操作数,由指令(iABC模式)中的C域的值,指定该寄存器在栈中的位置
PC:PC寄存器,指向当前执行指令的地址
Kst(n):从常量表中取出第n个值
Upvalues[n]:从upvalue实例列表中,取出第n个upvalue实例
Gbl[sym]:sym表示一个符号(数值、地址或字符串等),从全局表中,取出key为该符号的值
RK( B ):当B的值>=256时,从常量表中取出Kst(B-256)的值,当B RK( C ):同RK(B),只是将B值替换为C值
sBx:有符号值,它表示所有jump指令的相对位置
chunk:
它是一段能够被lua解释器编译运行的代码
它可以是一个lua脚本文件
或者是在交互模式中, 输入的包含一段代码的字符串
加载-编译-运行流程(luaL_loadfile):
load chunk 输出 | 栈 |
test.lua ————> 编译 ———> Proto —–> |LClosure:func_object|
print(“xxx”)
function func1, func2
typedef struct Proto {
CommonHeader;
int is_vararg; /* 该LClosure对象的参数,1=动态传入,0=不是*/
int nparam; /* 当is_vararg为0时生效,它表示该函数参数的数量*/
Instruction* code; /* Instruction结构,其实是一个typedef的int,
Instruction表示的就是虚拟机的指令,这是函数的指令列表*/
int sizecode; /* 指明code列表的大小*/
TValue* k; /* 基本数据类型常量列表*/
int sizek; /* 常量列表的大小*/
LocVar* locvars; /* local变量列表,主要包含的信息包括,
local变量的名称,以及它是第几个local变量
local变量是存在栈中的*/
int sizelocvar; /* local变量列表的大小*/
Upvaldesc* upvalues;/* upvalue信息列表,主要记录upvalue的名称,
以及其所在的位置,并不是upvalue实际
值的列表*/
int sizeupvalues; /* upvalue列表大小*/
struct Proto** p; /* 函数内部定义的函数,编译后会生成proto实例,
存放在这个列表中, 对应的LClosure实例只在被调用后创建*/
int sizep; /* proto列表的长度*/
TString* source; /* 记录的是脚本的路径*/
struct GCObject* gclist;
int maxstacksize; /* 栈的最大大小*/
} Proto;
typedef struct LClosure {
ClosureHeader;
Proto* p;
UpVal* upvals[1]; // Closure第一个upvalue是_ENV, 默认指向_G
} LClosure;
*内置函数可以提升调用效率, 但多占函数内存空间
结果:
test.lua -> LClosure(top level function)
.p = {func1, func2}
.code = {print}
.k = {“xxx”}
标准库用(luaL_openlibs), 调用方式类似light c function
typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
UpVal* upvals[1];
} CClosure;
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
import java.io.IOException; public class SetTime { public static void main(String args[]){ String osName = System.getProperty(“os.…