skynet源码阅读<1>--lua与c的基本交互

2024-02-13 06:08

本文主要是介绍skynet源码阅读<1>--lua与c的基本交互,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

阅读skynet的lua-c交互部分代码时,可以看到如下处理:

 
  1. struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));

  2.  

    那么,问题来了:skynet_context是如何作为upvalue与C函数绑定在一起的呢?这里以luaopen_skynet_core(lua_State *L)为例:

 
  1. int luaopen_skynet_core(lua_State *L) {

  2. luaL_checkversion(L);

  3. luaL_Reg l[] = { /*注册函数部分略去*/ { NULL, NULL } };

  4. luaL_newlibtable(L, l);

  5. lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context");

  6. struct skynet_context *ctx = lua_touserdata(L,-1);

  7. if (ctx == NULL) {

  8. return luaL_error(L, "Init skynet context first");

  9. }

  10. luaL_setfuncs(L,l,1);

  11. return 1;

  12. }

  13.  

  这里先通过luaL_newlibtable创建一张表T(函数指针表l并未实际注册到表T中,只是分配了相应大小的空间),然后从全局索引表中取出事先注册的skynet_context,接着调用luaL_setfuncs(L,l,1),将函数表注册到T中。注册时每个函数都会从栈顶取出指定数目的元素(这里为1)作为upvalue。到lua源码中看看这部分具体的实现如何:

 
  1. /*

  2. ** set functions from list 'l' into table at top - 'nup'; each

  3. ** function gets the 'nup' elements at the top as upvalues.

  4. ** Returns with only the table at the stack.

  5. */

  6. LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {

  7. luaL_checkstack(L, nup, "too many upvalues");

  8. for (; l->name != NULL; l++) { /* fill the table with given functions */

  9. int i;

  10. for (i = 0; i < nup; i++) /* copy upvalues to the top */

  11. lua_pushvalue(L, -nup);

  12. lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */

  13. lua_setfield(L, -(nup + 2), l->name); /*-(nup+2)位置的元素正是之前创建的表T;这里以函数entry的name为key,向其注册闭包*/

  14. }

  15. lua_pop(L, nup); /* remove upvalues */

  16. }

  17.  

    可以看到扫描每个函数entry时,都会将push到栈顶的upvalues全部复制新的一份出来到栈顶,接着通过lua_pushcclosure创建C闭包,并注册到表T中去。函数表注册完毕后,再清理掉栈顶的upvalues。此时栈顶就是完成注册的表T了。至此,每个C闭包已经关联了skynet-context作为它们的upvalue。至于skynet_context,则是加载lua服务时,会创建C服务snlua(负责加载lua虚拟机),此时会将context实例注册到lua虚拟机的全局注册表中。细节见service_snlua.c,这里不再赘述。

    先看下C闭包的定义(lobject.h):

 
  1. typedef struct CClosure {

  2. ClosureHeader;

  3. lua_CFunction f;

  4. TValue upvalue[1]; /* list of upvalues */

  5. } CClosure;

  6.  

    除了ClosureHeader之外(包括upvalue的个数,CommonHeader的对象类型tt_,标记mark_等),可以看到C闭包包含一个函数指针f和一个upvalue数组。upvalue数组的实际大小是根据upvalue的个数来创建的。这里要做的事情就是把upvalue拷进C闭包中,如下(lapi.c):

 
  1. LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {

  2. lua_lock(L);

  3. if (n == 0) {

  4. setfvalue(L->top, fn);

  5. }

  6. else {

  7. CClosure *cl;

  8. api_checknelems(L, n);

  9. api_check(L, n <= MAXUPVAL, "upvalue index too large");

  10. cl = luaF_newCclosure(L, n);

  11. cl->f = fn;

  12. L->top -= n;

  13. while (n--) {

  14. setobj2n(L, &cl->upvalue[n], L->top + n);

  15. /* does not need barrier because closure is white */

  16. }

  17. setclCvalue(L, L->top, cl);

  18. }

  19. api_incr_top(L);

  20. luaC_checkGC(L);

  21. lua_unlock(L);

  22. }

  23.  

    这里将栈上的元素copy到cl的upvalue数组中,然后再将cl设置到栈顶。之前被这里的while循环绕了一下,设n为N0,减一之后为N1,那么n--的运算结果为N0,但是循环中n的值则是N1,二者并不一样,逻辑是正确的。

    那么剩下的最后一个问题是,在C函数中,是如何取到相关的upvalue的?首先是通过lua_upvalueindex(1)拿到对应的upvalue的索引:

 
  1. #define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i))

  2.  

  在本例中,进入lua_touserdata,可以看到index2addr的实现:

 
  1. static TValue *index2addr (lua_State *L, int idx) {

  2. CallInfo *ci = L->ci;

  3. if (idx > 0) {

  4. TValue *o = ci->func + idx;

  5. api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index");

  6. if (o >= L->top) return NONVALIDVALUE;

  7. else return o;

  8. }

  9. else if (!ispseudo(idx)) { /* negative index */

  10. api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index");

  11. return L->top + idx;

  12. }

  13. else if (idx == LUA_REGISTRYINDEX)

  14. return &G(L)->l_registry;

  15. else { /* upvalues */

  16. idx = LUA_REGISTRYINDEX - idx;

  17. api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large");

  18. if (ttislcf(ci->func)) /* light C function? */

  19. return NONVALIDVALUE; /* it has no upvalues */

  20. else {

  21. CClosure *func = clCvalue(ci->func);

  22. return (idx <= func->nupvalues) ? &func->upvalue[idx-1] : NONVALIDVALUE;

  23. }

  24. }

  25. }

  26.  

    在upvalue的处理这块,先是还原upvalue在C闭包中的index,然后从当前的CallInfo中取到闭包,拿到upvalue返回。

