diff options
Diffstat (limited to 'src/backend/storage/ipc/shmem.c')
-rw-r--r-- | src/backend/storage/ipc/shmem.c | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c new file mode 100644 index 00000000000..4eba3729ac8 --- /dev/null +++ b/src/backend/storage/ipc/shmem.c @@ -0,0 +1,561 @@ +/*------------------------------------------------------------------------- + * + * shmem.c-- + * create shared memory and initialize shared memory data structures. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/storage/ipc/shmem.c,v 1.1.1.1 1996/07/09 06:21:54 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * POSTGRES processes share one or more regions of shared memory. + * The shared memory is created by a postmaster and is "attached to" + * by each of the backends. The routines in this file are used for + * allocating and binding to shared memory data structures. + * + * NOTES: + * (a) There are three kinds of shared memory data structures + * available to POSTGRES: fixed-size structures, queues and hash + * tables. Fixed-size structures contain things like global variables + * for a module and should never be allocated after the process + * initialization phase. Hash tables have a fixed maximum size, but + * their actual size can vary dynamically. When entries are added + * to the table, more space is allocated. Queues link data structures + * that have been allocated either as fixed size structures or as hash + * buckets. Each shared data structure has a string name to identify + * it (assigned in the module that declares it). + * + * (b) During initialization, each module looks for its + * shared data structures in a hash table called the "Binding Table". + * If the data structure is not present, the caller can allocate + * a new one and initialize it. If the data structure is present, + * the caller "attaches" to the structure by initializing a pointer + * in the local address space. + * The binding table has two purposes: first, it gives us + * a simple model of how the world looks when a backend process + * initializes. If something is present in the binding table, + * it is initialized. If it is not, it is uninitialized. Second, + * the binding table allows us to allocate shared memory on demand + * instead of trying to preallocate structures and hard-wire the + * sizes and locations in header files. If you are using a lot + * of shared memory in a lot of different places (and changing + * things during development), this is important. + * + * (c) memory allocation model: shared memory can never be + * freed, once allocated. Each hash table has its own free list, + * so hash buckets can be reused when an item is deleted. However, + * if one hash table grows very large and then shrinks, its space + * cannot be redistributed to other tables. We could build a simple + * hash bucket garbage collector if need be. Right now, it seems + * unnecessary. + * + * See InitSem() in sem.c for an example of how to use the + * binding table. + * + */ +#include <stdio.h> +#include <string.h> +#include "postgres.h" +#include "storage/ipc.h" +#include "storage/shmem.h" +#include "storage/spin.h" +#include "utils/hsearch.h" +#include "utils/elog.h" + +/* shared memory global variables */ + +unsigned long ShmemBase = 0; /* start and end address of + * shared memory + */ +static unsigned long ShmemEnd = 0; +static unsigned long ShmemSize = 0; /* current size (and default) */ + +SPINLOCK ShmemLock; /* lock for shared memory allocation */ + +SPINLOCK BindingLock; /* lock for binding table access */ + +static unsigned long *ShmemFreeStart = NULL; /* pointer to the OFFSET of + * first free shared memory + */ +static unsigned long *ShmemBindingTabOffset = NULL; /* start of the binding + * table (for bootstrap) + */ +static int ShmemBootstrap = FALSE; /* flag becomes true when shared mem + * is created by POSTMASTER + */ + +static HTAB *BindingTable = NULL; + +/* --------------------- + * ShmemBindingTabReset() - Resets the binding table to NULL.... + * useful when the postmaster destroys existing shared memory + * and creates all new segments after a backend crash. + * ---------------------- + */ +void +ShmemBindingTabReset() +{ + BindingTable = (HTAB *)NULL; +} + +/* + * CreateSharedRegion() -- + * + * This routine is called once by the postmaster to + * initialize the shared buffer pool. Assume there is + * only one postmaster so no synchronization is necessary + * until after this routine completes successfully. + * + * key is a unique identifier for the shmem region. + * size is the size of the region. + */ +static IpcMemoryId ShmemId; + +void +ShmemCreate(unsigned int key, unsigned int size) +{ + if (size) + ShmemSize = size; + /* create shared mem region */ + if ((ShmemId=IpcMemoryCreate(key,ShmemSize,IPCProtection)) + ==IpcMemCreationFailed) { + elog(FATAL,"ShmemCreate: cannot create region"); + exit(1); + } + + /* ShmemBootstrap is true if shared memory has been + * created, but not yet initialized. Only the + * postmaster/creator-of-all-things should have + * this flag set. + */ + ShmemBootstrap = TRUE; +} + +/* + * InitShmem() -- map region into process address space + * and initialize shared data structures. + * + */ +int +InitShmem(unsigned int key, unsigned int size) +{ + Pointer sharedRegion; + unsigned long currFreeSpace; + + HASHCTL info; + int hash_flags; + BindingEnt * result,item; + bool found; + IpcMemoryId shmid; + + /* if zero key, use default memory size */ + if (size) + ShmemSize = size; + + /* default key is 0 */ + + /* attach to shared memory region (SysV or BSD OS specific) */ + if (ShmemBootstrap && key == PrivateIPCKey) + /* if we are running backend alone */ + shmid = ShmemId; + else + shmid = IpcMemoryIdGet(IPCKeyGetBufferMemoryKey(key), ShmemSize); + sharedRegion = IpcMemoryAttach(shmid); + if (sharedRegion == NULL) { + elog(FATAL,"AttachSharedRegion: couldn't attach to shmem\n"); + return(FALSE); + } + + /* get pointers to the dimensions of shared memory */ + ShmemBase = (unsigned long) sharedRegion; + ShmemEnd = (unsigned long) sharedRegion + ShmemSize; + currFreeSpace = 0; + + /* First long in shared memory is the count of available space */ + ShmemFreeStart = (unsigned long *) ShmemBase; + /* next is a shmem pointer to the binding table */ + ShmemBindingTabOffset = ShmemFreeStart + 1; + + currFreeSpace += + sizeof(ShmemFreeStart) + sizeof(ShmemBindingTabOffset); + + /* bootstrap initialize spin locks so we can start to use the + * allocator and binding table. + */ + if (! InitSpinLocks(ShmemBootstrap, IPCKeyGetSpinLockSemaphoreKey(key))) { + return(FALSE); + } + + /* We have just allocated additional space for two spinlocks. + * Now setup the global free space count + */ + if (ShmemBootstrap) { + *ShmemFreeStart = currFreeSpace; + } + + /* if ShmemFreeStart is NULL, then the allocator won't work */ + Assert(*ShmemFreeStart); + + /* create OR attach to the shared memory binding table */ + info.keysize = BTABLE_KEYSIZE; + info.datasize = BTABLE_DATASIZE; + hash_flags = (HASH_ELEM); + + /* This will acquire the binding table lock, but not release it. */ + BindingTable = ShmemInitHash("BindingTable", + BTABLE_SIZE,BTABLE_SIZE, + &info,hash_flags); + + if (! BindingTable) { + elog(FATAL,"InitShmem: couldn't initialize Binding Table"); + return(FALSE); + } + + /* Now, check the binding table for an entry to the binding + * table. If there is an entry there, someone else created + * the table. Otherwise, we did and we have to initialize it. + */ + memset(item.key, 0, BTABLE_KEYSIZE); + strncpy(item.key,"BindingTable",BTABLE_KEYSIZE); + + result = (BindingEnt *) + hash_search(BindingTable,(char *) &item,HASH_ENTER, &found); + + + if (! result ) { + elog(FATAL,"InitShmem: corrupted binding table"); + return(FALSE); + } + + if (! found) { + /* bootstrapping shmem: we have to initialize the + * binding table now. + */ + + Assert(ShmemBootstrap); + result->location = MAKE_OFFSET(BindingTable->hctl); + *ShmemBindingTabOffset = result->location; + result->size = BTABLE_SIZE; + + ShmemBootstrap = FALSE; + + } else { + Assert(! ShmemBootstrap); + } + /* now release the lock acquired in ShmemHashInit */ + SpinRelease (BindingLock); + + Assert (result->location == MAKE_OFFSET(BindingTable->hctl)); + + return(TRUE); +} + +/* + * ShmemAlloc -- allocate word-aligned byte string from + * shared memory + * + * Assumes ShmemLock and ShmemFreeStart are initialized. + * Returns: real pointer to memory or NULL if we are out + * of space. Has to return a real pointer in order + * to be compatable with malloc(). + */ +long * +ShmemAlloc(unsigned long size) +{ + unsigned long tmpFree; + long *newSpace; + + /* + * ensure space is word aligned. + * + * Word-alignment is not good enough. We have to be more + * conservative: doubles need 8-byte alignment. (We probably only need + * this on RISC platforms but this is not a big waste of space.) + * - ay 12/94 + */ + if (size % sizeof(double)) + size += sizeof(double) - (size % sizeof(double)); + + Assert(*ShmemFreeStart); + + SpinAcquire(ShmemLock); + + tmpFree = *ShmemFreeStart + size; + if (tmpFree <= ShmemSize) { + newSpace = (long *)MAKE_PTR(*ShmemFreeStart); + *ShmemFreeStart += size; + } else { + newSpace = NULL; + } + + SpinRelease(ShmemLock); + + if (! newSpace) { + elog(NOTICE,"ShmemAlloc: out of memory "); + } + return(newSpace); +} + +/* + * ShmemIsValid -- test if an offset refers to valid shared memory + * + * Returns TRUE if the pointer is valid. + */ +int +ShmemIsValid(unsigned long addr) +{ + return ((addr<ShmemEnd) && (addr>=ShmemBase)); +} + +/* + * ShmemInitHash -- Create/Attach to and initialize + * shared memory hash table. + * + * Notes: + * + * assume caller is doing some kind of synchronization + * so that two people dont try to create/initialize the + * table at once. Use SpinAlloc() to create a spinlock + * for the structure before creating the structure itself. + */ +HTAB * +ShmemInitHash(char *name, /* table string name for binding */ + long init_size, /* initial size */ + long max_size, /* max size of the table */ + HASHCTL *infoP, /* info about key and bucket size */ + int hash_flags) /* info about infoP */ +{ + bool found; + long * location; + + /* shared memory hash tables have a fixed max size so that the + * control structures don't try to grow. The segbase is for + * calculating pointer values. The shared memory allocator + * must be specified. + */ + infoP->segbase = (long *) ShmemBase; + infoP->alloc = ShmemAlloc; + infoP->max_size = max_size; + hash_flags |= HASH_SHARED_MEM; + + /* look it up in the binding table */ + location = + ShmemInitStruct(name,my_log2(max_size) + sizeof(HHDR),&found); + + /* binding table is corrupted. Let someone else give the + * error message since they have more information + */ + if (location == NULL) { + return(0); + } + + /* it already exists, attach to it rather than allocate and + * initialize new space + */ + if (found) { + hash_flags |= HASH_ATTACH; + } + + /* these structures were allocated or bound in ShmemInitStruct */ + /* control information and parameters */ + infoP->hctl = (long *) location; + /* directory for hash lookup */ + infoP->dir = (long *) (location + sizeof(HHDR)); + + return(hash_create(init_size, infoP, hash_flags));; +} + +/* + * ShmemPIDLookup -- lookup process data structure using process id + * + * Returns: TRUE if no error. locationPtr is initialized if PID is + * found in the binding table. + * + * NOTES: + * only information about success or failure is the value of + * locationPtr. + */ +bool +ShmemPIDLookup(int pid, SHMEM_OFFSET* locationPtr) +{ + BindingEnt * result,item; + bool found; + + Assert (BindingTable); + memset(item.key, 0, BTABLE_KEYSIZE); + sprintf(item.key,"PID %d",pid); + + SpinAcquire(BindingLock); + result = (BindingEnt *) + hash_search(BindingTable,(char *) &item, HASH_ENTER, &found); + + if (! result) { + + SpinRelease(BindingLock); + elog(WARN,"ShmemInitPID: BindingTable corrupted"); + return(FALSE); + + } + + if (found) { + *locationPtr = result->location; + } else { + result->location = *locationPtr; + } + + SpinRelease(BindingLock); + return (TRUE); +} + +/* + * ShmemPIDDestroy -- destroy binding table entry for process + * using process id + * + * Returns: offset of the process struct in shared memory or + * INVALID_OFFSET if not found. + * + * Side Effect: removes the entry from the binding table + */ +SHMEM_OFFSET +ShmemPIDDestroy(int pid) +{ + BindingEnt * result,item; + bool found; + SHMEM_OFFSET location; + + Assert(BindingTable); + + memset(item.key, 0, BTABLE_KEYSIZE); + sprintf(item.key,"PID %d",pid); + + SpinAcquire(BindingLock); + result = (BindingEnt *) + hash_search(BindingTable,(char *) &item, HASH_REMOVE, &found); + + if (found) + location = result->location; + SpinRelease(BindingLock); + + if (! result) { + + elog(WARN,"ShmemPIDDestroy: PID table corrupted"); + return(INVALID_OFFSET); + + } + + if (found) + return (location); + else { + return(INVALID_OFFSET); + } +} + +/* + * ShmemInitStruct -- Create/attach to a structure in shared + * memory. + * + * This is called during initialization to find or allocate + * a data structure in shared memory. If no other processes + * have created the structure, this routine allocates space + * for it. If it exists already, a pointer to the existing + * table is returned. + * + * Returns: real pointer to the object. FoundPtr is TRUE if + * the object is already in the binding table (hence, already + * initialized). + */ +long * +ShmemInitStruct(char *name, unsigned long size, bool *foundPtr) +{ + BindingEnt * result,item; + long * structPtr; + + strncpy(item.key,name,BTABLE_KEYSIZE); + item.location = BAD_LOCATION; + + SpinAcquire(BindingLock); + + if (! BindingTable) { + /* Assert() is a macro now. substitutes inside quotes. */ + char *strname = "BindingTable"; + + /* If the binding table doesnt exist, we fake it. + * + * If we are creating the first binding table, then let + * shmemalloc() allocate the space for a new HTAB. Otherwise, + * find the old one and return that. Notice that the + * BindingLock is held until the binding table has been completely + * initialized. + */ + Assert (! strcmp(name,strname)) ; + if (ShmemBootstrap) { + /* in POSTMASTER/Single process */ + + *foundPtr = FALSE; + return((long *)ShmemAlloc(size)); + + } else { + Assert (ShmemBindingTabOffset); + + *foundPtr = TRUE; + return((long *)MAKE_PTR(*ShmemBindingTabOffset)); + } + + + } else { + /* look it up in the bindint table */ + result = (BindingEnt *) + hash_search(BindingTable,(char *) &item,HASH_ENTER, foundPtr); + } + + if (! result) { + + SpinRelease(BindingLock); + + elog(WARN,"ShmemInitStruct: Binding Table corrupted"); + return(NULL); + + } else if (*foundPtr) { + /* + * Structure is in the binding table so someone else has allocated + * it already. The size better be the same as the size we are + * trying to initialize to or there is a name conflict (or worse). + */ + if (result->size != size) { + SpinRelease(BindingLock); + + elog(NOTICE,"ShmemInitStruct: BindingTable entry size is wrong"); + /* let caller print its message too */ + return(NULL); + } + structPtr = (long *)MAKE_PTR(result->location); + } else { + + /* It isn't in the table yet. allocate and initialize it */ + structPtr = ShmemAlloc((long)size); + if (! structPtr) { + /* out of memory */ + Assert (BindingTable); + (void) hash_search(BindingTable,(char *) &item,HASH_REMOVE, foundPtr); + SpinRelease(BindingLock); + *foundPtr = FALSE; + + elog(NOTICE,"ShmemInitStruct: cannot allocate '%s'", + name); + return(NULL); + } + result->size = size; + result->location = MAKE_OFFSET(structPtr); + } + Assert (ShmemIsValid((unsigned long)structPtr)); + + SpinRelease(BindingLock); + return(structPtr); +} + + + |