SQlite源码分析

函数代码解析

Mem3.c中主要涉及到两个结构体,这里介绍一下第一个比较重要的结构体Mem3Block,该结构体定义如下:

typedef struct Mem3Block Mem3Block;
struct Mem3Block {
  union {
    struct {
      u32 prevSize;   /* Size of previous chunk in Mem3Block elements */
      u32 size4x;     /* 4x the size of current chunk in Mem3Block elements */
    } hdr;
    struct {
      u32 next;       /* Index in mem3.aPool[] of next free chunk */
      u32 prev;       /* Index in mem3.aPool[] of previous free chunk */
    } list;
  } u;
};

前面已经提到过,一个chunk有两个或两个以上的block组成,这个结构体就是用来形成chunk链表的。可以看到,第一个block有8个字节,其中,前四个字节(u32 prevSive)用来存储链表中前一个chunk块的大小,后四个字节(u32 size4x)用来存储当前块大小的四倍,为什么size4x是当前chunk大小的四倍呢?原因是最后一个bit和倒数第二个bit有特殊的含义,最后一个bit是否为1, 用来保存当前块是否已经分配出去了,倒数第二个bit 是否为1 上一个块是否被分配出去了。除此之外的另外一个结构体也是很重要的,它定义了整个mem3.c中用到的全局变量,这里就不再具体介绍了。

下面是两个关于内存分配的实现函数部分代码:

实现内存分配:

static void *memsys3FromMaster(u32 nBlock){
  assert( sqlite3_mutex_held(mem3.mutex) );
  assert( mem3.szMaster>=nBlock );
  if( nBlock>=mem3.szMaster-1 ){
    /* Use the entire master */  使用整个master
    void *p = memsys3Checkout(mem3.iMaster, mem3.szMaster);
    mem3.iMaster = 0;
    mem3.szMaster = 0;
    mem3.mnMaster = 0;
    return p;
  }else{
    /* Split the master block.  Return the tail. */切割master进行分配
    u32 newi, x;
    newi = mem3.iMaster + mem3.szMaster - nBlock;
    assert( newi > mem3.iMaster+1 );
    mem3.aPool[mem3.iMaster+mem3.szMaster-1].u.hdr.prevSize = nBlock;
    mem3.aPool[mem3.iMaster+mem3.szMaster-1].u.hdr.size4x |= 2;
    mem3.aPool[newi-1].u.hdr.size4x = nBlock*4 + 1;
    mem3.szMaster -= nBlock;
    mem3.aPool[newi-1].u.hdr.prevSize = mem3.szMaster;
    x = mem3.aPool[mem3.iMaster-1].u.hdr.size4x & 2;
    mem3.aPool[mem3.iMaster-1].u.hdr.size4x = mem3.szMaster*4 | x;
    if( mem3.szMaster < mem3.mnMaster ){
      mem3.mnMaster = mem3.szMaster;
    }
    return (void*)&mem3.aPool[newi];
  }
}

这个函数主要实现的功能是内存分配。Mem3.c又叫实验性内存分配器,在系统初始化前就分配了一块内存,之后这块内存就被固定下来。这块固定的内存就是master,每一次用户需要使用内存时,最开始都要从master上切割对应大小的块,上面这个函数实现的功能就是从master上分配内存的过程。不管什么时候,总保证master是最大的块。用过后的块根据其大小,连接成空闲块链表,用户需要时直接从空闲双链表中取。

因此就涉及到对双向链表的删除和插入操作,其中,删除对应的真正意义是,该块被用户申请走了,被利用了,就要将其拿出链表。对应实现函数代码如下:

static void memsys3UnlinkFromList(u32 i, u32 *pRoot){
  u32 next = mem3.aPool[i].u.list.next;
  u32 prev = mem3.aPool[i].u.list.prev;
  assert( sqlite3_mutex_held(mem3.mutex) );
  if( prev==0 ){
    *pRoot = next;
  }else{
    mem3.aPool[prev].u.list.next = next;
  }
  if( next ){
    mem3.aPool[next].u.list.prev = prev;
  }
  mem3.aPool[i].u.list.next = 0;
  mem3.aPool[i].u.list.prev = 0;
}

同样,插入就意味着用户使用完内存后,要将其插入到空闲链表,这样就防止了内存泄漏问题,具体实现代码如下:

static void memsys3LinkIntoList(u32 i, u32 *pRoot){
  assert( sqlite3_mutex_held(mem3.mutex) );
  mem3.aPool[i].u.list.next = *pRoot;
  mem3.aPool[i].u.list.prev = 0;
  if( *pRoot ){
    mem3.aPool[*pRoot].u.list.prev = i;
  }
  *pRoot = i;
}

除了以上几个主要函数外,其他函数代码我就不继续介绍了,在源码阅读里面已经有详细注释。