lua 源碼分析之線程對象lua_State

lua_State 中放的是 lua 虛擬機中的環境表、註冊表、運行堆棧、虛擬機的上下文等數據。api

 從一個主線程(特指 lua 虛擬機中的線程,即 coroutine)中建立出來的新的 lua_State 會共享大部分數據,但會擁有一個獨立的運行堆棧。因此一個線程對象擁有一個lua_State。數組

(ps:lua 的coroutine的使用參考: http://blog.csdn.NET/wusheng520/article/details/7954666)數據結構

lua_State共享的數據部分是全局狀態機(包含GC的數據).lua_State 的運行堆棧爲調用棧,lua_State 的數據棧包含當前調用棧信息。函數

 

一、lua_state線程對象
(1)lua_state的上下文數據
從 C 層面看待 lua ,lua 的虛擬機對象就是一個lua_state 。 但實際上,真正的 lua虛擬機對象被隱藏起來了。那就是 lstate.h 中定義的結構體  global_State。同一 lua 虛擬機中的全部執行線程,共享了一塊全局數據 global_state 。
lua_state 是暴露給用戶的數據類型,既表示一個 lua 程序的執行狀態,也指代 lua 的一個線程(在官方文檔中)。每一個線程擁有獨立的數據棧以及函數調用棧,還有獨立的調試鉤子和錯誤處理設置。因此咱們不該當簡單的把lua_state 當作一個靜態的數據集,它是一個lua 線程的執行狀態。全部的lua C API 都是圍繞這個狀態機:
或把數據壓入堆棧,或取出,或執行棧頂的函數,或繼續上次被中斷的執行過程。優化

struct lua_State {
  CommonHeader;
  lu_byte status;
  StkId top;  /* first free slot in the stack */
  StkId base;  /* base of current function */
  global_State *l_G;
  CallInfo *ci;  /* call info for current function */
  const Instruction *savedpc;  /* `savedpc' of current function */
  StkId stack_last;  /* last free slot in the stack */
  StkId stack;  /* stack base */
  CallInfo *end_ci;  /* points after end of ci array*/
  CallInfo *base_ci;  /* array of CallInfo's */
  int stacksize;
  int size_ci;  /* size of array `base_ci' */
  unsigned short nCcalls;  /* number of nested C calls */
  unsigned short baseCcalls;  /* nested C calls when resuming coroutine */
  lu_byte hookmask;
  lu_byte allowhook;
  int basehookcount;
  int hookcount;
  lua_Hook hook;
  TValue l_gt;  /* table of globals */
  TValue env;  /* temporary place for environments */
  GCObject *openupval;  /* list of open upvalues in this stack */
  GCObject *gclist;
  struct lua_longjmp *errorJmp;  /* current error recover point */
  ptrdiff_t errfunc;  /* current error handling function (stack index) */
};

lua_State是圍繞程序如何執行來設計的,數據棧和調用棧都在其中。
其中:this

/* 
** Common Header for all collectable objects (in macro form, to be 
** included in other objects) 
*/  
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked  

全部可回收的對象都有這個結構

(2)全局狀態機
共享的一塊全局數據 global_stateatom

/* 
** `global state', shared by all threads of this state 
*/  
typedef struct global_State {  
  stringtable strt;  /* hash table for strings */  
  lua_Alloc frealloc;  /* function to reallocate memory */  
  void *ud;         /* auxiliary data to `frealloc' */  
  lu_byte currentwhite;  
  lu_byte gcstate;  /* state of garbage collector */  
  int sweepstrgc;  /* position of sweep in `strt' */  
  GCObject *rootgc;  /* list of all collectable objects */  
  GCObject **sweepgc;  /* position of sweep in `rootgc' */  
  GCObject *gray;  /* list of gray objects */  
  GCObject *grayagain;  /* list of objects to be traversed atomically */  
  GCObject *weak;  /* list of weak tables (to be cleared) */  
  GCObject *tmudata;  /* last element of list of userdata to be GC */  
  Mbuffer buff;  /* temporary buffer for string concatentation */  
  lu_mem GCthreshold;  
  lu_mem totalbytes;  /* number of bytes currently allocated */  
  lu_mem estimate;  /* an estimate of number of bytes actually in use */  
  lu_mem gcdept;  /* how much GC is `behind schedule' */  
  int gcpause;  /* size of pause between successive GCs */  
  int gcstepmul;  /* GC `granularity' */  
  lua_CFunction panic;  /* to be called in unprotected errors */  
  TValue l_registry;  
  struct lua_State *mainthread;  
  UpVal uvhead;  /* head of double-linked list of all open upvalues */  
  struct Table *mt[NUM_TAGS];  /* metatables for basic types */  
  TString *tmname[TM_N];  /* array with tag-method names */  
} global_State;  

從 lua 的使用者的角度看,global_state 是不可見的。咱們沒法用公開的 api 取到它的指針,也不須要引用它。但分析lua 的實現就不能繞開這個部分。
global_state  裏面有對主線程的引用,有註冊表管理全部全局數據,有全局字符串表,有內存管理函數,有GC 須要的把全部對象串聯起來的相關信息,以及一切 lua 在工做時須要的工做內存。
經過 lua_newstate 建立一個新的 lua 虛擬機時,第一塊申請的內存將用來保存主線程和這個全局狀態機。lua 的實現儘量的避免內存碎片,同時也減小內存分配和釋放的次數。它採用了一個小技巧,利用一個 LG 結構,把主線程 lua_state 和 global_state 分配在一塊兒。lua

lua_newstate 這個公開 API  定義在 lstate.c中,它初始化了全部 global_state 中將引用的數據。spa

LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {  
  int i;  
  lua_State *L;  
  global_State *g;  
  void *l = (*f)(ud, NULL, 0, state_size(LG));  
  if (l == NULL) return NULL;  
  L = tostate(l);  
  g = &((LG *)L)->g;  
  L->next = NULL;  
  L->tt = LUA_TTHREAD;  
  g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT);  
  L->marked = luaC_white(g);  
  set2bits(L->marked, FIXEDBIT, SFIXEDBIT);  
  preinit_state(L, g);  
  g->frealloc = f;  
  g->ud = ud;  
  g->mainthread = L;  
  g->uvhead.u.l.prev = &g->uvhead;  
  g->uvhead.u.l.next = &g->uvhead;  
  g->GCthreshold = 0;  /* mark it as unfinished state */  
  g->strt.size = 0;  
  g->strt.nuse = 0;  
  g->strt.hash = NULL;  
  setnilvalue(registry(L));  
  luaZ_initbuffer(L, &g->buff);  
  g->panic = NULL;  
  g->gcstate = GCSpause;  
  g->rootgc = obj2gco(L);  
  g->sweepstrgc = 0;  
  g->sweepgc = &g->rootgc;  
  g->gray = NULL;  
  g->grayagain = NULL;  
  g->weak = NULL;  
  g->tmudata = NULL;  
  g->totalbytes = sizeof(LG);  
  g->gcpause = LUAI_GCPAUSE;  
  g->gcstepmul = LUAI_GCMUL;  
  g->gcdept = 0;  
  for (i=0; i<NUM_TAGS; i++) g->mt[i] = NULL;  
  if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) {  
    /* memory allocation error: free partial state */  
    close_state(L);  
    L = NULL;  
  }  
  else  
    luai_userstateopen(L);  
  return L;  
} 

