diff --git a/configure/os/CONFIG.Common.linuxCommon b/configure/os/CONFIG.Common.linuxCommon index 4f91583d7..a230d1734 100644 --- a/configure/os/CONFIG.Common.linuxCommon +++ b/configure/os/CONFIG.Common.linuxCommon @@ -14,7 +14,7 @@ OS_CLASS = Linux CODE_CPPFLAGS = -D_REENTRANT -POSIX_CPPFLAGS = -D_POSIX_C_SOURCE=199506L -D_POSIX_THREADS -D_XOPEN_SOURCE=500 +POSIX_CPPFLAGS = -D_POSIX_C_SOURCE=200112L -D_POSIX_THREADS -D_XOPEN_SOURCE=500 POSIX_LDLIBS = -lpthread # -D_BSD_SOURCE for gethostname() in unistd.h as needed by cacChannelIO.cpp. diff --git a/configure/os/CONFIG.Common.solaris-sparc b/configure/os/CONFIG.Common.solaris-sparc index d47a5165e..7e8788048 100644 --- a/configure/os/CONFIG.Common.solaris-sparc +++ b/configure/os/CONFIG.Common.solaris-sparc @@ -20,7 +20,7 @@ COMPILER_LDFLAGS += -mt SOLARIS_VERSION = $(subst 5.,,$(shell uname -r)) -POSIX_CPPFLAGS += -D_POSIX_C_SOURCE=199506L $(POSIX_CPPFLAGS_$(SOLARIS_VERSION)) +POSIX_CPPFLAGS += -D_POSIX_C_SOURCE=200112L $(POSIX_CPPFLAGS_$(SOLARIS_VERSION)) POSIX_CPPFLAGS += -D_XOPEN_SOURCE=500 POSIX_LDLIBS += -lposix4 -lpthread $(POSIX_LDLIBS_$(SOLARIS_VERSION)) diff --git a/configure/os/CONFIG.Common.solaris-x86 b/configure/os/CONFIG.Common.solaris-x86 index d19083422..e3632a7bc 100644 --- a/configure/os/CONFIG.Common.solaris-x86 +++ b/configure/os/CONFIG.Common.solaris-x86 @@ -20,7 +20,7 @@ COMPILER_LDFLAGS += -mt SOLARIS_VERSION = $(subst 5.,,$(shell uname -r)) -POSIX_CPPFLAGS += -D_POSIX_C_SOURCE=199506L $(POSIX_CPPFLAGS_$(SOLARIS_VERSION)) +POSIX_CPPFLAGS += -D_POSIX_C_SOURCE=200112L $(POSIX_CPPFLAGS_$(SOLARIS_VERSION)) POSIX_CPPFLAGS += -D_XOPEN_SOURCE=500 POSIX_LDLIBS += -lposix4 -lpthread $(POSIX_LDLIBS_$(SOLARIS_VERSION)) diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index dcb324cdc..9bffde2c2 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -15,6 +15,18 @@ EPICS Base 3.15.0.x releases are not intended for use in production systems.

Changes between 3.15.0.1 and 3.15.0.2

+

Spin-locks API added

+ +

The new header file epicsSpin.h adds a portable spin-locks API which is +intended for locking very short sections of code (typically one or two lines of +C or C++) to provide a critical section that protects against race conditions. +On Posix platforms this uses the pthread_spinlock_t type if it's available but +falls back to a pthread_mutex_t if not; on the UP VxWorks and RTEMS platforms +the implementations lock out the CPU interrupts; the default implementation +(used where no better implementation is available for the platform) uses an +epicsMutex. Spin-locks may not be taken recursively, and the code inside the +critical section should always be short and deterministic.

+

Improvements to aToIPAddr()

