SQLite入門與分析(三)---內核概述(1)

寫在前面:從本章開始,我們開始進入SQLite的內核。爲了能更好的理解SQLite,我先從總的結構上討論一下內核,從全局把握SQLite很重要。SQLite的內核實現不是很難,但是也不是很簡單。總的來說分爲三個部分,本章主要討論虛擬機(Virtual Machine),但是這裏只是從原理上概述,不會太多的涉及實際代碼。但是概述完內核之後會仔細討論源代碼的。好了,下面我們來討論虛擬機(VM)。

 

1、虛擬機(Virtual Machine)
VDBE是SQLite的核心,它的上層模塊和下層模塊都是本質上都是爲它服務的。它的實現位於vbde.c, vdbe.h, vdbeapi.c, vdbeInt.h, 和vdbemem.c幾個文件中。它通過底層的基礎設施B+Tree執行由編譯器(Compiler)生成的字節代碼,這種字節代碼程序語言(bytecode programming lauguage)是爲了進行查詢,讀取和修改數據庫而專門設計的。
字節代碼在內存中被封裝成sqlite3_stmt對象(內部叫做Vdbe,見vdbeInt.h),Vdbe(或者說statement)包含執行程序所需要的一切:
a)    a bytecode program
b)    names and data types for all result columns
c)    values bound to input parameters
d)    a program counter
e)    an execution stack of operands
f)    an arbitrary amount of "numbered" memory cells
g)    other run-time state information (such as open BTree objects, sorters, lists, sets)


字節代碼和彙編程序十分類似,每一條指令由操作碼和三個操作數構成:<opcode, P1, P2, P3>。Opcode爲一定功能的操作碼,爲了理解,可以看成一個函數。P1是32位的有符號整數,p2是31位的無符號整數,它通常是導致跳轉(jump)的指令的目標地址(destination),當然這了有其它用途;p3爲一個以null結尾的字符串或者其它結構體的指針。和C API不同的是,VDBE操作碼經常變化,所以不應該用字節碼寫程序。
下面的幾個C API直接和VDBE交互:
• sqlite3_bind_xxx() functions
• sqlite3_step()
• sqlite3_reset()
• sqlite3_column_xxx() functions
• sqlite3_finalize()

爲了有個感性,下面看一個具體的字節碼程序:
sqlite> .m col
sqlite> .h on
sqlite> .w 4 15 3 3 15
sqlite> explain select * from episodes;
addr  opcode           p1   p2   p3
----  ---------------  ---  ---  ---------------
0     Goto                0    12
1     Integer             0    0
2     OpenRead         0    2    # episodes
3     SetNumColumns  0    3
4     Rewind             0    10
5     Recno               0    0
6     Column            0    1
7     Column            0    2
8     Callback           3    0
9     Next                0    5
10    Close               0    0
11    Halt                 0    0
12    Transaction       0    0
13    VerifyCookie      0    10
14    Goto               0    1
15    Noop               0    0

1.1、    棧(Stack)
一個VDBE程序通常由不同完成特定任務的段(section)構成,每一個段中,都有一些操作棧的指令。這是由於不同的指令有不同個數的參數,一些指令只有一個參數;一些指令沒有參數;一些指令有好幾個參數,這種情況下,三個操作數就不能滿足。
考慮到這些情況,指令採用棧來傳遞參數。(注:從彙編的角度來看,傳遞參數的方式有好幾種,比如:寄存器,全局變量,而堆棧是現代語言常用的方式,它具有很大的靈活性)。而這些指令不會自己做這些事情,所以在它們之前,需要其它一些指令的幫助。VDBE把計算的中間結果保存到內存單元(memory cells)中,其實,堆棧和內存單元都是基於Mem(見vdbeInt.h)數據結構(注:這裏的棧,內存單元都是虛擬的,記得一位計算機科學家說過:計算機科學中90%以上的科學都是虛擬化問題。一點不假,OS本質上也是虛擬機,而在這裏SQLite,我們也處處可見虛擬化的身影,到後面的OS Interface模塊中再仔細討論這個問題)。

1.2、程序體(Program Body)
這是一個打開episodes表的過程。
第一條指令:Integer是爲第二條指令作準備的,也就是把第二條指令執行需要的參數壓入堆棧,OpenRead從堆棧中取出參數值然後執行。SQLite可以通過ATTACH命令在一個連接中打開多個數據庫文件,每當SQLite打開一個數據,它就爲之賦一個索引號(index),main database的索引爲0,第一個數據庫爲1,依次如此。Integer指令數據庫索引的值壓入棧,而OpenRead從中取出值,並決定打開哪個數據,來看看SQLite文檔中的解釋:
     Open a read-only cursor for the database table whose root page is P2 in a database file.
The database file is determined by an integer from the top of the stack. 0 means the main database and 1 means the database used for temporary tables. Give the new cursor an identifier of P1. The P1 values need not be contiguous but all P1 values should be small integers. It is an error for P1 to be negative.
     If P2==0 then take the root page number from off of the stack.
     There will be a read lock on the database whenever there is an open cursor. If the data-
