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服务作为后台运行
版权归原作者 garagong 所有, 如有侵权,请联系我们删除。