/*
** 2007 August 14
**
** 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 contains the C functions that implement a memory
** allocation subsystem for use by SQLite.
**
** $Id: mem4.c,v 1.3 2008/06/18 17:09:10 danielk1977 Exp $
*/
#include "sqliteInt.h"
/*
** This version of the memory allocator attempts to obtain memory
** from mmap() if the size of the allocation is close to the size
** of a virtual memory page. If the size of the allocation is different
** from the virtual memory page size, then ordinary malloc() is used.
** Ordinary malloc is also used if space allocated to mmap() is
** exhausted.
**
** Enable this memory allocation by compiling with -DSQLITE_MMAP_HEAP_SIZE=nnn
** where nnn is the maximum number of bytes of mmap-ed memory you want
** to support. This module may choose to use less memory than requested.
**
*/
#ifdef SQLITE_MMAP_HEAP_SIZE
/*
** This is a test version of the memory allocator that attempts to
** use mmap() and madvise() for allocations and frees of approximately
** the virtual memory page size.
*/
#include <sys/types.h>
#include <sys/mman.h>
#include <errno.h>
#include <unistd.h>
/*
** All of the static variables used by this module are collected
** into a single structure named "mem". This is to keep the
** static variables organized and to reduce namespace pollution
** when this module is combined with other in the amalgamation.
*/
static struct {
/*
** The alarm callback and its arguments. The mem.mutex lock will
** be held while the callback is running. Recursive calls into
** the memory subsystem are allowed, but no new callbacks will be
** issued. The alarmBusy variable is set to prevent recursive
** callbacks.
*/
sqlite3_int64 alarmThreshold;
void (*alarmCallback)(void*, sqlite3_int64,int);
void *alarmArg;
int alarmBusy;
/*
** Mutex to control access to the memory allocation subsystem.
*/
sqlite3_mutex *mutex;
/*
** Current allocation and high-water mark.
*/
sqlite3_int64 nowUsed;
sqlite3_int64 mxUsed;
/*
** Current allocation and high-water marks for mmap allocated memory.
*/
sqlite3_int64 nowUsedMMap;
sqlite3_int64 mxUsedMMap;
/*
** Size of a single mmap page. Obtained from sysconf().
*/
int szPage;
int mnPage;
/*
** The number of available mmap pages.
*/
int nPage;
/*
** Index of the first free page. 0 means no pages have been freed.
*/
int firstFree;
/* First unused page on the top of the heap.
*/
int firstUnused;
/*
** Bulk memory obtained from from mmap().
*/
char *mmapHeap; /* first byte of the heap */
} mem;
/*
** Enter the mutex mem.mutex. Allocate it if it is not already allocated.
** The mmap() region is initialized the first time this routine is called.
*/
static void memsys4Enter(void){
if( mem.mutex==0 ){
mem.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
}
sqlite3_mutex_enter(mem.mutex);
}
/*
** Attempt to free memory to the mmap heap. This only works if
** the pointer p is within the range of memory addresses that
** comprise the mmap heap. Return 1 if the memory was freed
** successfully. Return 0 if the pointer is out of range.
*/
static int mmapFree(void *p){
char *z;
int idx, *a;
if( mem.mmapHeap==MAP_FAILED || mem.nPage==0 ){
return 0;
}
z = (char*)p;
idx = (z - mem.mmapHeap)/mem.szPage;
if( idx<1 || idx>=mem.nPage ){
return 0;
}
a = (int*)mem.mmapHeap;
a[idx] = a[mem.firstFree];
mem.firstFree = idx;
mem.nowUsedMMap -= mem.szPage;
madvise(p, mem.szPage, MADV_DONTNEED);
return 1;
}
/*
** Attempt to allocate nBytes from the mmap heap. Return a pointer
** to the allocated page. Or, return NULL if the allocation fails.
**
** The allocation will fail if nBytes is not the right size.
** Or, the allocation will fail if the mmap heap has been exhausted.
*/
static void *mmapAlloc(int nBytes){
int idx = 0;
if( nBytes>mem.szPage || nBytes<mem.mnPage ){
return 0;
}
if( mem.nPage==0 ){
mem.szPage = sysconf(_SC_PAGE_SIZE);
mem.mnPage = mem.szPage - mem.szPage/10;
mem.nPage = SQLITE_MMAP_HEAP_SIZE/mem.szPage;
if( mem.nPage * sizeof(int) > mem.szPage ){
mem.nPage = mem.szPage/sizeof(int);
}
mem.mmapHeap = mmap(0, mem.szPage*mem.nPage, PROT_WRITE|PROT_READ,
MAP_ANONYMOUS|MAP_SHARED, -1, 0);
if( mem.mmapHeap==MAP_FAILED ){
mem.firstUnused = errno;
}else{
mem.firstUnused = 1;
mem.nowUsedMMap = mem.szPage;
}
}
if( mem.mmapHeap==MAP_FAILED ){
return 0;
}
if( mem.firstFree ){
int idx = mem.firstFree;
int *a = (int*)mem.mmapHeap;
mem.firstFree = a[idx];
}else if( mem.firstUnused<mem.nPage ){
idx = mem.firstUnused++;
}
if( idx ){
mem.nowUsedMMap += mem.szPage;
if( mem.nowUsedMMap>mem.mxUsedMMap ){
mem.mxUsedMMap = mem.nowUsedMMap;
}
return (void*)&mem.mmapHeap[idx*mem.szPage];
}else{
return 0;
}
}
/*
** Release the mmap-ed memory region if it is currently allocated and
** is not in use.
*/
static void mmapUnmap(void){
if( mem.mmapHeap==MAP_FAILED ) return;
if( mem.nPage==0 ) return;
if( mem.nowUsedMMap>mem.szPage ) return;
munmap(mem.mmapHeap, mem.nPage*mem.szPage);
mem.nowUsedMMap = 0;
mem.nPage = 0;
}
/*
** Return the amount of memory currently checked out.
*/
sqlite3_int64 sqlite3_memory_used(void){
sqlite3_int64 n;
memsys4Enter();
n = mem.nowUsed + mem.nowUsedMMap;
sqlite3_mutex_leave(mem.mutex);
return n;
}
/*
** Return the maximum amount of memory that has ever been
** checked out since either the beginning of this process
** or since the most recent reset.
*/
sqlite3_int64 sqlite3_memory_highwater(int resetFlag){
sqlite3_int64 n;
memsys4Enter();
n = mem.mxUsed + mem.mxUsedMMap;
if( resetFlag ){
mem.mxUsed = mem.nowUsed;
mem.mxUsedMMap = mem.nowUsedMMap;
}
sqlite3_mutex_leave(mem.mutex);
return n;
}
/*
** Change the alarm callback
*/
int sqlite3_memory_alarm(
void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
void *pArg,
sqlite3_int64 iThreshold
){
memsys4Enter();
mem.alarmCallback = xCallback;
mem.alarmArg = pArg;
mem.alarmThreshold = iThreshold;
sqlite3_mutex_leave(mem.mutex);
return SQLITE_OK;
}
/*
** Trigger the alarm
*/
static void sqlite3MemsysAlarm(int nByte){
void (*xCallback)(void*,sqlite3_int64,int);
sqlite3_int64 nowUsed;
void *pArg;
if( mem.alarmCallback==0 || mem.alarmBusy ) return;
mem.alarmBusy = 1;
xCallback = mem.alarmCallback;
nowUsed = mem.nowUsed;
pArg = mem.alarmArg;
sqlite3_mutex_leave(mem.mutex);
xCallback(pArg, nowUsed, nByte);
sqlite3_mutex_enter(mem.mutex);
mem.alarmBusy = 0;
}
/*
** Allocate nBytes of memory
*/
static void *memsys4Malloc(int nBytes){
sqlite3_int64 *p = 0;
if( mem.alarmCallback!=0
&& mem.nowUsed+mem.nowUsedMMap+nBytes>=mem.alarmThreshold ){
sqlite3MemsysAlarm(nBytes);
}
if( (p = mmapAlloc(nBytes))==0 ){
p = malloc(nBytes+8);
if( p==0 ){
sqlite3MemsysAlarm(nBytes);
p = malloc(nBytes+8);
}
if( p ){
p[0] = nBytes;
p++;
mem.nowUsed += nBytes;
if( mem.nowUsed>mem.mxUsed ){
mem.mxUsed = mem.nowUsed;
}
}
}
return (void*)p;
}
/*
** Return the size of a memory allocation
*/
static int memsys4Size(void *pPrior){
char *z = (char*)pPrior;
int idx = mem.nPage ? (z - mem.mmapHeap)/mem.szPage : 0;
int nByte;
if( idx>=1 && idx<mem.nPage ){
nByte = mem.szPage;
}else{
sqlite3_int64 *p = pPrior;
p--;
nByte = (int)*p;
}
return nByte;
}
/*
** Free memory.
*/
static void memsys4Free(void *pPrior){
sqlite3_int64 *p;
int nByte;
if( mmapFree(pPrior)==0 ){
p = pPrior;
p--;
nByte = (int)*p;
mem.nowUsed -= nByte;
free(p);
if( mem.nowUsed==0 ){
mmapUnmap();
}
}
}
/*
** Allocate nBytes of memory
*/
void *sqlite3_malloc(int nBytes){
sqlite3_int64 *p = 0;
if( nBytes>0 ){
memsys4Enter();
p = memsys4Malloc(nBytes);
sqlite3_mutex_leave(mem.mutex);
}
return (void*)p;
}
/*
** Free memory.
*/
void sqlite3_free(void *pPrior){
if( pPrior==0 ){
return;
}
assert( mem.mutex!=0 );
sqlite3_mutex_enter(mem.mutex);
memsys4Free(pPrior);
sqlite3_mutex_leave(mem.mutex);
}
/*
** Change the size of an existing memory allocation
*/
void *sqlite3_realloc(void *pPrior, int nBytes){
int nOld;
sqlite3_int64 *p;
if( pPrior==0 ){
return sqlite3_malloc(nBytes);
}
if( nBytes<=0 ){
sqlite3_free(pPrior);
return 0;
}
nOld = memsys4Size(pPrior);
if( nBytes<=nOld && nBytes>=nOld-128 ){
return pPrior;
}
assert( mem.mutex!=0 );
sqlite3_mutex_enter(mem.mutex);
p = memsys4Malloc(nBytes);
if( p ){
if( nOld<nBytes ){
memcpy(p, pPrior, nOld);
}else{
memcpy(p, pPrior, nBytes);
}
memsys4Free(pPrior);
}
assert( mem.mutex!=0 );
sqlite3_mutex_leave(mem.mutex);
return (void*)p;
}
#endif /* SQLITE_MMAP_HEAP_SIZE */