Sqlite3原始碼學習(6)demovfs分析
demovfs是sqlite3裡最簡單的一個vfs實現,程式碼在demovfs.c裡。這個檔案裡的函式就demoWrite()函式稍微複雜點,其他函式基本都很簡單。
1.demovfs註冊
Sqlitetest_demovfs_Init():註冊register_demovfs和unregister_demovfs命令
register_demovfs():將demovfs註冊到vfs連結串列中
unregister_demovfs():將demovfs從vfs連結串列中移除
sqlite3_demovfs():定義demovfs中sqlite3_vfs結構體中函式指標的具體實現
sqlite3_vfs *sqlite3_demovfs(void){ static sqlite3_vfs demovfs = { 1, /* iVersion */ sizeof(DemoFile), /* szOsFile */ MAXPATHNAME, /* mxPathname */ 0, /* pNext */ "demo", /* zName */ 0, /* pAppData */ demoOpen, /* xOpen */ demoDelete, /* xDelete */ demoAccess, /* xAccess */ demoFullPathname, /* xFullPathname */ demoDlOpen, /* xDlOpen */ demoDlError, /* xDlError */ demoDlSym, /* xDlSym */ demoDlClose, /* xDlClose */ demoRandomness, /* xRandomness */ demoSleep, /* xSleep */ demoCurrentTime, /* xCurrentTime */ }; return &demovfs; }
2.demovfs的具體實現
demoOpen():該函式實現如下
static int demoOpen( sqlite3_vfs *pVfs, /* VFS */ const char *zName, /* File to open, or 0 for a temp file */ sqlite3_file *pFile, /* Pointer to DemoFile struct to populate */ int flags, /* Input SQLITE_OPEN_XXX flags */ int *pOutFlags /* Output SQLITE_OPEN_XXX flags (or NULL) */ ){ static const sqlite3_io_methods demoio = { 1, /* iVersion */ demoClose, /* xClose */ demoRead, /* xRead */ demoWrite, /* xWrite */ demoTruncate, /* xTruncate */ demoSync, /* xSync */ demoFileSize, /* xFileSize */ demoLock, /* xLock */ demoUnlock, /* xUnlock */ demoCheckReservedLock, /* xCheckReservedLock */ demoFileControl, /* xFileControl */ demoSectorSize, /* xSectorSize */ demoDeviceCharacteristics /* xDeviceCharacteristics */ }; DemoFile *p = (DemoFile*)pFile; /* Populate this structure */ int oflags = 0; /* flags to pass to open() call */ char *aBuf = 0; if( zName==0 ){ return SQLITE_IOERR; } if( flags&SQLITE_OPEN_MAIN_JOURNAL ){ aBuf = (char *)sqlite3_malloc(SQLITE_DEMOVFS_BUFFERSZ); if( !aBuf ){ return SQLITE_NOMEM; } } if( flags&SQLITE_OPEN_EXCLUSIVE ) oflags |= O_EXCL; if( flags&SQLITE_OPEN_CREATE ) oflags |= O_CREAT; if( flags&SQLITE_OPEN_READONLY ) oflags |= O_RDONLY; if( flags&SQLITE_OPEN_READWRITE ) oflags |= O_RDWR; memset(p, 0, sizeof(DemoFile)); p->fd = open(zName, oflags, 0600); if( p->fd<0 ){ sqlite3_free(aBuf); return SQLITE_CANTOPEN; } p->aBuffer = aBuf; if( pOutFlags ){ *pOutFlags = flags; } p->base.pMethods = &demoio; return SQLITE_OK; }
該函式定義並初始化了sqlite3_io_methods 結構體變數demoio,該結構體裡是一系列操作io的函式指標的具體實現。
接下來把輸入的pFile指標強制轉換成DemoFile型別並賦值給p,可以理解為DemoFile是sqlite3_file的派生類。DemoFile結構體宣告如下:
typedef struct DemoFile DemoFile;
struct DemoFile {
sqlite3_file base; /* Base class. Must be first. */
//檔案描述符
int fd; /* File descriptor */
// aBuffer在向寫入檔案時進行快取,最多快取8192個位元組,緩衝去主要針對頻率較高的對檔案//寫入少量資料,減小磁碟操作的開銷
char *aBuffer; /* Pointer to malloc'd buffer */
//已經存入緩衝區的位元組數
int nBuffer; /* Valid bytes of data in zBuffer */
//這個變數比較難理解,指快取區aBuffer的第一個位元組在檔案中的偏移,這個變數在寫快取的//時候會用到
sqlite3_int64 iBufferOfst; /* Offset in file of zBuffer[0] */
};
標誌變數flag指示開啟的是日誌檔案,則給寫快取指標分配空間,接下來呼叫linux下最基本的檔案io函式open(),引數oflags代表開啟的模式如建立、只讀、可讀寫等等,0600代表新建的檔案的許可權為可讀可寫,具體open函式的特性見以下網址
open函式返回一個描述符,將在檔案讀寫時使用。
demoDelete():通過unlink函式刪除傳入的檔名,雖然已經看不到刪除的檔案,但可能還殘留在磁碟中,通過獲取該檔案所在的目錄,並開啟檔案目錄,通過fsync函式將當前的檔案目錄更新到磁碟中。
demoAccess():確認檔案是否存在,是否可讀、可寫
demoFullPathname():獲取檔案完整的路徑,先通過getcwd()獲取當前執行目錄,並和檔名拼接成完整目錄。
demoDlOpen():呼叫外部動態庫用,暫未實現
demoDlError():呼叫外部動態庫用,暫未實現
demoDlSym():呼叫外部動態庫用,暫未實現
demoDlClose():呼叫外部動態庫用,暫未實現
demoRandomness():和隨機數相關,暫未實現
demoSleep():將執行緒掛起,單位us
demoCurrentTime():獲取當前儒略日時間
3.demoio的具體實現
demoClose():先將檔案快取寫到檔案中,釋放p->aBuffer,再關閉檔案
demoRead():通過lseek函式定位檔案當前的偏移,通過read函式讀取內容
demoWrite():這個函式比較複雜,將詳細分析
//這個函式根據輸入的資料指標、資料長度、和偏移位置將資料寫入到磁碟
static int demoDirectWrite(
DemoFile *p, /* File handle */
const void *zBuf, /* Buffer containing data to write */
int iAmt, /* Size of data to write in bytes */
sqlite_int64 iOfst /* File offset to write to */
){
off_t ofst; /* Return value from lseek() */
size_t nWrite; /* Return value from write() */
ofst = lseek(p->fd, iOfst, SEEK_SET);
if( ofst!=iOfst ){
return SQLITE_IOERR_WRITE;
}
nWrite = write(p->fd, zBuf, iAmt);
if( nWrite!=iAmt ){
return SQLITE_IOERR_WRITE;
}
return SQLITE_OK;
}
//這個函式把輸入的檔案指標中的寫快取寫到磁盤裡,並把緩衝區長度清0
static int demoFlushBuffer(DemoFile *p){
int rc = SQLITE_OK;
if( p->nBuffer ){
rc = demoDirectWrite(p, p->aBuffer, p->nBuffer, p->iBufferOfst);
p->nBuffer = 0;
}
return rc;
}
static int demoWrite(
sqlite3_file *pFile,
const void *zBuf,
int iAmt,
sqlite_int64 iOfst
){
DemoFile *p = (DemoFile*)pFile;
//向檔案寫資料時,如果寫快取的指標不為0,那麼先把資料寫到緩衝區,否則直接寫入磁碟
if( p->aBuffer ){
char *z = (char *)zBuf; /* Pointer to remaining data to write */
int n = iAmt; /* Number of bytes at z */
sqlite3_int64 i = iOfst; /* File offset to write to */
while( n>0 ){
int nCopy; /* Number of bytes to copy into buffer */
/* If the buffer is full, or if this data is not being written directly
** following the data already buffered, flush the buffer. Flushing
** the buffer is a no-op if it is empty.
*/
if( p->nBuffer==SQLITE_DEMOVFS_BUFFERSZ || p->iBufferOfst+p->nBuffer!=i ){
int rc = demoFlushBuffer(p);
if( rc!=SQLITE_OK ){
return rc;
}
}
assert( p->nBuffer==0 || p->iBufferOfst+p->nBuffer==i );
p->iBufferOfst = i - p->nBuffer;
/* Copy as much data as possible into the buffer. */
nCopy = SQLITE_DEMOVFS_BUFFERSZ - p->nBuffer;
if( nCopy>n ){
nCopy = n;
}
memcpy(&p->aBuffer[p->nBuffer], z, nCopy);
p->nBuffer += nCopy;
n -= nCopy;
i += nCopy;
z += nCopy;
}
}else{
return demoDirectWrite(p, zBuf, iAmt, iOfst);
}
return SQLITE_OK;
}
通過快取寫入時,每次寫滿SQLITE_DEMOVFS_BUFFERSZ個位元組再把快取寫入檔案,通過快取寫入時,每次寫資料必須是連續的,p->iBufferOfst+p->nBuffer!=i這個條件用來判斷這次寫入到檔案中的偏移是否和上一次緩衝區寫入到檔案的偏移保持連續,如果不連續說明上一次向檔案寫資料已經完成,但緩衝區還沒更新到磁碟,所以先把緩衝區的資料寫入到磁碟,再開始這次的資料寫入。
p->iBufferOfst = i - p->nBuffer;這句話也很難理解,可以這樣解釋:
p->iBufferOfst是當前緩衝區的第一個位元組對應在檔案中的偏移,一般情況下,如果是連續寫入的,那麼這句話不起作用,因為本來就滿足p->iBufferOfst+p->nBuffer == i ,向緩衝區寫入nCopy個位元組後,p->nBuffer+=nCopy,i+=nCopy,所以p->iBufferOfst保持不變,但是如果緩衝區寫滿後,會呼叫demoFlushBuffer把緩衝區寫入到磁碟,並且p->nBuffer清0,這時緩衝區對應磁碟的偏移就改變了,如果p->iBufferOfst上一次是0,那麼寫滿一次緩衝區後,就變為8192,再接著寫滿一次緩衝區後就變為8192*2,同時如果第一次寫入不是和上一次連續,那麼p->iBufferOfst = i - p->nBuffer還保證了下一次連續寫入時能夠滿足條件p->iBufferOfst+p->nBuffer == i,接下來就比較好理解了,每次向緩衝區寫入資料的最大值是nCopy = SQLITE_DEMOVFS_BUFFERSZ - p->nBuffer,將輸入資料寫入到緩衝區。
demoio中的其他函式或是比較簡單或是沒有實現,就不一一說明了,至此我們大概知道了一個最基本的vfs在幹什麼,接下來先去學pager層,之後再回過頭來看linux和win32平臺下一個完善的vfs的實現。