当前位置:首页> 正文

深入理解SQLite(5) 虚拟文件系统VFS-文件系统错误

前言

之前文章都一直在讲SQLite的API接口和事务,这篇文章就来讲一讲SQLite VFS的一些源码部分,在官网(www.sqlite.org/vfs.html)上有关于VFS的详细文档也参考,如果本人看的不过瘾的话,可以自行前往。

VFS也就是所谓的虚拟文件系统,因为sqlite3是夸平台的,所以运行在不同的平台上需要兼容不同的文件系统,这就是VFS的任务了,VFS就是对不同的文件系统做一个统一的接口。

简介

我们先看一张SQLite软件层次结构:

深入理解SQLite(5) 虚拟文件系统VFS


SQLite库的内部层次结构就是上图的组件栈。Tokenizer,Parser和Code Generator组件用于处理SQL语句并将它们转换为虚拟机语言或字节代码的可执行程序。

简单地说,这三个前三层实现了 sqlite3_prepare_v2()。前三层生成的字节代码是预准备语句。我在第2篇文章就已经讲到过SQlite学习(C/C++接口介绍),虚拟机模块负责运行SQL语句字节代码。

B-Tree模块将数据库文件存储到具有有序键和具体对数性能的多个键/值存储中(其实也就是B+树)深入理解SQLite(3)体系架构。

Pager模块负责将数据库文件的页面加载到内存中,用于实现和控制事务,以及创建和维护防止崩溃或电源故障后数据库损坏的日志文件。

OS接口是一个抽象,它提供了一组通用的实现方式,使SQLite能够适配不同的操作系统。OS层位于最底层,是和系统的磁盘文件存储直接打交道,在OS层里封装了VFS。简单地说,底部的四层实现了 sqlite3_step()。

VFS结构组成

先来直接看定义,VFS结构体,sqlite3_vfs:


typedef struct sqlite3_vfs sqlite3_vfs;
typedef void (*sqlite3_syscall_ptr)(void);
struct sqlite3_vfs {
int iVersion; /* Structure version number (currently 3) */
int szOsFile; /* Size of subclassed sqlite3_file */
int mxPathname; /* Maximum file pathname length */
sqlite3_vfs *pNext; /* Next registered VFS */
const char * zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
int flags, int *pOutFlags);
int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void);
void (*xDlClose)(sqlite3_vfs*, void*);
int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
int (*xSleep)(sqlite3_vfs*, int microseconds);
int (*xCurrentTime)(sqlite3_vfs*, double*);
int (*xGetLastError)(sqlite3_vfs*, int, char *);
/*
** The methods above are in version 1 of the sqlite_vfs object
** definition. Those that follow are added in version 2 or later
*/
int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
/*
** The methods above are in versions 1 and 2 of the sqlite_vfs object.
** Those below are for version 3 and greater.
*/
int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName);
const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
/*
** The methods above are in versions 1 through 3 of the sqlite_vfs object.
** New fields may be appended in future versions. The iVersion
** value will increment whenever this happens.
*/
};

解释下:

  • int szOsFile: file结构体的大小,继承自sqlite3_file,分配空间时要用
  • sqlite3_vfs *pNext;:指向下一个vfs节点的指针
  • const char * zName:vfs的名称
  • void *pAppData:这个指针指向一个存储了各种文件io操作方法的结构体地址,举例,win平台下的结构体是:

typedef struct winVfsAppData winVfsAppData;

struct winVfsAppData {

const sqlite3_io_methods *pMethod; /* The file I/O methods to use. */

void *pAppData; /* The extra pAppData, if any. */

BOOL bNoLock; /* Non-zero if locking is disabled. */

};

static winVfsAppData winAppData = {

&winIoMethod, /* pMethod */

0, /* pAppData */

0 /* bNoLock */

};

要实现一个VFS实体,就是实现sqlite3_vfs和sqlite3_io_methods结构体里的一系列方法,即函数指针的实体函数的实现,sqlite3_file结构体作为一个文件句柄的传入参数,在open时会将sqlite3_file指针强制转为特定的file指针,该file结构体从sqlite3_file继承,感觉有点像C++,sqlite3_file相当于一个基类,在不同VFS下有不同的派生类。

题外话:这种面向接口的编程思想,很多开源代码都会采用,当然我们在开发中也可以学以致用。

VFS的类型

在win下的vfs有:win32、win32-longpath、win32-none和win32-longpath-none,默认使用的是win32 vfs;

在类unix系统下的vfs有unix、unix-dotfil、unix-excl、unix-none等,默认使用的是unix vfs。s

qlite3内核在初始化时调用sqlite3_os_init()函数来注册vfs,sqlite3_os_init()在不同系统下有不同的实现。

除了上面这些在test文件里还实现了一些其他的vfs如:

  • test_demovfs.c:最简单的vfs,可以学习vfs的基本实现
  • test_onefile.c:用于嵌入式设备的vfs
  • test_quota.c:实现了一个名为quota的vfs、功能暂时不清楚,具体见官方文档
  • test_multiplex.c:这个vfs好像是用来处理大文件,具体见官方文档
  • test_journal.c:这个vfs主要测试回滚日志的存储
  • test_vfs.c:这个文件主要实现文件系统出错的模拟


VFS注册和使用

如果vfs没有在sqlite3_os_init()里注册,那么就要使用sqlite3_vfs_register函数来注册,其实现如下:


int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){
MUTEX_LOGIC(sqlite3_mutex *mutex;)
#ifndef SQLITE_OMIT_AUTOINIT
int rc = sqlite3_initialize();
if( rc ) return rc;
#endif
#ifdef SQLITE_ENABLE_API_ARMOR
if( pVfs==0 ) return SQLITE_MISUSE_BKPT;
#endif

MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
sqlite3_mutex_enter(mutex);
vfsUnlink(pVfs);
if( makeDflt || vfsList==0 ){
pVfs->pNext = vfsList;
vfsList = pVfs;
}else{
pVfs->pNext = vfsList->pNext;
vfsList->pNext = pVfs;
}
assert(vfsList);
sqlite3_mutex_leave(mutex);
return SQLITE_OK;
}

这个函数的传入参数为要注册的vfs地址pVfs和makeDflt,将pVfsv存储到链表中,vfsList为头节点,如果makeDflt不为0,那么pVfs作为头节点,否则pVfs指向头节点的下一个节点,这里添加节点时需要加锁,防止多线程时被重入。

注册后就可以通过调用sqlite3_open_v2()来替换vfs,下面以demo vfs为示例:


sqlite3_vfs_register(sqlite3_demovfs(), 1);//注册
int rc = sqlite3_open_v2("demo.db", &db, SQLITE_OPEN_READWRITE, "demo");//使用demo vfs替换默认的vfs

END

展开全文阅读

相关内容