二、執行狀態機的數據棧和調用棧
如下是分析 lua_state 中重要的兩個數據結構:數據棧和調用棧。
(1) 數據棧
lua 中的數據能夠這樣分爲兩類:值類型和引用類型。值類型能夠被任意複製,而引用類型共享一份數據,由 GC 負責維護生命期。lua 使用一個聯合 union Value 來保存數據.net

/* 
** Union of all Lua values 
*/  
typedef union {  
  GCObject *gc;  
  void *p;  
  lua_Number n;  
  int b;  
} Value;  

     從這裏咱們能夠看到,引用類型用一個指針 GCObject *gc 來間接引用,而其它值類型都直接保存在聯合中。爲了區分聯合中存放的數據類型,再額外綁定一個類型字段。

#define TValuefields Value value; int tt  
  
typedef struct lua_TValue {  
  TValuefields;  
} TValue;  

lua_state 的數據棧,就是一個 TValue 的數組。代碼中用 StkId 類型來指代對 TValue 的引用。

typedef TValue *StkId; /* index to stack elements */  

在 lstate.c 中,咱們能夠讀到對堆棧的初始化及釋放的代碼。

static void stack_init (lua_State *L1, lua_State *L) {  
  /* initialize CallInfo array */  
  L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo);//lua的調用棧  
  L1->ci = L1->base_ci;  
  L1->size_ci = BASIC_CI_SIZE;  
  L1->end_ci = L1->base_ci + L1->size_ci - 1;  
  /* initialize stack array */  
  L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue);//lua的數據棧  
  L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK;  
  L1->top = L1->stack;  
  L1->stack_last = L1->stack+(L1->stacksize - EXTRA_STACK)-1;  
  /* initialize first ci */  
  L1->ci->func = L1->top;//當前調用棧 的函數初始化爲當前數據棧頂  
  setnilvalue(L1->top++);  /* `function' entry for this `ci' */  
  L1->base = L1->ci->base = L1->top;  
  L1->ci->top = L1->top + LUA_MINSTACK;  
}  
static void freestack (lua_State *L, lua_State *L1) {  
  luaM_freearray(L, L1->base_ci, L1->size_ci, CallInfo);  
  luaM_freearray(L, L1->stack, L1->stacksize, TValue);  
}  

