国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

KVM的執行引擎(上) — 棧和幀

2019-11-18 16:25:55
字體:
來源:轉載
供稿:網友

接下來的兩篇將介紹在KVM中字節是如何執行的,這是KVM中比較核心的內容,分為兩部分來講,本篇先介紹虛擬機中的棧和幀是如何實現的。

 

首先來看一些全局指針,在頭文件kvm/vmcommon/h/interPRet.h中定義有以下結構:

Word-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
strUCt GlobalStateStruct { 
    BYTE*         gs_ip; /* Instruction pointer (program counter) */
    cell*         gs_sp; /* Execution stack pointer */
    cell*         gs_lp; /* Local variable pointer */
    FRAME         gs_fp; /* Current frame pointer */
    CONSTANTPOOL  gs_cp; /* Constant pool pointer */
};

這五個變量就像CPU中的寄存器一樣,在KVM的運行過程中起到非常基礎性的作用。它們分別是程序計數器、執行棧指針、局部變量指針、當前幀指針和當前常量池指針。

java虛擬機為每一個線程開設一個棧,棧中存儲的數據以“幀”為單位,虛擬機在調用一個新的方法時,會向棧中壓入一個新幀,幀內數據是這個方法的運行狀態,Java字節碼的執行總是在當前幀內進行,方法運行結束時這個幀會被彈出。所以這個棧可以稱為“方法棧”,幀可以稱為“方法幀”。

按照Java虛擬機的規范,一個幀應由三個部分組成:局部變量區,操作數棧和幀數據區。每個幀的局部變量區和操作數棧的大小都可能不一樣,要依方法本身的龐大程度而定,但在調用一個方法時,可以根據這個方法的字節碼計算出所需要的局部變量區和操作數棧的大小。規范對幀數據區的大小沒有規定,幀數據區的大小和內容可由虛擬機實現來決定。

局部變量區:

局部變量區一般會位于幀中最前面(即地址最小)的位置,它包含了對應方法的參數和局部變量,一般情況下,它的大小是向4字節對齊的,每4字節是一個“字”,變量以“字”為單位來存入。在它的最前面順序存放的是對應方法的參數,類型為int、float、reference和returnAddress的參數占一個“字”,類型為byte、short和char類型的參數對被轉化為int型,所以也占一個字;long和double類型的值要占用兩個字。當然,“字長”選為多少是由虛擬機實現自己來決定的,不是一定要選4字節為一個字,如果選8字節構成一個字的話,所有值都只占一個字,更加整齊,但是浪費了很多空間。

如果方法不是靜態的,那么虛擬機會自動將方法所在對象的句柄存在局部變量區中索引為0的位置,真正的參數從位置1開始存;而如果方法是靜態的,它就與具體的對象沒有關系,所以不必存放對象句柄,參數從位置0開始存放。

在局部變量區接下來的空間中,虛擬機可以按照任意的方式來存貯方法內的局部變量。

操作數棧:

操作數棧的作用相當于CPU中的通用寄存器,由于Java虛擬機是一臺虛擬的機器,它沒有真正的寄存器,而Java虛擬機也沒有選擇與CPU相似的方式來模擬通用寄存器,而是選擇了另一種方法 — 使用棧,Java指令所使用的操作數都從操作數棧中得到。

某方法在被調用的時候,同樣可計算出它需要多大的操作數棧,所以在一個幀中,操作數棧的大小也是固定,而它的位置可以由實現來決定,不過在接下來KVM的實例中我們會發現,把操作數棧放在幀的最后面(地址最大)的地方是一個好辦法。

幀數據區:

幀數據是由虛擬機實現任意設計的,通常它都被用來實現常量池解析和異常處理等等。

 

 

下面來看一看,在KVM中如何實現棧和幀。

數據結構:

在頭文件kvm/vmcommon/h/frame.h中定義了棧和幀的結構:

/* STACK */
struct stackStruct {
    STACK    next;
    short    size;
    short    xxunusedxx; /* must be multiple of 4 on all platforms */
    cell     cells[STACKCHUNKSIZE];
};
typedef struct stackStruct*         STACK;
 

每一個stackStruct結構體的變量就是一個Java棧或Java棧的一部分,因為每一個stackStruct結構的大小是固定的,如果不夠用,可以得用next指針來擴展成鏈表。size是本結構體的大小,xxunusedxx是剩余空間,cells則是實際的存貯空間。

每一個線程開始的時候都會生成一個新的stackStruct,在每一次壓入新幀的時候會查看剩余空間是否夠用,如果不夠用,還會再生成新的stackStruct。

frameStruct這個結構的大小是固定的,它并不是一個幀,而只是“幀數據區”,前面說過,由于局部變量區和操作數棧的大小都不固定,所以整個幀的大小也是不固定的。幀的空間是在調用方法的時候臨時計算出來的,然后在當前線程的棧中申請,frameStruct結構的指針占據其中的一個字,其余空間都給局部變量區和操作數棧用。

KVM中棧和幀的模型如下:(為理解方便,暫不考慮棧要擴展的情況)

