欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SQLite3源码学习之test_vfs框架实例讲解

程序员文章站 2022-12-10 15:13:25
test_vfs主要是用来做测试的,用来模拟文件系统出错的,代码实现在test_vfs.c里面。对于一些诸如机械故障、文件系统空间耗尽的错误是很难实际测出来的,test_vfs在O...

test_vfs主要是用来做测试的,用来模拟文件系统出错的,代码实现在test_vfs.c里面。对于一些诸如机械故障、文件系统空间耗尽的错误是很难实际测出来的,test_vfs在OS接口层和VFS之间插入一层测试的VFS用来模拟文件系统出错,进而验证SQLite对错误处理的稳定性。由Tcl脚本来控制模拟,在正常情况下还是调用原来的VFS。

1.注册VFS

在Tcl中使用格式如下:

testvfs VFSNAME SWITCHES

testvfs的实现函数为testvfs_cmd(),在这个函数里创建了一个新的命令VFSNAME用来设置一些测试场景,SWITCHES为testvfs命令的一些选项,如:

-noshm:置1禁止共享缓存

-default:把tvfs_vfs设置为首选VFS

-fullshm:使用原VFS的共享缓存

注册时首先声明VFS接口函数,在tvfs_vfs结构体里

static sqlite3_vfs tvfs_vfs

在pAppData记录原来默认的VFS

 Testvfs *p;                     /* New object */
  sqlite3_vfs *pVfs;              /* New VFS */

  zVfs = Tcl_GetString(objv[1]);//取得VFSNAME的名称
  nByte = sizeof(Testvfs) + (int)strlen(zVfs)+1;
  p = (Testvfs *)ckalloc(nByte);
  memset(p, 0, nByte);

p->pParent = sqlite3_vfs_find(0);//获取默认VFS

p->zName = (char *)&p[1];
memcpy(p->zName, zVfs, strlen(zVfs)+1);//设置当前vfs名称
pVfs->pAppData = (void *)p;//把信息存到pVfs->pAppData里

sqlite3_vfs_register(pVfs, isDefault);//注册VFS

在创建命令时,还把pVfs->pAppData的信息(即Testvfs *p的地址)传到了命令里供调用:

//声明
EXTERN Tcl_Command	Tcl_CreateObjCommand(Tcl_Interp *interp,
				const char *cmdName, Tcl_ObjCmdProc *proc,
				ClientData clientData,
				Tcl_CmdDeleteProc *deleteProc);
//创建新命令
Tcl_CreateObjCommand(interp, zVfs, testvfs_obj_cmd, p, testvfs_obj_del);
//命令函数,第1个参数即为上面传入的p
static int SQLITE_TCLAPI testvfs_obj_cmd(
  ClientData cd,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
)

2. Tcl命令参数

命令的回调函数是testvfs_obj_cmd,该命令中有多个执行参数如下:

shm

假设注册VFS时新建的命令是tvfs,使用格式为:

tvfs shm FILE VALUE

上述命令中,打开文件连接FILE对应的缓存,将其加入到一个数组对象中如果第4个参数存在,那么把VALUE对象数组的值拷贝到缓存,举例:

set blob [tvfs shm $file]

上述命令中新建一个数组对象,数组存放的是对应连接文件的缓存

Testvfs *p = (Testvfs *)cd;
/*搜索对应缓存*/
      for(pBuffer=p->pBuffer; pBuffer; pBuffer=pBuffer->pNext){
        if( 0==strcmp(pBuffer->zFile, zName) ) break;
      }

把缓存添加到数组对象,然后返回这个数组对象

 

 pObj = Tcl_NewObj();
      for(i=0; pBuffer->aPage[i]; i++){
        int pgsz = pBuffer->pgsz;
        if( pgsz==0 ) pgsz = 65536;
        Tcl_AppendObjToObj(pObj, Tcl_NewByteArrayObj(pBuffer->aPage[i], pgsz));//依次添加数组缓存
      }
      Tcl_SetObjResult(interp, pObj);//该命令返回的Tcl对象
      

假如blob对象的数组中的内容在Tcl中被修改,然后blob对象在该命令中以第4个参数被传入时,会把对象里的内容拷贝到缓存:

 

  if( objc==4 ){
        int n;
        u8 *a = Tcl_GetByteArrayFromObj(objv[3], &n);//数组长度
        int pgsz = pBuffer->pgsz;

        if( pgsz==0 ) pgsz = 65536;
        /*拷贝对象数组到缓存页*/
        for(i=0; i*pgszaPage[i], &a[i*pgsz], nByte);
        }
      }

script

注册脚本的回调函数,保存在p->pScript里,注册如下

tvfs script tvfs_cb  其中tvfs_cb是回调函数

 

filter

在VFS中有很多方法如:

static struct VfsMethod {
        char *zName;
        int mask;
      } vfsmethod [] = {
        { "xShmOpen",           TESTVFS_SHMOPEN_MASK },
        { "xShmLock",           TESTVFS_SHMLOCK_MASK },
        { "xShmBarrier",        TESTVFS_SHMBARRIER_MASK },
        { "xShmUnmap",          TESTVFS_SHMCLOSE_MASK },
        { "xShmMap",            TESTVFS_SHMMAP_MASK },
         ……
}

该命令对特定的方法设置过滤掩码

/*获取过滤的方法参数列表*/
      if( Tcl_ListObjGetElements(interp, objv[2], &nElem, &apElem) ){
        return TCL_ERROR;
      }
