Skip to contentSkip to conversation

rwsem-rt: Make rwsem rt closer to mainline

PATCH Applied Steven Rostedt 3 participants 11 messages Apr 9, 2014 – Feb 25, 2015 Locking primitives

lore
Share & tools
lore
#0 lore raw

cover rwsem-rt: Make rwsem rt closer to mainline

Looking at mainline's down_read() I noticed that reader locks of rwsems are not made to nest. In fact, they should not. Although, it may seem fine if a down_read() nests as multiple readers can have the lock, rwsems are fair locks. That is, if a writer were to block on a rwsem while readers have the lock, a new reader will also block. If a reader were to try to take the lock again while a writer was waiting, it would block, and cause a deadlock as it has the lock its trying to grab and wont let it go as the writer is waiting.

I also found that the rt_mutex_init() is identical in the two places it is defined in rtmutex.h.

Steven Rostedt (Red Hat) (2):
rwsem-rt: Do not allow readers to nest
rtmutex: Remove duplicate rt_mutex_init()

----

 include/linux/rtmutex.h  | 12 +++---------
 include/linux/rwsem_rt.h |  1 -
 kernel/rt.c              | 37 ++++++++-----------------------------
 3 files changed, 11 insertions(+), 39 deletions(-)
#1 lore raw

PATCH 1/2 rwsem-rt: Do not allow readers to nest

From: "Steven Rostedt (Red Hat)" <[email protected]>

The readers of mainline rwsems are not allowed to nest, the rwsems in the PREEMPT_RT kernel should not nest either.

---
 include/linux/rwsem_rt.h |  1 -
 kernel/rt.c              | 37 ++++++++-----------------------------
 2 files changed, 8 insertions(+), 30 deletions(-)
