lua的类型,lua_State,函数调用,协程

数据类型

lua中的数据可以这样分为两位:值类型和引用类型。引用类型创建时需要从堆上分配内存,复制时只需要复制指针,分配的内存由GC负责维护生命期。

所有lua类型都用一个union来表示:

/*
** Union of all Lua values
*/
typedef union {
  GCObject *gc;
  void *p; /* lightuserdata */
  lua_Number n;
  int b; /* boolean */
} Value;

引用类型用一个gc指针来引用,其他值类型都直接保存在Value中。

每个引用类型的开头都是CommonHeader;,所以都可强转成GCheader*使用:

/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked

/*
** Common header in struct form
*/
typedef struct {
  CommonHeader;
} GCheader;

/*
** Union of all collectable objects
*/
union GCObject {
  GCheader gch;
  TString ts;
  Udata u;
  Closure cl;
  Table h;
  Proto p;
  UpVal uv;
  lua_State th; /* thread */
};

为了区分Value中存放的数据类型,再额外绑定一个类型字段:

/*
** Tagged Values
*/
typedef struct {
  Value value;
  int tt;
} TValue;

lua_State、数据栈、调用栈

lua_State表示一个线程/协程(后面线程与协程通用)的状态,lua_newstate用于创建主线程:

/*
** Main thread combines a thread state and the global state
*/
typedef struct {
  lua_State l;
  global_State g;
} LG;

LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
  int i;
  lua_State *L;
  global_State *g;
  void *l = (*f)(ud, NULL, 0, state_size(LG));
  if (l == NULL) return NULL;
  ...

用lua_newstate创建主线程的时候,同时也创建了一个global_State,所有的线程共享这个global_State,luaE_newthread用于创建协程:

lua_State *luaE_newthread (lua_State *L) {
  lua_State *L1 = tostate(luaM_malloc(L, state_size(lua_State)));
  luaC_link(L, obj2gco(L1), LUA_TTHREAD);
  preinit_state(L1, G(L));
  ...

lua的数据栈是一个TValue数组,代码中用StkId类型用来指代对TValue的引用:

typedef TValue *StkId; /* index to stack elements */

调用栈放在CallInfo数组中,CallInfo保存着正在调用的函数的运行状态:

/*
** informations about a call
*/
typedef struct {
  StkId base; /* base for this function */
  StkId func; /* function index in the stack */
  StkId top; /* top for this function */
  const Instruction *savedpc;
  int nresults; /* expected number of results from this function */
  int tailcalls; /* number of tail calls lost under this entry */
} CallInfo;

正在调用的函数一定存在于数据栈上,由func引用正在调用的函数对象。
[base,top)指示了正在调用的函数的堆栈在数据栈上的范围。
为什么没有堆栈的当前位置?lua_State的top就是正在调用的函数的堆栈的位置啊。

/*
** `per thread' state
*/
struct lua_State {
  CommonHeader;
  lu_byte status;
  StkId top; /* first free slot in the stack */
  StkId base; /* base of current function */
  global_State *l_G;
  CallInfo *ci; /* call info for current function */
  const Instruction *savedpc; /* `savedpc' of current function */
  StkId stack_last; /* last free slot in the stack */
  StkId stack; /* stack base */
  CallInfo *last_ci; /* last free slot in the ci array*/
  CallInfo *base_ci; /* array of CallInfo's */
  int stacksize;
  int size_ci; /* size of array `base_ci' */
  ...

[stack,stack_last],[base_ci,last_ci]分别是数据栈数组和调用栈数组,stacksize,size_ci分别是两个数组的大小,在需要的时候它们会进行增长。
ci是当前正在调用的函数的运行状态,base是该函数的栈底指针。

lua_newstate和luaE_newthread都调用了stack_init来初始化堆栈:

static void stack_init (lua_State *L1, lua_State *L) {
  /* initialize CallInfo array */
  L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo);
  L1->ci = L1->base_ci;
  L1->size_ci = BASIC_CI_SIZE;
  L1->last_ci = L1->base_ci + L1->size_ci - 1;
  /* initialize stack array */
  L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue);
  L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK;
  L1->top = L1->stack;
  L1->stack_last = L1->stack + (L1->stacksize - EXTRA_STACK) - 1;
  /* initialize first ci */
  L1->ci->func = L1->top;
  setnilvalue(L1->top++); /* `function' entry for this `ci' */
  L1->base = L1->ci->base = L1->top;
  L1->ci->top = L1->top + LUA_MINSTACK;
}