一開始,數據棧的空間頗有限,只有 2 倍的 LUA_MINSTACK 的大小.

# define BASIC_STACK_SIZE (2*LUA_MINSTACK)  
#define LUA_MINSTACK 20 

數據棧不夠用的時候,能夠擴展。這種擴展是用 realloc 實現的,每次至少分配比原來大一倍的空間,並把舊的數據複製到新空間。

void luaD_growstack (lua_State *L, int n) {  
  if (n <= L->stacksize)  /* double size is enough? */  
    luaD_reallocstack(L, 2*L->stacksize);  
  else  
    luaD_reallocstack(L, L->stacksize + n);  
}  
void luaD_reallocstack (lua_State *L, int newsize) {  
  TValue *oldstack = L->stack;  
  int realsize = newsize + 1 + EXTRA_STACK;  
  lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1);  
  luaM_reallocvector(L, L->stack, L->stacksize, realsize, TValue);  
  L->stacksize = realsize;  
  L->stack_last = L->stack+newsize;  
  correctstack(L, oldstack);  
}  

數據棧擴展的過程,伴隨着數據拷貝。這些數據都是能夠直接值複製的,因此不須要在擴展以後修正其中的指針。 

但,有些外部結構對數據棧的引用須要修正爲正確的新地址。 這些須要修正的位置包括 upvalue以及調用棧對數據棧的引用。這個過程由 correctstack 函數實現。

static void correctstack (lua_State *L, TValue *oldstack) {  
  CallInfo *ci;  
  GCObject *up;  
  L->top = (L->top - oldstack) + L->stack;  
  for (up = L->openupval; up != NULL; up = up->gch.next)//修正upvalue  
    gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack;  
  for (ci = L->base_ci; ci <= L->ci; ci++) {//拷貝當前調用棧(修正調用棧)  
    ci->top = (ci->top - oldstack) + L->stack;  
    ci->base = (ci->base - oldstack) + L->stack;  
    ci->func = (ci->func - oldstack) + L->stack;  
  }  
  L->base = (L->base - oldstack) + L->stack;//拷貝當前函數(修正當前函數)  
}  

(2)調用棧
lua 把調用棧和數據棧分開保存。調用棧放在一個叫作 CallInfo 的結構中,以數組的形式儲存在虛擬機對象(線程對象)裏。

/* 
** informations about a call 
*/  
typedef struct CallInfo {  
  StkId base;  /* base for this function */  
  StkId func;  /* function index in the stack */  
  StkId top;  /* top for this function */  
  const Instruction *savedpc;  
  int nresults;  /* expected number of results from this function */  
  int tailcalls;  /* number of tail calls lost under this entry */  
} CallInfo;  

正在調用的函數必定存在於數據棧上,在 CallInfo 結構中,func 指向正在執行的函數在數據棧上的位置。須要記錄這個信息,是由於若是當前是一個 lua 函數,且傳入的參數個數不定的時候,須要用這個位置和當前數據棧底的位置相減,得到不定參數的準確數量.。(參考http://blog.csdn.net/chenjiayi_yun/article/details/8877235 lua虛擬機的體系結構)

同時,func 還能夠幫助咱們調試嵌入式 lua 代碼。在用 gdb這樣的調試器調試代碼時,能夠方便的查看 C 中的調用棧信息,但一旦嵌入 lua ,咱們很難理解運行過程當中的 lua 代碼的調用棧。不理解 lua 的內部結構,就可能面對一個簡單的lua_state L 變量一籌莫展。

經過訪問 func 引用的函數對象來了解函數是 C 函數仍是 Lua 函數。


實際上,遍歷 L 中的 base_ci域指向的 CallInfo 數組能夠得到完整的 lua 調用棧。而每一級的 CallInfo 中,均可以進一步的經過 func 域取得所在函數的更詳細信息。當 func 爲一個 lua 函數時,根據它的函數原型能夠得到源文件名、行號等諸多調試信息。

CallInfo 是一個標準的數組結構(lua5.1.4),壓棧時須要從新分配棧數組的內存。這個數組表達的是一個邏輯上的棧。

#define inc_ci(L) \  
  ((L->ci == L->end_ci) ? growCI(L) : \  
   (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci))  

特別的,lua5.2之後進行了優化,調用棧修改爲 雙向鏈表,不直接被GC 模塊管理,在運行過程當中,並非每次調入更深層次的函數,就馬上構造出一個CallInfo 節點。整個調用棧 CallInfo鏈表會在運行中被反覆複用。直到 GC 的時候才清理那些比當前調用層次更深的無用節點。

相關文章
相關標籤/搜索