转载于:https://www.cnblogs.com/Jackie-Snow/p/5927890.html

这篇关于skynet源码阅读<1>--lua与c的基本交互的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/704680

相关文章

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

C#连接SQL server数据库命令的基本步骤

《C#连接SQLserver数据库命令的基本步骤》文章讲解了连接SQLServer数据库的步骤,包括引入命名空间、构建连接字符串、使用SqlConnection和SqlCommand执行SQL操作,... 目录建议配合使用:如何下载和安装SQL server数据库-CSDN博客1. 引入必要的命名空间2.

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

ModelMapper基本使用和常见场景示例详解

《ModelMapper基本使用和常见场景示例详解》ModelMapper是Java对象映射库,支持自动映射、自定义规则、集合转换及高级配置(如匹配策略、转换器),可集成SpringBoot,减少样板... 目录1. 添加依赖2. 基本用法示例:简单对象映射3. 自定义映射规则4. 集合映射5. 高级配置匹

SQL BETWEEN 语句的基本用法详解

《SQLBETWEEN语句的基本用法详解》SQLBETWEEN语句是一个用于在SQL查询中指定查询条件的重要工具,它允许用户指定一个范围,用于筛选符合特定条件的记录,本文将详细介绍BETWEEN语... 目录概述BETWEEN 语句的基本用法BETWEEN 语句的示例示例 1:查询年龄在 20 到 30 岁

mysql中insert into的基本用法和一些示例

《mysql中insertinto的基本用法和一些示例》INSERTINTO用于向MySQL表插入新行,支持单行/多行及部分列插入,下面给大家介绍mysql中insertinto的基本用法和一些示例... 目录基本语法插入单行数据插入多行数据插入部分列的数据插入默认值注意事项在mysql中,INSERT I

mapstruct中的@Mapper注解的基本用法

《mapstruct中的@Mapper注解的基本用法》在MapStruct中,@Mapper注解是核心注解之一,用于标记一个接口或抽象类为MapStruct的映射器(Mapper),本文给大家介绍ma... 目录1. 基本用法2. 常用属性3. 高级用法4. 注意事项5. 总结6. 编译异常处理在MapSt

MyBatis ResultMap 的基本用法示例详解

《MyBatisResultMap的基本用法示例详解》在MyBatis中,resultMap用于定义数据库查询结果到Java对象属性的映射关系,本文给大家介绍MyBatisResultMap的基本... 目录MyBATis 中的 resultMap1. resultMap 的基本语法2. 简单的 resul

Java 枚举的基本使用方法及实际使用场景

《Java枚举的基本使用方法及实际使用场景》枚举是Java中一种特殊的类,用于定义一组固定的常量,枚举类型提供了更好的类型安全性和可读性,适用于需要定义一组有限且固定的值的场景,本文给大家介绍Jav... 目录一、什么是枚举?二、枚举的基本使用方法定义枚举三、实际使用场景代替常量状态机四、更多用法1.实现接