The libCom routine aToIPAddr() and the vxWorks implementation of the diff --git a/src/libCom/osi/Makefile b/src/libCom/osi/Makefile index e22091712..94fe92c61 100644 --- a/src/libCom/osi/Makefile +++ b/src/libCom/osi/Makefile @@ -20,6 +20,7 @@ INC += osdInterrupt.h INC += epicsMutex.h INC += osdMutex.h +INC += epicsSpin.h INC += epicsEvent.h INC += osdEvent.h INC += epicsMath.h @@ -107,6 +108,7 @@ Com_SRCS += osdThread.c Com_SRCS += osdThreadExtra.c Com_SRCS += osdThreadHooks.c Com_SRCS += osdMutex.c +Com_SRCS += osdSpin.c Com_SRCS += osdEvent.c Com_SRCS += osdTime.cpp Com_SRCS += osdProcess.c diff --git a/src/libCom/osi/epicsSpin.h b/src/libCom/osi/epicsSpin.h new file mode 100644 index 000000000..06bc44a09 --- /dev/null +++ b/src/libCom/osi/epicsSpin.h @@ -0,0 +1,31 @@ +/*************************************************************************\ +* Copyright (c) 2012 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2012 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef epicsSpinh +#define epicsSpinh + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct epicsSpin *epicsSpinId; + +epicsShareFunc epicsSpinId epicsSpinCreate(); +epicsShareFunc void epicsSpinDestroy(epicsSpinId); + +epicsShareFunc void epicsSpinLock(epicsSpinId); +epicsShareFunc int epicsSpinTryLock(epicsSpinId); +epicsShareFunc void epicsSpinUnlock(epicsSpinId); + +#ifdef __cplusplus +} +#endif + +#endif /* epicsSpinh */ diff --git a/src/libCom/osi/os/RTEMS/osdSpin.c b/src/libCom/osi/os/RTEMS/osdSpin.c new file mode 100644 index 000000000..78600e5f1 --- /dev/null +++ b/src/libCom/osi/os/RTEMS/osdSpin.c @@ -0,0 +1,54 @@ +/*************************************************************************\ +* Copyright (c) 2012 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2012 ITER Organization. +* Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Authors: Ralph Lange + * Andrew Johnson + * + * Based on epicsInterrupt.c (RTEMS implementation) by Eric Norum + */ + +/* + * RTEMS (single CPU): LOCK INTERRUPT + * + * CAVEAT: + * This implementation is for UP architectures only. + * + */ + +#include +#include + +#include "epicsSpin.h" + +typedef struct epicsSpin { + rtems_interrupt_level level; +} epicsSpin; + +epicsSpinId epicsSpinCreate() { + return calloc(1, sizeof(epicsSpin)); +} + +void epicsSpinDestroy(epicsSpinId spin) { + free(spin); +} + +void epicsSpinLock(epicsSpinId spin) { + rtems_interrupt_disable(spin->level); +} + +int epicsSpinTryLock(epicsSpinId spin) { + epicsSpinLock(spin); + return 0; +} + +void epicsSpinUnlock(epicsSpinId spin) { + rtems_interrupt_enable(spin->level); +} diff --git a/src/libCom/osi/os/default/osdSpin.c b/src/libCom/osi/os/default/osdSpin.c new file mode 100644 index 000000000..70d9fbace --- /dev/null +++ b/src/libCom/osi/os/default/osdSpin.c @@ -0,0 +1,74 @@ +/*************************************************************************\ +* Copyright (c) 2012 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2012 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#define epicsExportSharedSymbols +#include "errlog.h" +#include "epicsMutex.h" +#include "epicsSpin.h" + +/* + * Default: EPICS MUTEX IMPLEMENTATION + */ + +typedef struct epicsSpin { + epicsMutexId lock; +} epicsSpin; + +epicsSpinId epicsSpinCreate() { + epicsSpin *spin; + + spin = calloc(1, sizeof(*spin)); + if (!spin) + goto fail; + + spin->lock = epicsMutexCreate(); + if (!spin->lock) + goto fail; + + return spin; + +fail: + free(spin); + return NULL; +} + +void epicsSpinDestroy(epicsSpinId spin) { + epicsMutexDestroy(spin->lock); + free(spin); +} + +void epicsSpinLock(epicsSpinId spin) { + epicsMutexLockStatus status; + + status = epicsMutexLock(spin->lock); + if (status != epicsMutexLockOK) { + errlogPrintf("epicsSpin epicsMutexLock failed: error %s\n", + status == epicsMutexLockTimeout ? "epicsMutexLockTimeout" : "epicsMutexLockError"); + } +} + +int epicsSpinTryLock(epicsSpinId spin) { + epicsMutexLockStatus status; + + status = epicsMutexTryLock(spin->lock); + if (status == epicsMutexLockOK) return 0; + if (status == epicsMutexLockTimeout) return 1; + + errlogPrintf("epicsSpin epicsMutexTryLock failed: error epicsMutexLockError\n"); + return 2; +} + +void epicsSpinUnlock(epicsSpinId spin) { + epicsMutexUnlock(spin->lock); +} diff --git a/src/libCom/osi/os/posix/osdSpin.c b/src/libCom/osi/os/posix/osdSpin.c new file mode 100644 index 000000000..71c34cca8 --- /dev/null +++ b/src/libCom/osi/os/posix/osdSpin.c @@ -0,0 +1,154 @@ +/*************************************************************************\ +* Copyright (c) 2012 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2012 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include +#include +#include +#include +#include + +#include "errlog.h" +#include "epicsSpin.h" + +#define checkStatus(status,message) \ + if ((status)) { \ + errlogPrintf("epicsSpin %s failed: error %s\n", \ + (message), strerror((status))); \ + } + +#if defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) + +/* + * POSIX SPIN LOCKS IMPLEMENTATION + */ + +typedef struct epicsSpin { + pthread_spinlock_t lock; +} epicsSpin; + +epicsSpinId epicsSpinCreate() { + epicsSpin *spin; + int status; + + spin = calloc(1, sizeof(*spin)); + if (!spin) + goto fail; + + status = pthread_spin_init(&spin->lock, PTHREAD_PROCESS_PRIVATE); + checkStatus(status, "pthread_spin_init"); + if (status) + goto fail; + + return spin; + +fail: + free(spin); + return NULL; +} + +void epicsSpinDestroy(epicsSpinId spin) { + int status; + + status = pthread_spin_destroy(&spin->lock); + checkStatus(status, "pthread_spin_destroy"); + + free(spin); +} + +void epicsSpinLock(epicsSpinId spin) { + int status; + + status = pthread_spin_lock(&spin->lock); + checkStatus(status, "pthread_spin_lock"); +} + +int epicsSpinTryLock(epicsSpinId spin) { + int status; + + status = pthread_spin_trylock(&spin->lock); + if (status == EBUSY) + return 1; + checkStatus(status, "pthread_spin_trylock"); + return 0; +} + +void epicsSpinUnlock(epicsSpinId spin) { + int status; + + status = pthread_spin_unlock(&spin->lock); + checkStatus(status, "pthread_spin_unlock"); +} + +#else /* defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) */ + +/* + * POSIX MUTEX IMPLEMENTATION + */ + +typedef struct epicsSpin { + pthread_mutex_t lock; +} epicsSpin; + +epicsSpinId epicsSpinCreate() { + epicsSpin *spin; + int status; + + spin = calloc(1, sizeof(*spin)); + if (!spin) + goto fail; + + status = pthread_mutex_init(&spin->lock, NULL); + checkStatus(status, "pthread_mutex_init"); + if (status) + goto fail; + + return spin; + +fail: + free(spin); + return NULL; +} + +void epicsSpinDestroy(epicsSpinId spin) { + int status; + + status = pthread_mutex_destroy(&spin->lock); + checkStatus(status, "pthread_mutex_destroy"); + + free(spin); +} + +void epicsSpinLock(epicsSpinId spin) { + int status; + + status = pthread_mutex_lock(&spin->lock); + checkStatus(status, "pthread_mutex_lock"); +} + +int epicsSpinTryLock(epicsSpinId spin) { + int status; + + status = pthread_mutex_trylock(&spin->lock); + if (status == EBUSY) + return 1; + checkStatus(status, "pthread_mutex_trylock"); + return 0; +} + +void epicsSpinUnlock(epicsSpinId spin) { + int status; + + status = pthread_mutex_unlock(&spin->lock); + checkStatus(status, "pthread_mutex_unlock"); +} + +#endif /* defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) */ diff --git a/src/libCom/osi/os/vxWorks/osdSpin.c b/src/libCom/osi/os/vxWorks/osdSpin.c new file mode 100644 index 000000000..9b545347d --- /dev/null +++ b/src/libCom/osi/os/vxWorks/osdSpin.c @@ -0,0 +1,52 @@ +/*************************************************************************\ +* Copyright (c) 2012 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2012 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + * + * based on epicsInterrupt.c (vxWorks implementation) by Marty Kraimer + */ + +/* + * vxWorks (single CPU): LOCK INTERRUPT + * + * CAVEAT: + * This implementation will not compile on vxWorks SMP architectures. + * These architectures provide spinlocks, which must be used instead. + * + */ + +#include +#include + +#include "epicsSpin.h" + +typedef struct epicsSpin { + int key; +} epicsSpin; + +epicsSpinId epicsSpinCreate() { + return calloc(1, sizeof(epicsSpin)); +} + +void epicsSpinDestroy(epicsSpinId spin) { + free(spin); +} + +void epicsSpinLock(epicsSpinId spin) { + spin->key = intLock(); +} + +int epicsSpinTryLock(epicsSpinId spin) { + epicsSpinLock(spin); + return 0; +} + +void epicsSpinUnlock(epicsSpinId spin) { + intUnlock(spin->key); +} diff --git a/src/libCom/test/Makefile b/src/libCom/test/Makefile index 40abe97ca..bef859a8f 100755 --- a/src/libCom/test/Makefile +++ b/src/libCom/test/Makefile @@ -131,6 +131,11 @@ epicsMutexTest_SRCS += epicsMutexTest.cpp testHarness_SRCS += epicsMutexTest.cpp TESTS += epicsMutexTest +TESTPROD_HOST += epicsSpinTest +epicsSpinTest_SRCS += epicsSpinTest.c +testHarness_SRCS += epicsSpinTest.c +TESTS += epicsSpinTest + TESTPROD_HOST += epicsAtomicTest epicsAtomicTest_SRCS += epicsAtomicTest.cpp testHarness_SRCS += epicsAtomicTest.cpp diff --git a/src/libCom/test/epicsSpinTest.c b/src/libCom/test/epicsSpinTest.c new file mode 100644 index 000000000..707345b89 --- /dev/null +++ b/src/libCom/test/epicsSpinTest.c @@ -0,0 +1,185 @@ +/*************************************************************************\ +* Copyright (c) 2012 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2012 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + * + * based on epicsMutexTest by Marty Kraimer and Jeff Hill + * + */ + +#include +#include +#include +#include +#include +#include + +#include "epicsTime.h" +#include "epicsThread.h" +#include "epicsSpin.h" +#include "epicsEvent.h" +#include "errlog.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +typedef struct info { + int threadnum; + epicsSpinId spin; + int quit; +} info; + +void spinThread(void *arg) +{ + info *pinfo = (info *) arg; + testDiag("spinThread %d starting", pinfo->threadnum); + while (pinfo->quit--) { + epicsSpinLock(pinfo->spin); + testPass("spinThread %d epicsSpinLock taken", pinfo->threadnum); + epicsThreadSleep(.1); + epicsSpinUnlock(pinfo->spin); + epicsThreadSleep(.9); + } + testDiag("spinThread %d exiting", pinfo->threadnum); + return; +} + +static void lockPair(struct epicsSpin *spin) +{ + epicsSpinLock(spin); + epicsSpinUnlock(spin); +} + +static void tenLockPairs(struct epicsSpin *spin) +{ + lockPair(spin); + lockPair(spin); + lockPair(spin); + lockPair(spin); + lockPair(spin); + lockPair(spin); + lockPair(spin); + lockPair(spin); + lockPair(spin); + lockPair(spin); +} + +static void tenLockPairsSquared(struct epicsSpin *spin) +{ + tenLockPairs(spin); + tenLockPairs(spin); + tenLockPairs(spin); + tenLockPairs(spin); + tenLockPairs(spin); + tenLockPairs(spin); + tenLockPairs(spin); + tenLockPairs(spin); + tenLockPairs(spin); + tenLockPairs(spin); +} + +void epicsSpinPerformance () +{ + static const unsigned N = 10000; + unsigned i; + epicsSpinId spin; + epicsTimeStamp begin; + epicsTimeStamp end; + double delay; + + /* Initialize spinlock */ + spin = epicsSpinCreate(); + + /* test a single lock pair */ + epicsTimeGetCurrent(&begin); + for ( i = 0; i < N; i++ ) { + tenLockPairsSquared(spin); + } + epicsTimeGetCurrent(&end); + + delay = epicsTimeDiffInSeconds(&end, &begin); + delay /= N * 100u; /* convert to delay per lock pair */ + delay *= 1e6; /* convert to micro seconds */ + testDiag("lock()*1/unlock()*1 takes %f microseconds", delay); +} + +struct verifyTryLock { + epicsSpinId spin; + epicsEventId done; +}; + +static void verifyTryLockThread(void *pArg) +{ + struct verifyTryLock *pVerify = + (struct verifyTryLock *) pArg; + + testOk1(epicsSpinTryLock(pVerify->spin)); + epicsEventSignal(pVerify->done); +} + +static void verifyTryLock() +{ + struct verifyTryLock verify; + + verify.spin = epicsSpinCreate(); + verify.done = epicsEventMustCreate(epicsEventEmpty); + + testOk1(epicsSpinTryLock(verify.spin) == 0); + + epicsThreadCreate("verifyTryLockThread", 40, + epicsThreadGetStackSize(epicsThreadStackSmall), + verifyTryLockThread, &verify); + + testOk1(epicsEventWait(verify.done) == epicsEventWaitOK); + + epicsSpinUnlock(verify.spin); + epicsSpinDestroy(verify.spin); + epicsEventDestroy(verify.done); +} + +MAIN(epicsSpinTest) +{ + const int nthreads = 3; + const int nrounds = 5; + unsigned int stackSize; + epicsThreadId *id; + int i; + char **name; + void **arg; + info **pinfo; + epicsSpinId spin; + + testPlan(3 + nthreads * nrounds); + + verifyTryLock(); + + spin = epicsSpinCreate(); + + id = (epicsThreadId *) calloc(nthreads, sizeof(epicsThreadId)); + name = (char **) calloc(nthreads, sizeof(char *)); + arg = (void **) calloc(nthreads, sizeof(void *)); + pinfo = (info **) calloc(nthreads, sizeof(info *)); + stackSize = epicsThreadGetStackSize(epicsThreadStackSmall); + for (i = 0; i < nthreads; i++) { + name[i] = (char *) calloc(10, sizeof(char)); + sprintf(name[i],"task%d",i); + pinfo[i] = (info *) calloc(1, sizeof(info)); + pinfo[i]->threadnum = i; + pinfo[i]->spin = spin; + pinfo[i]->quit = nrounds; + arg[i] = pinfo[i]; + id[i] = epicsThreadCreate(name[i], 40, stackSize, + spinThread, + arg[i]); + } + epicsThreadSleep(2.0 + nrounds); + + epicsSpinPerformance(); + + return testDone(); +}