include/linux/rwsem_rt.h +0 -1
index e94d945..a81151c 100644
@@ -20,7 +20,6 @@
 
 struct rw_semaphore {
 	struct rt_mutex		lock;
-	int			read_depth;
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 	struct lockdep_map	dep_map;
 #endif
kernel/rt.c +6 -22
index 5d17727..bb72347 100644
@@ -316,10 +316,8 @@ EXPORT_SYMBOL(rt_up_write);
 
 void  rt_up_read(struct rw_semaphore *rwsem)
 {
-	if (--rwsem->read_depth == 0) {
-		rwsem_release(&rwsem->dep_map, 1, _RET_IP_);
-		rt_mutex_unlock(&rwsem->lock);
-	}
+	rwsem_release(&rwsem->dep_map, 1, _RET_IP_);
+	rt_mutex_unlock(&rwsem->lock);
 }
 EXPORT_SYMBOL(rt_up_read);
 
@@ -330,7 +328,6 @@ EXPORT_SYMBOL(rt_up_read);
 void  rt_downgrade_write(struct rw_semaphore *rwsem)
 {
 	BUG_ON(rt_mutex_owner(&rwsem->lock) != current);
-	rwsem->read_depth = 1;
 }
 EXPORT_SYMBOL(rt_downgrade_write);
 
@@ -367,37 +364,20 @@ void rt_down_write_nested_lock(struct rw_semaphore *rwsem,
 
 int  rt_down_read_trylock(struct rw_semaphore *rwsem)
 {
-	struct rt_mutex *lock = &rwsem->lock;
-	int ret = 1;
-
-	/*
-	 * recursive read locks succeed when current owns the rwsem,
-	 * but not when read_depth == 0 which means that the rwsem is
-	 * write locked.
-	 */
-	if (rt_mutex_owner(lock) != current) {
-		ret = rt_mutex_trylock(&rwsem->lock);
-		if (ret)
-			rwsem_acquire(&rwsem->dep_map, 0, 1, _RET_IP_);
-	} else if (!rwsem->read_depth) {
-		ret = 0;
-	}
+	int ret;
 
+	ret = rt_mutex_trylock(&rwsem->lock);
 	if (ret)
-		rwsem->read_depth++;
+		rwsem_acquire(&rwsem->dep_map, 0, 1, _RET_IP_);
+
 	return ret;
 }
 EXPORT_SYMBOL(rt_down_read_trylock);
 
 static void __rt_down_read(struct rw_semaphore *rwsem, int subclass)
 {
-	struct rt_mutex *lock = &rwsem->lock;
#3 lore raw

Re: [PATCH RT 1/2] rwsem-rt: Do not allow readers to nest

In reply to Steven Rostedt
Quoted 4 lines · * Steven Rostedt | 2014-04-08 22:47:01 [-0400]:

From: "Steven Rostedt (Red Hat)" <[email protected]>

The readers of mainline rwsems are not allowed to nest, the rwsems in the PREEMPT_RT kernel should not nest either.

I applied this and this is the reason why cpufreq isn't working. What I see in cpufreq is:

|         test.sh-788   [004] .......    61.416288: store: down_read_try
|         test.sh-788   [004] .......    61.416296: cpufreq_cpu_get: down_read_try
|         test.sh-788   [004] .......    61.416301: cpufreq_cpu_put.part.6: up_read
|         test.sh-788   [004] .......    61.416332: store: up_read

as you see, one code path takes the read path of rw_sema twice.

Looking at the generic implementation, we have:

|#define RWSEM_UNLOCKED_VALUE            0x00000000L
|#define RWSEM_ACTIVE_BIAS               0x00000001L
|#define RWSEM_WAITING_BIAS              (-RWSEM_ACTIVE_MASK-1)
| static inline int __down_read_trylock(struct rw_semaphore *sem)
| {
|         long tmp;
| 
|         while ((tmp = sem->count) >= 0) {
|                 if (tmp == cmpxchg(&sem->count, tmp,
|                                    tmp + RWSEM_ACTIVE_READ_BIAS)) {
|                         return 1;
|                 }
|         }
|         return 0;
| }

While sem->count is >= 0 we loop and take the semaphore. So we can have five readers at once. The first writer would set count to a negative value resulting in trylock failure.

|static inline void __down_read(struct rw_semaphore *sem)
|{
|        if (unlikely(atomic_long_inc_return((atomic_long_t*)&sem->count) <= 0))
|                rwsem_down_read_failed(sem);
|}

Here the same thing but without cmpxchg(). _If_ after an increment the value is negative then we take slowpath. Otherwise we have the lock.

I think I'm going to revert this patch. Where is it written that multiple readers of a RW-semaphore can not nest? According to the code we can even have multiple readers without nesting (two+ processes may take a reader lock).

Sebastian

#4 lore raw

Re: [PATCH RT 1/2] rwsem-rt: Do not allow readers to nest

Quoted 38 lines · On Wed, 18 Feb 2015 20:57:10 +0100 Sebastian Andrzej Siewior…
Quoted 4 lines · * Steven Rostedt | 2014-04-08 22:47:01 [-0400]:

From: "Steven Rostedt (Red Hat)" <[email protected]>

The readers of mainline rwsems are not allowed to nest, the rwsems in the PREEMPT_RT kernel should not nest either.

I applied this and this is the reason why cpufreq isn't working. What I see in cpufreq is:

|         test.sh-788   [004] .......    61.416288: store: down_read_try
|         test.sh-788   [004] .......    61.416296: cpufreq_cpu_get: down_read_try
|         test.sh-788   [004] .......    61.416301: cpufreq_cpu_put.part.6: up_read
|         test.sh-788   [004] .......    61.416332: store: up_read

as you see, one code path takes the read path of rw_sema twice.

Looking at the generic implementation, we have:

|#define RWSEM_UNLOCKED_VALUE            0x00000000L
|#define RWSEM_ACTIVE_BIAS               0x00000001L
|#define RWSEM_WAITING_BIAS              (-RWSEM_ACTIVE_MASK-1)
| static inline int __down_read_trylock(struct rw_semaphore *sem)
| {
|         long tmp;
| 
|         while ((tmp = sem->count) >= 0) {
|                 if (tmp == cmpxchg(&sem->count, tmp,
|                                    tmp + RWSEM_ACTIVE_READ_BIAS)) {
|                         return 1;
|                 }
|         }
|         return 0;
| }

While sem->count is >= 0 we loop and take the semaphore. So we can have five readers at once. The first writer would set count to a negative value resulting in trylock failure.

Quoted 8 lines
|static inline void __down_read(struct rw_semaphore *sem)
|{
|        if (unlikely(atomic_long_inc_return((atomic_long_t*)&sem->count) <= 0))
|                rwsem_down_read_failed(sem);
|}

Here the same thing but without cmpxchg(). _If_ after an increment the value is negative then we take slowpath. Otherwise we have the lock.

OK, so I need to make it so it can nest with trylock. I have to look at the patch again because it has been a while.

Quoted 5 lines

I think I'm going to revert this patch. Where is it written that multiple readers of a RW-semaphore can not nest? According to the code we can even have multiple readers without nesting (two+ processes may take a reader lock).

An RW sem must not do two down_read()s on the same lock (it's fine for a trylock if it has a fail safe for it). The reason is, the second down_read() will block if there's a writer waiting. Thus you are guaranteed a deadlock if you have the lock for read, a write comes in and waits, and you grab the RW sem again, because it will block, and the writer is waiting for the reader to release. Thus you have a deadlock.

I'll have to revisit this. I also need to revisit the multi readers (although Thomas hates it, but he even admitted there's a better way to do it. Now only if I could remember what that was ;-)

Thanks,

-- Steve

#5 lore raw

Re: [PATCH RT 1/2] rwsem-rt: Do not allow readers to nest

Quoted 47 lines · Steven Rostedt
Quoted 44 lines · On Wed, 18 Feb 2015 20:57:10 +0100 Sebastian Andrzej Siewior…
Quoted 4 lines · * Steven Rostedt | 2014-04-08 22:47:01 [-0400]:

From: "Steven Rostedt (Red Hat)" <[email protected]>

The readers of mainline rwsems are not allowed to nest, the rwsems in the PREEMPT_RT kernel should not nest either.

I applied this and this is the reason why cpufreq isn't working. What I see in cpufreq is:

|         test.sh-788   [004] .......    61.416288: store: down_read_try
|         test.sh-788   [004] .......    61.416296: cpufreq_cpu_get: down_read_try
|         test.sh-788   [004] .......    61.416301: cpufreq_cpu_put.part.6: up_read
|         test.sh-788   [004] .......    61.416332: store: up_read

as you see, one code path takes the read path of rw_sema twice.

Looking at the generic implementation, we have:

|#define RWSEM_UNLOCKED_VALUE            0x00000000L
|#define RWSEM_ACTIVE_BIAS               0x00000001L
|#define RWSEM_WAITING_BIAS              (-RWSEM_ACTIVE_MASK-1)
| static inline int __down_read_trylock(struct rw_semaphore *sem)
| {
|         long tmp;
|
|         while ((tmp = sem->count) >= 0) {
|                 if (tmp == cmpxchg(&sem->count, tmp,
|                                    tmp + RWSEM_ACTIVE_READ_BIAS)) {
|                         return 1;
|                 }
|         }
|         return 0;
| }

While sem->count is >= 0 we loop and take the semaphore. So we can have five readers at once. The first writer would set count to a negative value resulting in trylock failure.

|static inline void __down_read(struct rw_semaphore *sem)
|{
|        if (unlikely(atomic_long_inc_return((atomic_long_t*)&sem->count) <= 0))
|                rwsem_down_read_failed(sem);
|}
Quoted 5 lines
Quoted 2 lines

Here the same thing but without cmpxchg(). _If_ after an increment the value is negative then we take slowpath. Otherwise we have the lock.

OK, so I need to make it so it can nest with trylock. I have to look at the patch again because it has been a while.

When we reported this a few months ago, Thomas provided the following patch to fix the issue (which essentially reverted the patch) and appeared to be agreed on:

https://lkml.org/lkml/2014/11/5/844

#6 lore raw

Re: [PATCH RT 1/2] rwsem-rt: Do not allow readers to nest

In reply to Steven Rostedt
Quoted 5 lines · Steven Rostedt
Quoted 2 lines

Here the same thing but without cmpxchg(). _If_ after an increment the value is negative then we take slowpath. Otherwise we have the lock.

OK, so I need to make it so it can nest with trylock. I have to look at the patch again because it has been a while.

I have reverted the patch and can confirm that cpufreq works again.

I did some testing on vanilla and -RT:
- down_read(l) + down_read(l)
this triggers a lockdep warning about a possible deadlock the lock is obtained.

- down_read(l) + down_read_trylock()
this passes without a warning.

So I think we good now.

Quoted 7 lines

An RW sem must not do two down_read()s on the same lock (it's fine for a trylock if it has a fail safe for it). The reason is, the second down_read() will block if there's a writer waiting. Thus you are guaranteed a deadlock if you have the lock for read, a write comes in and waits, and you grab the RW sem again, because it will block, and the writer is waiting for the reader to release. Thus you have a deadlock.

I fully understand. However nesting is allowed according to the code in vanilla and now again in -RT. Lockdep complains properly so we should catch people doing it wrong in both trees.

Quoted 3 lines

I'll have to revisit this. I also need to revisit the multi readers (although Thomas hates it, but he even admitted there's a better way to do it. Now only if I could remember what that was ;-)

Okay. For now I keep the revert since it looks sane and simple.

Quoted 4 lines

Thanks,

-- Steve

Sebastian

#7 lore raw

PATCH 2/2 rtmutex: Remove duplicate rt_mutex_init()

In reply to Steven Rostedt

From: "Steven Rostedt (Red Hat)" <[email protected]>

The rt_mutex_init() macro is the same whether or not CONFIG_DEBUG_RT_MUTEX is set. Remove the duplicate.

---
 include/linux/rtmutex.h | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)
include/linux/rtmutex.h +3 -8
index fa18682..f7e79e8 100644
@@ -62,25 +62,19 @@ struct hrtimer_sleeper;
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname) \
 	, .name = #mutexname, .file = __FILE__, .line = __LINE__
 
-# define rt_mutex_init(mutex)					\
-	do {							\
-		raw_spin_lock_init(&(mutex)->wait_lock);	\
-		__rt_mutex_init(mutex, #mutex);			\
-	} while (0)
-
  extern void rt_mutex_debug_task_free(struct task_struct *tsk);
 #else
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname)
 
+# define rt_mutex_debug_task_free(t)			do { } while (0)
+#endif
+
 # define rt_mutex_init(mutex)					\
 	do {							\
 		raw_spin_lock_init(&(mutex)->wait_lock);	\
 		__rt_mutex_init(mutex, #mutex);			\
 	} while (0)
 
-# define rt_mutex_debug_task_free(t)			do { } while (0)
-#endif
#8 lore raw

Re: [PATCH RT 2/2] rtmutex: Remove duplicate rt_mutex_init()

Quoted 6 lines · * Steven Rostedt | 2014-04-08 22:47:02 [-0400]:

From: "Steven Rostedt (Red Hat)" <[email protected]>

The rt_mutex_init() macro is the same whether or not CONFIG_DEBUG_RT_MUTEX is set. Remove the duplicate.

This does not apply cleanly on v3.14-rt

include/linux/rtmutex.h struct hrtimer_sleeper; · line 70

Quoted 12 lines
include/linux/rtmutex.h +0 -6
@@ -62,25 +62,19 @@ struct hrtimer_sleeper;
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname) \
 	, .name = #mutexname, .file = __FILE__, .line = __LINE__
 
-# define rt_mutex_init(mutex)					\
-	do {							\
-		raw_spin_lock_init(&(mutex)->wait_lock);	\
-		__rt_mutex_init(mutex, #mutex);			\
-	} while (0)
-

The macro is the same in CONFIG_DEBUG_RT_MUTEXES and the else path. Shouldn't you remove both and define it before the ifdef? There are some users of this:
|drivers/i2c/i2c-core.c: rt_mutex_init(&adap->bus_lock); |drivers/media/usb/em28xx/em28xx-cards.c: rt_mutex_init(&dev->i2c_bus_lock);

Sebastian

#9 lore raw

Re: [PATCH RT 2/2] rtmutex: Remove duplicate rt_mutex_init()

Quoted 10 lines · On Fri, 2 May 2014 11:08:31 +0200 Sebastian Andrzej Siewior…
Quoted 6 lines · * Steven Rostedt | 2014-04-08 22:47:02 [-0400]:

From: "Steven Rostedt (Red Hat)" <[email protected]>

The rt_mutex_init() macro is the same whether or not CONFIG_DEBUG_RT_MUTEX is set. Remove the duplicate.

This does not apply cleanly on v3.14-rt

That's because I sent this out before 3.14-rt was released :-)

Quoted 7 lines
Quoted 7 lines
include/linux/rtmutex.h +0 -0
@@ -62,25 +62,19 @@ struct hrtimer_sleeper;
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname) \
 	, .name = #mutexname, .file = __FILE__, .line = __LINE__
 
Quoted 9 lines

include/linux/rtmutex.h struct hrtimer_sleeper; · line 70

Quoted 6 lines
include/linux/rtmutex.h +0 -6
-# define rt_mutex_init(mutex)					\
-	do {							\
-		raw_spin_lock_init(&(mutex)->wait_lock);	\
-		__rt_mutex_init(mutex, #mutex);			\
-	} while (0)
-

The macro is the same in CONFIG_DEBUG_RT_MUTEXES and the else path. Shouldn't you remove both and define it before the ifdef?

That's exactly what the patch did! Except that it defined it *after* the ifdef.

Here's the patch again (as it was cut in the reply):

include/linux/rtmutex.h +3 -9
@@ -62,25 +62,19 @@ struct hrtimer_sleeper;
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname) \
 	, .name = #mutexname, .file = __FILE__, .line = __LINE__
 
-# define rt_mutex_init(mutex)					\
-	do {							\
-		raw_spin_lock_init(&(mutex)->wait_lock);	\
-		__rt_mutex_init(mutex, #mutex);			\
-	} while (0)
-
  extern void rt_mutex_debug_task_free(struct task_struct *tsk);
 #else
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname)
 
+# define rt_mutex_debug_task_free(t)			do { } while (0)
+#endif
+
 # define rt_mutex_init(mutex)					\
 	do {							\
 		raw_spin_lock_init(&(mutex)->wait_lock);	\
 		__rt_mutex_init(mutex, #mutex);			\
 	} while (0)
 
-# define rt_mutex_debug_task_free(t)			do { } while (0)
-#endif
-
 #define __RT_MUTEX_INITIALIZER_PLAIN(mutexname) \
 	.wait_lock = __RAW_SPIN_LOCK_UNLOCKED(mutexname.wait_lock) \
 	, .wait_list = PLIST_HEAD_INIT(mutexname.wait_list) \

Take special note of the movement of the "#endif".

-- Steve

Quoted 6 lines

There are some
users of this:
|drivers/i2c/i2c-core.c: rt_mutex_init(&adap->bus_lock); |drivers/media/usb/em28xx/em28xx-cards.c: rt_mutex_init(&dev->i2c_bus_lock);

Sebastian

#10 lore raw

Re: [PATCH RT 2/2] rtmutex: Remove duplicate rt_mutex_init()

Quoted 3 lines · Steven Rostedt
Quoted 1 line

This does not apply cleanly on v3.14-rt

That's because I sent this out before 3.14-rt was released :-)

Ach right. Sorry, my memory…

Quoted 52 lines
Quoted 6 lines
Quoted 6 lines
include/linux/rtmutex.h +0 -0
@@ -62,25 +62,19 @@ struct hrtimer_sleeper;
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname) \
 	, .name = #mutexname, .file = __FILE__, .line = __LINE__
 
Quoted 9 lines

include/linux/rtmutex.h struct hrtimer_sleeper; · line 70

Quoted 6 lines
include/linux/rtmutex.h +0 -6
-# define rt_mutex_init(mutex)					\
-	do {							\
-		raw_spin_lock_init(&(mutex)->wait_lock);	\
-		__rt_mutex_init(mutex, #mutex);			\
-	} while (0)
-

The macro is the same in CONFIG_DEBUG_RT_MUTEXES and the else path. Shouldn't you remove both and define it before the ifdef?

That's exactly what the patch did! Except that it defined it *after* the ifdef.

Here's the patch again (as it was cut in the reply):

include/linux/rtmutex.h +3 -9
@@ -62,25 +62,19 @@ struct hrtimer_sleeper;
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname) \
 	, .name = #mutexname, .file = __FILE__, .line = __LINE__
 
-# define rt_mutex_init(mutex)					\
-	do {							\
-		raw_spin_lock_init(&(mutex)->wait_lock);	\
-		__rt_mutex_init(mutex, #mutex);			\
-	} while (0)
-
  extern void rt_mutex_debug_task_free(struct task_struct *tsk);
 #else
 # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname)
 
+# define rt_mutex_debug_task_free(t)			do { } while (0)
+#endif
+
 # define rt_mutex_init(mutex)					\
 	do {							\
 		raw_spin_lock_init(&(mutex)->wait_lock);	\
 		__rt_mutex_init(mutex, #mutex);			\
 	} while (0)
 
-# define rt_mutex_debug_task_free(t)			do { } while (0)
-#endif
-
 #define __RT_MUTEX_INITIALIZER_PLAIN(mutexname) \
 	.wait_lock = __RAW_SPIN_LOCK_UNLOCKED(mutexname.wait_lock) \
 	, .wait_list = PLIST_HEAD_INIT(mutexname.wait_list) \
Quoted 1 line

Take special note of the movement of the "#endif".

Ah. That is the trick. So lets try it again…

Quoted 1 line

-- Steve

Sebastian