可以看到first ci只是占了个位置,它的func只是一个空值。

c代码中的函数调用

c代码中调用函数(包括c函数和Lua函数)使用lua_call和lua_pcall。

void (lua_call) (lua_State *L, int nargs, int nresults);
int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc);

在使用lua时一个愚蠢的错误中同时展示了这两个函数的用法。
二者最终都通过调用luaD_call来实现主要逻辑,但lua_pcall多了一层错误处理机制,如果函数调用过程中出错,lua_pcall会捕获错误,并调用用户通过errfunc参数传入的错误处理函数,而lua_call则会直接退出程序。这套错误处理机制是通过c的longjmp或c++的异常机制来实现的。

/*
** Call a function (C or Lua). The function to be called is at *func.
** The arguments are on the stack, right after the function.
** When returns, all the results are on the stack, starting at the original
** function position.
*/
void luaD_call (lua_State *L, StkId func, int nResults) {
  if (++L->nCcalls >= LUAI_MAXCCALLS) {
    if (L->nCcalls == LUAI_MAXCCALLS)
      luaG_runerror(L, "C stack overflow");
    else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
      luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
  }
  if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */
    luaV_execute(L, 1); /* call it */
  L->nCcalls--;
  luaC_checkGC(L);
}

luaD_precall执行的是函数调用部分的工作,而luaD_poscall做的是函数返回的工作。对于c函数整个函数调用是连续的,luaD_precall在调用完c函数后可直接调用luaD_poscall完成工作;而lua函数执行完luaD_precall后,只是切换了lua_State的执行状态,被调用的函数的字节码尚未运行,luaD_precall返回后,调用luaV_execute去运行被调用函数的字节码,待到虚拟机执行到对应的return指令时,才会去调用luaD_poscall完成整次调用。

c函数的参数传递

使用lua_call或lua_pcall调用lua函数前,应先在栈上准备好函数对象和函数的参数,如果没有错误发生,函数返回时,函数对象和函数参数会出栈,函数的返回值会留在栈上。
而如果调用的是c函数时,一般是先通过lua_pushcclosure来创建c闭包,然后再调用lua_call或lua_pcall。

void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
  Closure *cl;
  lua_lock(L);
  luaC_checkGC(L);
  api_checknelems(L, n);
  cl = luaF_newCclosure(L, n, getcurrenv(L));
  cl->c.f = fn;
  L->top -= n;
  while (n--)
    setobj2n(L, &cl->c.upvalue[n], L->top+n);
  setclvalue(L, L->top, cl);
  lua_assert(iswhite(obj2gco(cl)));
  api_incr_top(L);
  lua_unlock(L);
}

调用lua_pushcclosure前,fn需要的参数都在栈上,lua_pushcclosure会把栈上的参数转移到c闭包的上值中,然后把c闭包压入栈。在函数fn中,需要用lua_getupvalue来读取参数。

lua还提供了另一种调用c函数的方式:

int lua_cpcall (lua_State *L, lua_CFunction func, void *ud);

在调用lua_cpcall前,不需要将参数压入栈,而是直接将参数通过ud传递给lua_cpcall。
lua_cpcall会创建一个没有上值的c闭包并压入栈中,然后将ud作为lightuserdata也压入栈,其余便和lua_pcall类似了。
在函数func中需要通过lua_touserdata取出参数。
lua_cpcall在调用luaD_call时nResults传入的是0,所以func并没有返回值。

协程

协程的创建就是新建了一个lua_State,然后将lua函数对象移到新建的lua_State上:

int luaB_cocreate (lua_State *L) {
  lua_State *NL = lua_newthread(L);
  luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1,
    "Lua function expected");
  lua_pushvalue(L, 1); /* move function to top */
  lua_xmove(L, NL, 1); /* move function from L to NL */
  return 1;
}

通过luaB_coresume启动/恢复协程,首个参数是协程对象:

static int luaB_coresume (lua_State *L) {
  lua_State *co = lua_tothread(L, 1);
  int r;
  luaL_argcheck(L, co, 1, "coroutine expected");
  r = auxresume(L, co, lua_gettop(L) - 1);
  if (r < 0) {
    lua_pushboolean(L, 0);
    lua_insert(L, -2);
    return 2; /* return false + error message */
  }
  else {
    lua_pushboolean(L, 1);
    lua_insert(L, -(r + 1));
    return r + 1; /* return true + `resume' returns */
  }
}