……
/*获取过滤方法的掩码*/
   for(i=0; imask = mask;//最后保存掩码

比如设置xShmOpen和xShmLock方法的过滤掩码

tvfs filter xShmOpen xShmOpen

那么只有这2个方法才能执行回调函数,如

 

  if( p->pScript && p->mask&TESTVFS_SHMOPEN_MASK ){
    /*判断xShmOpen掩码有没有设置,设置了才执行脚本的回调函数*/
    tvfsExecTcl(p, "xShmOpen", Tcl_NewStringObj(pFd->zFilename, -1), 0, 0, 0);
    if( tvfsResultCode(p, &rc) ){
      if( rc!=SQLITE_OK ) return rc;
    }
  }

ioerr、fullerr、cantopenerr

这3个命名用来设置模拟出错次数等

 

 case CMD_CANTOPENERR:
    case CMD_IOERR:
    case CMD_FULLERR: {
      TestFaultInject *pTest = 0;
      int iRet;

      switch( aSubcmd[i].eCmd ){
        case CMD_IOERR: pTest = &p->ioerr_err; break;
        case CMD_FULLERR: pTest = &p->full_err; break;
        case CMD_CANTOPENERR: pTest = &p->cantopen_err; break;
        default: assert(0);
      }
      iRet = pTest->nFail;//用来设置对象返回结果
      pTest->nFail = 0;//失败次数
      pTest->eFault = 0;//是否持续模拟出错
      pTest->iCnt = 0;//在出错前正常的次数
  /*接下来在命令中取得相关参数,代码从略*/
……

3. open

vfs打开的接口是tvfsOpen()函数,最主要的2个参数:

sqlite3_vfs *pVfs:

用来获取与vfs关联的应用数据,从而调用脚本回调函数

Tcl_Obj *pId = 0;
Testvfs *p = (Testvfs *)pVfs->pAppData;
if( p->pScript && p->mask&TESTVFS_OPEN_MASK ){
    Tcl_Obj *pArg = Tcl_NewObj();
    Tcl_IncrRefCount(pArg);
 ……
    tvfsExecTcl(p, "xOpen", Tcl_NewStringObj(pFd->zFilename, -1), pArg, 0, 0);
    Tcl_DecrRefCount(pArg);
    if( tvfsResultCode(p, &rc) ){
      if( rc!=SQLITE_OK ) return rc;
    }else{
      pId = Tcl_GetObjResult(p->interp);//此处返回的对象一般作为脚本回调函数的传入参数
    }
  }

sqlite3_file *pFile:

这是一个连接句柄,由PagerOpen分配空间,传入时将其转换为Testvfs类型,从而设置io方法等信息

#define PARENTVFS(x) (((Testvfs *)((x)->pAppData))->pParent)

  TestvfsFile *pTestfile = (TestvfsFile *)pFile;
  TestvfsFd *pFd;
  pFd = (TestvfsFd *)ckalloc(sizeof(TestvfsFd) + PARENTVFS(pVfs)->szOsFile);//为parent vfs分配连接句柄
  memset(pFd, 0, sizeof(TestvfsFd) + PARENTVFS(pVfs)->szOsFile);
  pFd->pShm = 0;
  pFd->pShmId = 0;
  pFd->zFilename = zName;
  pFd->pVfs = pVfs;
  pFd->pReal = (sqlite3_file *)&pFd[1];//此处跳过一个sizeof(TestvfsFd)长度的偏移,用来获得parent vfs的连接句柄
  memset(pTestfile, 0, sizeof(TestvfsFile));
  pTestfile->pFd = pFd;//绑定到连接句柄pFile上
rc = sqlite3OsOpen(PARENTVFS(pVfs), zName, pFd->pReal, flags, pOutFlags);//获取parent vfsd的句柄pFd->pReal
if( pFd->pReal->pMethods ){
    sqlite3_io_methods *pMethods;
    int nByte;

    if( pVfs->iVersion>1 ){
      nByte = sizeof(sqlite3_io_methods);
    }else{
      nByte = offsetof(sqlite3_io_methods, xShmMap);// 这个是求结构体成员的相对便宜地址的,宏展开为((size_t) &((sqlite3_io_methods *)0)->xShmMap)
    }

    pMethods = (sqlite3_io_methods *)ckalloc(nByte);
memcpy(pMethods, &tvfs_io_methods, nByte);
……
pFile->pMethods = pMethods;//绑定io_methods为tvfs_io_methods
}

4. 错误模拟

如果Tcl里配置了错误信息,那么返回错误,否则正常调用parent vfs的io方法,以下为open出错的模拟代码

static int tvfsInjectFault(TestFaultInject *p){
  int ret = 0;
  if( p->eFault ){//开启出错模拟
    p->iCnt--;//出错之前调用的次数
    if( p->iCnt==0 || (p->iCnt<0 && p->eFault==FAULT_INJECT_PERSISTENT ) ){
      ret = 1;
      p->nFail++;//记录出错次数
    }
  }
  return ret;
}

static int tvfsInjectIoerr(Testvfs *p){
  return tvfsInjectFault(&p->ioerr_err);//磁盘io错误
}

static int tvfsInjectFullerr(Testvfs *p){
  return tvfsInjectFault(&p->full_err);//磁盘空间不足错误
}
static int tvfsInjectCantopenerr(Testvfs *p){
  return tvfsInjectFault(&p->cantopen_err);//打开错误
}

  if( (p->mask&TESTVFS_OPEN_MASK) &&  tvfsInjectIoerr(p) ) return SQLITE_IOERR;
  if( tvfsInjectCantopenerr(p) ) return SQLITE_CANTOPEN;
  if( tvfsInjectFullerr(p) ) return SQLITE_FULL;