|
目录
一、异常处理机制 - setjmp和longjmp
二、异常处理机制 - 异常保护方法实现
三、异常处理机制 - 异常情况的处理
一、异常处理机制 - setjmp和longjmp
在讲解Lua的异常处理机制的时候,我们首先要看一下C语言的setjmp和longjmp的实现机制。
setjmp和longjmp分别承担非局部标号和goto作用。整体的逻辑如下:
使用setjmp,保存一个当前环境到jmp_buf跳转点,默认返回的值为0
程序继续执行,到某个地方调用longjmp,传入上面保存的jmp_buf,设置另外一个值,例如1
执行到longjmp函数的时候,会跳转到setjmp,并获取longjmp设置的新值
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
jmp_buf env;
int my_test(int a, int b) {
if (b == 0) {
printf("test 0\n");
longjmp(env, 1);
}
return a / b;
}
int main(int argc, char const *argv[]) {
int res = setjmp(env);
if (res == 0) { //执行my_func方法
printf("return from setjmp\n");
my_func(10, 0);
} else {//遇到longjmp,则进入该分支
printf("return from longjmp: %d\n", res);
}
return 0;
}
二、异常处理机制 - 异常保护方法实现
Lua的异常保护是通过一个luaD_rawrunprotected方法实现的。
函数最开始定义一个lua_longjmp结构体,用于保存当前执行环境,状态值设置为LUA_OK
然后调用LUAI_TRY函数,该函数实际是一个宏定义,将当前执行环境setjmp,并执行回调函数
如果回调函数执行内部,发生异常情况,则通过luaD_throw将异常抛出
异常抛出函数,会执行 LUAI_THROW函数,该函数是longjmp的宏定义,并且将返回值设置为1
由于执行了longjmp,则C语言内部方法会回到跳转点setjmp
LUAI_TRY函数判断setjmp的返回值,之前是0,现在由于longjmp设置了值为1,所以不会继续执行回调函数,回调函数被中断
#define LUAI_THROW(L,c) longjmp((c)->b, 1)
#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a }
/**
* 异常保护方法
* 通过回调Pfunc f,并用setjmp和longjpm方式,实现代码的中断并回到setjmp处
*
* #define LUAI_THROW(L,c) longjmp((c)->b, 1)
* #define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a }
*/
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
unsigned short oldnCcalls = L->nCcalls;
struct lua_longjmp lj;
lj.status = LUA_OK;
lj.previous = L->errorJmp; /* chain new error handler */
L->errorJmp = &lj;
/* if (setjmp((c)->b) == 0) { a } */
/* 当函数内部调用处理,遇到异常情况下,*/
LUAI_TRY(L, &lj,
(*f)(L, ud);
);
L->errorJmp = lj.previous; /* restore old error handler */
L->nCcalls = oldnCcalls;
return lj.status;
}
/**
* 抛出异常
* 其中L->errorJmp->status 状态值会设置为异常状态值
* LUAI_THROW longjmp((c)->b, 1)
* 通过longjmp跳转到 setjmp点
*/
l_noret luaD_throw (lua_State *L, int errcode) {
if (L->errorJmp) { /* thread has an error handler? */
L->errorJmp->status = errcode; /* set status */
LUAI_THROW(L, L->errorJmp); /* jump to it */
}
else { /* 没有error句柄,则直接中断处理 thread has no error handler */
global_State *g = G(L);
L->status = cast_byte(errcode); /* L->status=LUA_YIELD mark it as dead */
if (g->mainthread->errorJmp) { /* main thread has a handler? */
setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */
luaD_throw(g->mainthread, errcode); /* re-throw in main thread */
}
else { /* no handler at all; abort */
if (g->panic) { /* panic function? */
seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */
if (L->ci->top < L->top)
L->ci->top = L->top; /* pushing msg. can break this invariant */
lua_unlock(L);
g->panic(L); /* call panic function (last chance to jump out) */
}
abort();
}
}
}
三、异常处理机制 - 异常情况的处理
异常保护方法luaD_rawrunprotected主要在luaD_pcall中被调用(协程的yield也是通过该原理实现,下一章节讲解)。
该方法会返回执行的状态码,如果状态码非LUA_OK,则当前函数处理失败,CallInfo需要回滚到上一个ci
L->errorJmp是一个链表结构,通过L->errorJmp->errorJmp串联整个异常处理栈信息。
/**
* 函数调用主方法(异常保护方式)
* func:f_call方法
* u:CallS 调用的方法等信息
* old_top:函数调用前的栈顶 L->top
* ef:错误状态
*/
int luaD_pcall (lua_State *L, Pfunc func, void *u,
ptrdiff_t old_top, ptrdiff_t ef) {
int status;
CallInfo *old_ci = L->ci; //老的函数回调CallInfo
lu_byte old_allowhooks = L->allowhook; //是否允许钩子
unsigned short old_nny = L->nny;
ptrdiff_t old_errfunc = L->errfunc;
L->errfunc = ef;
status = luaD_rawrunprotected(L, func, u); //异常保护调用主函数
/* 处理失败,栈状态回滚? */
if (status != LUA_OK) { /* an error occurred? */
StkId oldtop = restorestack(L, old_top);
luaF_close(L, oldtop); /* close possible pending closures */
seterrorobj(L, status, oldtop);
L->ci = old_ci;
L->allowhook = old_allowhooks;
L->nny = old_nny;
luaD_shrinkstack(L);
}
L->errfunc = old_errfunc;
return status;
}
————————————————
版权声明:本文为CSDN博主「老码农zhuli」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/initphp/article/details/104282733
|
|