在auxresume中将coroutine.resume的其余参数从主线程转移至协程:

int auxresume (lua_State *L, lua_State *co, int narg) {
  int status = costatus(L, co);
  if (!lua_checkstack(co, narg))
    luaL_error(L, "too many arguments to resume");
  if (status != CO_SUS) {
    lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
    return -1; /* error flag */
  }
  lua_xmove(L, co, narg);
  lua_setlevel(L, co);
  status = lua_resume(co, narg);
  ...

然后去调用lua_resume,后续最终会调用resume:

void resume (lua_State *co, void *ud) {
  StkId firstArg = cast(StkId, ud);
  CallInfo *ci = co->ci;
  if (co->status == 0) { /* start coroutine? */
    lua_assert(ci == co->base_ci && firstArg - co->base == 1);
    lua_assert(isLfunction(co->base));
    if (luaD_precall(co, firstArg - 1, LUA_MULTRET) != PCRLUA)
      return;
  }
  else { /* resuming from previous yield */
    lua_assert(co->status == LUA_YIELD);
    co->status = 0;
    if (!f_isLua(ci)) { /* `common' yield? */
      /* finish interrupted execution of `OP_CALL' */
      lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL ||
                 GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL);
      if (luaD_poscall(co, firstArg)) /* complete it... */
        co->top = co->ci->top; /* and correct top if not multiple results */
    }
    else /* yielded inside a hook: just continue its execution */
      co->base = co->ci->base;
  }
  luaV_execute(co, cast_int(co->ci - co->base_ci));
}

启动协程时,和调用普通lua函数一样先调用luaD_precall,再调用luaV_execute,luaV_execute中调用函数的代码如下:

void luaV_execute (lua_State *L, int nexeccalls) {
  ...
  case OP_CALL: {
    int b = GETARG_B(i);
    int nresults = GETARG_C(i) - 1;
    if (b != 0) L->top = ra+b; /* else previous instruction set top */
    L->savedpc = pc;
    switch (luaD_precall(L, ra, nresults)) {
      case PCRLUA: {
        nexeccalls++;
        goto reentry; /* restart luaV_execute over new Lua function */
      }
      case PCRC: {
        /* it was a C function (`precall' called it); adjust results */
        if (nresults >= 0) L->top = L->ci->top;
        base = L->base;
        continue;
      }
      default: {
        return; /* yield */
      }
    }
  }
  ...
}

int luaD_precall (lua_State *L, StkId func, int nresults) {
  ...
  else { /* if is a C function, call it */
    CallInfo *ci;
    int n;
    luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */
    ci = inc_ci(L); /* now `enter' new function */
    ci->func = restorestack(L, funcr);
    L->base = ci->base = ci->func + 1;
    ci->top = L->top + LUA_MINSTACK;
    lua_assert(ci->top <= L->stack_last);
    ci->nresults = nresults;
    if (L->hookmask & LUA_MASKCALL)
      luaD_callhook(L, LUA_HOOKCALL, -1);
    lua_unlock(L);
    n = (*ci_func(ci)->c.f)(L); /* do the actual call */
    lua_lock(L);
    if (n < 0) /* yielding? */
      return PCRYIELD;
    else {
      // n表示c函数留在栈上的结果数量,luaD_poscall会将结果调整为nresults个,放到func开始的位置
      // 相当于将func和所有参数出栈,再将前nresults的返回值入栈
      luaD_poscall(L, L->top - n);
      return PCRC;
    }
  }
  ...
}

当lua函数中碰到coroutine.yield时,经luaD_precall->luaB_yield,再调用到lua_yield:

int lua_yield (lua_State *co, int nresults) {
  luai_userstateyield(co, nresults);
  lua_lock(co);
  if (co->nCcalls > co->baseCcalls)
    luaG_runerror(co, "attempt to yield across metamethod/C-call boundary");
  co->base = co->top - nresults; /* protect stack slots below */
  co->status = LUA_YIELD;
  lua_unlock(co);
  return -1;
}

lua_yield将协程置为LUA_YIELD状态,设置了co->base,然后一路返回到auxresume:

