• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • SQLite3源碼學習(22) Page Cache分析

    標簽: sqlite3  源碼  page cache  緩存

    上一篇學習了pcache1的機制,這是pagecache管理的一個插件,在這基礎上又封裝了一層,主要是用來處理臟頁(就是修改過的緩存頁),如臟頁的添加刪除和回收利用等,這部分代碼的實現在pcache.c里。

    1.數據結構

    pcache中,通過PCache結構對象作為連接句柄,每個緩存頁通過PgHdr來表示。

    pagecache中,所有的臟頁通過一個雙向鏈表來連接在一起,其結構關系如下圖所示:

    clip_image002[8]_thumb

     

       其中pCache->pDirty為鏈表的頭部,pCache->pDirtyTail為鏈表的尾部。

    2.臟頁的添加和刪除

    這個鏈表是按照LRU的順序來維護的,新的鏈表元素是從頭部插入,即頁面pp->DirtyNext更新。pCache->pDirty指向最新的頁面,pCache->pDirtyTail指向最老的頁面。

    鏈表的插入和刪除由pcacheManageDirtyList()函數來完成

    /* Allowed values for second argument to pcacheManageDirtyList() */
    #define PCACHE_DIRTYLIST_REMOVE   1    /* Remove pPage from dirty list */
    #define PCACHE_DIRTYLIST_ADD      2    /* Add pPage to the dirty list */
    #define PCACHE_DIRTYLIST_FRONT    3    /* Move pPage to the front of the list */
     
    /*
    ** Manage pPage's participation on the dirty list.  Bits of the addRemove
    ** argument determines what operation to do.  The 0x01 bit means first
    ** remove pPage from the dirty list.  The 0x02 means add pPage back to
    ** the dirty list.  Doing both moves pPage to the front of the dirty list.
    */
    static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){
      PCache *p = pPage->pCache;
     
      pcacheTrace(("%p.DIRTYLIST.%s %d\n", p,
                    addRemove==1 ? "REMOVE" : addRemove==2 ? "ADD" : "FRONT",
                    pPage->pgno));//打印調試信息
      //把頁面從鏈表移除
      if( addRemove & PCACHE_DIRTYLIST_REMOVE ){ 
        assert( pPage->pDirtyNext || pPage==p->pDirtyTail );
        assert( pPage->pDirtyPrev || pPage==p->pDirty );
      
    /* Update the PCache1.pSynced variable if necessary. */
        if( p->pSynced==pPage ){
          p->pSynced = pPage->pDirtyPrev;
        }
       
        if( pPage->pDirtyNext ){
          pPage->pDirtyNext->pDirtyPrev = pPage->pDirtyPrev;//讓下一個節點指向前一個節點
        }else{
          assert( pPage==p->pDirtyTail );
          //如果被刪除的頁面是最后一個,那么更新鏈表尾部
          p->pDirtyTail = pPage->pDirtyPrev;
        }
    if( pPage->pDirtyPrev ){
      //讓前一個節點指向后一個節點
          pPage->pDirtyPrev->pDirtyNext = pPage->pDirtyNext;
        }else{
          /* If there are now no dirty pages in the cache, set eCreate to 2. 
          ** This is an optimization that allows sqlite3PcacheFetch() to skip
          ** searching for a dirty page to eject from the cache when it might
          ** otherwise have to.  */
          assert( pPage==p->pDirty );
          //如果被刪的是頭部,那么更新鏈表頭部
          p->pDirty = pPage->pDirtyNext;
          assert( p->bPurgeable || p->eCreate==2 );
          if( p->pDirty==0 ){         /*OPTIMIZATION-IF-TRUE*/
            assert( p->bPurgeable==0 || p->eCreate==1 );
            //沒有臟頁的情況下,p->eCreate被設為2
            p->eCreate = 2;
          }
        }
        pPage->pDirtyNext = 0;
        pPage->pDirtyPrev = 0;
      }
      //在鏈表頭部插入新的頁面
      if( addRemove & PCACHE_DIRTYLIST_ADD ){
        assert( pPage->pDirtyNext==0 && pPage->pDirtyPrev==0 && p->pDirty!=pPage );
      
        pPage->pDirtyNext = p->pDirty;
        if( pPage->pDirtyNext ){
          assert( pPage->pDirtyNext->pDirtyPrev==0 );
          //讓上一個節點指向下一個節點
          pPage->pDirtyNext->pDirtyPrev = pPage;
    }else{
      //如果是第一個節點,那么添加尾部
          p->pDirtyTail = pPage;
          if( p->bPurgeable ){
            assert( p->eCreate==2 );
            //有臟頁存在時,p->eCreate置1
            p->eCreate = 1;
          }
    }
        //更新鏈表頭部
        p->pDirty = pPage;
     
        /* If pSynced is NULL and this page has a clear NEED_SYNC flag, set
        ** pSynced to point to it. Checking the NEED_SYNC flag is an 
        ** optimization, as if pSynced points to a page with the NEED_SYNC
        ** flag set sqlite3PcacheFetchStress() searches through all newer 
        ** entries of the dirty-list for a page with NEED_SYNC clear anyway.  */
        if( !p->pSynced 
         && 0==(pPage->flags&PGHDR_NEED_SYNC)   /*OPTIMIZATION-IF-FALSE*/
    ){
      // p->pSynced是一個標記頁,用來快速查找最新的已被同步的頁
          p->pSynced = pPage;
        }
      }
      pcacheDump(p);
    }

    3.頁面讀取

        讀取頁面的接口函數是sqlite3PcacheFetch(),在這個函數中需要通過sqlite3GlobalConfig.pcache2.xFetch()調用插件pcache1的接口,如果讀取的頁面不在緩存中時,由傳入的第3個參數eCreate來控制創建緩存頁的策略。

    eCreate的真值又由createFlagpCache->eCreate來決定,而pCache->eCreate的真值又由pCache->bPurgeablepCache->pDirty來決定,真值表如下:

    pCache->bPurgeable

    pCache->pDirty

    pCache->eCreate

    0

    0

    2

    0

    1

    2

    1

    1

    1

    1

    0

    2

     

    pCache->eCreate

    createFlag

    eCreate

    1

    0

    0

    2

    0

    0

    1

    3

    1

    2

    3

    2

     

    sqlite3_pcache_page *sqlite3PcacheFetch(
      PCache *pCache,       /* Obtain the page from this cache */
      Pgno pgno,            /* Page number to obtain */
      // createFlag傳入的值是0或3(即二進制11)
      int createFlag        /* If true, create page if it does not exist already */
    ){
      int eCreate;
      sqlite3_pcache_page *pRes;
     
      assert( pCache!=0 );
      assert( pCache->pCache!=0 );
      assert( createFlag==3 || createFlag==0 );
      //見第一個真值表第3行
      assert( pCache->eCreate==((pCache->bPurgeable && pCache->pDirty) ? 1 : 2) );
      //對于eCreate的具體處理見上一篇文章
      /* eCreate defines what to do if the page does not exist.
      **    0     Do not allocate a new page.  (createFlag==0)
      **    1     Allocate a new page if doing so is inexpensive.
      **          (createFlag==1 AND bPurgeable AND pDirty)
      **    2     Allocate a new page even it doing so is difficult.
      **          (createFlag==1 AND !(bPurgeable AND pDirty)
      */
      /*上面的注釋的意思是說如果cache slot可回收,并且存在臟頁的情況下,
      **如果緩存頁的數量達到最大時需要預留一些slot,不再回收或創建新的
      **緩存頁*/
      //見第2個真值表
      eCreate = createFlag & pCache->eCreate;
      assert( eCreate==0 || eCreate==1 || eCreate==2 );
      assert( createFlag==0 || pCache->eCreate==eCreate );
      //即eCreate==1+!(pCache->bPurgeable&&pCache->pDirty)
      //即bPurgeable和pDirty都滿足的情況下,eCreate是1
      assert( createFlag==0 || eCreate==1+(!pCache->bPurgeable||!pCache->pDirty) );
      pRes = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate);
      pcacheTrace(("%p.FETCH %d%s (result: %p)\n",pCache,pgno,
                   createFlag?" create":"",pRes));
      return pRes;
    }

      取到的頁面是一個sqlite3_pcache_page類型的對象,由上篇文章知道PgHdr1是該類型的一個繼承。

      根據這個對象,調用sqlite3PcacheFetchFinish()來獲得PgHdr對象,并初始化,這里有個比較有意思的地方,就是sqlite3PcacheFetchFinish()調用pcacheFetchFinishWithInit()初始化后,間接地遞歸調用自己。

    PgHdr *sqlite3PcacheFetchFinish(
      PCache *pCache,             /* Obtain the page from this cache */
      Pgno pgno,                  /* Page number obtained */
      sqlite3_pcache_page *pPage  /* Page obtained by prior PcacheFetch() call */
    ){
      PgHdr *pPgHdr;
     
      pPgHdr = (PgHdr *)pPage->pExtra;
     
      if( !pPgHdr->pPage ){
        return pcacheFetchFinishWithInit(pCache, pgno, pPage);
      }
      ……
      return pPgHdr;
    }
     
    static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit(
      PCache *pCache,             /* Obtain the page from this cache */
      Pgno pgno,                  /* Page number obtained */
      sqlite3_pcache_page *pPage  /* Page obtained by prior PcacheFetch() call */
    ){
      PgHdr *pPgHdr;
      assert( pPage!=0 );
      pPgHdr = (PgHdr*)pPage->pExtra;
      ……
      return sqlite3PcacheFetchFinish(pCache,pgno,pPage);
    }

    4.頁面讀取失敗后的處理

        如果頁面讀取失敗,那么說明頁緩存的數量已經超過最大值,那么找到一個已經sync的臟頁回收,如果沒找到,那么找一個最老的頁面來刷盤回收,但是如果還沒sync,通常還沒有獨占鎖,會返回一個busy

    回收一個臟頁后,不管成功沒成功都要為讀取失敗的頁面分配一個新的頁緩存,即把eCreate強制設為2

    /*
    ** If the sqlite3PcacheFetch() routine is unable to allocate a new
    ** page because no clean pages are available for reuse and the cache
    ** size limit has been reached, then this routine can be invoked to 
    ** try harder to allocate a page.  This routine might invoke the stress
    ** callback to spill dirty pages to the journal.  It will then try to
    ** allocate the new page and will only fail to allocate a new page on
    ** an OOM error.
    **
    ** This routine should be invoked only after sqlite3PcacheFetch() fails.
    */
    int sqlite3PcacheFetchStress(
      PCache *pCache,                 /* Obtain the page from this cache */
      Pgno pgno,                      /* Page number to obtain */
      sqlite3_pcache_page **ppPage    /* Write result here */
    ){
      PgHdr *pPg;
      if( pCache->eCreate==2 ) return 0;
      // pCache->szSpill是設置的一個可回收的閾值
      if( sqlite3PcachePagecount(pCache)>pCache->szSpill ){
        /* Find a dirty page to write-out and recycle. First try to find a 
        ** page that does not require a journal-sync (one with PGHDR_NEED_SYNC
        ** cleared), but if that is not possible settle for any other 
        ** unreferenced dirty page.
        **
        ** If the LRU page in the dirty list that has a clear PGHDR_NEED_SYNC
        ** flag is currently referenced, then the following may leave pSynced
        ** set incorrectly (pointing to other than the LRU page with NEED_SYNC
        ** cleared). This is Ok, as pSynced is just an optimization.  */
       //首先從pCache->pSynced開始搜索已經sync的page
        for(pPg=pCache->pSynced; 
            pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); 
            pPg=pPg->pDirtyPrev
        );
        //找到之后更新pCache->pSynced
        pCache->pSynced = pPg;
        //如果沒找到,那么就找一個沒有引用的頁
        if( !pPg ){
          for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev);
        }
        if( pPg ){
          int rc;
    #ifdef SQLITE_LOG_CACHE_SPILL
          sqlite3_log(SQLITE_FULL, 
                      "spill page %d making room for %d - cache used: %d/%d",
                      pPg->pgno, pgno,
                      sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache),
                    numberOfCachePages(pCache));
    #endif
          pcacheTrace(("%p.SPILL %d\n",pCache,pPg->pgno));
         // xStress和pStress由sqlite3PcacheOpen時傳入
         //該函數把臟頁刷到磁盤,并從臟頁鏈表中移除
          rc = pCache->xStress(pCache->pStress, pPg);
          pcacheDump(pCache);
        //如果沒有鎖資源,會返回SQLITE_BUSY
          if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
            return rc;
          }
        }
      }
      //不管page數量是否超限,都創建一個新的緩存頁
      *ppPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2);
      return *ppPage==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK;
    }

    5.結束

    關于page cache的內容,就基本講這么多吧,另外pcacheSortDirtyList()函數對臟頁按照頁號重新排序,這里用到了鏈表的歸并排序方法,將在下一篇文章中介紹,剩下的其他函數都是很容易理解的。

    另外再提2個問題:

    1.為什么只有存在臟頁的時候,讀取頁面的時候才設置page數量的最大值,即pCache->pDirty不為空的時候,eCreate的值才為1

    2.sqlite3PcacheFetchStress()函數回收臟頁的時候,為什么要先找已經syncpage

    2個問題單獨從page cache模塊中還沒看到答案,可能需要事務處理和日志模塊的相關知識,在以后對pager模塊完全理解透徹后再回過頭來看這2個問題。

     

    版權聲明:本文為pfysw原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/pfysw/article/details/79858630

    智能推薦

    MyBatis緩存Cache源碼分析

    一、緩存接口介紹 MyBatis的一級緩存和二級緩存都是使用的Cache接口實現類,它的方法定義如下: 其實現類有: 其中除PerpetualCache以外,其他的Cache實現都是使用了裝飾器模式,由底層的PerpetualCache完成實際的緩存,并在此基礎上添加了其他功能。 BlockingCache/SynchronizedCache:通過在get/put方式中加鎖,保證只有一個線程操作緩...

    Guava 源碼分析(Cache 原理)

    前言 Google 出的 Guava 是 Java 核心增強的庫,應用非常廣泛。 我平時用的也挺頻繁,這次就借助日常使用的 Cache 組件來看看 Google 大牛們是如何設計的。 緩存 本次主要討論緩存。 緩存在日常開發中舉足輕重,如果你的應用對某類數據有著較高的讀取頻次,并且改動較小時那就非常適合利用緩存來提高性能。 緩存之所以可以提高性能是因為它的讀取效率很高,就像是 CPU 的 L1、L...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    猜你喜歡

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    精品国产乱码久久久久久蜜桃不卡