Hive 浅析

2023-12-14 23:31:35

Hive是一个简单的LUA沙盒,除了基本的LUA解释器的功能以外,还提供了诸如热加载等功能。 了解HIVE的工作原理有利于了解Lua虚拟机的底层实现机理。 本文从是什么-怎么用-为什么三个维度介绍HIVE。

Hive

Hive是什么

hive是一个简单的LUA应用框架,目前基于LUA 5.3.4。

主要提供了文件沙盒,文件热加载以及一些基础的服务程序底层支持.

HIVE源码:hive - master - gems / hive-framework - 工蜂内网版 (woa.com)

Hive的使用

编译
  • 编译luna

     # at the hive-framework root directory
     cd luna && make
     cp luan.so ../hive/
    
  • 编译hive

     # at the hive-framework root directory
     cd hive && make
    
运行
  • 作为启动器的Hive

Hive本身只提供基础的热加载功能,并没有太多的功能。

你可以把Hive看作是一个简单的lua启动器,正如你使用lua file_name.lua一样,你也可以使用如下的命令行启动你的lua代码——

 # make sure the lua binary is under your PATH
 hive file_name.lua
 # just like lua file_name.lua!
  • 命令行参数

你也可以传递一些命令行参数进去,这些命令行参数会被打包放进一个表里,然后传递给你的脚本文件。

你可以使用hive.argsfile_name.lua中来获取这些参数。

 # ok,you can obtain
 hive file_name.lua arg1 arg2 arg3

例如在你自己的业务代码里,你可以写这样的代码:

 -- print the args
 -- test.lua
 for k,v in ipairs(hive.args) do
     print(string.format('%d:%s\n',k,v))
 end

保存为test.lua,然后在命令行中使用:

 hive test.lua arg1 arg2 arg3
 1:arg1
 2:arg2
 3:arg3
  • 业务代码

    被Hive启动的Lua的业务代码中,至少应该提供一个hive.run的实现——

     --test.lua
     hive.run = function()
         print('Hello world')
    

    Hive.run会被反复执行,效果如下所示——

     hive test.lua
     hello world
     hello world
     hello world
     hello world
     hello world
     hello world
     hello world
     hello world
     hello world
     hello world
     hello world
     hello world
     ...
    

除此以外,Hive还提供了一些feature,这些feature可以在Lua的环境中用,即,在你自己的业务代码中使用。

Hive的特性

本章从使用的角度介绍Hive的特性,如果需要详细了解实现原理,欢迎参阅源码剖析和Hive接口手册章节。

文件热加载

所谓文件热加载是指但凡使用import函数引入的文件(包括入口的文件,例如上面的test.lua)都会被Hive检测是否有更新。当Hive检测到文件时间戳改变以后,会自动重新进行读取。

请观察下面的示例——

 -- test.lua
 l = import('lobby.lua')
 hive.run = function() 
     print('in test.lua')
     print('the imported l is:',l.str)
     hive.sleep_ms(2000);
 end

其中lobby.lua是另外一个lua程序

 -- lobby.lua
 str = 'Yes, I am a lobby'

输入hive test.lua运行

 hive test.lua
 the imported l is:  Yes, I am a lobby
 in test.lua
 
 the imported l is:  Yes, I am a lobby
 in test.lua
 
 the imported l is:  Yes, I am a lobby
 in test.lua
 ...

在另外一个终端打开并修改lobby.lua文件,保存

 --lobby.lua
 str = 'No!I am not a lobby!'

原终端中的输出发生改变

 the imported l is:  No, I am not a lobby!
 in test.lua
 the imported l is:  No, I am not a lobby!
 in test.lua
 the imported l is:  No, I am not a lobby!
沙盒环境

使用import导入的文件,Hive会为之创建了一个沙盒环境这使得各个文件中的变量名不会冲突。

下面的例子说明了import函数的行为——

 -- lobby.lua
 function m_add(a,b)
     return a + b
 end 
 
 function m_sub(a,b)
     return a - b
 end
 
 return 1123

test.lua中引入该文件:

 l = import('lobby.lua')
 print(type(l))
 
 for k,v in pairs(l) do
     print(k,':',v)
 end
 
 -- for k,v in pairs(_G) do
 --     print(k,':',v)
 -- end
 
 print(l.m_add(1,2))
 print(l.m_sub(1000,2))

