0
点赞
收藏
分享

微信扫一扫

关于使用ProcessBuilder调用shell脚本,以及部署到docker中遇到的问题

月半小夜曲_ 2023-07-06 阅读 62

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_gettop(L); ++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 <- t[i] <- r=call(f(t[i])); t[i]=r
            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,gc一步到位执行完毕
        >= 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, 原来的两倍, <=strt->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值<=arraysize的元素转移到array中,
    并将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<256时,RK(B):=R(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;

举报

相关推荐

0 条评论