SQLite3源码学习之test_vfs的共享内存机制讲解

VFS的IO接口里提供了文件的共享缓存机制,在test_vfs里内置了一个Shared memory模块用来模拟测试文件的共享缓存,而不是使用原来的VFS提供的接口。

1.结构定义

pFd结构

每一个数据库文件的连接都对应着一个连接句柄pFile,上层函数调用VFS接口时会传入pFile,在test_vfs里pFile会被强制转换为TestvfsFile*类型,相当于sqlite3_file*的一个继承

typedef struct sqlite3_file sqlite3_file;
struct sqlite3_file {
  const struct sqlite3_io_methods *pMethods;  /* Methods for an open file */
};
sqlite3_file *pFile;// pFile作为连接句柄传入

/*强制转换为TestvfsFile*类型*/
typedef struct TestvfsFd TestvfsFd;
struct TestvfsFile {
  sqlite3_file base;              /* Base class.  Must be first */
  TestvfsFd *pFd;                 /* File data */
};
#define tvfsGetFd(pFile) (((TestvfsFile *)pFile)->pFd)

/*从连接句柄中提取pFd*/
typedef struct TestvfsFd TestvfsFd;
struct TestvfsFd {
  sqlite3_vfs *pVfs;              /* The VFS */
  const char *zFilename;          /* Filename as passed to xOpen() */
  sqlite3_file *pReal;            /* The real, underlying file descriptor */
  Tcl_Obj *pShmId;                /* Shared memory id for Tcl callbacks */

  TestvfsBuffer *pShm;            /* Shared memory buffer */
  u32 excllock;                   /* Mask of exclusive locks */
  u32 sharedlock;                 /* Mask of shared locks */
  TestvfsFd *pNext;               /* Next handle opened on the same file */
};
TestvfsFd *pFd = tvfsGetFd(pFile);//每个连接对应一个pFd

pBuffer结构

pBuffer用来存储共享缓存,每一个文件都有自己的缓存,这些缓存组成一个链表。如果有多个连接打开同一个文件,那么相同文件对应连接的pFd又会组成一个链表,pFile指向pFd的表头。

pBuffer的类型为TestvfsBuffer*,定义如下:

struct TestvfsBuffer {
  char *zFile;                    /* Associated file name */
  int pgsz;                       /* Page size */
  u8 *aPage[TESTVFS_MAX_PAGES];   /* Array of ckalloc'd pages */
  TestvfsFd *pFile;               /* List of open handles */
  TestvfsBuffer *pNext;           /* Next in linked list of all buffers */
};

缓存链表的表头pBuffer存在pFd->pVfs->pAppData里

结构关系

 

假设用多个连接打开同一个文件,其关系如下

 

2.具体实现

tvfsShmMap():

获取共享缓存中的具体页面空间

static int tvfsShmMap(
  sqlite3_file *pFile,            /* Handle open on database file */
  int iPage,                      /* Page to retrieve */
  int pgsz,                       /* Size of pages */
  int isWrite,                    /* True to extend file if necessary */
  void volatile **pp              /* OUT: Mapped memory */
){
  int rc = SQLITE_OK;
  TestvfsFd *pFd = tvfsGetFd(pFile);
  Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);

 ……
/*如果该连接的缓存不存在,为该连接分配缓存*/
  if( 0==pFd->pShm ){
    rc = tvfsShmOpen(pFile); 
    if( rc!=SQLITE_OK ){
      return rc;
    }
  }
……
/*如果第iPage页的缓存为空,那么分配pgsz 长度的空间*/
  if( rc==SQLITE_OK && isWrite && !pFd->pShm->aPage[iPage] ){
    tvfsAllocPage(pFd->pShm, iPage, pgsz);
  }
  *pp = (void volatile *)pFd->pShm->aPage[iPage];

  return rc;
}

tvfsShmOpen():

为对应的连接分配缓存地址,如果缓存不存在,那么新建一个节点插入到pFd链表中。

