/*------------------------------------------------------------------------- * * shmem.c * create shared memory and initialize shared memory data structures. * * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/storage/ipc/shmem.c,v 1.100 2008/01/01 19:45:51 momjian Exp $ * *------------------------------------------------------------------------- */ /* * POSTGRES processes share one or more regions of shared memory. * The shared memory is created by a postmaster and is inherited * by each backend via fork() (or, in some ports, via other OS-specific * methods). 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 shared memory * 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 within 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 "Shmem Index". * 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 shmem index 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 shmem index, * it is initialized. If it is not, it is uninitialized. Second, * the shmem index 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) In standard Unix-ish environments, individual backends do not * need to re-establish their local pointers into shared memory, because * they inherit correct values of those variables via fork() from the * postmaster. However, this does not work in the EXEC_BACKEND case. * In ports using EXEC_BACKEND, new backends have to set up their local * pointers using the method described in (b) above. * * (d) 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. */ #include "postgres.h" #include "access/transam.h" #include "miscadmin.h" #include "storage/lwlock.h" #include "storage/pg_shmem.h" #include "storage/shmem.h" #include "storage/spin.h" /* shared memory global variables */ static PGShmemHeader *ShmemSegHdr; /* shared mem segment header */ SHMEM_OFFSET ShmemBase; /* start address of shared memory */ static SHMEM_OFFSET ShmemEnd; /* end+1 address of shared memory */ slock_t *ShmemLock; /* spinlock for shared memory and LWLock * allocation */ static HTAB *ShmemIndex = NULL; /* primary index hashtable for shmem */ /* * InitShmemAccess() --- set up basic pointers to shared memory. * * Note: the argument should be declared "PGShmemHeader *seghdr", * but we use void to avoid having to include ipc.h in shmem.h. */ void InitShmemAccess(void *seghdr) { PGShmemHeader *shmhdr = (PGShmemHeader *) seghdr; ShmemSegHdr = shmhdr; ShmemBase = (SHMEM_OFFSET) shmhdr; ShmemEnd = ShmemBase + shmhdr->totalsize; } /* * InitShmemAllocation() --- set up shared-memory space allocation. * * This should be called only in the postmaster or a standalone backend. */ void InitShmemAllocation(void) { PGShmemHeader *shmhdr = ShmemSegHdr; Assert(shmhdr != NULL); /* * Initialize the spinlock used by ShmemAlloc. We have to do the space * allocation the hard way, since obviously ShmemAlloc can't be called * yet. */ ShmemLock = (slock_t *) (((char *) shmhdr) + shmhdr->freeoffset); shmhdr->freeoffset += MAXALIGN(sizeof(slock_t)); Assert(shmhdr->freeoffset <= shmhdr->totalsize); SpinLockInit(ShmemLock); /* ShmemIndex can't be set up yet (need LWLocks first) */ shmhdr->indexoffset = 0; ShmemIndex = (HTAB *) NULL; /* * Initialize ShmemVariableCache for transaction manager. (This doesn't * really belong here, but not worth moving.) */ ShmemVariableCache = (VariableCache) ShmemAlloc(sizeof(*ShmemVariableCache)); memset(ShmemVariableCache, 0, sizeof(*ShmemVariableCache)); } /* * ShmemAlloc -- allocate max-aligned chunk from shared memory * * Assumes ShmemLock and ShmemSegHdr 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 compatible with malloc(). */ void * ShmemAlloc(Size size) { Size newStart; Size newFree; void *newSpace; /* use volatile pointer to prevent code rearrangement */ volatile PGShmemHeader *shmemseghdr = ShmemSegHdr; /* * ensure all space is adequately aligned. */ size = MAXALIGN(size); Assert(shmemseghdr != NULL); SpinLockAcquire(ShmemLock); newStart = shmemseghdr->freeoffset; /* extra alignment for large requests, since they are probably buffers */ if (size >= BLCKSZ) newStart = BUFFERALIGN(newStart); newFree = newStart + size; if (newFree <= shmemseghdr->totalsize) { newSpace = (void *) MAKE_PTR(newStart); shmemseghdr->freeoffset = newFree; } else newSpace = NULL; SpinLockRelease(ShmemLock); if (!newSpace) ereport(WARNING, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of shared memory"))); return newSpace; } /* * ShmemIsValid -- test if an offset refers to valid shared memory * * Returns TRUE if the pointer is valid. */ bool ShmemIsValid(unsigned long addr) { return (addr < ShmemEnd) && (addr >= ShmemBase); } /* * InitShmemIndex() --- set up or attach to shmem index table. */ void InitShmemIndex(void) { HASHCTL info; int hash_flags; /* * Since ShmemInitHash calls ShmemInitStruct, which expects the ShmemIndex * hashtable to exist already, we have a bit of a circularity problem in * initializing the ShmemIndex itself. The special "ShmemIndex" hash * table name will tell ShmemInitStruct to fake it. */ /* create the shared memory shmem index */ info.keysize = SHMEM_INDEX_KEYSIZE; info.entrysize = sizeof(ShmemIndexEnt); hash_flags = HASH_ELEM; ShmemIndex = ShmemInitHash("ShmemIndex", SHMEM_INDEX_SIZE, SHMEM_INDEX_SIZE, &info, hash_flags); if (!ShmemIndex) elog(FATAL, "could not initialize Shmem Index"); } /* * ShmemInitHash -- Create and initialize, or attach to, a * shared memory hash table. * * We assume caller is doing some kind of synchronization * so that two people don't try to create/initialize the * table at once. * * max_size is the estimated maximum number of hashtable entries. This is * not a hard limit, but the access efficiency will degrade if it is * exceeded substantially (since it's used to compute directory size and * the hash table buckets will get overfull). * * init_size is the number of hashtable entries to preallocate. For a table * whose maximum size is certain, this should be equal to max_size; that * ensures that no run-time out-of-shared-memory failures can occur. */ HTAB * ShmemInitHash(const char *name, /* table string name for shmem index */ long init_size, /* initial table 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; void *location; /* * Hash tables allocated in shared memory have a fixed directory; it can't * grow or other backends wouldn't be able to find it. So, make sure we * make it big enough to start with. * * The shared memory allocator must be specified too. */ infoP->dsize = infoP->max_dsize = hash_select_dirsize(max_size); infoP->alloc = ShmemAlloc; hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE; /* look it up in the shmem index */ location = ShmemInitStruct(name, hash_get_shared_size(infoP, hash_flags), &found); /* * If fail, shmem index is corrupted. Let caller give the error message * since it has more information */ if (location == NULL) return NULL; /* * if it already exists, attach to it rather than allocate and initialize * new space */ if (found) hash_flags |= HASH_ATTACH; /* Pass location of hashtable header to hash_create */ infoP->hctl = (HASHHDR *) location; return hash_create(name, init_size, infoP, hash_flags); } /* * 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 process * has 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 shmem index (hence, already * initialized). */ void * ShmemInitStruct(const char *name, Size size, bool *foundPtr) { ShmemIndexEnt *result; void *structPtr; LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); if (!ShmemIndex) { PGShmemHeader *shmemseghdr = ShmemSegHdr; Assert(strcmp(name, "ShmemIndex") == 0); if (IsUnderPostmaster) { /* Must be initializing a (non-standalone) backend */ Assert(shmemseghdr->indexoffset != 0); structPtr = (void *) MAKE_PTR(shmemseghdr->indexoffset); *foundPtr = TRUE; } else { /* * If the shmem index doesn't exist, we are bootstrapping: we must * be trying to init the shmem index itself. * * Notice that the ShmemIndexLock is released before the shmem * index has been initialized. This should be OK because no other * process can be accessing shared memory yet. */ Assert(shmemseghdr->indexoffset == 0); structPtr = ShmemAlloc(size); shmemseghdr->indexoffset = MAKE_OFFSET(structPtr); *foundPtr = FALSE; } LWLockRelease(ShmemIndexLock); return structPtr; } /* look it up in the shmem index */ result = (ShmemIndexEnt *) hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr); if (!result) { LWLockRelease(ShmemIndexLock); ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of shared memory"))); } if (*foundPtr) { /* * Structure is in the shmem index 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) { LWLockRelease(ShmemIndexLock); elog(WARNING, "ShmemIndex entry size is wrong"); /* let caller print its message too */ return NULL; } structPtr = (void *) MAKE_PTR(result->location); } else { /* It isn't in the table yet. allocate and initialize it */ structPtr = ShmemAlloc(size); if (!structPtr) { /* out of memory */ Assert(ShmemIndex); hash_search(ShmemIndex, name, HASH_REMOVE, NULL); LWLockRelease(ShmemIndexLock); ereport(WARNING, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("could not allocate shared memory segment \"%s\"", name))); *foundPtr = FALSE; return NULL; } result->size = size; result->location = MAKE_OFFSET(structPtr); } Assert(ShmemIsValid((unsigned long) structPtr)); LWLockRelease(ShmemIndexLock); return structPtr; } /* * Add two Size values, checking for overflow */ Size add_size(Size s1, Size s2) { Size result; result = s1 + s2; /* We are assuming Size is an unsigned type here... */ if (result < s1 || result < s2) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("requested shared memory size overflows size_t"))); return result; } /* * Multiply two Size values, checking for overflow */ Size mul_size(Size s1, Size s2) { Size result; if (s1 == 0 || s2 == 0) return 0; result = s1 * s2; /* We are assuming Size is an unsigned type here... */ if (result / s2 != s1) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("requested shared memory size overflows size_t"))); return result; }