📄 pcache.c
字号:
/*** 2008 August 05**** The author disclaims copyright to this source code. In place of** a legal notice, here is a blessing:**** May you do good and not evil.** May you find forgiveness for yourself and forgive others.** May you share freely, never taking more than you give.***************************************************************************** This file implements that page cache.**** @(#) $Id: pcache.c,v 1.24 2008/08/29 09:10:03 danielk1977 Exp $*/#include "sqliteInt.h"/*** A complete page cache is an instance of this structure.**** A cache may only be deleted by its owner and while holding the** SQLITE_MUTEX_STATUS_LRU mutex.*/struct PCache { /********************************************************************* ** The first group of elements may be read or written at any time by ** the cache owner without holding the mutex. No thread other than the ** cache owner is permitted to access these elements at any time. */ PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */ PgHdr *pSynced; /* Last synced page in dirty page list */ int nRef; /* Number of pinned pages */ int nPinned; /* Number of pinned and/or dirty pages */ int nMax; /* Configured cache size */ int nMin; /* Configured minimum cache size */ /********************************************************************** ** The next group of elements are fixed when the cache is created and ** may not be changed afterwards. These elements can read at any time by ** the cache owner or by any thread holding the the mutex. Non-owner ** threads must hold the mutex when reading these elements to prevent ** the entire PCache object from being deleted during the read. */ int szPage; /* Size of every page in this cache */ int szExtra; /* Size of extra space for each page */ int bPurgeable; /* True if pages are on backing store */ void (*xDestroy)(PgHdr*); /* Called when refcnt goes 1->0 */ int (*xStress)(void*,PgHdr*); /* Call to try make a page clean */ void *pStress; /* Argument to xStress */ /********************************************************************** ** The final group of elements can only be accessed while holding the ** mutex. Both the cache owner and any other thread must hold the mutex ** to read or write any of these elements. */ int nPage; /* Total number of pages in apHash */ int nHash; /* Number of slots in apHash[] */ PgHdr **apHash; /* Hash table for fast lookup by pgno */ PgHdr *pClean; /* List of clean pages in use */};/*** Free slots in the page block allocator*/typedef struct PgFreeslot PgFreeslot;struct PgFreeslot { PgFreeslot *pNext; /* Next free slot */};/*** Global data for the page cache.*/static struct PCacheGlobal { int isInit; /* True when initialized */ sqlite3_mutex *mutex; /* static mutex MUTEX_STATIC_LRU */ int nMaxPage; /* Sum of nMaxPage for purgeable caches */ int nMinPage; /* Sum of nMinPage for purgeable caches */ int nCurrentPage; /* Number of purgeable pages allocated */ PgHdr *pLruHead, *pLruTail; /* LRU list of unused clean pgs */ /* Variables related to SQLITE_CONFIG_PAGECACHE settings. */ int szSlot; /* Size of each free slot */ void *pStart, *pEnd; /* Bounds of pagecache malloc range */ PgFreeslot *pFree; /* Free page blocks */} pcache = {0};/*** All global variables used by this module (all of which are grouped ** together in global structure "pcache" above) are protected by the static ** SQLITE_MUTEX_STATIC_LRU mutex. A pointer to this mutex is stored in** variable "pcache.mutex".**** Some elements of the PCache and PgHdr structures are protected by the ** SQLITE_MUTEX_STATUS_LRU mutex and other are not. The protected** elements are grouped at the end of the structures and are clearly** marked.**** Use the following macros must surround all access (read or write)** of protected elements. The mutex is not recursive and may not be** entered more than once. The pcacheMutexHeld() macro should only be** used within an assert() to verify that the mutex is being held.*/#define pcacheEnterMutex() sqlite3_mutex_enter(pcache.mutex)#define pcacheExitMutex() sqlite3_mutex_leave(pcache.mutex)#define pcacheMutexHeld() sqlite3_mutex_held(pcache.mutex)/*** Some of the assert() macros in this code are too expensive to run** even during normal debugging. Use them only rarely on long-running** tests. Enable the expensive asserts using the** -DSQLITE_ENABLE_EXPENSIVE_ASSERT=1 compile-time option.*/#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT# define expensive_assert(X) assert(X)#else# define expensive_assert(X)#endif/********************************** Linked List Management ********************/#if !defined(NDEBUG) && defined(SQLITE_ENABLE_EXPENSIVE_ASSERT)/*** This routine verifies that the number of entries in the hash table** is pCache->nPage. This routine is used within assert() statements** only and is therefore disabled during production builds.*/static int pcacheCheckHashCount(PCache *pCache){ int i; int nPage = 0; for(i=0; i<pCache->nHash; i++){ PgHdr *p; for(p=pCache->apHash[i]; p; p=p->pNextHash){ nPage++; } } assert( nPage==pCache->nPage ); return 1;}#endif /* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT */#if !defined(NDEBUG) && defined(SQLITE_ENABLE_EXPENSIVE_ASSERT)/*** Based on the current value of PCache.nRef and the contents of the** PCache.pDirty list, return the expected value of the PCache.nPinned** counter. This is only used in debugging builds, as follows:**** expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) );*/static int pcachePinnedCount(PCache *pCache){ PgHdr *p; int nPinned = pCache->nRef; for(p=pCache->pDirty; p; p=p->pNext){ if( p->nRef==0 ){ nPinned++; } } return nPinned;}#endif /* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT */#if !defined(NDEBUG) && defined(SQLITE_ENABLE_EXPENSIVE_ASSERT)/*** Check that the pCache->pSynced variable is set correctly. If it** is not, either fail an assert or return zero. Otherwise, return** non-zero. This is only used in debugging builds, as follows:**** expensive_assert( pcacheCheckSynced(pCache) );*/static int pcacheCheckSynced(PCache *pCache){ PgHdr *p = pCache->pDirtyTail; for(p=pCache->pDirtyTail; p!=pCache->pSynced; p=p->pPrev){ assert( p->nRef || (p->flags&PGHDR_NEED_SYNC) ); } return (p==0 || p->nRef || (p->flags&PGHDR_NEED_SYNC)==0);}#endif /* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT *//*** Remove a page from its hash table (PCache.apHash[]).*/static void pcacheRemoveFromHash(PgHdr *pPage){ assert( pcacheMutexHeld() ); if( pPage->pPrevHash ){ pPage->pPrevHash->pNextHash = pPage->pNextHash; }else{ PCache *pCache = pPage->pCache; u32 h = pPage->pgno % pCache->nHash; assert( pCache->apHash[h]==pPage ); pCache->apHash[h] = pPage->pNextHash; } if( pPage->pNextHash ){ pPage->pNextHash->pPrevHash = pPage->pPrevHash; } pPage->pCache->nPage--; expensive_assert( pcacheCheckHashCount(pPage->pCache) );}/*** Insert a page into the hash table**** The mutex must be held by the caller.*/static void pcacheAddToHash(PgHdr *pPage){ PCache *pCache = pPage->pCache; u32 h = pPage->pgno % pCache->nHash; assert( pcacheMutexHeld() ); pPage->pNextHash = pCache->apHash[h]; pPage->pPrevHash = 0; if( pCache->apHash[h] ){ pCache->apHash[h]->pPrevHash = pPage; } pCache->apHash[h] = pPage; pCache->nPage++; expensive_assert( pcacheCheckHashCount(pCache) );}/*** Attempt to increase the size the hash table to contain** at least nHash buckets.*/static int pcacheResizeHash(PCache *pCache, int nHash){ PgHdr *p; PgHdr **pNew; assert( pcacheMutexHeld() );#ifdef SQLITE_MALLOC_SOFT_LIMIT if( nHash*sizeof(PgHdr*)>SQLITE_MALLOC_SOFT_LIMIT ){ nHash = SQLITE_MALLOC_SOFT_LIMIT/sizeof(PgHdr *); }#endif pcacheExitMutex(); pNew = (PgHdr **)sqlite3Malloc(sizeof(PgHdr*)*nHash); pcacheEnterMutex(); if( !pNew ){ return SQLITE_NOMEM; } memset(pNew, 0, sizeof(PgHdr *)*nHash); sqlite3_free(pCache->apHash); pCache->apHash = pNew; pCache->nHash = nHash; pCache->nPage = 0; for(p=pCache->pClean; p; p=p->pNext){ pcacheAddToHash(p); } for(p=pCache->pDirty; p; p=p->pNext){ pcacheAddToHash(p); } return SQLITE_OK;}/*** Remove a page from a linked list that is headed by *ppHead.** *ppHead is either PCache.pClean or PCache.pDirty.*/static void pcacheRemoveFromList(PgHdr **ppHead, PgHdr *pPage){ int isDirtyList = (ppHead==&pPage->pCache->pDirty); assert( ppHead==&pPage->pCache->pClean || ppHead==&pPage->pCache->pDirty ); assert( pcacheMutexHeld() || ppHead!=&pPage->pCache->pClean ); if( pPage->pPrev ){ pPage->pPrev->pNext = pPage->pNext; }else{ assert( *ppHead==pPage ); *ppHead = pPage->pNext; } if( pPage->pNext ){ pPage->pNext->pPrev = pPage->pPrev; } if( isDirtyList ){ PCache *pCache = pPage->pCache; assert( pPage->pNext || pCache->pDirtyTail==pPage ); if( !pPage->pNext ){ pCache->pDirtyTail = pPage->pPrev; } if( pCache->pSynced==pPage ){ PgHdr *pSynced = pPage->pPrev; while( pSynced && (pSynced->flags&PGHDR_NEED_SYNC) ){ pSynced = pSynced->pPrev; } pCache->pSynced = pSynced; } }}/*** Add a page from a linked list that is headed by *ppHead.** *ppHead is either PCache.pClean or PCache.pDirty.*/static void pcacheAddToList(PgHdr **ppHead, PgHdr *pPage){ int isDirtyList = (ppHead==&pPage->pCache->pDirty); assert( ppHead==&pPage->pCache->pClean || ppHead==&pPage->pCache->pDirty ); if( (*ppHead) ){ (*ppHead)->pPrev = pPage; } pPage->pNext = *ppHead; pPage->pPrev = 0; *ppHead = pPage; if( isDirtyList ){ PCache *pCache = pPage->pCache; if( !pCache->pDirtyTail ){ assert( pPage->pNext==0 ); pCache->pDirtyTail = pPage; } if( !pCache->pSynced && 0==(pPage->flags&PGHDR_NEED_SYNC) ){ pCache->pSynced = pPage; } }}/*** Remove a page from the global LRU list*/static void pcacheRemoveFromLruList(PgHdr *pPage){ assert( sqlite3_mutex_held(pcache.mutex) ); assert( (pPage->flags&PGHDR_DIRTY)==0 ); if( pPage->pCache->bPurgeable==0 ) return; if( pPage->pNextLru ){ assert( pcache.pLruTail!=pPage ); pPage->pNextLru->pPrevLru = pPage->pPrevLru; }else{ assert( pcache.pLruTail==pPage ); pcache.pLruTail = pPage->pPrevLru; } if( pPage->pPrevLru ){ assert( pcache.pLruHead!=pPage ); pPage->pPrevLru->pNextLru = pPage->pNextLru; }else{ assert( pcache.pLruHead==pPage ); pcache.pLruHead = pPage->pNextLru; }}/*** Add a page to the global LRU list. The page is normally added** to the front of the list so that it will be the last page recycled.** However, if the PGHDR_REUSE_UNLIKELY bit is set, the page is added** to the end of the LRU list so that it will be the next to be recycled.*/static void pcacheAddToLruList(PgHdr *pPage){ assert( sqlite3_mutex_held(pcache.mutex) ); assert( (pPage->flags&PGHDR_DIRTY)==0 ); if( pPage->pCache->bPurgeable==0 ) return; if( pcache.pLruTail && (pPage->flags & PGHDR_REUSE_UNLIKELY)!=0 ){ /* If reuse is unlikely. Put the page at the end of the LRU list ** where it will be recycled sooner rather than later. */ assert( pcache.pLruHead ); pPage->pNextLru = 0; pPage->pPrevLru = pcache.pLruTail; pcache.pLruTail->pNextLru = pPage; pcache.pLruTail = pPage; pPage->flags &= ~PGHDR_REUSE_UNLIKELY; }else{ /* If reuse is possible. the page goes at the beginning of the LRU ** list so that it will be the last to be recycled. */ if( pcache.pLruHead ){ pcache.pLruHead->pPrevLru = pPage; } pPage->pNextLru = pcache.pLruHead; pcache.pLruHead = pPage; pPage->pPrevLru = 0; if( pcache.pLruTail==0 ){ pcache.pLruTail = pPage; } }}/*********************************************** Memory Allocation *************** Initialize the page cache memory pool.**** This must be called at start-time when no page cache lines are** checked out. This function is not threadsafe.*/void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){ PgFreeslot *p; sz &= ~7; pcache.szSlot = sz; pcache.pStart = pBuf; pcache.pFree = 0; while( n-- ){ p = (PgFreeslot*)pBuf; p->pNext = pcache.pFree; pcache.pFree = p; pBuf = (void*)&((char*)pBuf)[sz]; } pcache.pEnd = pBuf;}/*** Allocate a page cache line. Look in the page cache memory pool first** and use an element from it first if available. If nothing is available** in the page cache memory pool, go to the general purpose memory allocator.*/void *pcacheMalloc(int sz, PCache *pCache){ assert( sqlite3_mutex_held(pcache.mutex) ); if( sz<=pcache.szSlot && pcache.pFree ){ PgFreeslot *p = pcache.pFree; pcache.pFree = p->pNext; sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, sz); sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1); return (void*)p; }else{ void *p; /* Allocate a new buffer using sqlite3Malloc. Before doing so, exit the ** global pcache mutex and unlock the pager-cache object pCache. This is ** so that if the attempt to allocate a new buffer causes the the ** configured soft-heap-limit to be breached, it will be possible to ** reclaim memory from this pager-cache. */ pcacheExitMutex(); p = sqlite3Malloc(sz); pcacheEnterMutex();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -