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 | 服务作为后台运行 |
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!