Sqlite源码解读(四)

2021SC@SDUSC

上次讲了Sqlite分配内存的malloc()方法,free(),remalloc();Win32特定堆的最大初始大小,计算初始大小时所用的缓存大小,根据初始堆和默认页大小计算最大合法缓存大小;数组aSyscall[]用来保存所有可重写系统调用的名称和指针;以及用语所有Win32 VFSes的很重要的xSetSystemCall()方法。

接下来介绍VFS的内存分配和共享缓存机制

VFS的IO接口里提供了文件的共享缓存机制。该机制通过在test_vfs里内置一个Shared memory模块来模拟测试文件的共享缓存,以代替原来VFS提供的接口。

1.通用接口既可以给内核用,也可有由应用程序调用,主要是由以下几个函数组成:

void *sqlite3_malloc(int n);

void *sqlite3_realloc(void *pOld, int n);

扫描二维码关注公众号,回复: 13456569 查看本文章

void sqlite3_free(void *p);

sqlite3_uint64 sqlite3_msize(void *p);

void *sqlite3MallocZero(u64 n);

前面3个是对库函数malloc、realloc、free的封装,而sqlite3_msize用来查看分配的内存大小。

封装有以下几个作用:

1)统计内存的使用情况,由以下函数完成:

sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, n);   /*记录分配的最大值*/

nFull = sqlite3MallocSize(p);

sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nFull);  /*记录内存分配的总量*/

sqlite3StatusUp(SQLITE_STATUS_MALLOC_COUNT, 1);   /*记录分配的次数*/

2)内存分配请求失败或超过限制时,释放空闲的缓存页并重新分配:

sqlite3MallocAlarm(nFull);     /* nFull是请求分配的内存大小,按8字节对齐*/

3)为了保证线程安全,分配和释放时要加互斥锁:

sqlite3_mutex_enter(mem0.mutex);

sqlite3_mutex_leave(mem0.mutex);

2. 内部接口主要针对sqlite本身使用,使用时需要传入特定的数据库连接,接口由以下函数组成:

void *sqlite3DbMallocRaw(sqlite3 *db, u64 n);’

void *sqlite3DbRealloc(sqlite3 *db, void *p, u64 n);

void sqlite3DbFree(sqlite3 *db, void *p);

void *sqlite3DbReallocOrFree(sqlite3 *db, void *p, u64 n);

void *sqlite3DbMallocZero(sqlite3 *db, u64 n);

int sqlite3DbMallocSize(sqlite3 *db, void *p);

相较于通用接口,内部接口先判断分配内存的大小,如果内存较小会使用lookaside来分配,如果分配的内存超过每个slot的大小,那么使用通用接口来分配

共享缓存:

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; 

};

sqlite3_file *pFile;    /* pFile作为连接句柄传入*/

typedef struct TestvfsFd TestvfsFd;

struct TestvfsFile {

  sqlite3_file base;             

  TestvfsFd *pFd;            

};

#define tvfsGetFd(pFile) (((TestvfsFile *)pFile)->pFd)

/*从pFile中提取pFd*/

typedef struct TestvfsFd TestvfsFd;

struct TestvfsFd {

  sqlite3_vfs *pVfs;            

  const char *zFilename;          /*传递给xOpen()的文件名*/

  sqlite3_file *pReal;            /* 真实的底层文件描述符 */

  Tcl_Obj *pShmId;                /* Tcl回调的共享内存id */

  TestvfsBuffer *pShm;            /* 共享内存缓冲区*/

  u32 excllock;                   /* 排它锁的掩码 */

  u32 sharedlock;                 /* 共享锁的掩码 */

  TestvfsFd *pNext;               /* 在该文件上打开的下一个句柄 */

};

TestvfsFd *pFd = tvfsGetFd(pFile); /*每个连接都对应一个pFd*/

附:数据库两种基本的锁:排它锁和共享锁。

如果数据对象加上排它锁,则其它事务不能对它读取和修改。

如果加上共享锁,则该数据库对象可以被其他事务读取,但不能修改。

pBuffer结构

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

pBuffer的类型为TestvfsBuffer,如下:

struct TestvfsBuffer {

  char *zFile;                  

  int pgsz;                       /* 页面大小*/

  u8 *aPage[TESTVFS_MAX_PAGES];  

  TestvfsFd *pFile;               /* 打开的句柄列表 */

  TestvfsBuffer *pNext;           /* 所有缓冲区的链接列表中的下一个 */

};

Connection->pFd->pVfs->pAppData->pBuffer

2.具体实现

tvfsShmMap():  /*获取共享缓存中的具体页面空间*/

static int tvfsShmMap(

  sqlite3_file *pFile,    

  int iPage,           

  int pgsz,           

  int isWrite,          

  void volatile **pp   

){

  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;

}

tvfsShmLock():

如果多个线程的连接共享一个缓存,则需对共享缓存加锁,如果已经有排它锁了那么返回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<<n)-1) << ofst);    /*n为掩码数量,ofst为掩码偏移*/

    if( isLock ){

      TestvfsFd *p2;

      for(p2=pFd->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;

}

猜你喜欢

转载自blog.csdn.net/qq_53825872/article/details/120896654