base was unlocked prior to this instruction then a read lock is acquired as part of this instruction. A read lock allows other processes to read the database but prohibits any other process from modifying the database. The read lock is released when all cursors are closed. If this instruction attempts to get a read lock but fails, the script terminates with an SQLITE_BUSY error code.
     The P3 value is a pointer to a KeyInfo structure that defines the content and collating

sequence of indices. P3 is NULL for cursors that are not pointing to indices. 

再來看看SetNumColumns指令設置遊標將指向的列。P1爲遊標的索引(這裏爲0,剛剛打開),P2爲列的數目,episodes表有三列。
繼續Rewind指令,它將遊標重新設置到表的開始,它會檢查表是否爲空(即沒有記錄),如果沒有記錄,它會導致指令指針跳到P2指定的指令處。在這裏,P2爲10,即Close指令。一旦Rewind設置遊標,接下就執行5-9這幾條指令,它們的主要功能是遍歷結果集,Recno把由遊標P1指定的記錄的關鍵字壓入堆棧。Column指令從由P1指定的遊標,P2指定的列取值。5,6,7三條指令分別把id(primary key),season和name字段的值壓入棧。接下來,Callback指令從棧中取出三個值(P1),然後形成一個記錄數組,存儲在內存單元中(memory cell)。Callback會停止VDBE的操作,把控制權交給sqlite3_stemp(),該函數返回SQLITE_ROW。

一旦VDBE創建了記錄結構,我們就可以通過sqlite3_column_xxx() functions從記錄結構的域內取出值。當下次調用sqlite3_step()時,指令指針會指向Next指令,而Next指令會把遊標向移向下一行,如果有其它的記錄,它會跳到由P2指定的指令,在這裏爲指令5,創建一個新的記錄結構,一直循環,直到結果集的最後。Close指令會關閉遊標,然後執行Halt指令,結束VDBE程序。

1.3、程序開始與停止

現在來看看其餘的指令,Goto指令是一條跳轉指令,跳到P2處,即第12條指令。指令12是Transaction,它開始一個新的事務;然後執行VerifyCookie,它的主要功能VDBE程序編譯後,數據庫模式是否改變(即是否進行過更新操作)。這在SQLite中是一個很重要的概念,在SQL被sqlite3_prepare()編譯成VDBE代碼至程序調用sqlite3_step()執行字節碼的這段時間,另一個SQL命令可能會改變數據庫模式(such as ALTER TABLE, DROP TABLE, or CREATE TABLE)。一旦發生這種情況,之前編譯的statement就會變得無效,數據庫模式信息記錄在數據庫文件的根頁面中。類似,每一個statement都有一份用來比較的在編譯時刻該模式的備份,VerifyCookie的功能就是檢查它們是否匹配,如果不匹配,將採取相關操作。


如果兩者匹配,會執行下一條指令Goto;它會跳到程序的主要部分,即第一條指令,打開表讀取記錄。這裏有兩點值得注意:
(1)Transaction指令自己不會獲取鎖( The Transaction instruction doesn’t acquire any locks in itself)。它的功能相當於BEGIN,而實際是由OpenRead指令獲取share lock的。當事務關閉時釋放鎖,這取決於Halt指令,它會進行掃尾工作。
(2)statement對象(VDBE程序)所需的存儲空間在程序執行前就已經確定。這有原於兩個重要事實:首先,棧的深度不會比指令的數目還多(通常少得多)。其次,在執行VDBE程序之前,SQLite可以計算出爲分配資源所需要的內存。

1.4指令的類型(Instruction Types)
每條指令都完成特定的任務,而且通常和別的指令有關。大體上來說,指令可分爲三類:
(1)Value manipulation:這些指令通常完成算術運算,比如:add, subtract, divide;邏輯運算,比如:AND和OR;還有字符串操作。
(2)Data management:這些指令操作在內存和磁盤上的數據。內存指令進行棧操作或者在內存單元之間傳遞數據。磁盤操作指令控制B-tree和pager打開或操作遊標,開始或結束事務,等等。

(3)Control flow:控制指令主要是移動指令指針。

1.5、程序的執行(Program execution)
最後我們來看VM解釋器是如何實現以及字節代碼大致是如何執行的。在vdbe.c文件中有一個很關鍵的函數:
//執行VDBE程序
int sqlite3VdbeExec(
  Vdbe *p                    /* The VDBE */
)
該函數是執行VDBE程序的入口。來看看它的內部實現:

/*從這裏開始執行指令
**pc爲程序計數器(int)
*/
for(pc=p->pc; rc==SQLITE_OK; pc++){
  //取得操作碼
  pOp = &p->aOp[pc];
  switch( pOp->opcode ){
  case OP_Goto: {             /* jump */
      CHECK_FOR_INTERRUPT;
      pc = pOp->p2 - 1;
      break;
     }
    … …
   }
}
從這段代碼,我們大致可以推出VM執行的原理:VM解釋器實際上是一個包含大量switch語句的for循環,每一個switch語句實現一個特定的操作指令。

發佈了6 篇原創文章 · 獲贊 3 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章