From 90aebf7f5242b11bc9576f5d9052d755336b1bcc Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 27 Sep 2001 19:10:02 +0000 Subject: Move s_lock.c and spin.c into lmgr subdirectory, which seems a much more reasonable location for them. --- src/backend/storage/buffer/Makefile | 10 +- src/backend/storage/buffer/s_lock.c | 364 --------------------------------- src/backend/storage/ipc/Makefile | 5 +- src/backend/storage/ipc/spin.c | 397 ------------------------------------ src/backend/storage/lmgr/Makefile | 10 +- src/backend/storage/lmgr/s_lock.c | 364 +++++++++++++++++++++++++++++++++ src/backend/storage/lmgr/spin.c | 397 ++++++++++++++++++++++++++++++++++++ 7 files changed, 773 insertions(+), 774 deletions(-) delete mode 100644 src/backend/storage/buffer/s_lock.c delete mode 100644 src/backend/storage/ipc/spin.c create mode 100644 src/backend/storage/lmgr/s_lock.c create mode 100644 src/backend/storage/lmgr/spin.c (limited to 'src') diff --git a/src/backend/storage/buffer/Makefile b/src/backend/storage/buffer/Makefile index 54bf25e1377..217f2eafb3c 100644 --- a/src/backend/storage/buffer/Makefile +++ b/src/backend/storage/buffer/Makefile @@ -4,7 +4,7 @@ # Makefile for storage/buffer # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/backend/storage/buffer/Makefile,v 1.15 2000/08/31 16:10:30 petere Exp $ +# $Header: /cvsroot/pgsql/src/backend/storage/buffer/Makefile,v 1.16 2001/09/27 19:10:02 tgl Exp $ # #------------------------------------------------------------------------- @@ -12,7 +12,7 @@ subdir = src/backend/storage/buffer top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = buf_table.o buf_init.o bufmgr.o freelist.o localbuf.o s_lock.o +OBJS = buf_table.o buf_init.o bufmgr.o freelist.o localbuf.o all: SUBSYS.o @@ -23,11 +23,7 @@ depend dep: $(CC) -MM $(CFLAGS) *.c >depend clean: - rm -f SUBSYS.o $(OBJS) s_lock_test - -s_lock_test: s_lock.c - $(CC) $(CFLAGS) -DS_LOCK_TEST=1 s_lock.c -o s_lock_test - ./s_lock_test + rm -f SUBSYS.o $(OBJS) ifeq (depend,$(wildcard depend)) include depend diff --git a/src/backend/storage/buffer/s_lock.c b/src/backend/storage/buffer/s_lock.c deleted file mode 100644 index f0bfbaa71f1..00000000000 --- a/src/backend/storage/buffer/s_lock.c +++ /dev/null @@ -1,364 +0,0 @@ -/*------------------------------------------------------------------------- - * - * s_lock.c - * Spinlock support routines - * - * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/storage/buffer/Attic/s_lock.c,v 1.38 2001/08/29 11:54:12 petere Exp $ - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include -#include - -#include "miscadmin.h" -#include "storage/s_lock.h" - - -/*---------- - * Each time we busy spin we select the next element of this array as the - * number of microseconds to wait. This accomplishes pseudo random back-off. - * - * Note that on most platforms, specified values will be rounded up to the - * next multiple of a clock tick, which is often ten milliseconds (10000). - * So, we are being way overoptimistic to assume that these different values - * are really different, other than the last. But there are a few platforms - * with better-than-usual timekeeping, and on these we will get pretty good - * pseudo-random behavior. - * - * Total time to cycle through all 20 entries will be at least 100 msec, - * more commonly (10 msec resolution) 220 msec, and on some platforms - * as much as 420 msec (when the remainder of the current tick cycle is - * ignored in deciding when to time out, as on FreeBSD and older Linuxen). - * We use the 100msec figure to figure max_spins, so actual timeouts may - * be as much as four times the nominal value, but will never be less. - *---------- - */ -#define S_NSPINCYCLE 20 - -int s_spincycle[S_NSPINCYCLE] = -{1, 10, 100, 1000, - 10000, 1000, 1000, 1000, - 10000, 1000, 1000, 10000, - 1000, 1000, 10000, 1000, - 10000, 1000, 10000, 30000 -}; - -#define AVG_SPINCYCLE 5000 /* average entry in microsec: 100ms / 20 */ - -#define DEFAULT_TIMEOUT (100*1000000) /* default timeout: 100 sec */ - - -/* - * s_lock_stuck() - complain about a stuck spinlock - */ -static void -s_lock_stuck(volatile slock_t *lock, const char *file, const int line) -{ - fprintf(stderr, - "\nFATAL: s_lock(%p) at %s:%d, stuck spinlock. Aborting.\n", - lock, file, line); - fprintf(stdout, - "\nFATAL: s_lock(%p) at %s:%d, stuck spinlock. Aborting.\n", - lock, file, line); - abort(); -} - - -/* - * s_lock_sleep() - sleep a pseudo-random amount of time, check for timeout - * - * The 'timeout' is given in microsec, or may be 0 for "infinity". Note that - * this will be a lower bound (a fairly loose lower bound, on most platforms). - * - * 'microsec' is the number of microsec to delay per loop. Normally - * 'microsec' is 0, specifying to use the next s_spincycle[] value. - * Some callers may pass a nonzero interval, specifying to use exactly that - * delay value rather than a pseudo-random delay. - */ -void -s_lock_sleep(unsigned spins, int timeout, int microsec, - volatile slock_t *lock, - const char *file, const int line) -{ - struct timeval delay; - - if (microsec > 0) - { - delay.tv_sec = microsec / 1000000; - delay.tv_usec = microsec % 1000000; - } - else - { - delay.tv_sec = 0; - delay.tv_usec = s_spincycle[spins % S_NSPINCYCLE]; - microsec = AVG_SPINCYCLE; /* use average to figure timeout */ - } - - if (timeout > 0) - { - unsigned max_spins = timeout / microsec; - - if (spins > max_spins) - s_lock_stuck(lock, file, line); - } - - (void) select(0, NULL, NULL, NULL, &delay); -} - - -/* - * s_lock(lock) - take a spinlock with backoff - */ -void -s_lock(volatile slock_t *lock, const char *file, const int line) -{ - unsigned spins = 0; - - /* - * If you are thinking of changing this code, be careful. This same - * loop logic is used in other places that call TAS() directly. - * - * While waiting for a lock, we check for cancel/die interrupts (which is - * a no-op if we are inside a critical section). The interrupt check - * can be omitted in places that know they are inside a critical - * section. Note that an interrupt must NOT be accepted after - * acquiring the lock. - */ - while (TAS(lock)) - { - s_lock_sleep(spins++, DEFAULT_TIMEOUT, 0, lock, file, line); - CHECK_FOR_INTERRUPTS(); - } -} - -/* - * Various TAS implementations that cannot live in s_lock.h as no inline - * definition exists (yet). - * In the future, get rid of tas.[cso] and fold it into this file. - */ - - -#if defined(__GNUC__) -/************************************************************************* - * All the gcc flavors that are not inlined - */ - - -#if defined(__m68k__) -static void -tas_dummy() /* really means: extern int tas(slock_t - * **lock); */ -{ - __asm__ __volatile__( - "\ -.global _tas \n\ -_tas: \n\ - movel sp@(0x4),a0 \n\ - tas a0@ \n\ - beq _success \n\ - moveq #-128,d0 \n\ - rts \n\ -_success: \n\ - moveq #0,d0 \n\ - rts \n\ -"); -} - -#endif /* __m68k__ */ - -#if defined(__APPLE__) && defined(__ppc__) -/* used in darwin. */ -/* We key off __APPLE__ here because this function differs from - * the LinuxPPC implementation only in compiler syntax. - */ -static void -tas_dummy() -{ - __asm__ __volatile__( - "\ - .globl tas \n\ - .globl _tas \n\ -_tas: \n\ -tas: \n\ - lwarx r5,0,r3 \n\ - cmpwi r5,0 \n\ - bne fail \n\ - addi r5,r5,1 \n\ - stwcx. r5,0,r3 \n\ - beq success \n\ -fail: li r3,1 \n\ - blr \n\ -success: \n\ - li r3,0 \n\ - blr \n\ -"); -} - -#endif /* __APPLE__ && __ppc__ */ - -#if defined(__powerpc__) -/* Note: need a nice gcc constrained asm version so it can be inlined */ -static void -tas_dummy() -{ - __asm__ __volatile__( - "\ -.global tas \n\ -tas: \n\ - lwarx 5,0,3 \n\ - cmpwi 5,0 \n\ - bne fail \n\ - addi 5,5,1 \n\ - stwcx. 5,0,3 \n\ - beq success \n\ -fail: li 3,1 \n\ - blr \n\ -success: \n\ - li 3,0 \n\ - blr \n\ -"); -} - -#endif /* __powerpc__ */ - -#if defined(__mips__) && !defined(__sgi) -static void -tas_dummy() -{ - __asm__ __volatile__( - "\ -.global tas \n\ -tas: \n\ - .frame $sp, 0, $31 \n\ - ll $14, 0($4) \n\ - or $15, $14, 1 \n\ - sc $15, 0($4) \n\ - beq $15, 0, fail\n\ - bne $14, 0, fail\n\ - li $2, 0 \n\ - .livereg 0x2000FF0E,0x00000FFF \n\ - j $31 \n\ -fail: \n\ - li $2, 1 \n\ - j $31 \n\ -"); -} - -#endif /* __mips__ && !__sgi */ - -#else /* not __GNUC__ */ -/*************************************************************************** - * All non gcc - */ - - - -#if defined(sun3) -static void -tas_dummy() /* really means: extern int tas(slock_t - * *lock); */ -{ - asm("LLA0:"); - asm(" .data"); - asm(" .text"); - asm("|#PROC# 04"); - asm(" .globl _tas"); - asm("_tas:"); - asm("|#PROLOGUE# 1"); - asm(" movel sp@(0x4),a0"); - asm(" tas a0@"); - asm(" beq LLA1"); - asm(" moveq #-128,d0"); - asm(" rts"); - asm("LLA1:"); - asm(" moveq #0,d0"); - asm(" rts"); - asm(" .data"); -} - -#endif /* sun3 */ - - - -#if defined(NEED_SPARC_TAS_ASM) -/* - * sparc machines not using gcc - */ -static void -tas_dummy() /* really means: extern int tas(slock_t - * *lock); */ -{ - asm(".seg \"data\""); - asm(".seg \"text\""); - asm("_tas:"); - - /* - * Sparc atomic test and set (sparc calls it "atomic load-store") - */ - asm("ldstub [%r8], %r8"); - asm("retl"); - asm("nop"); -} - -#endif /* NEED_SPARC_TAS_ASM */ - - - - -#if defined(NEED_I386_TAS_ASM) -/* non gcc i386 based things */ -#endif /* NEED_I386_TAS_ASM */ - - - -#endif /* not __GNUC__ */ - - - - -/*****************************************************************************/ -#if defined(S_LOCK_TEST) - -/* - * test program for verifying a port. - */ - -volatile slock_t test_lock; - -void -main() -{ - S_INIT_LOCK(&test_lock); - - if (!S_LOCK_FREE(&test_lock)) - { - printf("S_LOCK_TEST: failed, lock not initialized.\n"); - exit(1); - } - - S_LOCK(&test_lock); - - if (S_LOCK_FREE(&test_lock)) - { - printf("S_LOCK_TEST: failed, lock not locked\n"); - exit(2); - } - - printf("S_LOCK_TEST: this will hang for a few minutes and then abort\n"); - printf(" with a 'stuck spinlock' message if S_LOCK()\n"); - printf(" and TAS() are working.\n"); - s_lock(&test_lock, __FILE__, __LINE__); - - printf("S_LOCK_TEST: failed, lock not locked~\n"); - exit(3); - -} - -#endif /* S_LOCK_TEST */ diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile index 44c9744b587..585ae7f6eff 100644 --- a/src/backend/storage/ipc/Makefile +++ b/src/backend/storage/ipc/Makefile @@ -1,7 +1,7 @@ # # Makefile for storage/ipc # -# $Header: /cvsroot/pgsql/src/backend/storage/ipc/Makefile,v 1.15 2000/10/20 21:03:47 petere Exp $ +# $Header: /cvsroot/pgsql/src/backend/storage/ipc/Makefile,v 1.16 2001/09/27 19:10:02 tgl Exp $ # subdir = src/backend/storage/ipc @@ -15,8 +15,7 @@ override CFLAGS+= -fno-inline endif endif -OBJS = ipc.o ipci.o shmem.o shmqueue.o sinval.o \ - sinvaladt.o spin.o +OBJS = ipc.o ipci.o shmem.o shmqueue.o sinval.o sinvaladt.o all: SUBSYS.o diff --git a/src/backend/storage/ipc/spin.c b/src/backend/storage/ipc/spin.c deleted file mode 100644 index 05bf5acbbb3..00000000000 --- a/src/backend/storage/ipc/spin.c +++ /dev/null @@ -1,397 +0,0 @@ -/*------------------------------------------------------------------------- - * - * spin.c - * routines for managing spin locks - * - * POSTGRES has two kinds of locks: semaphores (which put the - * process to sleep) and spinlocks (which are supposed to be - * short term locks). Spinlocks are implemented via test-and-set (TAS) - * instructions if possible, else via semaphores. The semaphore method - * is too slow to be useful :-( - * - * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/storage/ipc/Attic/spin.c,v 1.33 2001/06/27 23:31:39 tgl Exp $ - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include -#if !defined(HAS_TEST_AND_SET) && defined(HAVE_SYS_SEM_H) -#include -#endif - -#include "miscadmin.h" -#include "storage/proc.h" -#include "storage/s_lock.h" - - -/* Probably should move these to an appropriate header file */ -extern SPINLOCK BufMgrLock; -extern SPINLOCK OidGenLockId; -extern SPINLOCK XidGenLockId; -extern SPINLOCK ControlFileLockId; -extern SPINLOCK ShmemLock; -extern SPINLOCK ShmemIndexLock; -extern SPINLOCK LockMgrLock; -extern SPINLOCK SInvalLock; -extern SPINLOCK ProcStructLock; -extern SPINLOCK FreeSpaceLock; -#ifdef STABLE_MEMORY_STORAGE -extern SPINLOCK MMCacheLock; -#endif - - -/* - * Initialize identifiers for permanent spinlocks during startup - * - * The same identifiers are used for both TAS and semaphore implementations, - * although in one case they are indexes into a shmem array and in the other - * they are semaphore numbers. - */ -static void -InitSpinLockIDs(void) -{ - BufMgrLock = (SPINLOCK) BUFMGRLOCKID; - OidGenLockId = (SPINLOCK) OIDGENLOCKID; - XidGenLockId = (SPINLOCK) XIDGENLOCKID; - ControlFileLockId = (SPINLOCK) CNTLFILELOCKID; - ShmemLock = (SPINLOCK) SHMEMLOCKID; - ShmemIndexLock = (SPINLOCK) SHMEMINDEXLOCKID; - LockMgrLock = (SPINLOCK) LOCKMGRLOCKID; - SInvalLock = (SPINLOCK) SINVALLOCKID; - ProcStructLock = (SPINLOCK) PROCSTRUCTLOCKID; - FreeSpaceLock = (SPINLOCK) FREESPACELOCKID; -#ifdef STABLE_MEMORY_STORAGE - MMCacheLock = (SPINLOCK) MMCACHELOCKID; -#endif -} - - -#ifdef HAS_TEST_AND_SET - -/* real spin lock implementation */ - -typedef struct slock -{ - slock_t shlock; -} SLock; - -#ifdef LOCK_DEBUG -bool Trace_spinlocks = false; - -inline static void -PRINT_SLDEBUG(const char *where, SPINLOCK lockid, const SLock *lock) -{ - if (Trace_spinlocks) - elog(DEBUG, "%s: id=%d", where, lockid); -} - -#else /* not LOCK_DEBUG */ -#define PRINT_SLDEBUG(a,b,c) -#endif /* not LOCK_DEBUG */ - - -static SLock *SLockArray = NULL; - -#define SLOCKMEMORYSIZE ((int) MAX_SPINS * sizeof(SLock)) - -/* - * SLockShmemSize --- return shared-memory space needed - */ -int -SLockShmemSize(void) -{ - return MAXALIGN(SLOCKMEMORYSIZE); -} - -/* - * CreateSpinlocks --- create and initialize spinlocks during startup - */ -void -CreateSpinlocks(PGShmemHeader *seghdr) -{ - int id; - - /* - * We must allocate the space "by hand" because shmem.c isn't up yet - */ - SLockArray = (SLock *) (((char *) seghdr) + seghdr->freeoffset); - seghdr->freeoffset += MAXALIGN(SLOCKMEMORYSIZE); - Assert(seghdr->freeoffset <= seghdr->totalsize); - - /* - * Initialize all spinlocks to "unlocked" state - */ - for (id = 0; id < (int) MAX_SPINS; id++) - { - SLock *slckP = &(SLockArray[id]); - - S_INIT_LOCK(&(slckP->shlock)); - } - - /* - * Assign indexes for fixed spinlocks - */ - InitSpinLockIDs(); -} - -void -SpinAcquire(SPINLOCK lockid) -{ - SLock *slckP = &(SLockArray[lockid]); - - PRINT_SLDEBUG("SpinAcquire", lockid, slckP); - - /* - * Acquire the lock, then record that we have done so (for recovery in - * case of elog(ERROR) while holding the lock). Note we assume here - * that S_LOCK will not accept cancel/die interrupts once it has - * acquired the lock. However, interrupts should be accepted while - * waiting, if InterruptHoldoffCount is zero. - */ - S_LOCK(&(slckP->shlock)); - PROC_INCR_SLOCK(lockid); - - /* - * Lock out cancel/die interrupts until we exit the code section - * protected by the spinlock. This ensures that interrupts will not - * interfere with manipulations of data structures in shared memory. - */ - HOLD_INTERRUPTS(); - - PRINT_SLDEBUG("SpinAcquire/done", lockid, slckP); -} - -void -SpinRelease(SPINLOCK lockid) -{ - SLock *slckP = &(SLockArray[lockid]); - - PRINT_SLDEBUG("SpinRelease", lockid, slckP); - - /* - * Check that we are actually holding the lock we are releasing. This - * can be done only after MyProc has been initialized. - */ - Assert(!MyProc || MyProc->sLocks[lockid] > 0); - - /* - * Record that we no longer hold the spinlock, and release it. - */ - PROC_DECR_SLOCK(lockid); - S_UNLOCK(&(slckP->shlock)); - - /* - * Exit the interrupt holdoff entered in SpinAcquire(). - */ - RESUME_INTERRUPTS(); - - PRINT_SLDEBUG("SpinRelease/done", lockid, slckP); -} - -#else /* !HAS_TEST_AND_SET */ - -/* - * No TAS, so spinlocks are implemented using SysV semaphores. - * - * We support two slightly different APIs here: SpinAcquire/SpinRelease - * work with SPINLOCK integer indexes for the permanent spinlocks, which - * are all assumed to live in the first spinlock semaphore set. There - * is also an emulation of the s_lock.h TAS-spinlock macros; for that case, - * typedef slock_t stores the semId and sem number of the sema to use. - * The semas needed are created by CreateSpinlocks and doled out by - * s_init_lock_sema. - * - * Since many systems have a rather small SEMMSL limit on semas per set, - * we allocate the semaphores required in sets of SPINLOCKS_PER_SET semas. - * This value is deliberately made equal to PROC_NSEMS_PER_SET so that all - * sema sets allocated by Postgres will be the same size; that eases the - * semaphore-recycling logic in IpcSemaphoreCreate(). - * - * Note that the SpinLockIds array is not in shared memory; it is filled - * by the postmaster and then inherited through fork() by backends. This - * is OK because its contents do not change after shmem initialization. - */ - -#define SPINLOCKS_PER_SET PROC_NSEMS_PER_SET - -static IpcSemaphoreId *SpinLockIds = NULL; - -static int numSpinSets = 0; /* number of sema sets used */ -static int numSpinLocks = 0; /* total number of semas allocated */ -static int nextSpinLock = 0; /* next free spinlock index */ - -static void SpinFreeAllSemaphores(void); - -/* - * SLockShmemSize --- return shared-memory space needed - */ -int -SLockShmemSize(void) -{ - return 0; -} - -/* - * CreateSpinlocks --- create and initialize spinlocks during startup - */ -void -CreateSpinlocks(PGShmemHeader *seghdr) -{ - int i; - - if (SpinLockIds == NULL) - { - - /* - * Compute number of spinlocks needed. If this logic gets any - * more complicated, it should be distributed into the affected - * modules, similar to the way shmem space estimation is handled. - * - * For now, though, we just need the fixed spinlocks (MAX_SPINS), two - * spinlocks per shared disk buffer, and four spinlocks for XLOG. - */ - numSpinLocks = (int) MAX_SPINS + 2 * NBuffers + 4; - - /* might as well round up to a multiple of SPINLOCKS_PER_SET */ - numSpinSets = (numSpinLocks - 1) / SPINLOCKS_PER_SET + 1; - numSpinLocks = numSpinSets * SPINLOCKS_PER_SET; - - SpinLockIds = (IpcSemaphoreId *) - malloc(numSpinSets * sizeof(IpcSemaphoreId)); - Assert(SpinLockIds != NULL); - } - - for (i = 0; i < numSpinSets; i++) - SpinLockIds[i] = -1; - - /* - * Arrange to delete semas on exit --- set this up now so that we will - * clean up if allocation fails. We use our own freeproc, rather than - * IpcSemaphoreCreate's removeOnExit option, because we don't want to - * fill up the on_shmem_exit list with a separate entry for each - * semaphore set. - */ - on_shmem_exit(SpinFreeAllSemaphores, 0); - - /* Create sema sets and set all semas to count 1 */ - for (i = 0; i < numSpinSets; i++) - { - SpinLockIds[i] = IpcSemaphoreCreate(SPINLOCKS_PER_SET, - IPCProtection, - 1, - false); - } - - /* - * Assign indexes for fixed spinlocks - */ - Assert(MAX_SPINS <= SPINLOCKS_PER_SET); - InitSpinLockIDs(); - - /* Init counter for allocating dynamic spinlocks */ - nextSpinLock = MAX_SPINS; -} - -/* - * SpinFreeAllSemaphores - - * called at shmem_exit time, ie when exiting the postmaster or - * destroying shared state for a failed set of backends. - * Free up all the semaphores allocated for spinlocks. - */ -static void -SpinFreeAllSemaphores(void) -{ - int i; - - for (i = 0; i < numSpinSets; i++) - { - if (SpinLockIds[i] >= 0) - IpcSemaphoreKill(SpinLockIds[i]); - } - free(SpinLockIds); - SpinLockIds = NULL; -} - -/* - * SpinAcquire -- grab a fixed spinlock - * - * FAILS if the semaphore is corrupted. - */ -void -SpinAcquire(SPINLOCK lock) -{ - - /* - * See the TAS() version of this routine for primary commentary. - * - * NOTE we must pass interruptOK = false to IpcSemaphoreLock, to ensure - * that a cancel/die interrupt cannot prevent us from recording - * ownership of a lock we have just acquired. - */ - IpcSemaphoreLock(SpinLockIds[0], lock, false); - PROC_INCR_SLOCK(lock); - HOLD_INTERRUPTS(); -} - -/* - * SpinRelease -- release a fixed spin lock - * - * FAILS if the semaphore is corrupted - */ -void -SpinRelease(SPINLOCK lock) -{ - /* See the TAS() version of this routine for commentary */ -#ifdef USE_ASSERT_CHECKING - /* Check it's locked */ - int semval; - - semval = IpcSemaphoreGetValue(SpinLockIds[0], lock); - Assert(semval < 1); -#endif - Assert(!MyProc || MyProc->sLocks[lockid] > 0); - PROC_DECR_SLOCK(lock); - IpcSemaphoreUnlock(SpinLockIds[0], lock); - RESUME_INTERRUPTS(); -} - -/* - * s_lock.h hardware-spinlock emulation - */ - -void -s_init_lock_sema(volatile slock_t *lock) -{ - if (nextSpinLock >= numSpinLocks) - elog(FATAL, "s_init_lock_sema: not enough semaphores"); - lock->semId = SpinLockIds[nextSpinLock / SPINLOCKS_PER_SET]; - lock->sem = nextSpinLock % SPINLOCKS_PER_SET; - nextSpinLock++; -} - -void -s_unlock_sema(volatile slock_t *lock) -{ - IpcSemaphoreUnlock(lock->semId, lock->sem); -} - -bool -s_lock_free_sema(volatile slock_t *lock) -{ - return IpcSemaphoreGetValue(lock->semId, lock->sem) > 0; -} - -int -tas_sema(volatile slock_t *lock) -{ - /* Note that TAS macros return 0 if *success* */ - return !IpcSemaphoreTryLock(lock->semId, lock->sem); -} - -#endif /* !HAS_TEST_AND_SET */ diff --git a/src/backend/storage/lmgr/Makefile b/src/backend/storage/lmgr/Makefile index 37cf81b1aa7..bab933c8422 100644 --- a/src/backend/storage/lmgr/Makefile +++ b/src/backend/storage/lmgr/Makefile @@ -4,7 +4,7 @@ # Makefile for storage/lmgr # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/backend/storage/lmgr/Makefile,v 1.15 2001/01/25 03:31:16 tgl Exp $ +# $Header: /cvsroot/pgsql/src/backend/storage/lmgr/Makefile,v 1.16 2001/09/27 19:10:02 tgl Exp $ # #------------------------------------------------------------------------- @@ -12,18 +12,22 @@ subdir = src/backend/storage/lmgr top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = lmgr.o lock.o proc.o deadlock.o +OBJS = lmgr.o lock.o proc.o deadlock.o spin.o s_lock.o all: SUBSYS.o SUBSYS.o: $(OBJS) $(LD) $(LDREL) $(LDOUT) SUBSYS.o $(OBJS) +s_lock_test: s_lock.c + $(CC) $(CPPFLAGS) $(CFLAGS) -DS_LOCK_TEST=1 s_lock.c -o s_lock_test + ./s_lock_test + depend dep: $(CC) -MM $(CFLAGS) *.c >depend clean: - rm -f SUBSYS.o $(OBJS) + rm -f SUBSYS.o $(OBJS) s_lock_test ifeq (depend,$(wildcard depend)) include depend diff --git a/src/backend/storage/lmgr/s_lock.c b/src/backend/storage/lmgr/s_lock.c new file mode 100644 index 00000000000..6dc38b5955c --- /dev/null +++ b/src/backend/storage/lmgr/s_lock.c @@ -0,0 +1,364 @@ +/*------------------------------------------------------------------------- + * + * s_lock.c + * Spinlock support routines + * + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/s_lock.c,v 1.1 2001/09/27 19:10:02 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include + +#include "miscadmin.h" +#include "storage/s_lock.h" + + +/*---------- + * Each time we busy spin we select the next element of this array as the + * number of microseconds to wait. This accomplishes pseudo random back-off. + * + * Note that on most platforms, specified values will be rounded up to the + * next multiple of a clock tick, which is often ten milliseconds (10000). + * So, we are being way overoptimistic to assume that these different values + * are really different, other than the last. But there are a few platforms + * with better-than-usual timekeeping, and on these we will get pretty good + * pseudo-random behavior. + * + * Total time to cycle through all 20 entries will be at least 100 msec, + * more commonly (10 msec resolution) 220 msec, and on some platforms + * as much as 420 msec (when the remainder of the current tick cycle is + * ignored in deciding when to time out, as on FreeBSD and older Linuxen). + * We use the 100msec figure to figure max_spins, so actual timeouts may + * be as much as four times the nominal value, but will never be less. + *---------- + */ +#define S_NSPINCYCLE 20 + +int s_spincycle[S_NSPINCYCLE] = +{1, 10, 100, 1000, + 10000, 1000, 1000, 1000, + 10000, 1000, 1000, 10000, + 1000, 1000, 10000, 1000, + 10000, 1000, 10000, 30000 +}; + +#define AVG_SPINCYCLE 5000 /* average entry in microsec: 100ms / 20 */ + +#define DEFAULT_TIMEOUT (100*1000000) /* default timeout: 100 sec */ + + +/* + * s_lock_stuck() - complain about a stuck spinlock + */ +static void +s_lock_stuck(volatile slock_t *lock, const char *file, const int line) +{ + fprintf(stderr, + "\nFATAL: s_lock(%p) at %s:%d, stuck spinlock. Aborting.\n", + lock, file, line); + fprintf(stdout, + "\nFATAL: s_lock(%p) at %s:%d, stuck spinlock. Aborting.\n", + lock, file, line); + abort(); +} + + +/* + * s_lock_sleep() - sleep a pseudo-random amount of time, check for timeout + * + * The 'timeout' is given in microsec, or may be 0 for "infinity". Note that + * this will be a lower bound (a fairly loose lower bound, on most platforms). + * + * 'microsec' is the number of microsec to delay per loop. Normally + * 'microsec' is 0, specifying to use the next s_spincycle[] value. + * Some callers may pass a nonzero interval, specifying to use exactly that + * delay value rather than a pseudo-random delay. + */ +void +s_lock_sleep(unsigned spins, int timeout, int microsec, + volatile slock_t *lock, + const char *file, const int line) +{ + struct timeval delay; + + if (microsec > 0) + { + delay.tv_sec = microsec / 1000000; + delay.tv_usec = microsec % 1000000; + } + else + { + delay.tv_sec = 0; + delay.tv_usec = s_spincycle[spins % S_NSPINCYCLE]; + microsec = AVG_SPINCYCLE; /* use average to figure timeout */ + } + + if (timeout > 0) + { + unsigned max_spins = timeout / microsec; + + if (spins > max_spins) + s_lock_stuck(lock, file, line); + } + + (void) select(0, NULL, NULL, NULL, &delay); +} + + +/* + * s_lock(lock) - take a spinlock with backoff + */ +void +s_lock(volatile slock_t *lock, const char *file, const int line) +{ + unsigned spins = 0; + + /* + * If you are thinking of changing this code, be careful. This same + * loop logic is used in other places that call TAS() directly. + * + * While waiting for a lock, we check for cancel/die interrupts (which is + * a no-op if we are inside a critical section). The interrupt check + * can be omitted in places that know they are inside a critical + * section. Note that an interrupt must NOT be accepted after + * acquiring the lock. + */ + while (TAS(lock)) + { + s_lock_sleep(spins++, DEFAULT_TIMEOUT, 0, lock, file, line); + CHECK_FOR_INTERRUPTS(); + } +} + +/* + * Various TAS implementations that cannot live in s_lock.h as no inline + * definition exists (yet). + * In the future, get rid of tas.[cso] and fold it into this file. + */ + + +#if defined(__GNUC__) +/************************************************************************* + * All the gcc flavors that are not inlined + */ + + +#if defined(__m68k__) +static void +tas_dummy() /* really means: extern int tas(slock_t + * **lock); */ +{ + __asm__ __volatile__( + "\ +.global _tas \n\ +_tas: \n\ + movel sp@(0x4),a0 \n\ + tas a0@ \n\ + beq _success \n\ + moveq #-128,d0 \n\ + rts \n\ +_success: \n\ + moveq #0,d0 \n\ + rts \n\ +"); +} + +#endif /* __m68k__ */ + +#if defined(__APPLE__) && defined(__ppc__) +/* used in darwin. */ +/* We key off __APPLE__ here because this function differs from + * the LinuxPPC implementation only in compiler syntax. + */ +static void +tas_dummy() +{ + __asm__ __volatile__( + "\ + .globl tas \n\ + .globl _tas \n\ +_tas: \n\ +tas: \n\ + lwarx r5,0,r3 \n\ + cmpwi r5,0 \n\ + bne fail \n\ + addi r5,r5,1 \n\ + stwcx. r5,0,r3 \n\ + beq success \n\ +fail: li r3,1 \n\ + blr \n\ +success: \n\ + li r3,0 \n\ + blr \n\ +"); +} + +#endif /* __APPLE__ && __ppc__ */ + +#if defined(__powerpc__) +/* Note: need a nice gcc constrained asm version so it can be inlined */ +static void +tas_dummy() +{ + __asm__ __volatile__( + "\ +.global tas \n\ +tas: \n\ + lwarx 5,0,3 \n\ + cmpwi 5,0 \n\ + bne fail \n\ + addi 5,5,1 \n\ + stwcx. 5,0,3 \n\ + beq success \n\ +fail: li 3,1 \n\ + blr \n\ +success: \n\ + li 3,0 \n\ + blr \n\ +"); +} + +#endif /* __powerpc__ */ + +#if defined(__mips__) && !defined(__sgi) +static void +tas_dummy() +{ + __asm__ __volatile__( + "\ +.global tas \n\ +tas: \n\ + .frame $sp, 0, $31 \n\ + ll $14, 0($4) \n\ + or $15, $14, 1 \n\ + sc $15, 0($4) \n\ + beq $15, 0, fail\n\ + bne $14, 0, fail\n\ + li $2, 0 \n\ + .livereg 0x2000FF0E,0x00000FFF \n\ + j $31 \n\ +fail: \n\ + li $2, 1 \n\ + j $31 \n\ +"); +} + +#endif /* __mips__ && !__sgi */ + +#else /* not __GNUC__ */ +/*************************************************************************** + * All non gcc + */ + + + +#if defined(sun3) +static void +tas_dummy() /* really means: extern int tas(slock_t + * *lock); */ +{ + asm("LLA0:"); + asm(" .data"); + asm(" .text"); + asm("|#PROC# 04"); + asm(" .globl _tas"); + asm("_tas:"); + asm("|#PROLOGUE# 1"); + asm(" movel sp@(0x4),a0"); + asm(" tas a0@"); + asm(" beq LLA1"); + asm(" moveq #-128,d0"); + asm(" rts"); + asm("LLA1:"); + asm(" moveq #0,d0"); + asm(" rts"); + asm(" .data"); +} + +#endif /* sun3 */ + + + +#if defined(NEED_SPARC_TAS_ASM) +/* + * sparc machines not using gcc + */ +static void +tas_dummy() /* really means: extern int tas(slock_t + * *lock); */ +{ + asm(".seg \"data\""); + asm(".seg \"text\""); + asm("_tas:"); + + /* + * Sparc atomic test and set (sparc calls it "atomic load-store") + */ + asm("ldstub [%r8], %r8"); + asm("retl"); + asm("nop"); +} + +#endif /* NEED_SPARC_TAS_ASM */ + + + + +#if defined(NEED_I386_TAS_ASM) +/* non gcc i386 based things */ +#endif /* NEED_I386_TAS_ASM */ + + + +#endif /* not __GNUC__ */ + + + + +/*****************************************************************************/ +#if defined(S_LOCK_TEST) + +/* + * test program for verifying a port. + */ + +volatile slock_t test_lock; + +void +main() +{ + S_INIT_LOCK(&test_lock); + + if (!S_LOCK_FREE(&test_lock)) + { + printf("S_LOCK_TEST: failed, lock not initialized.\n"); + exit(1); + } + + S_LOCK(&test_lock); + + if (S_LOCK_FREE(&test_lock)) + { + printf("S_LOCK_TEST: failed, lock not locked\n"); + exit(2); + } + + printf("S_LOCK_TEST: this will hang for a few minutes and then abort\n"); + printf(" with a 'stuck spinlock' message if S_LOCK()\n"); + printf(" and TAS() are working.\n"); + s_lock(&test_lock, __FILE__, __LINE__); + + printf("S_LOCK_TEST: failed, lock not locked~\n"); + exit(3); + +} + +#endif /* S_LOCK_TEST */ diff --git a/src/backend/storage/lmgr/spin.c b/src/backend/storage/lmgr/spin.c new file mode 100644 index 00000000000..a1284cbbcae --- /dev/null +++ b/src/backend/storage/lmgr/spin.c @@ -0,0 +1,397 @@ +/*------------------------------------------------------------------------- + * + * spin.c + * routines for managing spin locks + * + * POSTGRES has two kinds of locks: semaphores (which put the + * process to sleep) and spinlocks (which are supposed to be + * short term locks). Spinlocks are implemented via test-and-set (TAS) + * instructions if possible, else via semaphores. The semaphore method + * is too slow to be useful :-( + * + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/spin.c,v 1.1 2001/09/27 19:10:02 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#if !defined(HAS_TEST_AND_SET) && defined(HAVE_SYS_SEM_H) +#include +#endif + +#include "miscadmin.h" +#include "storage/proc.h" +#include "storage/s_lock.h" + + +/* Probably should move these to an appropriate header file */ +extern SPINLOCK BufMgrLock; +extern SPINLOCK OidGenLockId; +extern SPINLOCK XidGenLockId; +extern SPINLOCK ControlFileLockId; +extern SPINLOCK ShmemLock; +extern SPINLOCK ShmemIndexLock; +extern SPINLOCK LockMgrLock; +extern SPINLOCK SInvalLock; +extern SPINLOCK ProcStructLock; +extern SPINLOCK FreeSpaceLock; +#ifdef STABLE_MEMORY_STORAGE +extern SPINLOCK MMCacheLock; +#endif + + +/* + * Initialize identifiers for permanent spinlocks during startup + * + * The same identifiers are used for both TAS and semaphore implementations, + * although in one case they are indexes into a shmem array and in the other + * they are semaphore numbers. + */ +static void +InitSpinLockIDs(void) +{ + BufMgrLock = (SPINLOCK) BUFMGRLOCKID; + OidGenLockId = (SPINLOCK) OIDGENLOCKID; + XidGenLockId = (SPINLOCK) XIDGENLOCKID; + ControlFileLockId = (SPINLOCK) CNTLFILELOCKID; + ShmemLock = (SPINLOCK) SHMEMLOCKID; + ShmemIndexLock = (SPINLOCK) SHMEMINDEXLOCKID; + LockMgrLock = (SPINLOCK) LOCKMGRLOCKID; + SInvalLock = (SPINLOCK) SINVALLOCKID; + ProcStructLock = (SPINLOCK) PROCSTRUCTLOCKID; + FreeSpaceLock = (SPINLOCK) FREESPACELOCKID; +#ifdef STABLE_MEMORY_STORAGE + MMCacheLock = (SPINLOCK) MMCACHELOCKID; +#endif +} + + +#ifdef HAS_TEST_AND_SET + +/* real spin lock implementation */ + +typedef struct slock +{ + slock_t shlock; +} SLock; + +#ifdef LOCK_DEBUG +bool Trace_spinlocks = false; + +inline static void +PRINT_SLDEBUG(const char *where, SPINLOCK lockid, const SLock *lock) +{ + if (Trace_spinlocks) + elog(DEBUG, "%s: id=%d", where, lockid); +} + +#else /* not LOCK_DEBUG */ +#define PRINT_SLDEBUG(a,b,c) +#endif /* not LOCK_DEBUG */ + + +static SLock *SLockArray = NULL; + +#define SLOCKMEMORYSIZE ((int) MAX_SPINS * sizeof(SLock)) + +/* + * SLockShmemSize --- return shared-memory space needed + */ +int +SLockShmemSize(void) +{ + return MAXALIGN(SLOCKMEMORYSIZE); +} + +/* + * CreateSpinlocks --- create and initialize spinlocks during startup + */ +void +CreateSpinlocks(PGShmemHeader *seghdr) +{ + int id; + + /* + * We must allocate the space "by hand" because shmem.c isn't up yet + */ + SLockArray = (SLock *) (((char *) seghdr) + seghdr->freeoffset); + seghdr->freeoffset += MAXALIGN(SLOCKMEMORYSIZE); + Assert(seghdr->freeoffset <= seghdr->totalsize); + + /* + * Initialize all spinlocks to "unlocked" state + */ + for (id = 0; id < (int) MAX_SPINS; id++) + { + SLock *slckP = &(SLockArray[id]); + + S_INIT_LOCK(&(slckP->shlock)); + } + + /* + * Assign indexes for fixed spinlocks + */ + InitSpinLockIDs(); +} + +void +SpinAcquire(SPINLOCK lockid) +{ + SLock *slckP = &(SLockArray[lockid]); + + PRINT_SLDEBUG("SpinAcquire", lockid, slckP); + + /* + * Acquire the lock, then record that we have done so (for recovery in + * case of elog(ERROR) while holding the lock). Note we assume here + * that S_LOCK will not accept cancel/die interrupts once it has + * acquired the lock. However, interrupts should be accepted while + * waiting, if InterruptHoldoffCount is zero. + */ + S_LOCK(&(slckP->shlock)); + PROC_INCR_SLOCK(lockid); + + /* + * Lock out cancel/die interrupts until we exit the code section + * protected by the spinlock. This ensures that interrupts will not + * interfere with manipulations of data structures in shared memory. + */ + HOLD_INTERRUPTS(); + + PRINT_SLDEBUG("SpinAcquire/done", lockid, slckP); +} + +void +SpinRelease(SPINLOCK lockid) +{ + SLock *slckP = &(SLockArray[lockid]); + + PRINT_SLDEBUG("SpinRelease", lockid, slckP); + + /* + * Check that we are actually holding the lock we are releasing. This + * can be done only after MyProc has been initialized. + */ + Assert(!MyProc || MyProc->sLocks[lockid] > 0); + + /* + * Record that we no longer hold the spinlock, and release it. + */ + PROC_DECR_SLOCK(lockid); + S_UNLOCK(&(slckP->shlock)); + + /* + * Exit the interrupt holdoff entered in SpinAcquire(). + */ + RESUME_INTERRUPTS(); + + PRINT_SLDEBUG("SpinRelease/done", lockid, slckP); +} + +#else /* !HAS_TEST_AND_SET */ + +/* + * No TAS, so spinlocks are implemented using SysV semaphores. + * + * We support two slightly different APIs here: SpinAcquire/SpinRelease + * work with SPINLOCK integer indexes for the permanent spinlocks, which + * are all assumed to live in the first spinlock semaphore set. There + * is also an emulation of the s_lock.h TAS-spinlock macros; for that case, + * typedef slock_t stores the semId and sem number of the sema to use. + * The semas needed are created by CreateSpinlocks and doled out by + * s_init_lock_sema. + * + * Since many systems have a rather small SEMMSL limit on semas per set, + * we allocate the semaphores required in sets of SPINLOCKS_PER_SET semas. + * This value is deliberately made equal to PROC_NSEMS_PER_SET so that all + * sema sets allocated by Postgres will be the same size; that eases the + * semaphore-recycling logic in IpcSemaphoreCreate(). + * + * Note that the SpinLockIds array is not in shared memory; it is filled + * by the postmaster and then inherited through fork() by backends. This + * is OK because its contents do not change after shmem initialization. + */ + +#define SPINLOCKS_PER_SET PROC_NSEMS_PER_SET + +static IpcSemaphoreId *SpinLockIds = NULL; + +static int numSpinSets = 0; /* number of sema sets used */ +static int numSpinLocks = 0; /* total number of semas allocated */ +static int nextSpinLock = 0; /* next free spinlock index */ + +static void SpinFreeAllSemaphores(void); + +/* + * SLockShmemSize --- return shared-memory space needed + */ +int +SLockShmemSize(void) +{ + return 0; +} + +/* + * CreateSpinlocks --- create and initialize spinlocks during startup + */ +void +CreateSpinlocks(PGShmemHeader *seghdr) +{ + int i; + + if (SpinLockIds == NULL) + { + + /* + * Compute number of spinlocks needed. If this logic gets any + * more complicated, it should be distributed into the affected + * modules, similar to the way shmem space estimation is handled. + * + * For now, though, we just need the fixed spinlocks (MAX_SPINS), two + * spinlocks per shared disk buffer, and four spinlocks for XLOG. + */ + numSpinLocks = (int) MAX_SPINS + 2 * NBuffers + 4; + + /* might as well round up to a multiple of SPINLOCKS_PER_SET */ + numSpinSets = (numSpinLocks - 1) / SPINLOCKS_PER_SET + 1; + numSpinLocks = numSpinSets * SPINLOCKS_PER_SET; + + SpinLockIds = (IpcSemaphoreId *) + malloc(numSpinSets * sizeof(IpcSemaphoreId)); + Assert(SpinLockIds != NULL); + } + + for (i = 0; i < numSpinSets; i++) + SpinLockIds[i] = -1; + + /* + * Arrange to delete semas on exit --- set this up now so that we will + * clean up if allocation fails. We use our own freeproc, rather than + * IpcSemaphoreCreate's removeOnExit option, because we don't want to + * fill up the on_shmem_exit list with a separate entry for each + * semaphore set. + */ + on_shmem_exit(SpinFreeAllSemaphores, 0); + + /* Create sema sets and set all semas to count 1 */ + for (i = 0; i < numSpinSets; i++) + { + SpinLockIds[i] = IpcSemaphoreCreate(SPINLOCKS_PER_SET, + IPCProtection, + 1, + false); + } + + /* + * Assign indexes for fixed spinlocks + */ + Assert(MAX_SPINS <= SPINLOCKS_PER_SET); + InitSpinLockIDs(); + + /* Init counter for allocating dynamic spinlocks */ + nextSpinLock = MAX_SPINS; +} + +/* + * SpinFreeAllSemaphores - + * called at shmem_exit time, ie when exiting the postmaster or + * destroying shared state for a failed set of backends. + * Free up all the semaphores allocated for spinlocks. + */ +static void +SpinFreeAllSemaphores(void) +{ + int i; + + for (i = 0; i < numSpinSets; i++) + { + if (SpinLockIds[i] >= 0) + IpcSemaphoreKill(SpinLockIds[i]); + } + free(SpinLockIds); + SpinLockIds = NULL; +} + +/* + * SpinAcquire -- grab a fixed spinlock + * + * FAILS if the semaphore is corrupted. + */ +void +SpinAcquire(SPINLOCK lock) +{ + + /* + * See the TAS() version of this routine for primary commentary. + * + * NOTE we must pass interruptOK = false to IpcSemaphoreLock, to ensure + * that a cancel/die interrupt cannot prevent us from recording + * ownership of a lock we have just acquired. + */ + IpcSemaphoreLock(SpinLockIds[0], lock, false); + PROC_INCR_SLOCK(lock); + HOLD_INTERRUPTS(); +} + +/* + * SpinRelease -- release a fixed spin lock + * + * FAILS if the semaphore is corrupted + */ +void +SpinRelease(SPINLOCK lock) +{ + /* See the TAS() version of this routine for commentary */ +#ifdef USE_ASSERT_CHECKING + /* Check it's locked */ + int semval; + + semval = IpcSemaphoreGetValue(SpinLockIds[0], lock); + Assert(semval < 1); +#endif + Assert(!MyProc || MyProc->sLocks[lockid] > 0); + PROC_DECR_SLOCK(lock); + IpcSemaphoreUnlock(SpinLockIds[0], lock); + RESUME_INTERRUPTS(); +} + +/* + * s_lock.h hardware-spinlock emulation + */ + +void +s_init_lock_sema(volatile slock_t *lock) +{ + if (nextSpinLock >= numSpinLocks) + elog(FATAL, "s_init_lock_sema: not enough semaphores"); + lock->semId = SpinLockIds[nextSpinLock / SPINLOCKS_PER_SET]; + lock->sem = nextSpinLock % SPINLOCKS_PER_SET; + nextSpinLock++; +} + +void +s_unlock_sema(volatile slock_t *lock) +{ + IpcSemaphoreUnlock(lock->semId, lock->sem); +} + +bool +s_lock_free_sema(volatile slock_t *lock) +{ + return IpcSemaphoreGetValue(lock->semId, lock->sem) > 0; +} + +int +tas_sema(volatile slock_t *lock) +{ + /* Note that TAS macros return 0 if *success* */ + return !IpcSemaphoreTryLock(lock->semId, lock->sem); +} + +#endif /* !HAS_TEST_AND_SET */ -- cgit v1.2.3