Skip to content

Commit

Permalink
Add support for rw semaphore under PREEMPT_RT_FULL
Browse files Browse the repository at this point in the history
The main complication from the RT patch set is that the RW semaphore
locks change such that read locks on an rwsem can be taken only by
a single thread.  All other threads are locked out. This single
thread can take a read lock multiple times though. The underlying
implementation changes to a mutex with an additional read_depth
count.

The implementation can be best understood by inspecting the RT
patch.  rwsem_rt.h and rt.c give the best insight into how RT
rwsem works. My implementation for rwsem_tryupgrade is basically
an inversion of rt_downgrade_write found in rt.c. Please see the
comments in the code.

Unfortunately, I have to drop SPLAT rwlock test4 completely as this
test tries to take multiple locks from different threads, which RT
rwsems do not support.  Otherwise SPLAT, zconfig.sh, zpios-sanity.sh
and zfs-tests.sh pass on my Debian-testing VM with the kernel
linux-image-4.8.0-1-rt-amd64.

Tested-by: kernelOfTruth <kerneloftruth@gmail.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Clemens Fruhwirth <clemens@endorphin.org>
Closes openzfs/zfs#5491
Closes torvalds#589
Closes torvalds#308
  • Loading branch information
clefru authored and behlendorf committed Dec 19, 2016
1 parent 6d064f7 commit 8e99d66
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 4 deletions.
9 changes: 7 additions & 2 deletions include/linux/rwsem_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@

#include <linux/rwsem.h>

#ifdef CONFIG_RWSEM_GENERIC_SPINLOCK
#if defined(CONFIG_PREEMPT_RT_FULL)
#define SPL_RWSEM_SINGLE_READER_VALUE (1)
#define SPL_RWSEM_SINGLE_WRITER_VALUE (0)
#elif defined(CONFIG_RWSEM_GENERIC_SPINLOCK)
#define SPL_RWSEM_SINGLE_READER_VALUE (1)
#define SPL_RWSEM_SINGLE_WRITER_VALUE (-1)
#else
Expand All @@ -36,7 +39,9 @@
#endif

/* Linux 3.16 changed activity to count for rwsem-spinlock */
#if defined(HAVE_RWSEM_ACTIVITY)
#if defined(CONFIG_PREEMPT_RT_FULL)
#define RWSEM_COUNT(sem) sem->read_depth
#elif defined(HAVE_RWSEM_ACTIVITY)
#define RWSEM_COUNT(sem) sem->activity
/* Linux 4.8 changed count to an atomic_long_t for !rwsem-spinlock */
#elif defined(HAVE_RWSEM_ATOMIC_LONG_COUNT)
Expand Down
32 changes: 31 additions & 1 deletion module/spl/spl-rwlock.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,37 @@

#define DEBUG_SUBSYSTEM S_RWLOCK

#if defined(CONFIG_RWSEM_GENERIC_SPINLOCK)
#if defined(CONFIG_PREEMPT_RT_FULL)

#include <linux/rtmutex.h>

static int
__rwsem_tryupgrade(struct rw_semaphore *rwsem)
{
ASSERT(rt_mutex_owner(&rwsem->lock) == current);

/*
* Under the realtime patch series, rwsem is implemented as a
* single mutex held by readers and writers alike. However,
* this implementation would prevent a thread from taking a
* read lock twice, as the mutex would already be locked on
* the second attempt. Therefore the implementation allows a
* single thread to take a rwsem as read lock multiple times
* tracking that nesting as read_depth counter.
*/
if (rwsem->read_depth <= 1) {
/*
* In case, the current thread has not taken the lock
* more than once as read lock, we can allow an
* upgrade to a write lock. rwsem_rt.h implements
* write locks as read_depth == 0.
*/
rwsem->read_depth = 0;
return (1);
}
return (0);
}
#elif defined(CONFIG_RWSEM_GENERIC_SPINLOCK)
static int
__rwsem_tryupgrade(struct rw_semaphore *rwsem)
{
Expand Down
25 changes: 24 additions & 1 deletion module/splat/splat-rwlock.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ void splat_init_rw_priv(rw_priv_t *rwp, struct file *file)
rwp->rw_type = 0;
}

#if defined(CONFIG_PREEMPT_RT_FULL)
static int
splat_rwlock_test1(struct file *file, void *arg)
{
/*
* This test will never succeed on PREEMPT_RT_FULL because these
* kernels only allow a single thread to hold the lock.
*/
return 0;
}
#else
static int
splat_rwlock_wr_thr(void *arg)
{
Expand Down Expand Up @@ -297,6 +308,7 @@ splat_rwlock_test1(struct file *file, void *arg)

return rc;
}
#endif

static void
splat_rwlock_test2_func(void *arg)
Expand Down Expand Up @@ -514,11 +526,22 @@ splat_rwlock_test4(struct file *file, void *arg)

splat_init_rw_priv(rwp, file);

/* Validate all combinations of rw_tryenter() contention */
/*
* Validate all combinations of rw_tryenter() contention.
*
* The concurrent reader test is modified for PREEMPT_RT_FULL
* kernels which do not permit concurrent read locks to be taken
* from different threads. The same thread is allowed to take
* the read lock multiple times.
*/
rc1 = splat_rwlock_test4_type(tq, rwp, -EBUSY, RW_WRITER, RW_WRITER);
rc2 = splat_rwlock_test4_type(tq, rwp, -EBUSY, RW_WRITER, RW_READER);
rc3 = splat_rwlock_test4_type(tq, rwp, -EBUSY, RW_READER, RW_WRITER);
#if defined(CONFIG_PREEMPT_RT_FULL)
rc4 = splat_rwlock_test4_type(tq, rwp, -EBUSY, RW_READER, RW_READER);
#else
rc4 = splat_rwlock_test4_type(tq, rwp, 0, RW_READER, RW_READER);
#endif
rc5 = splat_rwlock_test4_type(tq, rwp, 0, RW_NONE, RW_WRITER);
rc6 = splat_rwlock_test4_type(tq, rwp, 0, RW_NONE, RW_READER);

Expand Down

0 comments on commit 8e99d66

Please sign in to comment.