static int auxresume (lua_State *L, lua_State *co, int narg) {
  ...
  status = lua_resume(co, narg);
  if (status == 0 || status == LUA_YIELD) {
    int nres = lua_gettop(co);
    if (!lua_checkstack(L, nres + 1))
      luaL_error(L, "too many results to resume");
    lua_xmove(co, L, nres); /* move yielded values */
    return nres;
  }
  else {
    lua_xmove(co, L, 1); /* move error message */
    return -1; /* error flag */
  }
}

因为lua_yield设置的co->base,lua_gettop(co)返回coroutine.yield的参数个数,将这些参数转移到主线程后,便从coroutine.resume返回了。

注意到luaD_precall中发生yield时,并没有调用luaD_poscall,当协程恢复时需要补上,至于luaD_precall中函数正常返回时,为什么没修正L->top,那是在lua_call中修正的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

windows11如何设置无线网卡不休眠

为了在家里用向日葵等软件连接上公司的台式电脑&#xff0c;发现尴尬的事情&#xff1a;在家里连接时提示公司的电脑下线了。经排查&#xff0c;发现长时间不用时&#xff0c;公司的台式电脑的无线网卡休眠了。 windows11可以用下面的步骤设置无线网卡不休眠&#xff1a; 1. 设…

Sybase数据库分页查询(指定起始位置)

针对单表数据量过大的场景&#xff0c;分页查询必不可少。针对sybase数据库分页查询的案例全网稀少&#xff0c;特别是指定起始页的分页查询实现。 本文依靠实际开发场景&#xff0c;特此总结Sybase数据库分页查询&#xff08;指定起始位置&#xff09;。 目录 一、 SQL实现分…

SQL统计语句记录

