risc-v中文社区

 找回密码
 立即注册
查看: 1317|回复: 0

Lua源码分析 - 主流程篇 - 异常处理机制实现(09)

[复制链接]

347

主题

564

帖子

2237

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2237
发表于 2022-6-20 15:47:25 | 显示全部楼层 |阅读模式
目录

一、异常处理机制 - 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

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



Archiver|手机版|小黑屋|risc-v中文社区

GMT+8, 2024-4-26 19:21 , Processed in 0.014551 second(s), 17 queries .

risc-v中文社区论坛 官方网站

Copyright © 2018-2021, risc-v open source

快速回复 返回顶部 返回列表