當棧中只有一個幀時,棧的結構如圖所示:

 KVM的執行引擎(上) — 棧和幀(圖一)

當棧中只有一個幀時,幀的低字節區是局部變量,接下來會有一個字(4字節)指向幀數據區結構體,再接下來的空間就是操作數棧。

只有一幀時,幀中各部分的結構很明晰,但如果多于一幀時,情況就會有些復雜,下面看當再壓入一幀時的圖示:

KVM的執行引擎(上) — 棧和幀(圖二)

這個圖或許跟想像中的不一樣,兩幀數據之間出現了重疊。圖中畫出了一條虛線,這條虛線的位置是上一幀結束的位置,但是卻沒有成為新的一幀開始的位置,新的一幀在這之前就開始了。重疊的區域究竟是什么,可以讓兩幀共用呢?

當一個方法在執行時,如果一個指令需要參數,解釋器會到操作數棧里去裝載參數,如果這時的指令是調用一個方法的話(比如invokevirtual或invokestatic),待調用方法的參數應已經順序存在于操作數棧中,在執行調用指令的時候,這些參數被彈出,成為調用指令的參數,由于操作數棧在幀的最后面,所以這些參數后面再沒有本幀的有效數據。這些參數在當前幀的操作數棧中的排列順序與在新幀的局部變量區中的排列順序是一樣的,而且在新幀中,局部變量區在新幀的最前面,參數列表又在局部變量區的最前面,所以這部分數據是可以重用的,不會丟失有用的信息。

 

程序實現:

壓入幀和彈出幀的函數在源文件kvm/vmcommon/src/frame.c中:

void pushFrame(METHOD thisMethod);

void popFrame();

pushFrame()函數的一些關鍵代碼如下:

1    int thisFrameSize = thisMethod->frameSize;
2    int thisArgCount = thisMethod->argCount;
3    int thisLocalCount = thisFrameSize - thisArgCount;
4    …
5    cell* prev_sp = getSP() - thisArgCount; /* Very volatile! */
6    …
7    newFrame = (FRAME)(getSP() + thisLocalCount + 1);
8    …
9    /* Initialize info needed for popping the stack frame later on */
10    newFrame->previousSp = prev_sp;
11    newFrame->previousIp = getIP();
12    newFrame->previousFp = getFP();
13    …
14    /* Initialize the frame to execute the given method */
15    newFrame->thisMethod = thisMethod;
16    newFrame->syncObject = NIL; /* Initialized later if necessary */
17    …
18    /* Change virtual machine registers to execute the new method */
19    setFP(newFrame);
20    setSP((cell*)(newFrame + 1) - 1);
21    setIP(thisMethod->u.java.code);
22    setCP(thisMethod->ofClass->constPool);
23    ...

L1-L3分別讀出幀的大小、參數列表的大小和本幀實際申請空間的大小(從幀中減去與上一幀復用的部分);

sp是當前棧內的指針,也是操作數的指針,在新的一幀壓入之前,sp應指向操作數棧中最后一個參數的位置,所以L5中prev_sp所取得的是上一幀中函數參數列表的首地址,也就是新幀開始的位置,以后新方法返回的時候,新幀被彈出,這里應是操作數棧的當前位置,也就是sp的位置,函數的返回值要存放到這里;

L7為新幀申請了空間;

L10-L12為保存調用之前的寄存器狀態;

L19-L22為寄存器賦新值。

popFrame()函數比較簡單,主要就是調用了下面這個宏來恢復調用前寄存器的值:

#define POPFRAMEMACRO                                                          
    setSP(getFP()->previousSp);    /* Restore previous stack pointer */        
    setIP(getFP()->previousIp);    /* Restore previous instruction pointer */  
    setFP(getFP()->previousFp);    /* Restore previous frame pointer */        
    setLP(FRAMELOCALS(getFP()));   /* Restore previous locals pointer */       
    setCP(getFP()->thisMethod->ofClass->constPool);


/* FRAME (allocated inside execution stacks of threads) */
struct frameStruct {
    FRAME    previousFp; /* Stores the previous frame pointer */
    BYTE*    previousIp; /* Stores the previous program counter */
    cell*    previousSp; /* Stores the previous stack pointer */
    METHOD   thisMethod; /* Pointer to the method currently under execution */
    STACK    stack;      /* Stack chunk containing the frame */
    OBJECT   syncObject; /* Holds monitor object if synchronized method call */
};
typedef struct frameStruct*            FRAME;
進入討論組討論。

(出處:http://m.survivalescaperooms.com)



發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 富蕴县| 平果县| 赣州市| 应城市| 宿州市| 白沙| 正宁县| 武陟县| 梁河县| 临沂市| 岳阳县| 普兰县| 卓尼县| 清涧县| 丰镇市| 山东省| 安吉县| 全州县| 来凤县| 无锡市| 徐水县| 海南省| 垦利县| 石家庄市| 多伦县| 沐川县| 乌兰县| 睢宁县| 十堰市| 兰州市| 饶阳县| 商河县| 乌拉特前旗| 商南县| 旺苍县| 南雄市| 松原市| 从化市| 沅陵县| 阿合奇县| 漳平市|