其中,boxed对象表示了复杂数据类型,如元组,大整数,二进制等。而立即数表示的就是一些简单的小数据,如小整数,原子,pid 等。
但这里为什么会有catch?相信不少人都会有这样的疑问。所以,本文就围绕着 catch 做说明。
catch表达式
形式:catch Expr
如果 Expr执行过程没有异常发生,就返回Expr的执行结果。但如果异常发生了,就会被捕获。
1> catch 1+2.
3
2> catch 1+a.
{'EXIT',{badarith,[...]}}
3> catch throw(hello).
hello
catch立即数怎么产生的?
这个数据类型只在 Erlang VM内部使用,Erlang 程序不会直接操作这个数据类型。当代码中含有 catch 或者 try-catch语句时就会产生catch,而且,这个数据将会被放置到Erlang进程的栈上。
源码分析
下面以一个简单做说明。
-module(test).
-compile(export_all).
t() ->
catch erlang:now().
|
保存为test.erl,通过erlc -S test.erl 得到汇编代码 test.S,内容如下:
{function, t, 0, 2}.
{label,1}.
{line,[{location,"test.erl",4}]}.
{func_info,{atom,test},{atom,t},0}.
{label,2}.
{allocate,1,0}.
{'catch',{y,0},{f,3}}.
{line,[{location,"test.erl",5}]}.
{call_ext,0,{extfunc,erlang,now,0}}.
{label,3}.
{catch_end,{y,0}}.
{deallocate,1}.
return.
|
这里可以看到 catch 和 catch_end 是成对出现的。下面再编译成opcode吧,容易到VM代码中分析。
1> c(test).
{ok,test}
2> erts_debug:df(test).
ok
|
找到生成 test.dis,内容如下:
04B55938: i_func_info_IaaI 0 test t 0
04B5594C: allocate_tt 1 0
04B55954:catch_yfy(0) f(0000871B)
04B55960: call_bif_e erlang:now/0
04B55968:catch_end_yy(0)
04B55970: deallocate_return_Q 1
|
以上, allocate_tt 和 deallocate_return_Q 在 beam_hot.h实现,其他在 beam_emu.c 实现,都可以找相关代码。
先看下这几个指令:
// beam_emu.c
OpCase(catch_yf):
c_p->catches++; // catches数量加1
yb(Arg(0)) = Arg(1); // 把catch指针地址存入进程栈,即f(0000871B)
Next(2); // 执行下一条指令
// beam_emu.c
OpCase(catch_end_y): {
c_p->catches--; // 进程 catches数减1
make_blank(yb(Arg(0))); // 将catch立即数的值置NIL,数据将会丢掉
if (is_non_value(r(0))) { // 如果异常出现
if (x(1) == am_throw) { // 如果是 throw(Term),返回 Term
r(0) = x(2);
} else {
if (x(1) == am_error) { // 如果是 error(Term), 再带上当前堆栈的信息
SWAPOUT;
x(2) = add_stacktrace(c_p, x(2), x(3));
SWAPIN;
}
/* only x(2) is included in the rootset here */
if (E - HTOP < 3 || c_p->mbuf) { /* Force GC in case add_stacktrace()
* created heap fragments */
// 检查进程堆空间不足,执行gc避免出现堆外数据
SWAPOUT;
PROCESS_MAIN_CHK_LOCKS(c_p);
FCALLS -= erts_garbage_collect(c_p, 3, reg+2, 1);
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
PROCESS_MAIN_CHK_LOCKS(c_p);
SWAPIN;
}
r(0) = TUPLE2(HTOP, am_EXIT, x(2));
HTOP += 3;
}
}
CHECK_TERM(r(0));
Next(1); // 执行下一条指令
}
//beam_hot.h
OpCase(deallocate_return_Q):
{
DeallocateReturn(Arg(0));//释放分配的栈空间,返回上一个CP指令地址(注:CP是返回地址指针)
}
DeallocateReturn实际是个宏,代码如下:#define DeallocateReturn(Deallocate) \
do { \
int words_to_pop = (Deallocate); \
SET_I((BeamInstr *) cp_val(*E)); \ // 解析当前栈的指令地址,即获取上一个CP指令地址
E = ADD_BYTE_OFFSET(E, words_to_pop); \
CHECK_TERM(r(0)); \
Goto(*I); \//执行的指令
} while (0)
到这里,应该有同学开始疑惑了。这里说的catch ,真是前面提到的 catch立即数吗?
谈到 catch 立即数,很多同学可以找到以下这两个宏:
// erl_term.h
#define make_catch(x) (((x) << _TAG_IMMED2_SIZE) | _TAG_IMMED2_CATCH) // 转成catch立即树
#define is_catch(x) (((x) & _TAG_IMMED2_MASK) == _TAG_IMMED2_CATCH) // 是否catch立即数
这两个是 catch立即数的生成和判定,后面的代码会提到这两个宏的使用。
现在,我们来看下VM解析和加载 catch 代码的过程:
// beam_load.c 加载beam过程,有删节
static void final_touch(LoaderState* stp)
{
int i;
int on_load = stp->on_load;
unsigned catches;
Uint index;
BeamInstr* code = stp->code;
Module* modp;
/*
* 申请catch索引,填补catch_yf指令
* 前面的f(0000871B)就在这里产生的,指向了beam_catches结构数据
* 因为一个catch立即数放不了整个beam_catches数据,就只放了指针
*/
index = stp->catches;
catches = BEAM_CATCHES_NIL;
while (index != 0) { //遍历所有的catch_yf指令
BeamInstr next = code[index];
code[index] = BeamOpCode(op_catch_yf); // 指向catch_yf指令的opcode地址
// 获取 catch_end 指令地址,构造beam_catches结构数据
catches = beam_catches_cons((BeamInstr *)code[index+2], catches);
code[index+2] = make_catch(catches); // 将beam_catches索引位置转成 catch立即数
index = next;
}
modp = erts_put_module(stp->module);
modp->curr.catches = catches;
/*
* ....
*/
}
再来看下什么时候会执行到 这里的代码。
细心的同学就会发现,VM中很多异常都会这样调用:// 执行匿名函数
OpCase(i_apply_fun): {
BeamInstr *next;
SWAPOUT;
next = apply_fun(c_p, r(0), x(1), reg);
SWAPIN;
if (next != NULL) {
r(0) = reg[0];
SET_CP(c_p, I+1);
SET_I(next);
Dispatchfun();
}
goto find_func_info; // 遇到错误走这里
}
// 数学运算错误,或检查错误就会走这里
lb_Cl_error: {
if (Arg(0) != 0) { // 如果带了 label地址,就执行 jump指令
OpCase(jump_f): { // 这里就是 jump实现代码
jump_f:
SET_I((BeamInstr *) Arg(0));
Goto(*I);
}
}
ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
goto find_func_info; // 遇到错误走这里
}
// 等待消息超时
OpCase(i_wait_error): {
c_p->freason = EXC_TIMEOUT_VALUE;
goto find_func_info; // 遇到错误走这里
}
好了,再看下find_func_info 究竟是什么神通?
/* Fall through here */
find_func_info: {
reg[0] = r(0);
SWAPOUT;
I = handle_error(c_p, I, reg, NULL); // 获取异常错误指令地址
goto post_error_handling;
}
post_error_handling:
if (I == 0) { // 等待下次调度 erl_exit(),抛出异常中断
goto do_schedule;
} else {
r(0) = reg[0];
ASSERT(!is_value(r(0)));
if (c_p->mbuf) { // 存在堆外消息数据,执行gc
erts_garbage_collect(c_p, 0, reg+1, 3);
}
SWAPIN;
Goto(*I); // 执行指令
}
}
然后,简单看下handle_error函数。// erl_emu.c VM处理异常函数
static BeamInstr* handle_error(Process* c_p, BeamInstr* pc, Eterm* reg, BifFunction bf)
{
Eterm* hp;
Eterm Value = c_p->fvalue;
Eterm Args = am_true;
c_p->i = pc; /* In case we call erl_exit(). */
ASSERT(c_p->freason != TRAP); /* Should have been handled earlier. */
/*
* Check if we have an arglist for the top level call. If so, this
* is encoded in Value, so we have to dig out the real Value as well
* as the Arglist.
*/
if (c_p->freason & EXF_ARGLIST) {
Eterm* tp;
ASSERT(is_tuple(Value));
tp = tuple_val(Value);
Value = tp[1];
Args = tp[2];
}
/*
* Save the stack trace info if the EXF_SAVETRACE flag is set. The
* main reason for doing this separately is to allow throws to later
* become promoted to errors without losing the original stack
* trace, even if they have passed through one or more catch and
* rethrow. It also makes the creation of symbolic stack traces much
* more modular.
*/
if (c_p->freason & EXF_SAVETRACE) {
save_stacktrace(c_p, pc, reg, bf, Args);
}
/*
* Throws that are not caught are turned into 'nocatch' errors
*/
if ((c_p->freason & EXF_THROWN) && (c_p->catches <= 0) ) {
hp = HAlloc(c_p, 3);
Value = TUPLE2(hp, am_nocatch, Value);
c_p->freason = EXC_ERROR;
}
/* Get the fully expanded error term */
Value = expand_error_value(c_p, c_p->freason, Value);
/* Save final error term and stabilize the exception flags so no
further expansion is done. */
c_p->fvalue = Value;
c_p->freason = PRIMARY_EXCEPTION(c_p->freason);
/* Find a handler or die */
if ((c_p->catches > 0 || IS_TRACED_FL(c_p, F_EXCEPTION_TRACE))
&& !(c_p->freason & EXF_PANIC)) {
BeamInstr *new_pc;
/* The Beam handler code (catch_end or try_end) checks reg[0]
for THE_NON_VALUE to see if the previous code finished
abnormally. If so, reg[1], reg[2] and reg[3] should hold the
exception class, term and trace, respectively. (If the
handler is just a trap to native code, these registers will
be ignored.) */
reg[0] = THE_NON_VALUE;
reg[1] = exception_tag[GET_EXC_CLASS(c_p->freason)];
reg[2] = Value;
reg[3] = c_p->ftrace;
if ((new_pc = next_catch(c_p, reg))) { // 从进程栈上找到最近的 catch
c_p->cp = 0; /* To avoid keeping stale references. */
return new_pc; // 返回 catch end 指令地址
}
if (c_p->catches > 0) erl_exit(1, "Catch not found");
}
ERTS_SMP_UNREQ_PROC_MAIN_LOCK(c_p);
terminate_proc(c_p, Value);
ERTS_SMP_REQ_PROC_MAIN_LOCK(c_p);
return NULL; // 返回0,就是执行 erl_exit()
}
问题讨论
为什么catch要放在进程栈,然后利用立即数实现。
1、异常中断处理
erlang本身就有速错原则,发生错误就会抛出异常,并kill掉进程。如果需要捕获异常,并获取中断处的结果,就要记录中断时要返回的地址。
2、catch多层嵌套
因为catch允许多层嵌套结构,catch里面的函数代码还可以继续再catch,就无法用一个简单类型的变量表示。这需要一种数组或链表结构来表示catch层级的关系链。
问题延伸
进程堆与进程栈
至于catch实现为什么是进程栈,而不是进程堆,或者作为VM调度线程的变量?
首先,erlang VM的基本调度单位是erlang进程。如果执行某段代码,就要有运行erlang进程来执行。为什么我们可以在shell下肆无忌惮地运行代码,实际上我们看到的是由shell实现进程执行后返回给我们的结果。
而erlang进程执行代码过程中产生的大多数数据会放到进程的堆栈上(ets,binary,atom除外),而进程栈和进程堆是什么样的对应关系?
实际上,erlang进程的栈和堆在VM底层实现上都是在OS进程/线程的堆上,因为OS提供的栈空间实在有限。
这里,低位地址表示了堆底,高位地址表示了栈底。中间堆顶和栈顶的空白区域,表示了进程堆栈还未使用到的空间,使用内存时就向里收缩,不够时就执行gc
而erlang中,进程栈和进程堆的区别是栈只放入了简单的数据,如果是复杂数据,就只放头部(即前面最开始谈到的列表,boxed对象),然后把实际的数据放到堆中。
这里,尽管VM会把复杂数据存入erlang进程的堆上,但在栈上都保持了引用(或指针),但是,堆数据不会有引用(或指针)指向了栈。这么做,是为了减少GC的代价。这样,GC时只要扫描结构较小的栈就可以,不用扫描整个堆栈。而进程字典写操作,就保留引用指向进程堆(暂时不讨论堆外数据的情况)
而这里,catch实际表达的数据对象是一个beam_catch_t 结构,最少最多也只能一个立即数表示,然后指向索引或指针位置。而且,catch与进程运行上下文代码有关,允许多层嵌套,处理异常中断,如果像寄存器一样,作为VM调度线程的变量,将会引入更加复杂的设计
try-catch尾递归
try-catch语法结构内无法构成尾递归
t() ->
try
do_something(),
t()
catch
_:_ -> ok
end.
|
erlang编译时会生成 try 和 try_end 指令,而这里 t() 实际就只是执行一次本地函数,不能构成尾递归。这点很好解释,感兴趣的同学可以打印汇编码探寻这个问题。同样的问题,catch也存在。
erlang:hibernate
调用这个函数会使当前进程进入wait状态,同时减少其内存开销。适用场合是进程短时间不会收到任何消息,如果频繁收到消息就不适合了,否则频繁内存清理操作,也是不少开销。
但是,使用 erlang:hibernate 将会导致 catch或者try-catch 语句失效。
通过前面的内容可以知道,try catch 会往栈里面都压入了出错时候的返回地址,而 erlang:hibernate则会清空栈数据,将会导致try-catch失效。
解决办法如下,参考proc_lib:hibernate的实现:
hibernate(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
erlang:hibernate(?MODULE, wake_up, [M, F, A]).
wake_up(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
try
apply(M, F, A)
catch
_Class:Reason ->exit(Reason)
end.
|
最后语
很早之前就开始写这篇文章,但现阶段比较忙,不巧又遇上一点小风寒,所以文章写得仓促,标题打了“初稿”字样,希望能够见谅。但也不该成为写错内容的借口,如果你有看到错误,请提出批评指正,我看到就会改正,谢谢支持。
2015/4/3 修改源码分析中deallocate_return_Q的说明
参考:http://blog.csdn.net/mycwq/article/details/44661219
相关推荐
电子商务之价格优化算法:梯度下降:机器学习在价格优化中的角色.docx
ToadforOracle与Oracle数据库版本兼容性教程.docx
360浏览器银河麒麟版 for X86 适配兆芯 / 海光 / intel / AMD CPU
使用React.js构建,提供多种主题可供选择,并且易于定制。该项目旨在帮助开发者和自由职业者创建自己的个性化投资组合。 主要功能点 多种主题可供选择,包括绿色、黑白、蓝色、红色、橙色、紫色、粉色和黄色 易于定制,可以在src/data文件夹中更新个人信息 包含主页、关于、简历、教育、技能、经验、项目、成就、服务、推荐信、博客和联系等多个部分 支持通过Google表单收集联系信息 提供SEO优化建议 支持多种部署方式,如Netlify、Firebase、Heroku和GitHub Pages 技术栈主要 React.js Material-UI Axios React-fast-marquee React-helmet React-icons React-reveal React-router-dom React-router-hash-link React-slick Slick-carousel Validator
中小型企业财务管理系统 SSM毕业设计 附带论文 启动教程:https://www.bilibili.com/video/BV1GK1iYyE2B
python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。
电子商务之价格优化算法:线性回归:价格优化策略实施.docx
内容概要:报告详细介绍了企业数字化转型的驱动因素、数字化转型方案分类及其应用场景,重点关注了云计算、超连接、数字孪生、人工智能、分布式账本、增材制造、人机接口、数据共享、工业物联网等关键技术。这些技术不仅支持了企业的运营效率提升和业务模式创新,也为实现更快、更开放、更高效的数字化转型提供了支撑。报告最后提出了企业实施数字化转型的六个步骤。 适合人群:企业高级管理人员、技术人员、咨询顾问,以及对工业数字化转型感兴趣的读者。 使用场景及目标:帮助企业制定和实施数字化转型策略,优化运营模式,提升业务效率,增强市场竞争力。同时,也可作为政府部门、研究机构和行业协会的参考文献。 其他说明:报告中提到的关键技术及其应用场景对企业数字化转型具有重要的指导意义,特别是对于那些希望通过数字化转型实现业务创新和升级的企业。
基于java的线上选课系统的设计与实现答辩PPT.pptx
安装前的准备 1、安装Python:确保你的计算机上已经安装了Python。你可以在命令行中输入python --version或python3 --version来检查是否已安装以及安装的版本。 个人建议:在anaconda中自建不同python版本的环境,方法如下(其他版本照葫芦画瓢): 比如创建python3.8环境,anaconda命令终端输入:conda create -n py38 python==3.8 2、安装pip:pip是Python的包管理工具,用于安装和管理Python包。你可以通过输入pip --version或pip3 --version来检查pip是否已安装。 安装WHL安装包 1、打开命令行(或打开anaconda命令行终端): 在Windows上,你可以搜索“cmd”或“命令提示符”并打开它。 在macOS或Linux上,你可以打开“终端”。 2、cd到whl文件所在目录安装: 使用cd命令导航到你下载的whl文件所在的文件夹。 终端输入:pip install xxx.whl安装即可(xxx.whl指的是csdn下载解压出来的whl) 3、等待安装完成: 命令行会显示安装进度,并在安装完成后返回提示符。 以上是简单安装介绍,小白也能会,简单好用,从此再也不怕下载安装超时问题。 使用过程遇到问题可以私信,我可以帮你解决! 收起
电子商务之价格优化算法:贝叶斯定价:贝叶斯网络在电子商务定价中的应用.docx
IMG_20241105_235746.jpg
基于java的毕业设计选题系统答辩PPT.pptx
专升本考试资料全套.7z
Trustwave DbProtect:数据库活动监控策略制定.docx
基于VB的程序实例,可供参考学习使用
本压缩包资源说明,你现在往下拉可以看到压缩包内容目录 我是批量上传的基于SpringBoot+Vue的项目,所以描述都一样;有源码有数据库脚本,系统都是测试过可运行的,看文件名即可区分项目~ |Java|SpringBoot|Vue|前后端分离| 开发语言:Java 框架:SpringBoot,Vue JDK版本:JDK1.8 数据库:MySQL 5.7+(推荐5.7,8.0也可以) 数据库工具:Navicat 开发软件: idea/eclipse(推荐idea) Maven包:Maven3.3.9+ 系统环境:Windows/Mac
该源码项目是一款基于Thinkphp5框架的Java插件设计,包含114个文件,其中Java源文件60个,PNG图片32个,XML配置文件7个,GIF图片7个,Git忽略文件1个,LICENSE文件1个,Markdown文件1个,Xmind文件1个,Idea项目文件1个,以及JAR文件1个。
数据库开发和管理最佳实践.pdf
本压缩包资源说明,你现在往下拉可以看到压缩包内容目录 我是批量上传的基于SpringBoot+Vue的项目,所以描述都一样;有源码有数据库脚本,系统都是测试过可运行的,看文件名即可区分项目~ |Java|SpringBoot|Vue|前后端分离| 开发语言:Java 框架:SpringBoot,Vue JDK版本:JDK1.8 数据库:MySQL 5.7+(推荐5.7,8.0也可以) 数据库工具:Navicat 开发软件: idea/eclipse(推荐idea) Maven包:Maven3.3.9+ 系统环境:Windows/Mac