1.达梦数据库 统计指定单位的12个月份的业务数据 SELECT a.DEPT_ID, b.dept_name, a.USER_NAME, count(a.dept_id) as count, sum(case when to_char(a.CREATE_TIME,yyyy-mm) 2023-01 THEN 1 else 0 end) as one,sum(case when to_char(a.CREATE_TIME,yyyy-mm) 2023-02 T…

JavaScript手写专题——图片懒加载、滚动节流、防抖手写

图片懒加载场景&#xff1a;在一些图片量比较大的网站&#xff08;比如电商网站首页&#xff0c;或者团购网站、小游戏首页等&#xff09;&#xff0c;如果我们尝试在用户打开页面的时候&#xff0c;就把所有的图片资源加载完毕&#xff0c;那么很可能会造成白屏、卡顿等现象&a…

内网安全-隧道技术SSHDNSICMPSMB上线通讯LinuxMac 简单总结

第126天&#xff1a;内网安全-隧道技术&SSH&DNS&ICMP&SMB&上线通讯Linux&Mac_内网安全-隧道技术_ssh_dns_icmp_smb_上线通讯linux_mac-CSDN博客 内网渗透—隧道技术_隧道技术csdn-CSDN博客 #SMB 隧道&通讯&上线 判断&#xff1a;445 通讯 上…

Azure Windows2012升级2016

Azure Windows2012升级2016 在自己电脑配置Azure PowerShell前置条件PowerShell 登录到 Azure Azure 中运行 Windows Server 的 VM 的就地升级前置条件&#xff0c;生成一块OS磁盘将生成的OS磁盘附件到需升级的服务器执行就地升级到 Windows Server 2016 升级后配置故障恢复 在…

一觉醒来 AI科技圈发生的大小事儿 05月09日

&#x1f4f3;AlphaFold 3 重磅问世&#xff0c;全面预测蛋白质与所有生命分子相互作用及结构&#xff0c;准确性远超以往水平 Google DeepMind发布了AlphaFold3模型&#xff0c;能够联合预测蛋白质、核酸、小分子等复合物结构&#xff0c;准确性显著提高&#xff0c;对跨生物…

代码随想录算法训练营第36期DAY22

DAY22 654最大二叉树 自己做的时候忽略了&#xff1a;nums.length>1的题给条件。所以每次递归都要判断是否size()>1&#xff0c;不要空的。 /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *rig…

让数据更「高效」一点!IvorySQL在Neon平台上的迅速部署和灵活应用

IvorySQL本身就是一个100%兼容PostgreSQL最新内核的开源数据库系统&#xff0c;而Neon Autoscaling Platform通常支持多种数据库和应用程序。将IvorySQL集成到该平台后&#xff0c;可以进一步增强与其他系统和应用程序的兼容性&#xff0c;同时更全面的体验IvorySQL的Oracle兼容…

深入探究 Spring Boot Starter:从概念到实践

序言 Spring Boot Starter 是 Spring Boot 生态系统中的一个核心概念&#xff0c;它为开发者提供了一种便捷的方式来捆绑和配置应用程序所需的依赖项。本文将深入探讨 Spring Boot Starter 的概念、原理以及如何创建自定义的 Starter。 一、什么是 Spring Boot Starter Spri…

docker 安装elasticsearch8.X

docker 安装elasticsearch8.X 安装elasticsearch8.X前言安装elasticsearch安装elasticsearch-analysis-ik安装kibana 安装elasticsearch8.X 前言 由于需要安装elasticsearch、IK分词插件、kibana。所以需要保持这三者的版本一致性。 elasticsearch 8.12.2 kibana 8.12.2 ela…

科沃斯梦碎“扫地茅”,钱东奇跌落“风口”

昔日“扫地茅“不香了&#xff0c;科沃斯跌落神坛。 4月27日&#xff0c;科沃斯发布2023年报显示&#xff1a;2023年&#xff0c;科沃斯的营收为155.02亿元&#xff0c;同比增加1.16%&#xff1b;同期&#xff0c;净利为6.10亿元&#xff0c;同比减少63.96%。科沃斯的经营业绩…

Mysql数据在磁盘上的存储结构

一. 前言 一行数据的存储格式大致如下所示: 变长字段的长度列表&#xff0c;null值列表&#xff0c;数据头&#xff0c;column01的值&#xff0c;column02的值&#xff0c;column0n的值… 二. 变长字段 在MySQL里有一些字段的长度是变长的&#xff0c;是不固定的&#xff0c;…

可视化-实验五-Pyecharts工具包的使用及文本数据可视化

1.2.1 pyecharts的数据类型以及新的数据导入逻辑 由于pyecharts背后封装的js库&#xff0c;会涉及到数据类型转化。它暂时要求输入数据必须是python的基础数据类型&#xff0c;比如字符串&#xff0c;列表&#xff0c;字典&#xff0c;而不能是序列这样的数据类型。因此序列输入…

RockChip Android13 添加/删除ListPreference方法

概述: 本章将讲述在Android添加或删除ListPreference的几种方法,并以EthernetSettingsActivity为例,添加/删除一项ListPreference: 默认效果图: 添加后效果图: 方法一: 1、全部添加xml 在Activity类中使用addPreferencesFromResource()方法解析XML文件并添加Prefere…

Node.js安装与配置环境 v20.13.1(LTS)

1 下载 Node.js — Run JavaScript Everywhere LTS -- long-term support&#xff0c;长期维护版本 如果要下载其他版本在download里选择下载 2 安装 一路点击next&#xff0c;默认安装路径C:\Program Files\nodejs 3 环境变量配置 1&#xff09;Path环境变量增加nodejs安装…

艾体宝方案 | 加密USB金融解决方案

在现代金融行业中&#xff0c;保护敏感数据和合规性已成为至关重要的任务。为了帮助金融公司应对移动性风险和合规挑战&#xff0c;我们提供了一种高效的加密USB解决方案。 一、为什么金融公司需要加密USB解决方案 1、降低移动性风险 金融服务公司正在迅速过渡到一种模式&a…

将本地托管模型与 Elastic AI Assistant 结合使用的好处

作者&#xff1a;来自 Elastic James Spiteri, Dhrumil Patel 当今公共部门组织利用生成式人工智能解决安全挑战的一种方式。 凭借其筛选大量数据以发现异常模式的能力&#xff0c;生成式人工智能现在在帮助团队保护其组织免受网络威胁方面发挥着关键作用。 它还可以帮助安全专…

短信平台群发服务有什么优点

短信平台群发服务有什么优点 提高营销效率 短信平台群发服务利用自动化技术&#xff0c;可以帮助企业迅速向大量潜在客户营销信息。相比传统的逐一方式&#xff0c;群发服务可以同时大批目标客户&#xff0c;大大提高了营销效率。企业可以轻松地在短时间内覆盖更多的潜在客户&…

JavaSE——异常(2/2)-异常的处理(记录异常并提示 、尝试重新修复)

目录 记录异常并提示 案例演示 流程解析 写法优化 尝试重新修复 开发中对于异常的常见处理方式 一层一层往上抛出异常&#xff0c;并且在最上层捕获异常&#xff0c;分为两种不同的处理方式。 例如&#xff0c;B站网页报错就是采取的第一种方式&#xff1a; 记录异常并…
最新文章