0


Hive 浅析

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.args

file_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
版权归原作者 garagong 所有, 如有侵权,请联系我们删除。

“Hive 浅析”的评论:

还没有评论