static int tvfsShmOpen(sqlite3_file *pFile){
  Testvfs *p;
  int rc = SQLITE_OK;             /* Return code */
  TestvfsBuffer *pBuffer;         /* Buffer to open connection to */
  TestvfsFd *pFd;                 /* The testvfs file structure */

  pFd = tvfsGetFd(pFile);
  p = (Testvfs *)pFd->pVfs->pAppData;
  assert( 0==p->isFullshm );
  assert( pFd->pShmId && pFd->pShm==0 && pFd->pNext==0 );
     ……

  /* Search for a TestvfsBuffer. Create a new one if required. */
  for(pBuffer=p->pBuffer; pBuffer; pBuffer=pBuffer->pNext){
    if( 0==strcmp(pFd->zFilename, pBuffer->zFile) ) break;
  }
  /*如果缓存链表中没有该连接文件的缓存,新建一个节点插入到pBuffer链表中*/
  if( !pBuffer ){
    int szName = (int)strlen(pFd->zFilename);
    int nByte = sizeof(TestvfsBuffer) + szName + 1;
    pBuffer = (TestvfsBuffer *)ckalloc(nByte);
    memset(pBuffer, 0, nByte);
    pBuffer->zFile = (char *)&pBuffer[1];
    memcpy(pBuffer->zFile, pFd->zFilename, szName+1);
    pBuffer->pNext = p->pBuffer;
    p->pBuffer = pBuffer;
  }
  /*把新连接插入到pFd链表的表头,并让pBuffer->pFile指向表头*/
  /* Connect the TestvfsBuffer to the new TestvfsShm handle and return. */
  pFd->pNext = pBuffer->pFile;
  pBuffer->pFile = pFd;
  pFd->pShm = pBuffer;

  return SQLITE_OK;
}

tvfsShmUnmap():

把连接从pFd链表中移除,移除后对应的pBuffer如果没有与之对应的连接,那么释放pBuffer的所有空间

static int tvfsShmUnmap(
  sqlite3_file *pFile,
  int deleteFlag
){
  int rc = SQLITE_OK;
  TestvfsFd *pFd = tvfsGetFd(pFile);
  Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
  TestvfsBuffer *pBuffer = pFd->pShm;
  TestvfsFd **ppFd;

/*把连接从pFd链表中移除, pBuffer->pFile存放头节点地址*/
  for(ppFd=&pBuffer->pFile; *ppFd!=pFd; ppFd=&((*ppFd)->pNext));
  assert( (*ppFd)==pFd );
  *ppFd = pFd->pNext;//注意ppFd是二级指针,假如pPrev是pFd的上一个节点地址,那么*ppFd相当于pPrev->pNext
  pFd->pNext = 0;
/*如果pFd是pBuffer的最后一个连接,那么释放pBuffer->pFile */
  if( pBuffer->pFile==0 ){
    int i;
    TestvfsBuffer **pp;
    for(pp=&p->pBuffer; *pp!=pBuffer; pp=&((*pp)->pNext));
    *pp = (*pp)->pNext;
    for(i=0; pBuffer->aPage[i]; i++){
      ckfree((char *)pBuffer->aPage[i]);
    }
    ckfree((char *)pBuffer);
  }
  pFd->pShm = 0;

  return rc;
}

tvfsShmLock():

如果多个线程的连接共享一个缓存,需要对共享缓存加锁,如果已经有独占锁了那么返回busy。

如果加的是独占锁已经有共享锁了,那么也返回busy,也就是说有共享锁的情况下不能获取独占锁,这是为了防止中途读取了一半的数据的时候被修改。

这里似乎有个漏洞,如果持续不断地加共享锁,那么会导致独占锁一直加不上,也就不能写数据了。

static int tvfsShmLock(
  sqlite3_file *pFile,
  int ofst,
  int n,
  int flags
){
  int rc = SQLITE_OK;
  TestvfsFd *pFd = tvfsGetFd(pFile);
  Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData)
  ……
  if( rc==SQLITE_OK ){
    int isLock = (flags & SQLITE_SHM_LOCK);
    int isExcl = (flags & SQLITE_SHM_EXCLUSIVE);
    u32 mask = (((1<pShm->pFile; p2; p2=p2->pNext){
        if( p2==pFd ) continue;
        if( (p2->excllock&mask) || (isExcl && p2->sharedlock&mask) ){
          rc = SQLITE_BUSY;
          break;
        }
      }
      /*获得锁资源后对缓存加锁*/
      if( rc==SQLITE_OK ){
        if( isExcl )  pFd->excllock |= mask;
        if( !isExcl ) pFd->sharedlock |= mask;
      }
    }else{/*释放锁*/
      if( isExcl )  pFd->excllock &= (~mask);
      if( !isExcl ) pFd->sharedlock &= (~mask);
    }
  }

  return rc;
}
(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