kernel: add support for ticket mutex locks
RFC PATCH 2/2 kernel: add support for ticket mutex locks
mutex_reserve_lock, and mutex_reserve_lock_interruptible:
Lock a buffer with a reservation_id set. reservation_id must not be set to 0, since this is a special value that means no reservation_id.
Normally if reservation_id is not set, or is older than the reservation_id that's currently set on the mutex, the behavior will be to wait normally.
However, if the reservation_id is newer than the current reservation_id, -EAGAIN will be returned, and this function must unreserve all other mutexes and then redo a blocking lock with normal mutex calls to prevent a deadlock, then call mutex_locked_set_reservation on successful locking to set the reservation_id inside the lock.
These functions will return -EDEADLK instead of -EAGAIN if reservation_id is the same as the reservation_id that's attempted to lock the mutex with, since in that case you presumably attempted to lock the same lock twice.
mutex_reserve_trylock:
Similar to normal trylock, but sets reservation_id as well. Returns true if locked, false if failed.
mutex_unreserve_unlock:
Unlock a buffer reserved with the previous calls.
mutex_locked_set_reservation:
After a mutex is acquired with a normal mutex_lock or *_interruptible because mutex_reserve_* returned -EAGAIN, set the reservation_id and wake up any sleepers just in case.
Missing at the moment:
lockdep warnings when wrongly calling mutex_unreserve_unlock or mutex_unlock, depending on whether reservation_id was set previously or not.
Design:
I chose for ticket_mutex to encapsulate struct mutex, so the extra memory usage and atomic set on init will only happen when you deliberately create a ticket lock.
Since the mutexes are mostly meant to protect buffer object serialization in ttm, not much contention is expected. I could be slightly smarter with wakeups, but this would be at the expense at adding a field to struct mutex_waiter. Because this would add overhead to all cases where ticket_mutexes are not used, and ticket_mutexes are less performance sensitive anyway since they only protect buffer objects, I didn't want to do this. It's still better than ttm always calling wake_up_all, which does a unconditional spin_lock_irqsave/irqrestore.
I needed this in kernel/mutex.c because of the extensions to __lock_common, which are hopefully optimized away for all normal paths.
---
include/linux/mutex.h +60 -1
@@ -62,6 +62,11 @@ struct mutex {#endif }; +struct ticket_mutex { +struct mutex base; +atomic_t reservation_id; +}; +/* * This is the control structure for tasks blocked on mutex, * which resides on the blocked task's kernel stack:
@@ -109,12 +114,24 @@ static inline void mutex_destroy(struct mutex *lock) {} __DEBUG_MUTEX_INITIALIZER(lockname) \ __DEP_MAP_MUTEX_INITIALIZER(lockname) } +#define __TICKET_MUTEX_INITIALIZER (lockname) \ + { .base = __MUTEX_INITIALIZER(lockname) \ + , .reservation_id = ATOMIC_INIT(0) } +#define DEFINE_MUTEX (mutexname) \ struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)extern void __mutex_init (struct mutex *lock,const char *name,struct lock_class_key *key); +static inline void __ticket_mutex_init (struct ticket_mutex *lock, +const char *name, +struct lock_class_key *key) +{ +__mutex_init (&lock->base, name, key); +atomic_set (&lock->reservation_id,0 ); +} +/** * mutex_is_locked - is the mutex locked * @lock: the mutex to be queried
@@ -133,26 +150,62 @@ static inline int mutex_is_locked(struct mutex *lock)#ifdef CONFIG_DEBUG_LOCK_ALLOC extern void mutex_lock_nested (struct mutex *lock,unsigned int subclass);extern void _mutex_lock_nest_lock (struct mutex *lock,struct lockdep_map *nest_lock); +extern int __must_check mutex_lock_interruptible_nested (struct mutex *lock,unsigned int subclass);extern int __must_check mutex_lock_killable_nested (struct mutex *lock,unsigned int subclass); +extern int __must_check _mutex_reserve_lock (struct ticket_mutex *lock, +struct lockdep_map *nest_lock, +unsigned long reservation_id); + +extern int __must_check _mutex_reserve_lock_interruptible (struct ticket_mutex *, +struct lockdep_map *nest_lock, +unsigned long reservation_id); + +extern int __must_check mutex_reserve_trylock (struct ticket_mutex *lock, +unsigned long reservation_id); +#define mutex_lock (lock) mutex_lock_nested(lock, 0)#define mutex_lock_interruptible (lock) mutex_lock_interruptible_nested(lock, 0)#define mutex_lock_killable (lock) mutex_lock_killable_nested(lock, 0)#define mutex_lock_nest_lock (lock, nest_lock) \ do { \ - typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ + typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ _mutex_lock_nest_lock(lock, &(nest_lock)->dep_map); \ } while (0) +#define mutex_reserve_lock (lock, nest_lock, reservation_id) \ +({ \ + typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ + _mutex_reserve_lock(lock, &(nest_lock)->dep_map, reservation_id); \ +}) + +#define mutex_reserve_lock_interruptible (lock, nest_lock, reservation_id) \ +({ \ + typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ + _mutex_reserve_lock_interruptible(lock, &(nest_lock)->dep_map, \ + reservation_id); \ +}) +#else extern void mutex_lock (struct mutex *lock);extern int __must_check mutex_lock_interruptible (struct mutex *lock);extern int __must_check mutex_lock_killable (struct mutex *lock); +extern int __must_check _mutex_reserve_lock (struct ticket_mutex *lock, +unsigned long reservation_id); +extern int __must_check _mutex_reserve_lock_interruptible (struct ticket_mutex *, +unsigned long reservation_id); + +#define mutex_reserve_lock (lock, nest_lock, reservation_id) \ + _mutex_reserve_lock(lock, reservation_id) + +#define mutex_reserve_lock_interruptible (lock, nest_lock, reservation_id) \ + _mutex_reserve_lock_interruptible(lock, reservation_id) +# define mutex_lock_nested (lock, subclass) mutex_lock(lock)# define mutex_lock_interruptible_nested (lock, subclass) mutex_lock_interruptible(lock)# define mutex_lock_killable_nested (lock, subclass) mutex_lock_killable(lock)
@@ -167,6 +220,12 @@ extern int __must_check mutex_lock_killable(struct mutex *lock);*/ extern int mutex_trylock (struct mutex *lock);extern void mutex_unlock (struct mutex *lock); +extern void mutex_unreserve_unlock (struct ticket_mutex *lock); +extern int __must_check mutex_reserve_trylock (struct ticket_mutex *lock, +unsigned long reservation_id); +extern void mutex_locked_set_reservation (struct ticket_mutex *lock, +unsigned long reservation_id); +extern int atomic_dec_and_mutex_lock (atomic_t *cnt,struct mutex *lock);#ifndef CONFIG_HAVE_ARCH_MUTEX_CPU_RELAX
kernel/mutex.c +293 -16
407 lines not on this page · raw message
RFC PATCH v2 2/2 kernel: add support for ticket mutex locks
Hey,
I didn't receive much (any) feedback on my last rfc patch, so sending the updated version with some cc's added. This is the final version I hope.
RFC patch v1 is just adding __mutex_fastpath_lock_retval_arg for all archs, which is mostly a copy paste of __mutex_fastpath_lock_retval.
Changes since RFC patch v1:
- Updated to use atomic_long instead of atomic, since the reservation_id was a long.
- added mutex_reserve_lock_slow and mutex_reserve_lock_intr_slow
- removed mutex_locked_set_reservation_id (or w/e it was called)
mutex_reserve_lock, and mutex_reserve_lock_interruptible:
Lock a buffer with a reservation_id set. reservation_id must not be set to 0, since this is a special value that means no reservation_id.
Normally if reservation_id is not set, or is older than the reservation_id that's currently set on the mutex, the behavior will be to wait normally.
However, if the reservation_id is newer than the current reservation_id, -EAGAIN will be returned, and this function must unreserve all other mutexes and then redo a blocking lock with normal mutex calls to prevent a deadlock, then call mutex_locked_set_reservation on successful locking to set the reservation_id inside the lock.
These functions will return -EDEADLK instead of -EAGAIN if reservation_id is the same as the reservation_id that's attempted to lock the mutex with, since in that case you presumably attempted to lock the same lock twice.
mutex_reserve_lock_slow and mutex_reserve_lock_intr_slow:
Similar to mutex_reserve_lock, except it won't backoff with -EAGAIN. This is useful after mutex_reserve_lock failed with -EAGAIN, and you unreserved all buffers so no deadlock can occur.
mutex_unreserve_unlock:
Unlock a buffer reserved with the previous calls.
Missing at the moment, maybe TODO?
* lockdep warnings when wrongly calling mutex_unreserve_unlock or mutex_unlock, depending on whether reservation_id was set previously or not.
- Does lockdep have something for this or do I need to extend struct mutex?
* Check if lockdep warns if you unlock a lock that other locks were nested to.
- spin_lock(m); spin_lock_nest_lock(a, m); spin_unlock(m); spin_unlock(a); would be nice if it gave a splat. Have to recheck if it does, though..
Design:
I chose for ticket_mutex to encapsulate struct mutex, so the extra memory usage and atomic set on init will only happen when you deliberately create a ticket lock.
Since the mutexes are mostly meant to protect buffer object serialization in ttm, not much contention is expected. I could be slightly smarter with wakeups, but this would be at the expense at adding a field to struct mutex_waiter. Because this would add overhead to all cases where ticket_mutexes are not used, and ticket_mutexes are less performance sensitive anyway since they only protect buffer objects, I didn't want to do this. It's still better than ttm always calling wake_up_all, which does a unconditional spin_lock_irqsave/irqrestore.
I needed this in kernel/mutex.c because of the extensions to __lock_common, which are hopefully optimized away for all normal paths.
---
include/linux/mutex.h +85 -1
@@ -62,6 +62,11 @@ struct mutex {#endif }; +struct ticket_mutex { +struct mutex base; +atomic_long_t reservation_id; +}; +/* * This is the control structure for tasks blocked on mutex, * which resides on the blocked task's kernel stack:
@@ -109,12 +114,24 @@ static inline void mutex_destroy(struct mutex *lock) {} __DEBUG_MUTEX_INITIALIZER(lockname) \ __DEP_MAP_MUTEX_INITIALIZER(lockname) } +#define __TICKET_MUTEX_INITIALIZER (lockname) \ + { .base = __MUTEX_INITIALIZER(lockname) \ + , .reservation_id = ATOMIC_LONG_INIT(0) } +#define DEFINE_MUTEX (mutexname) \ struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)extern void __mutex_init (struct mutex *lock,const char *name,struct lock_class_key *key); +static inline void __ticket_mutex_init (struct ticket_mutex *lock, +const char *name, +struct lock_class_key *key) +{ +__mutex_init (&lock->base, name, key); +atomic_long_set (&lock->reservation_id,0 ); +} +/** * mutex_is_locked - is the mutex locked * @lock: the mutex to be queried
@@ -133,26 +150,91 @@ static inline int mutex_is_locked(struct mutex *lock)#ifdef CONFIG_DEBUG_LOCK_ALLOC extern void mutex_lock_nested (struct mutex *lock,unsigned int subclass);extern void _mutex_lock_nest_lock (struct mutex *lock,struct lockdep_map *nest_lock); +extern int __must_check mutex_lock_interruptible_nested (struct mutex *lock,unsigned int subclass);extern int __must_check mutex_lock_killable_nested (struct mutex *lock,unsigned int subclass); +extern int __must_check _mutex_reserve_lock (struct ticket_mutex *lock, +struct lockdep_map *nest_lock, +unsigned long reservation_id); + +extern int __must_check _mutex_reserve_lock_interruptible (struct ticket_mutex *, +struct lockdep_map *nest_lock, +unsigned long reservation_id); + +extern void _mutex_reserve_lock_slow (struct ticket_mutex *lock, +struct lockdep_map *nest_lock, +unsigned long reservation_id); + +extern int __must_check _mutex_reserve_lock_intr_slow (struct ticket_mutex *, +struct lockdep_map *nest_lock, +unsigned long reservation_id); +#define mutex_lock (lock) mutex_lock_nested(lock, 0)#define mutex_lock_interruptible (lock) mutex_lock_interruptible_nested(lock, 0)#define mutex_lock_killable (lock) mutex_lock_killable_nested(lock, 0)#define mutex_lock_nest_lock (lock, nest_lock) \ do { \ - typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ + typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ _mutex_lock_nest_lock(lock, &(nest_lock)->dep_map); \ } while (0) +#define mutex_reserve_lock (lock, nest_lock, reservation_id) \ +({ \ + typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ + _mutex_reserve_lock(lock, &(nest_lock)->dep_map, reservation_id); \ +}) + +#define mutex_reserve_lock_interruptible (lock, nest_lock, reservation_id) \ +({ \ + typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ + _mutex_reserve_lock_interruptible(lock, &(nest_lock)->dep_map, \ + reservation_id); \ +}) + +#define mutex_reserve_lock_slow (lock, nest_lock, reservation_id) \ +do { \ + typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ + _mutex_reserve_lock_slow(lock, &(nest_lock)->dep_map, reservation_id); \ +} while (0) + +#define mutex_reserve_lock_intr_slow (lock, nest_lock, reservation_id) \ +({ \ + typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \ + _mutex_reserve_lock_intr_slow(lock, &(nest_lock)->dep_map, \ + reservation_id); \ +}) +#else extern void mutex_lock (struct mutex *lock);extern int __must_check mutex_lock_interruptible (struct mutex *lock);extern int __must_check mutex_lock_killable (struct mutex *lock); +extern int __must_check _mutex_reserve_lock (struct ticket_mutex *lock, +unsigned long reservation_id); +extern int __must_check _mutex_reserve_lock_interruptible (struct ticket_mutex *, +unsigned long reservation_id); + +extern void _mutex_reserve_lock_slow (struct ticket_mutex *lock, +unsigned long reservation_id); +extern int __must_check _mutex_reserve_lock_intr_slow (struct ticket_mutex *, +unsigned long reservation_id); + +#define mutex_reserve_lock (lock, nest_lock, reservation_id) \ + _mutex_reserve_lock(lock, reservation_id) + +#define mutex_reserve_lock_interruptible (lock, nest_lock, reservation_id) \ + _mutex_reserve_lock_interruptible(lock, reservation_id) + +#define mutex_reserve_lock_slow (lock, nest_lock, reservation_id) \ + _mutex_reserve_lock_slow(lock, reservation_id) + +#define mutex_reserve_lock_intr_slow (lock, nest_lock, reservation_id) \ + _mutex_reserve_lock_intr_slow(lock, reservation_id) +# define mutex_lock_nested (lock, subclass) mutex_lock(lock)# define mutex_lock_interruptible_nested (lock, subclass) mutex_lock_interruptible(lock)# define mutex_lock_killable_nested (lock, subclass) mutex_lock_killable(lock)
@@ -167,6 +249,8 @@ extern int __must_check mutex_lock_killable(struct mutex *lock);*/ extern int mutex_trylock (struct mutex *lock);extern void mutex_unlock (struct mutex *lock); +extern void mutex_unreserve_unlock (struct ticket_mutex *lock); +extern int atomic_dec_and_mutex_lock (atomic_t *cnt,struct mutex *lock);#ifndef CONFIG_HAVE_ARCH_MUTEX_CPU_RELAX
kernel/mutex.c +302 -15
416 lines not on this page · raw message