得到结果如下所示:可见,之前的定义都放在一个表中,且返回值被忽略了。

 table
 m_add   :   function: 0x228faa0
 m_sub   :   function: 0x229be80
 3
 998

如果使用require,则有所不同

 > require('lobby')
 1123
 > v = require('lobby')
 > m_add
 function: 0xa5c340
 > m_sub
 function: 0xa5c010

有何不同?

如果使用自带的require函数,这里会有两个区别。

  • 你可以获取到require的返回值
  • 全局的作用域被影响,即这里的_G

本章节不涉及原理部分的阐述,如有需要,请参阅Hive接口手册。

源码剖析

热加载实现原理

这个主要和Hive::run代码有关:

 ...
 if(!lua_call_object_function(L, &err, this, "import", std::tie(), filename))
         die(err);
 
     while (lua_get_object_function(L, this, "run")) {
         if(!lua_call_function(L, &err, 0, 0))
             die(err);
 
         int64_t now = ::get_time_ms();
         if (m_reload_time > 0 && now > last_check + m_reload_time) {
             lua_call_object_function(L, nullptr, this, "reload");
             last_check = now;
         }
         lua_settop(L, top);
     }
 
     lua_close(L);
 }
  • 首先将用户的代码(#2),使用import函数进行加载。如果没有错,则继续进入一个循环。这个import函数,是Hive提前定义好的一个函数。后面还会介绍。
  • 循环(#5)不断地从Lua环境中获取到Run函数的地址,然后去调用,如果获取不到函数地址,则循环直接中止,执行完毕。
  • 否则则直接进行调用获取到的Run函数。
  • 执行完毕以后,检查import的文件是否有更新,如果有,则调用reload函数重新进行加载。

因此,我们可以说,如果业务代码中没有定义RUN函数,则系统会直接返回。

沙盒环境实现原理

所谓沙盒环境,其实就是不管怎么加载代码,都用一个table给装起来,而不污染全局的环境(详情可见上面的特性章节)。

沙盒的实现原理主要和Hive提前定义的load函数有关。

 static const char* g_sandbox = u8R"__(
 hive.files = {};
 hive.meta = {__index=function(t, k) return _G[k]; end};
 hive.print = function(...) end; --do nothing
 
 local get_filenode = function(filename)
     local rootpath = os.getenv("LUA_ROOT");
     local withroot = rootpath and hive.get_full_path(rootpath).."/"..filename or filename;
     local fullpath = hive.get_full_path(withroot) or withroot;
 
     local node = hive.files[fullpath];
     if node then
         return node;
     end
 
     local env = {};
     setmetatable(env, hive.meta);
     node = {env=env, fullpath=fullpath, filename=filename};
     hive.files[fullpath] = node;
     return node;
 end
 
 function import(filename)
     local node = get_filenode(filename);
     if not node.time then
         node.time = hive.get_file_time(node.fullpath);
         try_load(node);
     end
     return node.env;
 end
 
 hive.reload = function()
     local now = os.time();
     local update = true;
     while update do
         update = false;
         for path, node in pairs(hive.files) do
             local filetime = hive.get_file_time(node.fullpath);
             if filetime ~= node.time and filetime ~= 0 and math.abs(now - filetime) > 1 then
                 node.time = filetime;
                 update = true;
                 try_load(node);
             end
         end
     end
 end
 )__";
 

上面的定义,是Hive在加载用户的文件之前会调用的。

主要关注import函数——

  • 函数首先执行get_filenode函数。该函数首先到全局的hive.files表中进行查找,如果找到了,则直接返回该node,这里的node,其实就是一个表,一个虚拟的沙箱。否则就新建一张表,这张表的元表是固定的hive.meta,即如果在该表中无法找到,则到_G中进行查找。这里的_G事实上就是导入的文件的环境。
  • 如果是新导入的文件,则对其进行加载即可,记录下导入的时间(方便后面检查是否有更新)

Hive接口手册

除了前面章节中所提到的内容,Hive其实还有一些其他的接口暴露给了用户。使用hive.xxx即可访问。

API作用
get_version返回版本号
get_file_time获取文件的修改时间
get_full_path获取文件的绝对路径
get_time_ms/get_time_ns获取当前的时间(Epoch格式)
sleep_ms睡眠
daemon服务作为后台运行

文章来源:https://blog.csdn.net/gyshun/article/details/134883943
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。